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