1 /* vifm
2 * Copyright (C) 2011 xaizek.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #include "trash.h"
20
21 #include <sys/stat.h> /* stat chmod() */
22 #include <unistd.h> /* getuid() */
23
24 #include <assert.h> /* assert() */
25 #include <errno.h> /* EROFS errno */
26 #include <stddef.h> /* NULL size_t */
27 #include <stdio.h> /* remove() snprintf() */
28 #include <stdlib.h> /* free() realloc() */
29 #include <string.h> /* memmove() strchr() strcmp() strdup() strlen() strspn() */
30
31 #include "cfg/config.h"
32 #include "compat/fs_limits.h"
33 #include "compat/os.h"
34 #include "compat/mntent.h"
35 #include "compat/reallocarray.h"
36 #include "modes/dialogs/msg_dialog.h"
37 #include "utils/fs.h"
38 #include "utils/log.h"
39 #include "utils/path.h"
40 #include "utils/str.h"
41 #include "utils/string_array.h"
42 #include "utils/utils.h"
43 #include "background.h"
44 #include "ops.h"
45 #include "registers.h"
46 #include "undo.h"
47
48 #define ROOTED_SPEC_PREFIX "%r/"
49 #define ROOTED_SPEC_PREFIX_LEN (sizeof(ROOTED_SPEC_PREFIX) - 1U)
50
51 /* Describes file location relative to one of registered trash directories.
52 * Argument for get_resident_type_traverser().*/
53 typedef enum
54 {
55 TRT_OUT_OF_TRASH, /* Not in trash. */
56 TRT_IN_TRASH, /* Direct child of one of trash directories. */
57 TRT_IN_REMOVED_DIRECTORY, /* Child of one of removed directories. */
58 }
59 TrashResidentType;
60
61 /* Type of path check. */
62 typedef enum
63 {
64 PREFIXED_WITH, /* Checks that path starts with a prefix. */
65 SAME_AS, /* Checks that path is equal to the other one. */
66 }
67 PathCheckType;
68
69 /* Client of the traverse_specs() function. Should return non-zero to stop
70 * traversal. */
71 typedef int (*traverser)(const char base_path[], const char trash_dir[],
72 int user_specific, void *arg);
73
74 /* List of trash directories. */
75 typedef struct
76 {
77 char **trashes; /* List of trashes. */
78 char *can_delete; /* Whether can delete some trash on empty ('1'/'0'). */
79 int ntrashes; /* Number of elements in trashes array. */
80 }
81 trashes_list;
82
83 /* State for get_list_of_trashes() traverser. */
84 typedef struct
85 {
86 trashes_list *list; /* List of valid trash directories. */
87 const char *spec; /* Trash directory name specification. */
88 int allow_empty; /* Whether empty directories should be included. */
89 }
90 get_list_of_trashes_traverser_state;
91
92 static int validate_spec(const char spec[]);
93 static int create_trash_dir(const char trash_dir[], int user_specific);
94 static int try_create_trash_dir(const char trash_dir[], int user_specific);
95 static void empty_trash_dirs(void);
96 static void empty_trash_dir(const char trash_dir[], int can_delete);
97 static void empty_trash_in_bg(bg_op_t *bg_op, void *arg);
98 static void remove_trash_entries(const char trash_dir[]);
99 static int find_in_trash(const char original_path[], const char trash_path[]);
100 static trashes_list get_list_of_trashes(int allow_empty);
101 static int get_list_of_trashes_traverser(struct mntent *entry, void *arg);
102 static int is_trash_valid(const char trash_dir[], int allow_empty);
103 static void add_trash_to_list(trashes_list *list, const char path[],
104 int can_delete);
105 static void remove_from_trash(const char trash_name[]);
106 static void free_entry(const trash_entry_t *entry);
107 static int pick_trash_dir_traverser(const char base_path[],
108 const char trash_dir[], int user_specific, void *arg);
109 static int is_rooted_trash_dir(const char spec[]);
110 static TrashResidentType get_resident_type(const char path[]);
111 static int get_resident_type_traverser(const char path[],
112 const char trash_dir[], int user_specific, void *arg);
113 static int is_trash_directory_traverser(const char path[],
114 const char trash_dir[], int user_specific, void *arg);
115 static int path_is(PathCheckType check, const char path[], const char other[]);
116 static int entry_is(PathCheckType check, trash_entry_t *entry,
117 const char other[]);
118 static const char * get_real_trash_name(trash_entry_t *entry);
119 static void make_real_path(const char path[], char buf[], size_t buf_len);
120 static void traverse_specs(const char base_path[], traverser client, void *arg);
121 static char * expand_uid(const char spec[], int *expanded);
122 static char * get_rooted_trash_dir(const char base_path[], const char spec[]);
123 static char * format_root_spec(const char spec[], const char mount_point[]);
124
125 trash_entry_t *trash_list;
126 int trash_list_size;
127
128 static char **specs;
129 static int nspecs;
130
131 int
trash_set_specs(const char new_specs[])132 trash_set_specs(const char new_specs[])
133 {
134 char **dirs = NULL;
135 int ndirs = 0;
136
137 int error;
138 char *free_this;
139 char *spec;
140
141 error = 0;
142 free_this = strdup(new_specs);
143 spec = free_this;
144
145 for(;;)
146 {
147 char *const p = until_first(spec, ',');
148 const int last_element = *p == '\0';
149 *p = '\0';
150
151 if(!validate_spec(spec))
152 {
153 error = 1;
154 break;
155 }
156
157 ndirs = add_to_string_array(&dirs, ndirs, spec);
158
159 if(last_element)
160 {
161 break;
162 }
163 spec = p + 1;
164 }
165
166 free(free_this);
167
168 if(!error)
169 {
170 free_string_array(specs, nspecs);
171 specs = dirs;
172 nspecs = ndirs;
173
174 copy_str(cfg.trash_dir, sizeof(cfg.trash_dir), new_specs);
175 }
176 else
177 {
178 free_string_array(dirs, ndirs);
179 }
180
181 return error;
182 }
183
184 /* Validates trash directory specification. Returns non-zero if it's OK,
185 * otherwise zero is returned and an error message is displayed. */
186 static int
validate_spec(const char spec[])187 validate_spec(const char spec[])
188 {
189 int valid = 1;
190 int with_uid;
191 char *const expanded_spec = expand_uid(spec, &with_uid);
192
193 if(is_path_absolute(expanded_spec))
194 {
195 if(create_trash_dir(expanded_spec, with_uid) != 0)
196 {
197 valid = 0;
198 }
199 }
200 else if(!is_rooted_trash_dir(expanded_spec))
201 {
202 show_error_msgf("Error Setting Trash Directory",
203 "The path specification is of incorrect format: %s", expanded_spec);
204 valid = 0;
205 }
206
207 free(expanded_spec);
208 return valid;
209 }
210
211 /* Ensures existence of trash directory. Displays error message dialog, if
212 * directory creation failed. Returns zero on success, otherwise non-zero value
213 * is returned. */
214 static int
create_trash_dir(const char trash_dir[],int user_specific)215 create_trash_dir(const char trash_dir[], int user_specific)
216 {
217 LOG_FUNC_ENTER;
218
219 if(try_create_trash_dir(trash_dir, 0) != 0)
220 {
221 show_error_msgf("Error Setting Trash Directory",
222 "Could not set trash directory to %s: %s", trash_dir, strerror(errno));
223 return 1;
224 }
225
226 return 0;
227 }
228
229 /* Tries to create trash directory. Returns zero on success, otherwise non-zero
230 * value is returned. */
231 static int
try_create_trash_dir(const char trash_dir[],int user_specific)232 try_create_trash_dir(const char trash_dir[], int user_specific)
233 {
234 LOG_FUNC_ENTER;
235
236 if(!is_dir_writable(trash_dir))
237 {
238 if(make_path(trash_dir, 0777) != 0)
239 {
240 #ifndef _WIN32
241 /* Do not treat it as an error if trash is not writable because
242 * file-system is mounted read-only. User should be aware of it. */
243 return (errno != EROFS);
244 #else
245 return 1;
246 #endif
247 }
248 }
249
250 if(user_specific)
251 {
252 if(chmod(trash_dir, 0700) != 0)
253 {
254 return 1;
255 }
256 }
257
258 return 0;
259 }
260
261 void
trash_empty_all(void)262 trash_empty_all(void)
263 {
264 regs_remove_trashed_files(NULL);
265 empty_trash_dirs();
266 un_clear_cmds_with_trash(NULL);
267 remove_trash_entries(NULL);
268 }
269
270 /* Empties all trash directories (all specifications on all mount points are
271 * expanded). */
272 static void
empty_trash_dirs(void)273 empty_trash_dirs(void)
274 {
275 const trashes_list list = get_list_of_trashes(1);
276 int i;
277 for(i = 0; i < list.ntrashes; ++i)
278 {
279 empty_trash_dir(list.trashes[i], list.can_delete[i] == '1');
280 }
281
282 free_string_array(list.trashes, list.ntrashes);
283 free(list.can_delete);
284 }
285
286 void
trash_empty(const char trash_dir[])287 trash_empty(const char trash_dir[])
288 {
289 regs_remove_trashed_files(trash_dir);
290 empty_trash_dir(trash_dir, 0);
291 un_clear_cmds_with_trash(trash_dir);
292 remove_trash_entries(trash_dir);
293 }
294
295 /* Removes all files inside given trash directory (even those that this instance
296 * of vifm is not aware of). */
297 static void
empty_trash_dir(const char trash_dir[],int can_delete)298 empty_trash_dir(const char trash_dir[], int can_delete)
299 {
300 /* XXX: should we rename directory and delete files from it to exclude
301 * possibility of deleting newly added files? */
302
303 char *const task_desc = format_str("Empty trash: %s", trash_dir);
304 char *const op_desc = format_str("Emptying %s", replace_home_part(trash_dir));
305
306 /* Yes, this isn't pretty. It's a simple way to bundle string and bool. */
307 char *trash_dir_copy = format_str("%c%s", can_delete ? '1' : '0', trash_dir);
308
309 if(bg_execute(task_desc, op_desc, BG_UNDEFINED_TOTAL, 1, &empty_trash_in_bg,
310 trash_dir_copy) != 0)
311 {
312 free(trash_dir_copy);
313 }
314
315 free(op_desc);
316 free(task_desc);
317 }
318
319 /* Entry point for a background task that removes files in a single trash
320 * directory. */
321 static void
empty_trash_in_bg(bg_op_t * bg_op,void * arg)322 empty_trash_in_bg(bg_op_t *bg_op, void *arg)
323 {
324 char *const trash_info = arg;
325
326 remove_dir_content(trash_info + 1);
327 if(trash_info[0] == '1')
328 {
329 (void)remove(trash_info + 1);
330 }
331
332 free(trash_info);
333 }
334
335 /* Removes entries that belong to specified trash directory. Removes all if
336 * trash_dir is NULL. */
337 static void
remove_trash_entries(const char trash_dir[])338 remove_trash_entries(const char trash_dir[])
339 {
340 int i;
341 int j = 0;
342
343 for(i = 0; i < trash_list_size; ++i)
344 {
345 if(trash_dir == NULL || entry_is(PREFIXED_WITH, &trash_list[i], trash_dir))
346 {
347 free_entry(&trash_list[i]);
348 continue;
349 }
350
351 trash_list[j++] = trash_list[i];
352 }
353
354 trash_list_size = j;
355 if(trash_list_size == 0)
356 {
357 free(trash_list);
358 trash_list = NULL;
359 }
360 }
361
362 void
trash_file_moved(const char src[],const char dst[])363 trash_file_moved(const char src[], const char dst[])
364 {
365 if(trash_has_path(dst))
366 {
367 if(trash_add_entry(src, dst) != 0)
368 {
369 LOG_ERROR_MSG("Failed to add to trash: (`%s`, `%s`)", src, dst);
370 }
371 }
372 else if(trash_has_path(src))
373 {
374 remove_from_trash(src);
375 }
376 }
377
378 int
trash_add_entry(const char original_path[],const char trash_name[])379 trash_add_entry(const char original_path[], const char trash_name[])
380 {
381 /* XXX: we check duplicates by original_path+trash_name, which allows
382 * multiple original path to be mapped to one trash file, might want to
383 * forbid this. */
384 int pos = find_in_trash(original_path, trash_name);
385 if(pos >= 0)
386 {
387 LOG_INFO_MSG("File is already in trash: (`%s`, `%s`)", original_path,
388 trash_name);
389 return 0;
390 }
391 pos = -(pos + 1);
392
393 void *p = reallocarray(trash_list, trash_list_size + 1, sizeof(*trash_list));
394 if(p == NULL)
395 {
396 return -1;
397 }
398 trash_list = p;
399
400 trash_entry_t entry = {
401 .path = strdup(original_path),
402 .trash_name = strdup(trash_name),
403 .real_trash_name = NULL,
404 };
405 if(entry.path == NULL || entry.trash_name == NULL)
406 {
407 free_entry(&entry);
408 return -1;
409 }
410
411 ++trash_list_size;
412 memmove(trash_list + pos + 1, trash_list + pos,
413 sizeof(*trash_list)*(trash_list_size - 1 - pos));
414 trash_list[pos] = entry;
415 return 0;
416 }
417
418 int
trash_has_entry(const char original_path[],const char trash_path[])419 trash_has_entry(const char original_path[], const char trash_path[])
420 {
421 return (find_in_trash(original_path, trash_path) >= 0);
422 }
423
424 /* Finds position of an entry in trash_list or whereto it should be inserted in
425 * it. Returns non-negative number of successful search and negative index
426 * offset by one otherwise (0 -> -1, 1 -> -2, etc.). */
427 static int
find_in_trash(const char original_path[],const char trash_path[])428 find_in_trash(const char original_path[], const char trash_path[])
429 {
430 char real_trash_path[PATH_MAX*2 + 1];
431 real_trash_path[0] = '\0';
432
433 int l = 0;
434 int u = trash_list_size - 1;
435 while(l <= u)
436 {
437 const int i = l + (u - l)/2;
438
439 int cmp = stroscmp(trash_list[i].path, original_path);
440 if(cmp == 0)
441 {
442 const char *entry_real = get_real_trash_name(&trash_list[i]);
443 if(real_trash_path[0] == '\0')
444 {
445 make_real_path(trash_path, real_trash_path, sizeof(real_trash_path));
446 }
447 cmp = stroscmp(entry_real, real_trash_path);
448 }
449
450 if(cmp == 0)
451 {
452 return i;
453 }
454 else if(cmp < 0)
455 {
456 l = i + 1;
457 }
458 else
459 {
460 u = i - 1;
461 }
462 }
463 return -l - 1;
464 }
465
466 char **
trash_list_trashes(int * ntrashes)467 trash_list_trashes(int *ntrashes)
468 {
469 trashes_list list = get_list_of_trashes(0);
470 free(list.can_delete);
471 *ntrashes = list.ntrashes;
472 return list.trashes;
473 }
474
475 /* Lists trash directories. Caller should free array and all its elements using
476 * free(). */
477 static trashes_list
get_list_of_trashes(int allow_empty)478 get_list_of_trashes(int allow_empty)
479 {
480 trashes_list list = {
481 .trashes = NULL,
482 .can_delete = NULL,
483 .ntrashes = 0,
484 };
485
486 int i;
487 for(i = 0; i < nspecs; ++i)
488 {
489 int with_uid;
490 char *const spec = expand_uid(specs[i], &with_uid);
491 if(is_rooted_trash_dir(spec))
492 {
493 get_list_of_trashes_traverser_state state = {
494 .list = &list,
495 .spec = spec,
496 .allow_empty = allow_empty,
497 };
498 (void)traverse_mount_points(&get_list_of_trashes_traverser, &state);
499 }
500 else if(is_trash_valid(spec, allow_empty))
501 {
502 add_trash_to_list(&list, spec, with_uid);
503 }
504 free(spec);
505 }
506
507 return list;
508 }
509
510 /* traverse_mount_points() client that collects valid trash directories into a
511 * list of trash directories. */
512 static int
get_list_of_trashes_traverser(struct mntent * entry,void * arg)513 get_list_of_trashes_traverser(struct mntent *entry, void *arg)
514 {
515 get_list_of_trashes_traverser_state *const params = arg;
516 trashes_list *const list = params->list;
517 const char *const spec = params->spec;
518
519 char *const trash_dir = format_root_spec(spec, entry->mnt_dir);
520 if(is_trash_valid(trash_dir, params->allow_empty))
521 {
522 add_trash_to_list(list, trash_dir, 1);
523 }
524 free(trash_dir);
525
526 return 0;
527 }
528
529 /* Adds trash to list of trashes. */
530 static void
add_trash_to_list(trashes_list * list,const char path[],int can_delete)531 add_trash_to_list(trashes_list *list, const char path[], int can_delete)
532 {
533 size_t len = list->ntrashes;
534 if(strappendch(&list->can_delete, &len, can_delete ? '1' : '0') == 0)
535 {
536 list->ntrashes = add_to_string_array(&list->trashes, list->ntrashes, path);
537 }
538 }
539
540 /* Checks whether trash directory is valid (at least exists and is writable).
541 * Returns non-zero if so, otherwise zero is returned. */
542 static int
is_trash_valid(const char trash_dir[],int allow_empty)543 is_trash_valid(const char trash_dir[], int allow_empty)
544 {
545 return is_dir_writable(trash_dir)
546 && (allow_empty || !is_dir_empty(trash_dir));
547 }
548
549 int
trash_restore(const char trash_name[])550 trash_restore(const char trash_name[])
551 {
552 int i;
553 char full[PATH_MAX + 1];
554 char path[PATH_MAX + 1];
555
556 for(i = 0; i < trash_list_size; ++i)
557 {
558 if(entry_is(SAME_AS, &trash_list[i], trash_name))
559 {
560 break;
561 }
562 }
563 if(i >= trash_list_size)
564 {
565 return -1;
566 }
567
568 copy_str(path, sizeof(path), trash_list[i].path);
569 copy_str(full, sizeof(full), trash_list[i].trash_name);
570 if(perform_operation(OP_MOVE, NULL, NULL, full, path) == 0)
571 {
572 char *msg, *p;
573 size_t len;
574
575 un_group_reopen_last();
576
577 msg = un_replace_group_msg(NULL);
578 len = strlen(msg);
579 p = realloc(msg, COMMAND_GROUP_INFO_LEN);
580 if(p == NULL)
581 len = COMMAND_GROUP_INFO_LEN;
582 else
583 msg = p;
584
585 snprintf(msg + len, COMMAND_GROUP_INFO_LEN - len, "%s%s",
586 (msg[len - 2] != ':') ? ", " : "", strchr(trash_name, '_') + 1);
587 un_replace_group_msg(msg);
588 free(msg);
589
590 un_group_add_op(OP_MOVE, NULL, NULL, full, path);
591 un_group_close();
592 remove_from_trash(trash_name);
593 return 0;
594 }
595 return -1;
596 }
597
598 /* Removes record about the file in the trash. Does nothing if no such record
599 * found. */
600 static void
remove_from_trash(const char trash_name[])601 remove_from_trash(const char trash_name[])
602 {
603 int i;
604 for(i = 0; i < trash_list_size; ++i)
605 {
606 if(entry_is(SAME_AS, &trash_list[i], trash_name))
607 {
608 break;
609 }
610 }
611 if(i >= trash_list_size)
612 {
613 return;
614 }
615
616 free_entry(&trash_list[i]);
617 memmove(trash_list + i, trash_list + i + 1,
618 sizeof(*trash_list)*((trash_list_size - 1) - i));
619
620 --trash_list_size;
621 }
622
623 /* Frees memory allocated by given trash entry. */
624 static void
free_entry(const trash_entry_t * entry)625 free_entry(const trash_entry_t *entry)
626 {
627 free(entry->path);
628 free(entry->trash_name);
629 free(entry->real_trash_name);
630 }
631
632 char *
trash_gen_path(const char base_path[],const char name[])633 trash_gen_path(const char base_path[], const char name[])
634 {
635 struct stat st;
636 char buf[PATH_MAX + 1];
637 int i;
638 char *const trash_dir = trash_pick_dir(base_path);
639
640 if(trash_dir == NULL)
641 {
642 return NULL;
643 }
644
645 i = 0;
646 do
647 {
648 snprintf(buf, sizeof(buf), "%s/%03d_%s", trash_dir, i++, name);
649 chosp(buf);
650 }
651 while(os_lstat(buf, &st) == 0);
652
653 free(trash_dir);
654
655 return strdup(buf);
656 }
657
658 char *
trash_pick_dir(const char base_path[])659 trash_pick_dir(const char base_path[])
660 {
661 char real_path[PATH_MAX + 1];
662 char *trash_dir = NULL;
663
664 /* We want all links resolved to do not mistakenly attribute removed files to
665 * location of a symbolic link. */
666 if(os_realpath(base_path, real_path) == real_path)
667 {
668 /* If realpath() fails, just go with the original path. */
669 system_to_internal_slashes(real_path);
670 base_path = real_path;
671 }
672
673 traverse_specs(base_path, &pick_trash_dir_traverser, &trash_dir);
674 return trash_dir;
675 }
676
677 /* traverse_specs client that finds first available trash directory suitable for
678 * the base_path. */
679 static int
pick_trash_dir_traverser(const char base_path[],const char trash_dir[],int user_specific,void * arg)680 pick_trash_dir_traverser(const char base_path[], const char trash_dir[],
681 int user_specific, void *arg)
682 {
683 if(try_create_trash_dir(trash_dir, user_specific) == 0)
684 {
685 char **const result = arg;
686 *result = strdup(trash_dir);
687 return 1;
688 }
689 return 0;
690 }
691
692 /* Checks whether the spec refers to a rooted trash directory. Returns non-zero
693 * if so, otherwise zero is returned. */
694 static int
is_rooted_trash_dir(const char spec[])695 is_rooted_trash_dir(const char spec[])
696 {
697 return starts_with_lit(spec, ROOTED_SPEC_PREFIX)
698 && spec[ROOTED_SPEC_PREFIX_LEN] != '\0';
699 }
700
701 int
trash_has_path(const char path[])702 trash_has_path(const char path[])
703 {
704 return get_resident_type(path) != TRT_OUT_OF_TRASH;
705 }
706
707 int
trash_has_path_at(const char trash_dir[],const char path[])708 trash_has_path_at(const char trash_dir[], const char path[])
709 {
710 if(trash_dir == NULL)
711 {
712 return trash_has_path(path);
713 }
714
715 return path_is(PREFIXED_WITH, path, trash_dir);
716 }
717
718 /* Gets status of file relative to trash directories. Returns the status. */
719 static TrashResidentType
get_resident_type(const char path[])720 get_resident_type(const char path[])
721 {
722 TrashResidentType resident_type = TRT_OUT_OF_TRASH;
723 traverse_specs(path, &get_resident_type_traverser, &resident_type);
724 return resident_type;
725 }
726
727 /* traverse_specs client that check that the path is under one of trash
728 * directories. Accepts pointer to TrashResidentType as argument. */
729 static int
get_resident_type_traverser(const char path[],const char trash_dir[],int user_specific,void * arg)730 get_resident_type_traverser(const char path[], const char trash_dir[],
731 int user_specific, void *arg)
732 {
733 if(path_is(PREFIXED_WITH, path, trash_dir))
734 {
735 TrashResidentType *const result = arg;
736 char *const parent_dir = strdup(path);
737
738 remove_last_path_component(parent_dir);
739 *result = path_is(SAME_AS, parent_dir, trash_dir)
740 ? TRT_IN_TRASH
741 : TRT_IN_REMOVED_DIRECTORY;
742
743 free(parent_dir);
744
745 return 1;
746 }
747 return 0;
748 }
749
750 int
trash_is_at_path(const char path[])751 trash_is_at_path(const char path[])
752 {
753 int trash_directory = 0;
754 traverse_specs(path, &is_trash_directory_traverser, &trash_directory);
755 return trash_directory;
756 }
757
758 /* traverse_specs client that check that the path is one of trash
759 * directories. */
760 static int
is_trash_directory_traverser(const char path[],const char trash_dir[],int user_specific,void * arg)761 is_trash_directory_traverser(const char path[], const char trash_dir[],
762 int user_specific, void *arg)
763 {
764 if(path_is(SAME_AS, path, trash_dir))
765 {
766 int *const result = arg;
767 *result = 1;
768 return 1;
769 }
770 return 0;
771 }
772
773 /* Performs specified check on a path. The check is constructed in such a way
774 * that all path components except for the last one are expanded. Returns
775 * non-zero if path passes the check, otherwise zero is returned. */
776 static int
path_is(PathCheckType check,const char path[],const char other[])777 path_is(PathCheckType check, const char path[], const char other[])
778 {
779 char path_real[PATH_MAX*2], other_real[PATH_MAX*2];
780
781 make_real_path(path, path_real, sizeof(path_real));
782 make_real_path(other, other_real, sizeof(other_real));
783
784 return (check == PREFIXED_WITH)
785 ? path_starts_with(path_real, other_real)
786 : (stroscmp(path_real, other_real) == 0);
787 }
788
789 /* An optimized for entries version of path_is(). */
790 static int
entry_is(PathCheckType check,trash_entry_t * entry,const char other[])791 entry_is(PathCheckType check, trash_entry_t *entry, const char other[])
792 {
793 char other_real[PATH_MAX*2 + 1];
794 make_real_path(other, other_real, sizeof(other_real));
795
796 const char *entry_real = get_real_trash_name(entry);
797
798 return (check == PREFIXED_WITH)
799 ? path_starts_with(entry_real, other_real)
800 : (stroscmp(entry_real, other_real) == 0);
801 }
802
803 /* Retrieves path to the entry with all symbolic but last one resolved.
804 * Returns the path. */
805 static const char *
get_real_trash_name(trash_entry_t * entry)806 get_real_trash_name(trash_entry_t *entry)
807 {
808 if(entry->real_trash_name == NULL)
809 {
810 char real[PATH_MAX*2 + 1];
811 make_real_path(entry->trash_name, real, sizeof(real));
812 entry->real_trash_name = strdup(real);
813 }
814 return entry->real_trash_name;
815 }
816
817 /* Resolves all but last path components in the path. Permanently caches
818 * results of previous invocations. */
819 static void
make_real_path(const char path[],char buf[],size_t buf_len)820 make_real_path(const char path[], char buf[], size_t buf_len)
821 {
822 /* This cache only grows, but as number of distinct trash directories is
823 * likely to be limited, not much space should be wasted. */
824 static char **link, **real;
825 static int nrecords;
826
827 int pos;
828 char copy[PATH_MAX*2];
829 char real_dir[PATH_MAX*2];
830
831 copy_str(copy, sizeof(copy), path);
832 remove_last_path_component(copy);
833
834 pos = string_array_pos(link, nrecords, copy);
835 if(pos != -1)
836 {
837 copy_str(real_dir, sizeof(real_dir), real[pos]);
838 }
839 else if(os_realpath(copy, real_dir) != real_dir)
840 {
841 copy_str(real_dir, sizeof(real_dir), path);
842 }
843 else
844 {
845 if(add_to_string_array(&link, nrecords, copy) == nrecords + 1)
846 {
847 if(add_to_string_array(&real, nrecords, real_dir) == nrecords + 1)
848 {
849 ++nrecords;
850 }
851 else
852 {
853 free(link[nrecords]);
854 }
855 }
856 }
857
858 build_path(buf, buf_len, real_dir, get_last_path_component(path));
859 }
860
861 /* Calls client traverser for each trash directory specification defined by
862 * specs array. */
863 static void
traverse_specs(const char base_path[],traverser client,void * arg)864 traverse_specs(const char base_path[], traverser client, void *arg)
865 {
866 int i;
867 for(i = 0; i < nspecs; ++i)
868 {
869 char *to_free = NULL;
870 const char *trash_dir;
871 int with_uid;
872
873 char *const spec = expand_uid(specs[i], &with_uid);
874 if(is_rooted_trash_dir(spec))
875 {
876 to_free = get_rooted_trash_dir(base_path, spec);
877 trash_dir = to_free;
878 }
879 else
880 {
881 trash_dir = spec;
882 }
883
884 if(trash_dir != NULL && client(base_path, trash_dir, with_uid, arg))
885 {
886 free(to_free);
887 free(spec);
888 break;
889 }
890
891 free(to_free);
892 free(spec);
893 }
894 }
895
896 /* Expands trailing %u into real user ID. Sets *expanded to reflect the fact
897 * whether expansion took place. Returns newly allocated string. */
898 static char *
expand_uid(const char spec[],int * expanded)899 expand_uid(const char spec[], int *expanded)
900 {
901 #ifndef _WIN32
902 char *copy = strdup(spec);
903 *expanded = cut_suffix(copy, "%u");
904 if(*expanded)
905 {
906 char uid_str[32];
907 size_t len = strlen(copy);
908 snprintf(uid_str, sizeof(uid_str), "%lu", (unsigned long)getuid());
909 (void)strappend(©, &len, uid_str);
910 }
911 return copy;
912 #else
913 *expanded = 0;
914 return strdup(spec);
915 #endif
916 }
917
918 /* Expands rooted trash directory specification into a string. Returns NULL on
919 * error, otherwise newly allocated string that should be freed by the caller is
920 * returned. */
921 static char *
get_rooted_trash_dir(const char base_path[],const char spec[])922 get_rooted_trash_dir(const char base_path[], const char spec[])
923 {
924 char full[PATH_MAX + 1];
925 if(get_mount_point(base_path, sizeof(full), full) == 0)
926 {
927 return format_root_spec(spec, full);
928 }
929
930 return NULL;
931 }
932
933 /* Expands rooted trash directory specification into a string. Returns newly
934 * allocated string that should be freed by the caller. */
935 static char *
format_root_spec(const char spec[],const char mount_point[])936 format_root_spec(const char spec[], const char mount_point[])
937 {
938 return format_str("%s%s%s", mount_point,
939 ends_with_slash(mount_point) ? "" : "/", spec + ROOTED_SPEC_PREFIX_LEN);
940 }
941
942 const char *
trash_get_real_name_of(const char trash_path[])943 trash_get_real_name_of(const char trash_path[])
944 {
945 const char *real_name = after_last(trash_path, '/');
946
947 assert(is_path_absolute(trash_path) && "Expected full path to a file.");
948
949 if(get_resident_type(trash_path) == TRT_IN_TRASH)
950 {
951 const size_t prefix_len = strspn(real_name, "0123456789");
952 if(real_name[prefix_len] == '_')
953 {
954 real_name += prefix_len + 1;
955 }
956 }
957
958 return real_name;
959 }
960
961 void
trash_prune_dead_entries(void)962 trash_prune_dead_entries(void)
963 {
964 int i, j;
965
966 j = 0;
967 for(i = 0; i < trash_list_size; ++i)
968 {
969 if(!path_exists(trash_list[i].trash_name, NODEREF))
970 {
971 free(trash_list[i].path);
972 free(trash_list[i].trash_name);
973 continue;
974 }
975
976 trash_list[j++] = trash_list[i];
977 }
978 trash_list_size = j;
979 }
980
981 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
982 /* vim: set cinoptions+=t0 filetype=c : */
983