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_cpmv.h"
21 
22 #include <assert.h> /* assert() */
23 #include <string.h> /* strcmp() strdup() */
24 
25 #include "compat/reallocarray.h"
26 #include "modes/dialogs/msg_dialog.h"
27 #include "ui/cancellation.h"
28 #include "ui/fileview.h"
29 #include "ui/statusbar.h"
30 #include "utils/fs.h"
31 #include "utils/path.h"
32 #include "utils/str.h"
33 #include "utils/string_array.h"
34 #include "filelist.h"
35 #include "flist_pos.h"
36 #include "fops_common.h"
37 #include "fops_misc.h"
38 #include "ops.h"
39 #include "trash.h"
40 #include "undo.h"
41 
42 static int cp_file(const char src_dir[], const char dst_dir[], const char src[],
43 		const char dst[], CopyMoveLikeOp op, int cancellable, ops_t *ops,
44 		int force);
45 static int cpmv_prepare(view_t *view, char ***list, int *nlines,
46 		CopyMoveLikeOp op, int force, char undo_msg[], size_t undo_msg_len,
47 		char dst_path[], size_t dst_path_len, int *from_file);
48 static int is_copy_list_ok(const char dst[], int count, char *list[],
49 		int force);
50 static int check_for_clashes(view_t *view, CopyMoveLikeOp op,
51 		const char dst_path[], char *list[], char *marked[], int nlines);
52 static const char * cmlo_to_str(CopyMoveLikeOp op);
53 static void cpmv_files_in_bg(bg_op_t *bg_op, void *arg);
54 static void cpmv_file_in_bg(ops_t *ops, const char src[], const char dst[],
55 		int move, int force, int from_trash, const char dst_dir[]);
56 static int cp_file_f(const char src[], const char dst[], CopyMoveLikeOp op,
57 		int bg, int cancellable, ops_t *ops, int force);
58 
59 int
fops_cpmv(view_t * view,char * list[],int nlines,CopyMoveLikeOp op,int force)60 fops_cpmv(view_t *view, char *list[], int nlines, CopyMoveLikeOp op, int force)
61 {
62 	int err;
63 	int nmarked_files;
64 	int custom_fnames;
65 	int i;
66 	char undo_msg[COMMAND_GROUP_INFO_LEN + 1];
67 	dir_entry_t *entry;
68 	char dst_dir[PATH_MAX + 1];
69 	int from_file;
70 	ops_t *ops;
71 
72 	if((op == CMLO_LINK_REL || op == CMLO_LINK_ABS) && !symlinks_available())
73 	{
74 		show_error_msg("Symbolic Links Error",
75 				"Your OS doesn't support symbolic links");
76 		return 0;
77 	}
78 
79 	err = cpmv_prepare(view, &list, &nlines, op, force, undo_msg,
80 			sizeof(undo_msg), dst_dir, sizeof(dst_dir), &from_file);
81 	if(err != 0)
82 	{
83 		return err > 0;
84 	}
85 
86 	const int same_dir = pane_in_dir(view, dst_dir);
87 	if(same_dir && force)
88 	{
89 		show_error_msg("Operation Error",
90 				"Forcing overwrite when destination and source is same directory will "
91 				"lead to losing data");
92 		return 0;
93 	}
94 
95 	switch(op)
96 	{
97 		case CMLO_COPY:
98 			ops = fops_get_ops(OP_COPY, "Copying", flist_get_dir(view), dst_dir);
99 			break;
100 		case CMLO_MOVE:
101 			ops = fops_get_ops(OP_MOVE, "Moving", flist_get_dir(view), dst_dir);
102 			break;
103 		case CMLO_LINK_REL:
104 		case CMLO_LINK_ABS:
105 			ops = fops_get_ops(OP_SYMLINK, "Linking", flist_get_dir(view), dst_dir);
106 			break;
107 
108 		default:
109 			assert(0 && "Unexpected operation type.");
110 			return 0;
111 	}
112 
113 	nmarked_files = fops_enqueue_marked_files(ops, view, dst_dir, 0);
114 
115 	un_group_open(undo_msg);
116 	i = 0;
117 	entry = NULL;
118 	custom_fnames = (nlines > 0);
119 	while(iter_marked_entries(view, &entry) && !ui_cancellation_requested())
120 	{
121 		/* Must be at this level as dst might point into this buffer. */
122 		char src_full[PATH_MAX + 1];
123 
124 		char dst_full[PATH_MAX + 8];
125 		const char *dst = (custom_fnames ? list[i] : entry->name);
126 		int err, from_trash;
127 
128 		get_full_path_of(entry, sizeof(src_full), src_full);
129 		from_trash = trash_has_path(src_full);
130 
131 		if(from_trash && !custom_fnames)
132 		{
133 			snprintf(src_full, sizeof(src_full), "%s/%s", entry->origin, dst);
134 			chosp(src_full);
135 			dst = trash_get_real_name_of(src_full);
136 		}
137 
138 		snprintf(dst_full, sizeof(dst_full), "%s/%s", dst_dir, dst);
139 		if(path_exists(dst_full, NODEREF) && !from_trash)
140 		{
141 			(void)perform_operation(OP_REMOVESL, NULL, NULL, dst_full, NULL);
142 		}
143 
144 		if(op == CMLO_COPY)
145 		{
146 			fops_progress_msg("Copying files", i, nmarked_files);
147 		}
148 		else if(op == CMLO_MOVE)
149 		{
150 			fops_progress_msg("Moving files", i, nmarked_files);
151 		}
152 
153 		if(op == CMLO_MOVE)
154 		{
155 			err = fops_mv_file(entry->name, entry->origin, dst, dst_dir, OP_MOVE, 1,
156 					ops);
157 			if(err != 0)
158 			{
159 				view->list_pos = fpos_find_by_name(view, entry->name);
160 			}
161 		}
162 		else
163 		{
164 			err = cp_file(entry->origin, dst_dir, entry->name, dst, op, 1, ops,
165 					force);
166 		}
167 
168 		ops_advance(ops, err == 0);
169 
170 		++i;
171 	}
172 	un_group_close();
173 
174 	if(same_dir || flist_custom_active(view))
175 	{
176 		ui_view_schedule_reload(view);
177 	}
178 	ui_view_schedule_reload(view == curr_view ? other_view : curr_view);
179 
180 	if(from_file)
181 	{
182 		free_string_array(list, nlines);
183 	}
184 
185 	ui_sb_msgf("%d file%s successfully processed%s", ops->succeeded,
186 			(ops->succeeded == 1) ? "" : "s", fops_get_cancellation_suffix());
187 
188 	fops_free_ops(ops);
189 
190 	return 1;
191 }
192 
193 void
fops_replace(view_t * view,const char dst[],int force)194 fops_replace(view_t *view, const char dst[], int force)
195 {
196 	char undo_msg[2*PATH_MAX + 32];
197 	dir_entry_t *entry;
198 	char dst_dir[PATH_MAX + 1];
199 	char src_full[PATH_MAX + 1];
200 	const char *const fname = get_last_path_component(dst);
201 	ops_t *ops;
202 	void *cp = (void *)(size_t)1;
203 	view_t *const other = (view == curr_view) ? other_view : curr_view;
204 
205 	copy_str(dst_dir, sizeof(dst_dir), dst);
206 	remove_last_path_component(dst_dir);
207 
208 	entry = &view->dir_entry[view->list_pos];
209 	get_full_path_of(entry, sizeof(src_full), src_full);
210 
211 	if(paths_are_same(src_full, dst))
212 	{
213 		/* Nothing to do if destination and source are the same file. */
214 		return;
215 	}
216 
217 	ops = fops_get_ops(OP_COPY, "Copying", flist_get_dir(view), dst_dir);
218 
219 	snprintf(undo_msg, sizeof(undo_msg), "Copying %s to %s",
220 			replace_home_part(src_full), dst_dir);
221 
222 	un_group_open(undo_msg);
223 
224 	if(path_exists(dst, NODEREF) && force)
225 	{
226 		(void)fops_delete_current(other, 1, 1);
227 	}
228 
229 	fops_progress_msg("Copying files", 0, 1);
230 
231 	if(!ui_cancellation_requested() && !is_valid_dir(dst_dir) &&
232 			perform_operation(OP_MKDIR, NULL, cp, dst_dir, NULL) == 0)
233 	{
234 		un_group_add_op(OP_MKDIR, cp, NULL, dst_dir, "");
235 	}
236 
237 	if(!ui_cancellation_requested())
238 	{
239 		(void)cp_file(entry->origin, dst_dir, entry->name, fname, CMLO_COPY, 1, ops,
240 				1);
241 	}
242 
243 	un_group_close();
244 
245 	ui_view_schedule_reload(other);
246 
247 	fops_free_ops(ops);
248 }
249 
250 /* Adapter for cp_file_f() that accepts paths broken into directory/file
251  * parts. */
252 static int
cp_file(const char src_dir[],const char dst_dir[],const char src[],const char dst[],CopyMoveLikeOp op,int cancellable,ops_t * ops,int force)253 cp_file(const char src_dir[], const char dst_dir[], const char src[],
254 		const char dst[], CopyMoveLikeOp op, int cancellable, ops_t *ops, int force)
255 {
256 	char full_src[PATH_MAX + 1], full_dst[PATH_MAX + 1];
257 
258 	to_canonic_path(src, src_dir, full_src, sizeof(full_src));
259 	to_canonic_path(dst, dst_dir, full_dst, sizeof(full_dst));
260 
261 	return cp_file_f(full_src, full_dst, op, 0, cancellable, ops, force);
262 }
263 
264 int
fops_cpmv_bg(view_t * view,char * list[],int nlines,int move,int force)265 fops_cpmv_bg(view_t *view, char *list[], int nlines, int move, int force)
266 {
267 	int err;
268 	size_t i;
269 	char task_desc[COMMAND_GROUP_INFO_LEN];
270 	bg_args_t *args = calloc(1, sizeof(*args));
271 
272 	args->nlines = nlines;
273 	args->move = move;
274 	args->force = force;
275 
276 	err = cpmv_prepare(view, &list, &args->nlines, move ? CMLO_MOVE : CMLO_COPY,
277 			force, task_desc, sizeof(task_desc), args->path, sizeof(args->path),
278 			&args->from_file);
279 	if(err != 0)
280 	{
281 		fops_free_bg_args(args);
282 		return err > 0;
283 	}
284 
285 	args->list = args->from_file ? list :
286 	             args->nlines > 0 ? copy_string_array(list, nlines) : NULL;
287 
288 	fops_prepare_for_bg_task(view, args);
289 
290 	args->is_in_trash = malloc(args->sel_list_len);
291 	for(i = 0U; i < args->sel_list_len; ++i)
292 	{
293 		args->is_in_trash[i] = trash_has_path(args->sel_list[i]);
294 	}
295 
296 	if(args->list == NULL)
297 	{
298 		int i;
299 		args->nlines = args->sel_list_len;
300 		args->list = reallocarray(NULL, args->nlines, sizeof(*args->list));
301 		for(i = 0; i < args->nlines; ++i)
302 		{
303 			args->list[i] =
304 				strdup(fops_get_dst_name(args->sel_list[i], args->is_in_trash[i]));
305 		}
306 	}
307 
308 	args->ops = fops_get_bg_ops(move ? OP_MOVE : OP_COPY,
309 			move ? "moving" : "copying", args->path);
310 
311 	if(bg_execute(task_desc, "...", args->sel_list_len, 1, &cpmv_files_in_bg,
312 				args) != 0)
313 	{
314 		fops_free_bg_args(args);
315 
316 		show_error_msg("Can't process files",
317 				"Failed to initiate background operation");
318 	}
319 
320 	return 0;
321 }
322 
323 /* Performs general preparations for file copy/move-like operations: resolving
324  * destination path, validating names, checking for conflicts, formatting undo
325  * message.  Returns zero on success, otherwise positive number for status bar
326  * message and negative number for other errors. */
327 static int
cpmv_prepare(view_t * view,char *** list,int * nlines,CopyMoveLikeOp op,int force,char undo_msg[],size_t undo_msg_len,char dst_path[],size_t dst_path_len,int * from_file)328 cpmv_prepare(view_t *view, char ***list, int *nlines, CopyMoveLikeOp op,
329 		int force, char undo_msg[], size_t undo_msg_len, char dst_path[],
330 		size_t dst_path_len, int *from_file)
331 {
332 	view_t *const other = (view == curr_view) ? other_view : curr_view;
333 
334 	char **marked;
335 	size_t nmarked;
336 	int error = 0;
337 
338 	if(op == CMLO_MOVE)
339 	{
340 		if(!fops_view_can_be_changed(view))
341 		{
342 			return -1;
343 		}
344 	}
345 	else if(op == CMLO_COPY && !fops_can_read_marked_files(view))
346 	{
347 		return -1;
348 	}
349 
350 	if(*nlines == 1)
351 	{
352 		if(fops_check_dir_path(other, (*list)[0], dst_path, dst_path_len))
353 		{
354 			*nlines = 0;
355 		}
356 	}
357 	else
358 	{
359 		copy_str(dst_path, dst_path_len, fops_get_dst_dir(other, -1));
360 	}
361 
362 	if(!fops_view_can_be_extended(other, -1) ||
363 			!fops_is_dir_writable(DR_DESTINATION, dst_path))
364 	{
365 		return -1;
366 	}
367 
368 	marked = fops_grab_marked_files(view, &nmarked);
369 
370 	*from_file = *nlines < 0;
371 	if(*from_file)
372 	{
373 		*list = fops_edit_list(nmarked, marked, nlines, 1);
374 		if(*list == NULL)
375 		{
376 			free_string_array(marked, nmarked);
377 			return -1;
378 		}
379 	}
380 
381 	if(*nlines > 0 &&
382 			(!fops_is_name_list_ok(nmarked, *nlines, *list, NULL) ||
383 			!is_copy_list_ok(dst_path, *nlines, *list, force)))
384 	{
385 		error = 1;
386 	}
387 	if(*nlines == 0 && !is_copy_list_ok(dst_path, nmarked, marked, force))
388 	{
389 		error = 1;
390 	}
391 
392 	/* Custom views can contain several files with the same name. */
393 	if(flist_custom_active(view))
394 	{
395 		size_t i;
396 		for(i = 0U; i < nmarked && !error; ++i)
397 		{
398 			if(is_in_string_array(marked, i, marked[i]))
399 			{
400 				ui_sb_errf("Source name \"%s\" duplicates", marked[i]);
401 				curr_stats.save_msg = 1;
402 				error = 1;
403 			}
404 		}
405 	}
406 
407 	if(check_for_clashes(view, op, dst_path, *list, marked, *nlines) != 0)
408 	{
409 		error = 1;
410 	}
411 
412 	free_string_array(marked, nmarked);
413 
414 	if(error)
415 	{
416 		redraw_view(view);
417 		if(*from_file)
418 		{
419 			free_string_array(*list, *nlines);
420 		}
421 		return 1;
422 	}
423 
424 	snprintf(undo_msg, undo_msg_len, "%s from %s to ", cmlo_to_str(op),
425 			replace_home_part(flist_get_dir(view)));
426 	snprintf(undo_msg + strlen(undo_msg), undo_msg_len - strlen(undo_msg),
427 			"%s: ", replace_home_part(dst_path));
428 	fops_append_marked_files(view, undo_msg, (*nlines > 0) ? *list : NULL);
429 
430 	return 0;
431 }
432 
433 /* Checks whether list of files doesn't mention any existing files.  Returns
434  * non-zero if everything is fine, otherwise zero is returned. */
435 static int
is_copy_list_ok(const char dst[],int count,char * list[],int force)436 is_copy_list_ok(const char dst[], int count, char *list[], int force)
437 {
438 	int i;
439 
440 	if(force)
441 	{
442 		return 1;
443 	}
444 
445 	for(i = 0; i < count; ++i)
446 	{
447 		if(path_exists_at(dst, list[i], DEREF))
448 		{
449 			ui_sb_errf("File \"%s\" already exists", list[i]);
450 			return 0;
451 		}
452 	}
453 	return 1;
454 }
455 
456 /* Checks whether operation is OK from the point of view of losing files due to
457  * tree clashes (child move over parent or vice versa).  Returns zero if
458  * everything is fine, otherwise non-zero is returned. */
459 static int
check_for_clashes(view_t * view,CopyMoveLikeOp op,const char dst_path[],char * list[],char * marked[],int nlines)460 check_for_clashes(view_t *view, CopyMoveLikeOp op, const char dst_path[],
461 		char *list[], char *marked[], int nlines)
462 {
463 	dir_entry_t *entry = NULL;
464 	int i = 0;
465 	while(iter_marked_entries(view, &entry))
466 	{
467 		char src_full[PATH_MAX + 1], dst_full[PATH_MAX + 1];
468 		const char *const dst_name = (nlines > 0) ? list[i] : marked[i];
469 		++i;
470 
471 		get_full_path_of(entry, sizeof(src_full), src_full);
472 
473 		snprintf(dst_full, sizeof(dst_full), "%s/%s", dst_path, dst_name);
474 		chosp(dst_full);
475 
476 		if(ONE_OF(op, CMLO_MOVE, CMLO_COPY) && is_in_subtree(dst_full, src_full, 0))
477 		{
478 			ui_sb_errf("Can't move/copy parent inside itself: %s",
479 					replace_home_part(src_full));
480 			curr_stats.save_msg = 1;
481 			return 1;
482 		}
483 
484 		if(is_in_subtree(src_full, dst_full, 0))
485 		{
486 			ui_sb_errf("Operation would result in loss contents of %s",
487 					replace_home_part(src_full));
488 			curr_stats.save_msg = 1;
489 			return 1;
490 		}
491 	}
492 	return 0;
493 }
494 
495 /* Gets string representation of a copy/move-like operation.  Returns the
496  * string. */
497 static const char *
cmlo_to_str(CopyMoveLikeOp op)498 cmlo_to_str(CopyMoveLikeOp op)
499 {
500 	switch(op)
501 	{
502 		case CMLO_COPY:
503 			return "copy";
504 		case CMLO_MOVE:
505 			return "move";
506 		case CMLO_LINK_REL:
507 			return "rlink";
508 		case CMLO_LINK_ABS:
509 			return "alink";
510 
511 		default:
512 			assert(0 && "Unexpected operation type.");
513 			return "";
514 	}
515 }
516 
517 /* Entry point for a background task that copies/moves files. */
518 static void
cpmv_files_in_bg(bg_op_t * bg_op,void * arg)519 cpmv_files_in_bg(bg_op_t *bg_op, void *arg)
520 {
521 	size_t i;
522 	bg_args_t *const args = arg;
523 	ops_t *ops = args->ops;
524 	fops_bg_ops_init(ops, bg_op);
525 
526 	if(ops->use_system_calls)
527 	{
528 		size_t i;
529 		bg_op_set_descr(bg_op, "estimating...");
530 		for(i = 0U; i < args->sel_list_len; ++i)
531 		{
532 			const char *const src = args->sel_list[i];
533 			const char *const dst = args->list[i];
534 			ops_enqueue(ops, src, dst);
535 		}
536 	}
537 
538 	for(i = 0U; i < args->sel_list_len; ++i)
539 	{
540 		const char *const src = args->sel_list[i];
541 		const char *const dst = args->list[i];
542 		bg_op_set_descr(bg_op, src);
543 		cpmv_file_in_bg(ops, src, dst, args->move, args->force,
544 				args->is_in_trash[i], args->path);
545 		++bg_op->done;
546 	}
547 
548 	fops_free_bg_args(args);
549 }
550 
551 /* Actual implementation of background file copying/moving. */
552 static void
cpmv_file_in_bg(ops_t * ops,const char src[],const char dst[],int move,int force,int from_trash,const char dst_dir[])553 cpmv_file_in_bg(ops_t *ops, const char src[], const char dst[], int move,
554 		int force, int from_trash, const char dst_dir[])
555 {
556 	char dst_full[PATH_MAX + 1];
557 	snprintf(dst_full, sizeof(dst_full), "%s/%s", dst_dir, dst);
558 	if(force && path_exists(dst_full, DEREF) && !from_trash)
559 	{
560 		(void)perform_operation(OP_REMOVESL, NULL, (void *)1, dst_full, NULL);
561 	}
562 
563 	if(move)
564 	{
565 		(void)fops_mv_file_f(src, dst_full, OP_MOVE, 1, 1, ops);
566 	}
567 	else
568 	{
569 		(void)cp_file_f(src, dst_full, CMLO_COPY, 1, 1, ops, 0);
570 	}
571 }
572 
573 /* Copies file from one location to another.  Returns zero on success, otherwise
574  * non-zero is returned. */
575 static int
cp_file_f(const char src[],const char dst[],CopyMoveLikeOp op,int bg,int cancellable,ops_t * ops,int force)576 cp_file_f(const char src[], const char dst[], CopyMoveLikeOp op, int bg,
577 		int cancellable, ops_t *ops, int force)
578 {
579 	if(strcmp(src, dst) == 0)
580 	{
581 		return 0;
582 	}
583 
584 	OPS file_op;
585 	char rel_path[PATH_MAX + 1];
586 	if(op == CMLO_COPY)
587 	{
588 		file_op = force ? OP_COPYF : OP_COPY;
589 	}
590 	else
591 	{
592 		file_op = OP_SYMLINK;
593 
594 		if(op == CMLO_LINK_REL)
595 		{
596 			char dst_dir[PATH_MAX + 1];
597 
598 			copy_str(dst_dir, sizeof(dst_dir), dst);
599 			remove_last_path_component(dst_dir);
600 
601 			copy_str(rel_path, sizeof(rel_path), make_rel_path(src, dst_dir));
602 			src = rel_path;
603 		}
604 	}
605 
606 	int result = perform_operation(file_op, ops, cancellable ? NULL : (void *)1,
607 			src, dst);
608 	if(result == 0 && !bg)
609 	{
610 		un_group_add_op(file_op, NULL, NULL, src, dst);
611 	}
612 	return result;
613 }
614 
615 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
616 /* vim: set cinoptions+=t0 : */
617