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