1 /* vifm
2  * Copyright (C) 2001 Ken Steen.
3  * Copyright (C) 2011 xaizek.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19 
20 #include "fops_misc.h"
21 
22 #include <sys/stat.h> /* stat */
23 #include <sys/types.h> /* gid_t uid_t */
24 
25 #include <string.h> /* strdup() strlen() */
26 
27 #include "cfg/config.h"
28 #include "compat/os.h"
29 #include "modes/dialogs/msg_dialog.h"
30 #include "ui/cancellation.h"
31 #include "ui/fileview.h"
32 #include "ui/statusbar.h"
33 #include "ui/ui.h"
34 #include "utils/cancellation.h"
35 #include "utils/fs.h"
36 #include "utils/path.h"
37 #include "utils/str.h"
38 #include "utils/string_array.h"
39 #include "utils/test_helpers.h"
40 #include "utils/utils.h"
41 #include "cmd_completion.h"
42 #include "filelist.h"
43 #include "flist_pos.h"
44 #include "flist_sel.h"
45 #include "fops_common.h"
46 #include "registers.h"
47 #include "trash.h"
48 #include "undo.h"
49 
50 /* Arguments pack for dir_size_bg() background function. */
51 typedef struct
52 {
53 	char *path; /* Full path to directory to process, will be freed. */
54 	int force;  /* Whether cached values should be ignored. */
55 }
56 dir_size_args_t;
57 
58 static int delete_file(dir_entry_t *entry, ops_t *ops, int reg, int use_trash,
59 		int nested);
60 static const char * get_top_dir(const view_t *view);
61 static void delete_files_in_bg(bg_op_t *bg_op, void *arg);
62 static void delete_file_in_bg(ops_t *ops, const char path[], int use_trash);
63 static int prepare_register(int reg);
64 static void change_link_cb(const char new_target[]);
65 static int complete_filename(const char str[], void *arg);
66 static int is_clone_list_ok(int count, char *list[]);
67 TSTATIC const char * gen_clone_name(const char dir[], const char normal_name[]);
68 static int clone_file(const dir_entry_t *entry, const char path[],
69 		const char clone[], ops_t *ops);
70 static void get_group_file_list(char *list[], int count, char buf[]);
71 static void go_to_first_file(view_t *view, char *names[], int count);
72 static void update_dir_entry_size(dir_entry_t *entry, int force);
73 static void start_dir_size_calc(const char path[], int force);
74 static void dir_size_bg(bg_op_t *bg_op, void *arg);
75 static void dir_size(bg_op_t *bg_op, char path[], int force);
76 static int bg_cancellation_hook(void *arg);
77 static void redraw_after_path_change(view_t *view, const char path[]);
78 #ifndef _WIN32
79 static void change_owner_cb(const char new_owner[]);
80 static int complete_owner(const char str[], void *arg);
81 static void change_group_cb(const char new_group[]);
82 static int complete_group(const char str[], void *arg);
83 #endif
84 
85 int
fops_delete(view_t * view,int reg,int use_trash)86 fops_delete(view_t *view, int reg, int use_trash)
87 {
88 	char undo_msg[COMMAND_GROUP_INFO_LEN];
89 	int i;
90 	dir_entry_t *entry;
91 	int nmarked_files;
92 	ops_t *ops;
93 	const char *const top_dir = get_top_dir(view);
94 	const char *const curr_dir = top_dir == NULL ? flist_get_dir(view) : top_dir;
95 
96 	if(!fops_view_can_be_changed(view))
97 	{
98 		return 0;
99 	}
100 
101 	use_trash = use_trash && cfg.use_trash;
102 
103 	strlist_t marked = {};
104 	entry = NULL;
105 	while(iter_marked_entries(view, &entry))
106 	{
107 		if(!flist_custom_active(view))
108 		{
109 			char name[NAME_MAX + 1];
110 			format_entry_name(entry, NF_FULL, sizeof(name), name);
111 			marked.nitems = add_to_string_array(&marked.items, marked.nitems, name);
112 			continue;
113 		}
114 
115 		char short_path[PATH_MAX + 1];
116 		get_short_path_of(view, entry, NF_FULL, 0, sizeof(short_path), short_path);
117 		marked.nitems = add_to_string_array(&marked.items, marked.nitems,
118 				short_path);
119 	}
120 	if(!confirm_deletion(marked.items, marked.nitems, use_trash))
121 	{
122 		free_string_array(marked.items, marked.nitems);
123 		return 0;
124 	}
125 	free_string_array(marked.items, marked.nitems);
126 
127 	/* This check for the case when we are for sure in the trash. */
128 	if(use_trash && top_dir != NULL && trash_has_path(top_dir))
129 	{
130 		show_error_msg("Can't perform deletion",
131 				"Current directory is under trash directory");
132 		return 0;
133 	}
134 
135 	if(use_trash)
136 	{
137 		reg = prepare_register(reg);
138 	}
139 
140 	snprintf(undo_msg, sizeof(undo_msg), "%celete in %s: ", use_trash ? 'd' : 'D',
141 			replace_home_part(curr_dir));
142 	fops_append_marked_files(view, undo_msg, NULL);
143 	un_group_open(undo_msg);
144 
145 	ops = fops_get_ops(OP_REMOVE, use_trash ? "deleting" : "Deleting", curr_dir,
146 			curr_dir);
147 
148 	nmarked_files = fops_enqueue_marked_files(ops, view, NULL, use_trash);
149 
150 	entry = NULL;
151 	i = 0;
152 	while(iter_marked_entries(view, &entry) && !ui_cancellation_requested())
153 	{
154 		int result;
155 
156 		fops_progress_msg("Deleting files", i++, nmarked_files);
157 		result = delete_file(entry, ops, reg, use_trash, 0);
158 
159 		if(result == 0 && entry_to_pos(view, entry) == view->list_pos)
160 		{
161 			if(view->list_pos + 1 < view->list_rows)
162 			{
163 				++view->list_pos;
164 			}
165 		}
166 
167 		ops_advance(ops, result == 0);
168 	}
169 
170 	regs_update_unnamed(reg);
171 
172 	un_group_close();
173 
174 	ui_view_reset_selection_and_reload(view);
175 	ui_view_schedule_reload(view == curr_view ? other_view : curr_view);
176 
177 	ui_sb_msgf("%d %s %celeted%s", ops->succeeded,
178 			(ops->succeeded == 1) ? "file" : "files", use_trash ? 'd' : 'D',
179 			fops_get_cancellation_suffix());
180 
181 	fops_free_ops(ops);
182 	regs_sync_to_shared_memory();
183 	return 1;
184 }
185 
186 int
fops_delete_current(view_t * view,int use_trash,int nested)187 fops_delete_current(view_t *view, int use_trash, int nested)
188 {
189 	char undo_msg[COMMAND_GROUP_INFO_LEN];
190 	dir_entry_t *entry;
191 	ops_t *ops;
192 	const char *const top_dir = get_top_dir(view);
193 	const char *const curr_dir = top_dir == NULL ? flist_get_dir(view) : top_dir;
194 
195 	use_trash = use_trash && cfg.use_trash;
196 
197 	/* This check for the case when we are for sure in the trash. */
198 	if(use_trash && top_dir != NULL && trash_has_path(top_dir))
199 	{
200 		show_error_msg("Can't perform deletion",
201 				"Current directory is under trash directory");
202 		return 0;
203 	}
204 
205 	snprintf(undo_msg, sizeof(undo_msg), "%celete in %s: ", use_trash ? 'd' : 'D',
206 			replace_home_part(curr_dir));
207 	if(!nested)
208 	{
209 		un_group_open(undo_msg);
210 	}
211 
212 	ops = fops_get_ops(OP_REMOVE, use_trash ? "deleting" : "Deleting", curr_dir,
213 			curr_dir);
214 
215 	entry = &view->dir_entry[view->list_pos];
216 
217 	fops_progress_msg("Deleting files", 0, 1);
218 	(void)delete_file(entry, ops, BLACKHOLE_REG_NAME, use_trash, nested);
219 
220 	if(!nested)
221 	{
222 		un_group_close();
223 		ui_views_reload_filelists();
224 	}
225 
226 	ui_sb_msgf("%d %s %celeted%s", ops->succeeded,
227 			(ops->succeeded == 1) ? "file" : "files", use_trash ? 'd' : 'D',
228 			fops_get_cancellation_suffix());
229 
230 	fops_free_ops(ops);
231 	return 1;
232 }
233 
234 /* Removes single file specified by its entry.  Returns zero on success,
235  * otherwise non-zero is returned. */
236 static int
delete_file(dir_entry_t * entry,ops_t * ops,int reg,int use_trash,int nested)237 delete_file(dir_entry_t *entry, ops_t *ops, int reg, int use_trash, int nested)
238 {
239 	char full_path[PATH_MAX + 1];
240 	int result;
241 
242 	get_full_path_of(entry, sizeof(full_path), full_path);
243 
244 	if(!use_trash)
245 	{
246 		result = perform_operation(OP_REMOVE, ops, NULL, full_path, NULL);
247 		/* For some reason "rm" sometimes returns 0 on cancellation. */
248 		if(path_exists(full_path, NODEREF))
249 		{
250 			result = -1;
251 		}
252 		if(result == 0)
253 		{
254 			un_group_add_op(OP_REMOVE, NULL, NULL, full_path, "");
255 		}
256 	}
257 	else if(trash_is_at_path(full_path))
258 	{
259 		show_error_msg("Can't perform deletion",
260 				"You cannot delete trash directory to trash");
261 		result = -1;
262 	}
263 	else if(trash_has_path(full_path))
264 	{
265 		show_error_msgf("Skipping file deletion",
266 				"File is already in trash: %s", full_path);
267 		result = -1;
268 	}
269 	else
270 	{
271 		const OPS op = nested ? OP_MOVETMP4 : OP_MOVE;
272 		char *const dest = trash_gen_path(entry->origin, entry->name);
273 		if(dest != NULL)
274 		{
275 			result = perform_operation(op, ops, NULL, full_path, dest);
276 			/* For some reason "rm" sometimes returns 0 on cancellation. */
277 			if(path_exists(full_path, DEREF))
278 			{
279 				result = -1;
280 			}
281 			if(result == 0)
282 			{
283 				un_group_add_op(op, NULL, NULL, full_path, dest);
284 				regs_append(reg, dest);
285 			}
286 			free(dest);
287 		}
288 		else
289 		{
290 			show_error_msgf("No trash directory is available",
291 					"Either correct trash directory paths or prune files.  "
292 					"Deletion failed on: %s", entry->name);
293 			result = -1;
294 		}
295 	}
296 
297 	return result;
298 }
299 
300 int
fops_delete_bg(view_t * view,int use_trash)301 fops_delete_bg(view_t *view, int use_trash)
302 {
303 	char task_desc[COMMAND_GROUP_INFO_LEN];
304 	bg_args_t *args;
305 	const char *const top_dir = get_top_dir(view);
306 	const char *const curr_dir = top_dir == NULL ? flist_get_dir(view) : top_dir;
307 
308 	if(!fops_view_can_be_changed(view))
309 	{
310 		return 0;
311 	}
312 
313 	use_trash = use_trash && cfg.use_trash;
314 
315 	if(use_trash && top_dir != NULL && trash_has_path(top_dir))
316 	{
317 		show_error_msg("Can't perform deletion",
318 				"Current directory is under trash directory");
319 		return 0;
320 	}
321 
322 	args = calloc(1, sizeof(*args));
323 	args->use_trash = use_trash;
324 
325 	fops_prepare_for_bg_task(view, args);
326 	if(args->sel_list_len == 0)
327 	{
328 		fops_free_bg_args(args);
329 		ui_sb_msg("Nothing to delete");
330 		return 1;
331 	}
332 
333 	if(use_trash)
334 	{
335 		unsigned int i;
336 		for(i = 0U; i < args->sel_list_len; ++i)
337 		{
338 			const char *const full_file_path = args->sel_list[i];
339 			if(trash_is_at_path(full_file_path))
340 			{
341 				show_error_msg("Can't perform deletion",
342 						"You cannot delete trash directory to trash");
343 				fops_free_bg_args(args);
344 				return 0;
345 			}
346 			else if(trash_has_path(full_file_path))
347 			{
348 				show_error_msgf("Skipping file deletion",
349 						"File is already in trash: %s", full_file_path);
350 				fops_free_bg_args(args);
351 				return 0;
352 			}
353 		}
354 	}
355 
356 	if(!confirm_deletion(args->sel_list, args->sel_list_len, use_trash))
357 	{
358 		fops_free_bg_args(args);
359 		return 0;
360 	}
361 
362 	snprintf(task_desc, sizeof(task_desc), "%celete in %s: ",
363 			use_trash ? 'd' : 'D', replace_home_part(curr_dir));
364 
365 	fops_append_marked_files(view, task_desc, NULL);
366 
367 	args->ops = fops_get_bg_ops(use_trash ? OP_REMOVE : OP_REMOVESL,
368 			use_trash ? "deleting" : "Deleting", args->path);
369 
370 	if(bg_execute(task_desc, "...", args->sel_list_len, 1, &delete_files_in_bg,
371 				args) != 0)
372 	{
373 		fops_free_bg_args(args);
374 
375 		show_error_msg("Can't perform deletion",
376 				"Failed to initiate background operation");
377 	}
378 	return 0;
379 }
380 
381 /* Retrieves root directory of file system sub-tree (for regular or tree views).
382  * Returns the path or NULL (for custom views). */
383 static const char *
get_top_dir(const view_t * view)384 get_top_dir(const view_t *view)
385 {
386 	if(flist_custom_active(view) && !cv_tree(view->custom.type))
387 	{
388 		return NULL;
389 	}
390 	return flist_get_dir(view);
391 }
392 
393 /* Entry point for a background task that deletes files. */
394 static void
delete_files_in_bg(bg_op_t * bg_op,void * arg)395 delete_files_in_bg(bg_op_t *bg_op, void *arg)
396 {
397 	size_t i;
398 	bg_args_t *const args = arg;
399 	ops_t *ops = args->ops;
400 	fops_bg_ops_init(ops, bg_op);
401 
402 	if(ops->use_system_calls)
403 	{
404 		size_t i;
405 		bg_op_set_descr(bg_op, "estimating...");
406 		for(i = 0U; i < args->sel_list_len; ++i)
407 		{
408 			const char *const src = args->sel_list[i];
409 			char *trash_dir = (args->use_trash ? trash_pick_dir(src) : args->path);
410 			ops_enqueue(ops, src, trash_dir);
411 			if(trash_dir != args->path)
412 			{
413 				free(trash_dir);
414 			}
415 		}
416 	}
417 
418 	for(i = 0U; i < args->sel_list_len; ++i)
419 	{
420 		const char *const src = args->sel_list[i];
421 		bg_op_set_descr(bg_op, src);
422 		delete_file_in_bg(ops, src, args->use_trash);
423 		++bg_op->done;
424 	}
425 
426 	fops_free_bg_args(args);
427 }
428 
429 /* Actual implementation of background file removal. */
430 static void
delete_file_in_bg(ops_t * ops,const char path[],int use_trash)431 delete_file_in_bg(ops_t *ops, const char path[], int use_trash)
432 {
433 	if(!use_trash)
434 	{
435 		(void)perform_operation(OP_REMOVE, ops, NULL, path, NULL);
436 		return;
437 	}
438 
439 	if(!trash_is_at_path(path))
440 	{
441 		const char *const fname = get_last_path_component(path);
442 		char *const trash_name = trash_gen_path(path, fname);
443 		const char *const dest = (trash_name != NULL) ? trash_name : fname;
444 		(void)perform_operation(OP_MOVE, ops, NULL, path, dest);
445 		free(trash_name);
446 	}
447 }
448 
449 int
fops_yank(view_t * view,int reg)450 fops_yank(view_t *view, int reg)
451 {
452 	int nyanked_files;
453 	dir_entry_t *entry;
454 
455 	reg = prepare_register(reg);
456 
457 	nyanked_files = 0;
458 	entry = NULL;
459 	while(iter_marked_entries(view, &entry))
460 	{
461 		char full_path[PATH_MAX + 1];
462 		get_full_path_of(entry, sizeof(full_path), full_path);
463 
464 		if(regs_append(reg, full_path) == 0)
465 		{
466 			++nyanked_files;
467 		}
468 	}
469 
470 	regs_update_unnamed(reg);
471 
472 	ui_sb_msgf("%d file%s yanked", nyanked_files,
473 			(nyanked_files == 1) ? "" : "s");
474 
475 	regs_sync_to_shared_memory();
476 
477 	return 1;
478 }
479 
480 /* Transforms "A-"Z register to "a-"z or clears the reg.  So that for "A-"Z new
481  * values will be appended to "a-"z, for other registers old values will be
482  * removed.  Returns possibly modified value of the reg parameter. */
483 static int
prepare_register(int reg)484 prepare_register(int reg)
485 {
486 	if(reg >= 'A' && reg <= 'Z')
487 	{
488 		reg += 'a' - 'A';
489 		/* We're going to append to a register and thus need its contents to be up
490 		 * to date. */
491 		regs_sync_from_shared_memory();
492 	}
493 	else
494 	{
495 		regs_clear(reg);
496 	}
497 	return reg;
498 }
499 
500 int
fops_retarget(view_t * view)501 fops_retarget(view_t *view)
502 {
503 	char full_path[PATH_MAX + 1];
504 	char linkto[PATH_MAX + 1];
505 	const dir_entry_t *const entry = get_current_entry(view);
506 
507 	if(!symlinks_available())
508 	{
509 		show_error_msg("Symbolic Links Error",
510 				"Your OS doesn't support symbolic links");
511 		return 0;
512 	}
513 	if(!fops_view_can_be_changed(view))
514 	{
515 		return 0;
516 	}
517 
518 	if(fentry_is_fake(entry))
519 	{
520 		ui_sb_err("Entry doesn't correspond to a file");
521 		return 1;
522 	}
523 	if(entry->type != FT_LINK)
524 	{
525 		ui_sb_err("File is not a symbolic link");
526 		return 1;
527 	}
528 
529 	get_full_path_of(entry, sizeof(full_path), full_path);
530 	if(get_link_target(full_path, linkto, sizeof(linkto)) != 0)
531 	{
532 		show_error_msg("Error", "Can't read link");
533 		return 0;
534 	}
535 
536 	fops_line_prompt("Link target: ", linkto, &change_link_cb, &complete_filename,
537 			0);
538 	return 0;
539 }
540 
541 /* Handles users response for new link target prompt. */
542 static void
change_link_cb(const char new_target[])543 change_link_cb(const char new_target[])
544 {
545 	char undo_msg[2*PATH_MAX + 32];
546 	char full_path[PATH_MAX + 1];
547 	char linkto[PATH_MAX + 1];
548 	const char *fname;
549 	ops_t *ops;
550 	const char *const curr_dir = flist_get_dir(curr_view);
551 
552 	if(is_null_or_empty(new_target))
553 	{
554 		return;
555 	}
556 
557 	curr_stats.confirmed = 1;
558 
559 	get_current_full_path(curr_view, sizeof(full_path), full_path);
560 	if(get_link_target(full_path, linkto, sizeof(linkto)) != 0)
561 	{
562 		show_error_msg("Error", "Can't read link");
563 		return;
564 	}
565 
566 	ops = fops_get_ops(OP_SYMLINK2, "re-targeting", curr_dir, curr_dir);
567 
568 	fname = get_last_path_component(full_path);
569 	snprintf(undo_msg, sizeof(undo_msg), "cl in %s: on %s from \"%s\" to \"%s\"",
570 			replace_home_part(flist_get_dir(curr_view)), fname, linkto, new_target);
571 	un_group_open(undo_msg);
572 
573 	if(perform_operation(OP_REMOVESL, ops, NULL, full_path, NULL) == 0)
574 	{
575 		un_group_add_op(OP_REMOVESL, NULL, NULL, full_path, linkto);
576 	}
577 	if(perform_operation(OP_SYMLINK2, ops, NULL, new_target, full_path) == 0)
578 	{
579 		un_group_add_op(OP_SYMLINK2, NULL, NULL, new_target, full_path);
580 	}
581 
582 	un_group_close();
583 
584 	fops_free_ops(ops);
585 }
586 
587 /* Command-line path completion callback.  Returns completion start offset. */
588 static int
complete_filename(const char str[],void * arg)589 complete_filename(const char str[], void *arg)
590 {
591 	const char *name_begin = after_last(str, '/');
592 	return name_begin - str + filename_completion(str, CT_ALL_WOE, 0);
593 }
594 
595 int
fops_clone(view_t * view,char * list[],int nlines,int force,int copies)596 fops_clone(view_t *view, char *list[], int nlines, int force, int copies)
597 {
598 	int i;
599 	char undo_msg[COMMAND_GROUP_INFO_LEN + 1];
600 	char dst_path[PATH_MAX + 1];
601 	char **marked;
602 	size_t nmarked;
603 	int custom_fnames;
604 	int nmarked_files;
605 	int with_dir = 0;
606 	int from_file;
607 	dir_entry_t *entry;
608 	ops_t *ops;
609 	const char *const curr_dir = flist_get_dir(view);
610 
611 	if(!fops_can_read_marked_files(view))
612 	{
613 		return 0;
614 	}
615 
616 	if(nlines == 1)
617 	{
618 		with_dir = fops_check_dir_path(view, list[0], dst_path, sizeof(dst_path));
619 		if(with_dir)
620 		{
621 			nlines = 0;
622 		}
623 		else if(!fops_view_can_be_extended(view, -1))
624 		{
625 			return 0;
626 		}
627 	}
628 	else
629 	{
630 		if(!fops_view_can_be_extended(view, -1))
631 		{
632 			return 0;
633 		}
634 
635 		copy_str(dst_path, sizeof(dst_path), fops_get_dst_dir(view, -1));
636 	}
637 	if(!fops_is_dir_writable(with_dir ? DR_DESTINATION : DR_CURRENT, dst_path))
638 	{
639 		return 0;
640 	}
641 
642 	marked = fops_grab_marked_files(view, &nmarked);
643 
644 	from_file = nlines < 0;
645 	if(from_file)
646 	{
647 		list = fops_edit_list(nmarked, marked, &nlines, 0);
648 		if(list == NULL)
649 		{
650 			free_string_array(marked, nmarked);
651 			return 0;
652 		}
653 	}
654 
655 	free_string_array(marked, nmarked);
656 
657 	if(nlines > 0 &&
658 			(!fops_is_name_list_ok(nmarked, nlines, list, NULL) ||
659 			(!force && !is_clone_list_ok(nlines, list))))
660 	{
661 		redraw_view(view);
662 		if(from_file)
663 		{
664 			free_string_array(list, nlines);
665 		}
666 		return 1;
667 	}
668 
669 	flist_sel_stash(view);
670 
671 	if(with_dir)
672 	{
673 		snprintf(undo_msg, sizeof(undo_msg), "clone in %s to %s: ", curr_dir,
674 				list[0]);
675 	}
676 	else
677 	{
678 		snprintf(undo_msg, sizeof(undo_msg), "clone in %s: ", curr_dir);
679 	}
680 	fops_append_marked_files(view, undo_msg, list);
681 
682 	ops = fops_get_ops(OP_COPY, "Cloning", curr_dir,
683 			with_dir ? list[0] : curr_dir);
684 
685 	nmarked_files = fops_enqueue_marked_files(ops, view, dst_path, 0);
686 
687 	custom_fnames = (nlines > 0);
688 
689 	un_group_open(undo_msg);
690 	entry = NULL;
691 	i = 0;
692 	while(iter_marked_entries(view, &entry) && !ui_cancellation_requested())
693 	{
694 		int err;
695 		int j;
696 		const char *const name = entry->name;
697 		const char *const clone_dst = with_dir ? dst_path : entry->origin;
698 		const char *clone_name;
699 		if(custom_fnames)
700 		{
701 			clone_name = list[i];
702 		}
703 		else
704 		{
705 			clone_name = path_exists_at(clone_dst, name, NODEREF)
706 			           ? gen_clone_name(clone_dst, name)
707 			           : name;
708 		}
709 
710 		fops_progress_msg("Cloning files", i, nmarked_files);
711 
712 		err = 0;
713 		for(j = 0; j < copies; ++j)
714 		{
715 			if(path_exists_at(clone_dst, clone_name, NODEREF))
716 			{
717 				clone_name = gen_clone_name(clone_dst, custom_fnames ? list[i] : name);
718 			}
719 			err += clone_file(entry, clone_dst, clone_name, ops);
720 		}
721 
722 		/* Don't update cursor position if more than one file is cloned. */
723 		if(nmarked == 1U)
724 		{
725 			fentry_rename(view, entry, clone_name);
726 		}
727 		ops_advance(ops, err == 0);
728 
729 		++i;
730 	}
731 	un_group_close();
732 
733 	ui_views_reload_filelists();
734 	if(from_file)
735 	{
736 		free_string_array(list, nlines);
737 	}
738 
739 	ui_sb_msgf("%d file%s cloned%s", ops->succeeded,
740 			(ops->succeeded == 1) ? "" : "s", fops_get_cancellation_suffix());
741 
742 	fops_free_ops(ops);
743 	return 1;
744 }
745 
746 /* Checks consistency of user-supplied list of names for clones. */
747 static int
is_clone_list_ok(int count,char * list[])748 is_clone_list_ok(int count, char *list[])
749 {
750 	int i;
751 	for(i = 0; i < count; ++i)
752 	{
753 		if(path_exists(list[i], NODEREF))
754 		{
755 			ui_sb_errf("File \"%s\" already exists", list[i]);
756 			return 0;
757 		}
758 	}
759 	return 1;
760 }
761 
762 /* Generates name of clone for a file.  Returns pointer to statically allocated
763  * buffer. */
764 TSTATIC const char *
gen_clone_name(const char dir[],const char normal_name[])765 gen_clone_name(const char dir[], const char normal_name[])
766 {
767 	static char result[NAME_MAX + 1];
768 
769 	char extension[NAME_MAX + 1];
770 	int i;
771 	size_t len;
772 	char *p;
773 
774 	copy_str(result, sizeof(result), normal_name);
775 	chosp(result);
776 
777 	copy_str(extension, sizeof(extension),
778 			cut_extension(result[0] == '.' ? &result[1] : result));
779 
780 	len = strlen(result);
781 	i = 1;
782 	if(result[len - 1] == ')' && (p = strrchr(result, '(')) != NULL)
783 	{
784 		char *t;
785 		long l;
786 		if((l = strtol(p + 1, &t, 10)) > 0 && t[1] == '\0')
787 		{
788 			len = p - result;
789 			i = l + 1;
790 		}
791 	}
792 
793 	do
794 	{
795 		snprintf(result + len, sizeof(result) - len, "(%d)%s%s", i++,
796 				(extension[0] == '\0') ? "" : ".", extension);
797 	}
798 	while(path_exists_at(dir, result, NODEREF));
799 
800 	return result;
801 }
802 
803 /* Clones single file/directory to directory specified by the path under name in
804  * the clone.  Returns zero on success, otherwise non-zero is returned. */
805 static int
clone_file(const dir_entry_t * entry,const char path[],const char clone[],ops_t * ops)806 clone_file(const dir_entry_t *entry, const char path[], const char clone[],
807 		ops_t *ops)
808 {
809 	char full_path[PATH_MAX + 1];
810 	char clone_name[PATH_MAX + 1];
811 
812 	to_canonic_path(clone, path, clone_name, sizeof(clone_name));
813 	if(path_exists(clone_name, DEREF))
814 	{
815 		if(perform_operation(OP_REMOVESL, NULL, NULL, clone_name, NULL) != 0)
816 		{
817 			return 1;
818 		}
819 	}
820 
821 	get_full_path_of(entry, sizeof(full_path), full_path);
822 
823 	if(perform_operation(OP_COPY, ops, NULL, full_path, clone_name) != 0)
824 	{
825 		return 1;
826 	}
827 
828 	un_group_add_op(OP_COPY, NULL, NULL, full_path, clone_name);
829 	return 0;
830 }
831 
832 int
fops_mkdirs(view_t * view,int at,char ** names,int count,int create_parent)833 fops_mkdirs(view_t *view, int at, char **names, int count, int create_parent)
834 {
835 	char buf[COMMAND_GROUP_INFO_LEN + 1];
836 	int i;
837 	int n;
838 	void *cp;
839 	const char *const dst_dir = fops_get_dst_dir(view, at);
840 
841 	if(!fops_view_can_be_extended(view, at))
842 	{
843 		return 1;
844 	}
845 
846 	cp = (void *)(size_t)create_parent;
847 
848 	for(i = 0; i < count; ++i)
849 	{
850 		char full[PATH_MAX + 1];
851 
852 		if(is_in_string_array(names, i, names[i]))
853 		{
854 			ui_sb_errf("Name \"%s\" duplicates", names[i]);
855 			return 1;
856 		}
857 		if(names[i][0] == '\0')
858 		{
859 			ui_sb_errf("Name #%d is empty", i + 1);
860 			return 1;
861 		}
862 
863 		to_canonic_path(names[i], dst_dir, full, sizeof(full));
864 		if(path_exists(full, NODEREF))
865 		{
866 			ui_sb_errf("File \"%s\" already exists", names[i]);
867 			return 1;
868 		}
869 	}
870 
871 	snprintf(buf, sizeof(buf), "mkdir in %s: ", replace_home_part(dst_dir));
872 
873 	get_group_file_list(names, count, buf);
874 	un_group_open(buf);
875 	n = 0;
876 	for(i = 0; i < count && !ui_cancellation_requested(); ++i)
877 	{
878 		char full[PATH_MAX + 1];
879 		to_canonic_path(names[i], dst_dir, full, sizeof(full));
880 
881 		if(perform_operation(OP_MKDIR, NULL, cp, full, NULL) == 0)
882 		{
883 			un_group_add_op(OP_MKDIR, cp, NULL, full, "");
884 			++n;
885 		}
886 		else if(i == 0)
887 		{
888 			--i;
889 			++names;
890 			--count;
891 		}
892 	}
893 	un_group_close();
894 
895 	if(count > 0)
896 	{
897 		if(create_parent)
898 		{
899 			for(i = 0; i < count; ++i)
900 			{
901 				break_at(names[i], '/');
902 			}
903 		}
904 		go_to_first_file(view, names, count);
905 	}
906 
907 	ui_sb_msgf("%d director%s created%s", n, (n == 1) ? "y" : "ies",
908 			fops_get_cancellation_suffix());
909 	return 1;
910 }
911 
912 int
fops_mkfiles(view_t * view,int at,char * names[],int count)913 fops_mkfiles(view_t *view, int at, char *names[], int count)
914 {
915 	int i;
916 	int n;
917 	char buf[COMMAND_GROUP_INFO_LEN + 1];
918 	ops_t *ops;
919 	const char *const dst_dir = fops_get_dst_dir(view, at);
920 
921 	if(!fops_view_can_be_extended(view, at))
922 	{
923 		return 0;
924 	}
925 
926 	for(i = 0; i < count; ++i)
927 	{
928 		char full[PATH_MAX + 1];
929 
930 		if(is_in_string_array(names, i, names[i]))
931 		{
932 			ui_sb_errf("Name \"%s\" duplicates", names[i]);
933 			return 1;
934 		}
935 		if(names[i][0] == '\0')
936 		{
937 			ui_sb_errf("Name #%d is empty", i + 1);
938 			return 1;
939 		}
940 
941 		to_canonic_path(names[i], dst_dir, full, sizeof(full));
942 		if(path_exists(full, NODEREF))
943 		{
944 			ui_sb_errf("File \"%s\" already exists", names[i]);
945 			return 1;
946 		}
947 	}
948 
949 	ops = fops_get_ops(OP_MKFILE, "touching", dst_dir, dst_dir);
950 
951 	snprintf(buf, sizeof(buf), "touch in %s: ", replace_home_part(dst_dir));
952 
953 	get_group_file_list(names, count, buf);
954 	un_group_open(buf);
955 	n = 0;
956 	for(i = 0; i < count && !ui_cancellation_requested(); ++i)
957 	{
958 		char full[PATH_MAX + 1];
959 		to_canonic_path(names[i], dst_dir, full, sizeof(full));
960 
961 		if(perform_operation(OP_MKFILE, ops, NULL, full, NULL) == 0)
962 		{
963 			un_group_add_op(OP_MKFILE, NULL, NULL, full, "");
964 			++n;
965 		}
966 	}
967 	un_group_close();
968 
969 	if(n > 0)
970 	{
971 		go_to_first_file(view, names, count);
972 	}
973 
974 	ui_sb_msgf("%d file%s created%s", n, (n == 1) ? "" : "s",
975 			fops_get_cancellation_suffix());
976 
977 	fops_free_ops(ops);
978 	return 1;
979 }
980 
981 /* Fills undo message buffer with list of files.  buf should be at least
982  * COMMAND_GROUP_INFO_LEN characters length. */
983 static void
get_group_file_list(char * list[],int count,char buf[])984 get_group_file_list(char *list[], int count, char buf[])
985 {
986 	size_t len;
987 	int i;
988 
989 	len = strlen(buf);
990 	for(i = 0; i < count && len < COMMAND_GROUP_INFO_LEN; ++i)
991 	{
992 		fops_append_fname(buf, len, list[i]);
993 		len = strlen(buf);
994 	}
995 }
996 
997 /* Sets view cursor to point at first file found found in supplied list of file
998  * names. */
999 static void
go_to_first_file(view_t * view,char * names[],int count)1000 go_to_first_file(view_t *view, char *names[], int count)
1001 {
1002 	load_dir_list(view, 1);
1003 
1004 	int i;
1005 	for(i = 0; i < view->list_rows; ++i)
1006 	{
1007 		if(is_in_string_array(names, count, view->dir_entry[i].name))
1008 		{
1009 			view->list_pos = i;
1010 			break;
1011 		}
1012 	}
1013 
1014 	redraw_view(view);
1015 }
1016 
1017 int
fops_restore(view_t * view)1018 fops_restore(view_t *view)
1019 {
1020 	int m, n;
1021 	dir_entry_t *entry;
1022 
1023 	/* This is general check for regular views only. */
1024 	if(!flist_custom_active(view) && !trash_is_at_path(view->curr_dir))
1025 	{
1026 		show_error_msg("Restore error", "Not a top-level trash directory.");
1027 		return 0;
1028 	}
1029 
1030 	un_group_open("restore: ");
1031 	un_group_close();
1032 
1033 	m = 0;
1034 	n = 0;
1035 	entry = NULL;
1036 	while(iter_marked_entries(view, &entry) && !ui_cancellation_requested())
1037 	{
1038 		char full_path[PATH_MAX + 1];
1039 		get_full_path_of(entry, sizeof(full_path), full_path);
1040 
1041 		if(trash_is_at_path(entry->origin) && trash_restore(full_path) == 0)
1042 		{
1043 			++m;
1044 		}
1045 		++n;
1046 	}
1047 
1048 	ui_view_schedule_reload(view);
1049 
1050 	ui_sb_msgf("Restored %d of %d%s", m, n, fops_get_cancellation_suffix());
1051 	return 1;
1052 }
1053 
1054 void
fops_size_bg(view_t * view,int force)1055 fops_size_bg(view_t *view, int force)
1056 {
1057 	/* flist_set_marking(view, 1) isn't helpful here as it won't mark "..". */
1058 	int user_selection = !view->pending_marking;
1059 	flist_set_marking(view, 0);
1060 
1061 	dir_entry_t *curr = get_current_entry(view);
1062 	if(!curr->marked && user_selection)
1063 	{
1064 		update_dir_entry_size(curr, force);
1065 		return;
1066 	}
1067 
1068 	dir_entry_t *entry = NULL;
1069 	while(iter_marked_entries(view, &entry))
1070 	{
1071 		update_dir_entry_size(entry, force);
1072 	}
1073 }
1074 
1075 /* Initiates background size calculation for view entry. */
1076 static void
update_dir_entry_size(dir_entry_t * entry,int force)1077 update_dir_entry_size(dir_entry_t *entry, int force)
1078 {
1079 	if(fentry_is_fake(entry) || !fentry_is_dir(entry))
1080 	{
1081 		return;
1082 	}
1083 
1084 	char full_path[PATH_MAX + 1];
1085 	if(is_parent_dir(entry->name))
1086 	{
1087 		copy_str(full_path, sizeof(full_path), entry->origin);
1088 	}
1089 	else
1090 	{
1091 		get_full_path_of(entry, sizeof(full_path), full_path);
1092 	}
1093 	start_dir_size_calc(full_path, force);
1094 }
1095 
1096 /* Initiates background directory size calculation. */
1097 static void
start_dir_size_calc(const char path[],int force)1098 start_dir_size_calc(const char path[], int force)
1099 {
1100 	char task_desc[PATH_MAX + 32];
1101 	dir_size_args_t *args;
1102 
1103 	args = malloc(sizeof(*args));
1104 	args->path = strdup(path);
1105 	args->force = force;
1106 
1107 	snprintf(task_desc, sizeof(task_desc), "Calculating size: %s", path);
1108 
1109 	if(bg_execute(task_desc, path, BG_UNDEFINED_TOTAL, 0, &dir_size_bg,
1110 				args) != 0)
1111 	{
1112 		free(args->path);
1113 		free(args);
1114 
1115 		show_error_msg("Can't calculate size",
1116 				"Failed to initiate background operation");
1117 	}
1118 }
1119 
1120 /* Entry point for a background task that calculates size of a directory. */
1121 static void
dir_size_bg(bg_op_t * bg_op,void * arg)1122 dir_size_bg(bg_op_t *bg_op, void *arg)
1123 {
1124 	dir_size_args_t *const args = arg;
1125 
1126 	dir_size(bg_op, args->path, args->force);
1127 
1128 	free(args->path);
1129 	free(args);
1130 }
1131 
1132 /* Calculates directory size and triggers view updates if necessary.  Changes
1133  * path. */
1134 static void
dir_size(bg_op_t * bg_op,char path[],int force)1135 dir_size(bg_op_t *bg_op, char path[], int force)
1136 {
1137 	const cancellation_t bg_cancellation_info = {
1138 		.arg = bg_op,
1139 		.hook = &bg_cancellation_hook,
1140 	};
1141 
1142 	(void)fops_dir_size(path, force, &bg_cancellation_info);
1143 
1144 	remove_last_path_component(path);
1145 
1146 	redraw_after_path_change(&lwin, path);
1147 	redraw_after_path_change(&rwin, path);
1148 }
1149 
1150 /* Implementation of cancellation hook for background tasks. */
1151 static int
bg_cancellation_hook(void * arg)1152 bg_cancellation_hook(void *arg)
1153 {
1154 	return bg_op_cancelled(arg);
1155 }
1156 
1157 /* Schedules view redraw in case path change might have affected it. */
1158 static void
redraw_after_path_change(view_t * view,const char path[])1159 redraw_after_path_change(view_t *view, const char path[])
1160 {
1161 	if(path_starts_with(view->curr_dir, path) || flist_custom_active(view))
1162 	{
1163 		ui_view_schedule_redraw(view);
1164 	}
1165 }
1166 
1167 uint64_t
fops_dir_size(const char path[],int force_update,const cancellation_t * cancellation)1168 fops_dir_size(const char path[], int force_update,
1169 		const cancellation_t *cancellation)
1170 {
1171 	struct dirent *dentry;
1172 	const char *slash;
1173 	uint64_t size;
1174 
1175 	time_t mtime = 0;
1176 	uint64_t inode = DCACHE_UNKNOWN;
1177 #ifndef _WIN32
1178 	struct stat s;
1179 	if(os_stat(path, &s) == 0)
1180 	{
1181 		mtime = s.st_mtime;
1182 		inode = s.st_ino;
1183 	}
1184 #endif
1185 
1186 	DIR *dir = os_opendir(path);
1187 	if(dir == NULL)
1188 	{
1189 		return 0U;
1190 	}
1191 
1192 	slash = (ends_with_slash(path) ? "" : "/");
1193 	size = 0U;
1194 	while((dentry = os_readdir(dir)) != NULL)
1195 	{
1196 		char full_path[PATH_MAX + 1];
1197 
1198 		if(is_builtin_dir(dentry->d_name))
1199 		{
1200 			continue;
1201 		}
1202 
1203 		snprintf(full_path, sizeof(full_path), "%s%s%s", path, slash,
1204 				dentry->d_name);
1205 		if(fops_is_dir_entry(full_path, dentry))
1206 		{
1207 			uint64_t dir_size;
1208 			dcache_get_at(full_path, mtime, inode, &dir_size, NULL);
1209 			if(dir_size == DCACHE_UNKNOWN || force_update)
1210 			{
1211 				dir_size = fops_dir_size(full_path, force_update, cancellation);
1212 			}
1213 			size += dir_size;
1214 		}
1215 		else
1216 		{
1217 			size += get_file_size(full_path);
1218 		}
1219 
1220 		if(cancellation_requested(cancellation))
1221 		{
1222 			os_closedir(dir);
1223 			return 0U;
1224 		}
1225 	}
1226 
1227 	os_closedir(dir);
1228 
1229 	(void)dcache_set_at(path, inode, size, DCACHE_UNKNOWN);
1230 	return size;
1231 }
1232 
1233 #ifndef _WIN32
1234 
1235 int
fops_chown(int u,int g,uid_t uid,gid_t gid)1236 fops_chown(int u, int g, uid_t uid, gid_t gid)
1237 {
1238 /* Integer to pointer conversion. */
1239 #define V(e) (void *)(long)(e)
1240 
1241 	view_t *const view = curr_view;
1242 	char undo_msg[COMMAND_GROUP_INFO_LEN + 1];
1243 	ops_t *ops;
1244 	dir_entry_t *entry;
1245 	const char *const curr_dir = flist_get_dir(view);
1246 
1247 	snprintf(undo_msg, sizeof(undo_msg), "ch%s in %s: ",
1248 			((u && g) || u) ? "own" : "grp", replace_home_part(curr_dir));
1249 
1250 	ops = fops_get_ops(OP_CHOWN, "re-owning", curr_dir, curr_dir);
1251 	(void)fops_enqueue_marked_files(ops, view, NULL, 0);
1252 
1253 	fops_append_marked_files(view, undo_msg, NULL);
1254 	un_group_open(undo_msg);
1255 
1256 	entry = NULL;
1257 	while(iter_marked_entries(view, &entry) && !ui_cancellation_requested())
1258 	{
1259 		int full_success = (u != 0) + (g != 0);
1260 		char full_path[PATH_MAX + 1];
1261 		get_full_path_of(entry, sizeof(full_path), full_path);
1262 
1263 		if(u && perform_operation(OP_CHOWN, ops, V(uid), full_path, NULL) == 0)
1264 		{
1265 			un_group_add_op(OP_CHOWN, V(uid), V(entry->uid), full_path, "");
1266 		}
1267 		else
1268 		{
1269 			full_success -= (u != 0);
1270 		}
1271 
1272 		if(g && perform_operation(OP_CHGRP, ops, V(gid), full_path, NULL) == 0)
1273 		{
1274 			un_group_add_op(OP_CHGRP, V(gid), V(entry->gid), full_path, "");
1275 		}
1276 		else
1277 		{
1278 			full_success -= (g != 0);
1279 		}
1280 
1281 		ops_advance(ops, full_success == (u != 0) + (g != 0));
1282 	}
1283 	un_group_close();
1284 
1285 	ui_sb_msgf("%d file%s fully processed%s", ops->succeeded,
1286 			(ops->succeeded == 1) ? "" : "s", fops_get_cancellation_suffix());
1287 	fops_free_ops(ops);
1288 
1289 	ui_view_reset_selection_and_reload(view);
1290 	return 1;
1291 #undef V
1292 }
1293 
1294 void
fops_chuser(void)1295 fops_chuser(void)
1296 {
1297 	if(mark_selection_or_current(curr_view) == 0)
1298 	{
1299 		show_error_msg("Change owner", "No files to process");
1300 		return;
1301 	}
1302 	fops_line_prompt("New owner: ", "", &change_owner_cb, &complete_owner, 0);
1303 }
1304 
1305 /* Handles users response for new file owner name prompt. */
1306 static void
change_owner_cb(const char new_owner[])1307 change_owner_cb(const char new_owner[])
1308 {
1309 	uid_t uid;
1310 
1311 	if(is_null_or_empty(new_owner))
1312 	{
1313 		return;
1314 	}
1315 
1316 	if(get_uid(new_owner, &uid) != 0)
1317 	{
1318 		ui_sb_errf("Invalid user name: \"%s\"", new_owner);
1319 		curr_stats.save_msg = 1;
1320 		return;
1321 	}
1322 
1323 	curr_stats.save_msg = fops_chown(1, 0, uid, 0);
1324 }
1325 
1326 /* Command-line user name completion callback.  Returns completion start
1327  * offset. */
1328 static int
complete_owner(const char str[],void * arg)1329 complete_owner(const char str[], void *arg)
1330 {
1331 	complete_user_name(str);
1332 	return 0;
1333 }
1334 
1335 void
fops_chgroup(void)1336 fops_chgroup(void)
1337 {
1338 	if(mark_selection_or_current(curr_view) == 0)
1339 	{
1340 		show_error_msg("Change group", "No files to process");
1341 		return;
1342 	}
1343 	fops_line_prompt("New group: ", "", &change_group_cb, &complete_group, 0);
1344 }
1345 
1346 /* Handles users response for new file group name prompt. */
1347 static void
change_group_cb(const char new_group[])1348 change_group_cb(const char new_group[])
1349 {
1350 	gid_t gid;
1351 
1352 	if(is_null_or_empty(new_group))
1353 	{
1354 		return;
1355 	}
1356 
1357 	if(get_gid(new_group, &gid) != 0)
1358 	{
1359 		ui_sb_errf("Invalid group name: \"%s\"", new_group);
1360 		curr_stats.save_msg = 1;
1361 		return;
1362 	}
1363 
1364 	curr_stats.save_msg = fops_chown(0, 1, 0, gid);
1365 }
1366 
1367 /* Command-line group name completion callback.  Returns completion start
1368  * offset. */
1369 static int
complete_group(const char str[],void * arg)1370 complete_group(const char str[], void *arg)
1371 {
1372 	complete_group_name(str);
1373 	return 0;
1374 }
1375 
1376 #endif
1377 
1378 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
1379 /* vim: set cinoptions+=t0 : */
1380