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(&copy, &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