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