Go Back

0 /*

1 * Copyright (c) 2023 RMF <rawmonk@firemail.cc>

2 *

3 * Permission to use, copy, modify, and distribute this software for any

4 * purpose with or without fee is hereby granted, provided that the above

5 * copyright notice and this permission notice appear in all copies.

6 *

7 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES

8 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF

9 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR

10 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES

11 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN

12 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF

13 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

14 */

15 #ifdef __linux__

16 #define _GNU_SOURCE

17 #else

18 #define _BSD_SOURCE

19 #endif

20 #include <stdio.h>

21 #include <stdlib.h>

22 #include <dirent.h>

23 #include <fcntl.h>

24 #include <errno.h>

25 #include <string.h>

26 #include <pwd.h>

27 #include <unistd.h>

28 #include <sys/stat.h>

29 #include <sys/types.h>

30 #include <fts.h>

31 #include "client.h"

32 #include "strlcpy.h"

33 #include "file.h"

34 #include "view.h"

35 #include "trash.h"

36 #include "util.h"

37

38 #define TRASH "/.trash"

39 #define ID_LENGTH 32

40

41 static int gethome(char *buf, size_t length) {

42

43 struct passwd *pw;

44 char *home;

45 int fd;

46

47 home = getenv("HOME");

48 if (home) {

49 fd = open(home, O_DIRECTORY);

50 if (fd > -1) {

51 close(fd);

52 return strlcpy(buf, home, length);

53 }

54 }

55

56 pw = getpwuid(geteuid());

57 if (!pw) return -1;

58 fd = open(pw->pw_dir, O_DIRECTORY);

59 if (fd < 0) {

60 close(fd);

61 return -1;

62 }

63 return strlcpy(buf, pw->pw_dir, length);

64 }

65

66 static int trash_path(char *path, size_t length) {

67

68 char buf[1024];

69

70 int len = gethome(buf, length);

71 if (len == -1 || len + sizeof(TRASH) >= length) goto clean;

72

73 len = strlcpy(path, buf, length);

74 strlcpy(&path[len], TRASH, sizeof(path) - length);

75

76 if (!strncmp(path, buf, length)) goto clean;

77

78 return 0;

79 clean: /* make sure we're not deleting a whole folder by accident */

80 memset(path, 0, length);

81 strlcpy(path, "/nonexistent/folder", sizeof(path));

82 return -1;

83 }

84

85 int trash_init() {

86 char path[PATH_MAX];

87 int home, trash;

88

89 if (gethome(V(path)) == -1) return -1;

90

91 home = open(path, O_DIRECTORY);

92 if (home < 0) goto fail;

93

94 trash = openat(home, ".trash", O_DIRECTORY);

95 if (trash > -1) {

96 close(home);

97 return trash;

98 }

99

100 if (mkdirat(home, ".trash", 0700)) goto fail;

101

102 trash = openat(home, ".trash", O_DIRECTORY);

103 if (trash < 0) goto fail;

104

105 return trash;

106 fail:

107 if (home > -1)

108 close(home);

109 return -1;

110 }

111

112 int trash_send(int fd, char *path, char *name) {

113

114 char buf[PATH_MAX * 2], id[ID_LENGTH + 1];

115 int info, len, error;

116

117 info = openat(client.trash, "info", O_WRONLY|O_CREAT|O_APPEND, 0600);

118 if (info < 0)

119 return -1;

120

121 do {

122 size_t i;

123 int try;

124

125 i = 0;

126 while (i < sizeof(id) - 1) {

127 id[i] = 'a' + rand() % 26;

128 i++;

129 }

130 id[sizeof(id) - 1] = '\0';

131

132 /* check if there's not already a file with that id */

133 try = openat(client.trash, id, O_RDONLY);

134 if (try < 0) break;

135 close(i);

136 } while (1);

137

138 trash_path(V(buf));

139 error = file_move(path, fd, name, client.trash, buf, id);

140 if (!error) {

141 len = snprintf(V(buf), "%s %s/%s\n", id, path, name);

142 error = -(write(info, buf, len) != len);

143 }

144 close(info);

145

146 return error;

147 }

148

149 int trash_clear() {

150

151 char path[PATH_MAX], cmd[PATH_MAX * 2];

152

153 if (trash_path(V(path))) return -1;

154 snprintf(V(cmd), "rm -r %s", path);

155 if (system(cmd)) return -1;

156

157 close(client.trash);

158 client.trash = trash_init();

159 if (client.trash < 0) return -1;

160

161 return 0;

162 }

163

164 int trash_rawpath(struct view *view, char *out, size_t length) {

165

166 char path[PATH_MAX];

167

168 if (view->fd != TRASH_FD) return -1;

169 if (trash_path(V(path))) return -1;

170 if (snprintf(out, length, "%s/%s", path,

171 (char*)SELECTED(view).other) >= (int)length)

172 return -1;

173 return 0;

174 }

175

176 int trash_restore(struct view *view) {

177

178 size_t i;

179 char path[PATH_MAX];

180 int error = 0;

181

182 if (view->fd != TRASH_FD) {

183 errno = EINVAL;

184 return -1;

185 }

186

187 if (trash_path(V(path))) return -1;

188

189 i = 0;

190 while (i < view->length) {

191

192 char src[PATH_MAX];

193 char *id;

194 size_t j = i++;

195 int fd;

196

197 if (!view->entries[j].selected) continue;

198 id = view->entries[j].other;

199 snprintf(V(src), "%s/%s", path, id);

200

201 /* check if file exist before using rename */

202 fd = open(view->entries[j].name, 0);

203 if (fd > -1) {

204 close(fd);

205 errno = EEXIST;

206 error = -1;

207 continue;

208 }

209

210 if (rename(src, view->entries[j].name)) {

211 char *name;

212 if (errno != EXDEV) return -1;

213 STRCPY(src, view->entries[j].name);

214 name = strrchr(src, '/');

215 if (!name) return -1;

216 *name = '\0';

217 name++;

218 fd = open(src, O_DIRECTORY);

219 if (fd < 0) return -1;

220 if (file_move(path, client.trash, id, fd, src, name))

221 return -1;

222 }

223 view->entries[j].selected = -1;

224 }

225 return error;

226 }

227

228 int trash_refresh(struct view *view) {

229

230 void *next, *prev;

231 size_t i;

232 int fd, rewrite;

233

234 i = 0;

235 rewrite = 0;

236 while (i < view->length) {

237 switch (view->entries[i].selected) {

238 case -1:

239 rewrite = 1;

240 break;

241 case 1:

242 view->entries[i].selected = 0;

243 break;

244 }

245 i++;

246 }

247 if (!rewrite) return 0;

248

249 /* rewrite info file */

250 fd = openat(client.trash, "info", O_CREAT|O_WRONLY|O_TRUNC);

251 if (!fd) return -1;

252

253 i = 0;

254 while (i < view->length) {

255

256 char c;

257 size_t j = i++;

258

259 if (view->entries[j].selected == -1) continue;

260

261 write(fd, view->entries[j].other, ID_LENGTH);

262 c = ' ';

263 write(fd, &c, 1);

264 write(fd, view->entries[j].name,

265 strnlen(V(view->entries[j].name)));

266 c = '\n';

267 write(fd, &c, 1);

268 }

269 close(fd);

270

271 i = 0;

272 while (i < view->length) {

273 free(view->entries[i].other);

274 }

275

276 free(view->entries);

277

278 next = view->next;

279 prev = view->prev;

280 trash_view(view);

281 view->next = next;

282 view->prev = prev;

283

284 return 0;

285 }

286

287 int trash_view(struct view* view) {

288

289 int fd;

290 size_t i;

291 char buf[PATH_MAX * 2];

292

293 PZERO(view);

294 STRCPY(view->path, "Trash");

295 view->fd = TRASH_FD;

296

297 fd = openat(client.trash, "info", O_RDONLY);

298 if (fd < 0) return 0;

299

300 i = 0;

301 while (1) {

302

303 void *ptr;

304 char id[ID_LENGTH];

305 char c;

306 size_t j;

307

308 j = read(fd, V(id));

309 if (!j) { /* success : end of file */

310 close(fd);

311 qsort(view->entries, view->length,

312 sizeof(struct entry), file_sort);

313 return 0;

314 }

315 if (j != sizeof(id)) break;

316

317 j = 0;

318 while (j < sizeof(id)) {

319 if (id[j] > 'z' || id[j] < 'a') break;

320 j++;

321 }

322 if (j != sizeof(id)) break;

323

324 if (read(fd, &c, 1) != 1 || c != ' ') break;

325

326 j = 0;

327 while (j < sizeof(buf)) {

328 if (read(fd, &c, 1) != 1) break;

329 if (c == '\n') {

330 buf[j] = 0;

331 break;

332 }

333 buf[j] = c;

334 j++;

335 }

336

337 ptr = realloc(view->entries, sizeof(struct entry) * (i + 1));

338 if (!ptr) break;

339 view->entries = ptr;

340 RZERO(view->entries[i]);

341

342 STRCPY(view->entries[i].name, buf);

343 view->entries[i].type = DT_REG;

344 {

345 struct stat s;

346 char path[ID_LENGTH + 1];

347 memcpy(path, id, ID_LENGTH);

348 path[ID_LENGTH] = 0;

349 view->entries[i].type =

350 fstatat(client.trash, path, &s, 0) ?

351 DT_REG : (S_ISDIR(s.st_mode) ?

352 DT_DIR : DT_REG);

353 }

354 view->length = i + 1;

355

356 view->entries[i].other = calloc(ID_LENGTH + 1, 1);

357 if (!view->entries[i].other) break;

358 memcpy(view->entries[i].other, V(id));

359

360 i++;

361 }

362

363 for (i = 0; i < view->length; i++)

364 free(view->entries[i].other);

365 free(view->entries);

366 close(fd);

367 return -1;

368 }

369