(file_op_context_t): rename members.
[midnight-commander.git] / src / filemanager / file.c
blob6e811f551e579c62e844c37ad6fe56474eb0def4
1 /*
2 File management.
4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
7 Written by:
8 Janne Kukonlehto, 1994, 1995
9 Fred Leeflang, 1994, 1995
10 Miguel de Icaza, 1994, 1995, 1996
11 Jakub Jelinek, 1995, 1996
12 Norbert Warmuth, 1997
13 Pavel Machek, 1998
14 Andrew Borodin <aborodin@vmail.ru>, 2011-2022
16 The copy code was based in GNU's cp, and was written by:
17 Torbjorn Granlund, David MacKenzie, and Jim Meyering.
19 The move code was based in GNU's mv, and was written by:
20 Mike Parker and David MacKenzie.
22 Janne Kukonlehto added much error recovery to them for being used
23 in an interactive program.
25 This file is part of the Midnight Commander.
27 The Midnight Commander is free software: you can redistribute it
28 and/or modify it under the terms of the GNU General Public License as
29 published by the Free Software Foundation, either version 3 of the License,
30 or (at your option) any later version.
32 The Midnight Commander is distributed in the hope that it will be useful,
33 but WITHOUT ANY WARRANTY; without even the implied warranty of
34 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 GNU General Public License for more details.
37 You should have received a copy of the GNU General Public License
38 along with this program. If not, see <http://www.gnu.org/licenses/>.
42 * Please note that all dialogs used here must be safe for background
43 * operations.
46 /** \file src/filemanager/file.c
47 * \brief Source: file management
50 /* {{{ Include files */
52 #include <config.h>
54 #include <ctype.h>
55 #include <errno.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <string.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <unistd.h>
63 #include "lib/global.h"
64 #include "lib/tty/tty.h"
65 #include "lib/tty/key.h"
66 #include "lib/search.h"
67 #include "lib/strutil.h"
68 #include "lib/util.h"
69 #include "lib/vfs/vfs.h"
70 #include "lib/vfs/utilvfs.h"
71 #include "lib/widget.h"
73 #include "src/setup.h"
74 #ifdef ENABLE_BACKGROUND
75 #include "src/background.h" /* do_background() */
76 #endif
78 /* Needed for other_panel and WTree */
79 #include "dir.h"
80 #include "filenot.h"
81 #include "tree.h"
82 #include "filemanager.h" /* other_panel */
83 #include "layout.h" /* rotate_dash() */
84 #include "ioblksize.h" /* io_blksize() */
86 #include "file.h"
88 /* }}} */
90 /*** global variables ****************************************************************************/
92 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
93 const char *op_names[3] = {
94 N_("DialogTitle|Copy"),
95 N_("DialogTitle|Move"),
96 N_("DialogTitle|Delete")
99 /*** file scope macro definitions ****************************************************************/
101 #define FILEOP_UPDATE_INTERVAL 2
102 #define FILEOP_STALLING_INTERVAL 4
103 #define FILEOP_UPDATE_INTERVAL_US (FILEOP_UPDATE_INTERVAL * G_USEC_PER_SEC)
104 #define FILEOP_STALLING_INTERVAL_US (FILEOP_STALLING_INTERVAL * G_USEC_PER_SEC)
106 /*** file scope type declarations ****************************************************************/
108 /* This is a hard link cache */
109 typedef struct
111 const struct vfs_class *vfs;
112 dev_t dev;
113 ino_t ino;
114 mode_t st_mode;
115 vfs_path_t *src_vpath;
116 vfs_path_t *dst_vpath;
117 } link_t;
119 /* Status of the destination file */
120 typedef enum
122 DEST_NONE = 0, /**< Not created */
123 DEST_SHORT_QUERY, /**< Created, not fully copied, query to do */
124 DEST_SHORT_KEEP, /**< Created, not fully copied, keep it */
125 DEST_SHORT_DELETE, /**< Created, not fully copied, delete it */
126 DEST_FULL /**< Created, fully copied */
127 } dest_status_t;
129 /* Status of hard link creation */
130 typedef enum
132 HARDLINK_OK = 0, /**< Hardlink was created successfully */
133 HARDLINK_CACHED, /**< Hardlink was added to the cache */
134 HARDLINK_NOTLINK, /**< This is not a hard link */
135 HARDLINK_UNSUPPORTED, /**< VFS doesn't support hard links */
136 HARDLINK_ERROR, /**< Hard link creation error */
137 HARDLINK_ABORT /**< Stop file operation after hardlink creation error */
138 } hardlink_status_t;
141 * This array introduced to avoid translation problems. The former (op_names)
142 * is assumed to be nouns, suitable in dialog box titles; this one should
143 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
144 * (I don't use spaces around the words, because someday they could be
145 * dropped, when widgets get smarter)
148 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
149 static const char *op_names1[] = {
150 N_("FileOperation|Copy"),
151 N_("FileOperation|Move"),
152 N_("FileOperation|Delete")
156 * These are formats for building a prompt. Parts encoded as follows:
157 * %o - operation from op_names1
158 * %f - file/files or files/directories, as appropriate
159 * %m - "with source mask" or question mark for delete
160 * %s - source name (truncated)
161 * %d - number of marked files
162 * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
164 /* xgettext:no-c-format */
165 static const char *one_format = N_("%o %f%n\"%s\"%m");
166 /* xgettext:no-c-format */
167 static const char *many_format = N_("%o %d %f%m");
169 static const char *prompt_parts[] = {
170 N_("file"),
171 N_("files"),
172 N_("directory"),
173 N_("directories"),
174 N_("files/directories"),
175 /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
176 N_(" with source mask:")
179 /*** forward declarations (file scope functions) *************************************************/
181 /*** file scope variables ************************************************************************/
183 /* the hard link cache */
184 static GSList *linklist = NULL;
186 /* the files-to-be-erased list */
187 static GQueue *erase_list = NULL;
190 * This list holds information about just created target directories and is used to detect
191 * when an directory is copied into itself (we don't want to copy infinitely).
193 static GSList *dest_dirs = NULL;
195 /* --------------------------------------------------------------------------------------------- */
196 /*** file scope functions ************************************************************************/
197 /* --------------------------------------------------------------------------------------------- */
199 static void
200 dirsize_status_locate_buttons (dirsize_status_msg_t *dsm)
202 status_msg_t *sm = STATUS_MSG (dsm);
203 Widget *wd = WIDGET (sm->dlg);
204 int y, x;
205 WRect r;
207 y = wd->rect.y + 5;
208 x = wd->rect.x;
210 if (!dsm->allow_skip)
212 /* single button: "Abort" */
213 x += (wd->rect.cols - dsm->abort_button->rect.cols) / 2;
214 r = dsm->abort_button->rect;
215 r.y = y;
216 r.x = x;
217 widget_set_size_rect (dsm->abort_button, &r);
219 else
221 /* two buttons: "Abort" and "Skip" */
222 int cols;
224 cols = dsm->abort_button->rect.cols + dsm->skip_button->rect.cols + 1;
225 x += (wd->rect.cols - cols) / 2;
226 r = dsm->abort_button->rect;
227 r.y = y;
228 r.x = x;
229 widget_set_size_rect (dsm->abort_button, &r);
230 x += dsm->abort_button->rect.cols + 1;
231 r = dsm->skip_button->rect;
232 r.y = y;
233 r.x = x;
234 widget_set_size_rect (dsm->skip_button, &r);
238 /* --------------------------------------------------------------------------------------------- */
240 static char *
241 build_dest (file_op_context_t *ctx, const char *src, const char *dest, FileProgressStatus *status)
243 char *s, *q;
244 const char *fnsource;
246 *status = FILE_CONT;
248 s = g_strdup (src);
250 /* We remove \n from the filename since regex routines would use \n as an anchor */
251 /* this is just to be allowed to maniupulate file names with \n on it */
252 for (q = s; *q != '\0'; q++)
253 if (*q == '\n')
254 *q = ' ';
256 fnsource = x_basename (s);
258 if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
260 q = NULL;
261 *status = FILE_SKIP;
263 else
265 q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
266 if (ctx->search_handle->error != MC_SEARCH_E_OK)
268 if (ctx->search_handle->error_str != NULL)
269 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
271 *status = FILE_ABORT;
275 MC_PTR_FREE (s);
277 if (*status == FILE_CONT)
279 char *repl_dest;
281 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
282 if (ctx->search_handle->error == MC_SEARCH_E_OK)
283 s = mc_build_filename (repl_dest, q, (char *) NULL);
284 else
286 if (ctx->search_handle->error_str != NULL)
287 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
289 *status = FILE_ABORT;
292 g_free (repl_dest);
295 g_free (q);
297 return s;
300 /* --------------------------------------------------------------------------------------------- */
302 static void
303 free_link (void *data)
305 link_t *lp = (link_t *) data;
307 vfs_path_free (lp->src_vpath, TRUE);
308 vfs_path_free (lp->dst_vpath, TRUE);
309 g_free (lp);
312 /* --------------------------------------------------------------------------------------------- */
314 static inline void *
315 free_erase_list (GQueue *lp)
317 if (lp != NULL)
318 g_queue_free_full (lp, free_link);
320 return NULL;
323 /* --------------------------------------------------------------------------------------------- */
325 static inline void *
326 free_linklist (GSList *lp)
328 g_slist_free_full (lp, free_link);
330 return NULL;
333 /* --------------------------------------------------------------------------------------------- */
335 static const link_t *
336 is_in_linklist (const GSList *lp, const vfs_path_t *vpath, const struct stat *sb)
338 const struct vfs_class *class;
339 ino_t ino = sb->st_ino;
340 dev_t dev = sb->st_dev;
342 class = vfs_path_get_last_path_vfs (vpath);
344 for (; lp != NULL; lp = (const GSList *) g_slist_next (lp))
346 const link_t *lnk = (const link_t *) lp->data;
348 if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev)
349 return lnk;
352 return NULL;
355 /* --------------------------------------------------------------------------------------------- */
357 * Check and made hardlink
359 * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
360 * and a hardlink was successfully made
363 static hardlink_status_t
364 check_hardlinks (const vfs_path_t *src_vpath, const struct stat *src_stat,
365 const vfs_path_t *dst_vpath, gboolean *ignore_all)
367 link_t *lnk;
368 ino_t ino = src_stat->st_ino;
369 dev_t dev = src_stat->st_dev;
371 if (src_stat->st_nlink < 2)
372 return HARDLINK_NOTLINK;
373 if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0)
374 return HARDLINK_UNSUPPORTED;
376 lnk = (link_t *) is_in_linklist (linklist, src_vpath, src_stat);
377 if (lnk != NULL)
379 int stat_result;
380 struct stat link_stat;
382 stat_result = mc_stat (lnk->src_vpath, &link_stat);
384 if (stat_result == 0 && link_stat.st_ino == ino && link_stat.st_dev == dev)
386 const struct vfs_class *lp_name_class;
387 const struct vfs_class *my_vfs;
389 lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath);
390 my_vfs = vfs_path_get_last_path_vfs (src_vpath);
392 if (lp_name_class == my_vfs)
394 const struct vfs_class *p_class, *dst_name_class;
396 dst_name_class = vfs_path_get_last_path_vfs (dst_vpath);
397 p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath);
399 if (dst_name_class == p_class)
401 gboolean ok;
403 while (!(ok = (mc_stat (lnk->dst_vpath, &link_stat) == 0)) && !*ignore_all)
405 FileProgressStatus status;
407 status =
408 file_error (TRUE, _("Cannot stat hardlink source file \"%s\"\n%s"),
409 vfs_path_as_str (lnk->dst_vpath));
410 if (status == FILE_ABORT)
411 return HARDLINK_ABORT;
412 if (status == FILE_RETRY)
413 continue;
414 if (status == FILE_IGNORE_ALL)
415 *ignore_all = TRUE;
416 break;
419 /* if stat() finished unsuccessfully, don't try to create link */
420 if (!ok)
421 return HARDLINK_ERROR;
423 while (!(ok = (mc_link (lnk->dst_vpath, dst_vpath) == 0)) && !*ignore_all)
425 FileProgressStatus status;
427 status =
428 file_error (TRUE, _("Cannot create target hardlink \"%s\"\n%s"),
429 vfs_path_as_str (dst_vpath));
430 if (status == FILE_ABORT)
431 return HARDLINK_ABORT;
432 if (status == FILE_RETRY)
433 continue;
434 if (status == FILE_IGNORE_ALL)
435 *ignore_all = TRUE;
436 break;
439 /* Success? */
440 return (ok ? HARDLINK_OK : HARDLINK_ERROR);
445 if (!*ignore_all)
447 FileProgressStatus status;
449 /* Message w/o "Retry" action.
451 * FIXME: Can't say what errno is here. Define it and don't display.
453 * file_error() displays a message with text representation of errno
454 * and the string passed to file_error() should provide the format "%s"
455 * for that at end (see previous file_error() call for the reference).
456 * But if format for errno isn't provided, it is safe, because C standard says:
457 * "If the format is exhausted while arguments remain, the excess arguments
458 * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999,
459 * section 7.19.6.1, paragraph 2).
462 errno = 0;
463 status =
464 file_error (FALSE, _("Cannot create target hardlink \"%s\""),
465 vfs_path_as_str (dst_vpath));
467 if (status == FILE_ABORT)
468 return HARDLINK_ABORT;
470 if (status == FILE_IGNORE_ALL)
471 *ignore_all = TRUE;
474 return HARDLINK_ERROR;
477 lnk = g_try_new (link_t, 1);
478 if (lnk != NULL)
480 lnk->vfs = vfs_path_get_last_path_vfs (src_vpath);
481 lnk->ino = ino;
482 lnk->dev = dev;
483 lnk->st_mode = 0;
484 lnk->src_vpath = vfs_path_clone (src_vpath);
485 lnk->dst_vpath = vfs_path_clone (dst_vpath);
487 linklist = g_slist_prepend (linklist, lnk);
490 return HARDLINK_CACHED;
493 /* --------------------------------------------------------------------------------------------- */
495 * Duplicate the contents of the symbolic link src_vpath in dst_vpath.
496 * Try to make a stable symlink if the option "stable symlink" was
497 * set in the file mask dialog.
498 * If dst_path is an existing symlink it will be deleted silently
499 * (upper levels take already care of existing files at dst_vpath).
502 static FileProgressStatus
503 make_symlink (file_op_context_t *ctx, const vfs_path_t *src_vpath, const vfs_path_t *dst_vpath)
505 const char *src_path;
506 const char *dst_path;
507 char link_target[MC_MAXPATHLEN];
508 int len;
509 FileProgressStatus return_status;
510 struct stat dst_stat;
511 gboolean dst_is_symlink;
512 vfs_path_t *link_target_vpath = NULL;
514 src_path = vfs_path_as_str (src_vpath);
515 dst_path = vfs_path_as_str (dst_vpath);
517 dst_is_symlink = (mc_lstat (dst_vpath, &dst_stat) == 0) && S_ISLNK (dst_stat.st_mode);
519 retry_src_readlink:
520 len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1);
521 if (len < 0)
523 if (ctx->ignore_all)
524 return_status = FILE_IGNORE_ALL;
525 else
527 return_status = file_error (TRUE, _("Cannot read source link \"%s\"\n%s"), src_path);
528 if (return_status == FILE_IGNORE_ALL)
529 ctx->ignore_all = TRUE;
530 if (return_status == FILE_RETRY)
531 goto retry_src_readlink;
533 goto ret;
536 link_target[len] = '\0';
538 if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath)))
540 message (D_ERROR, MSG_ERROR,
541 _("Cannot make stable symlinks across "
542 "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
543 ctx->stable_symlinks = FALSE;
546 if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
548 const char *r;
550 r = strrchr (src_path, PATH_SEP);
551 if (r != NULL)
553 size_t slen;
554 GString *p;
555 vfs_path_t *q;
557 slen = r - src_path + 1;
559 p = g_string_sized_new (slen + len);
560 g_string_append_len (p, src_path, slen);
562 if (g_path_is_absolute (dst_path))
563 q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
564 else
565 q = vfs_path_build_filename (p->str, dst_path, (char *) NULL);
567 if (vfs_path_tokens_count (q) > 1)
569 char *s = NULL;
570 vfs_path_t *tmp_vpath1, *tmp_vpath2;
572 g_string_append_len (p, link_target, len);
573 tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
574 tmp_vpath2 = vfs_path_from_str (p->str);
575 s = diff_two_paths (tmp_vpath1, tmp_vpath2);
576 vfs_path_free (tmp_vpath2, TRUE);
577 vfs_path_free (tmp_vpath1, TRUE);
578 g_strlcpy (link_target, s != NULL ? s : p->str, sizeof (link_target));
579 g_free (s);
582 g_string_free (p, TRUE);
583 vfs_path_free (q, TRUE);
586 link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON);
588 retry_dst_symlink:
589 if (mc_symlink (link_target_vpath, dst_vpath) == 0)
591 /* Success */
592 return_status = FILE_CONT;
593 goto ret;
596 * if dst_exists, it is obvious that this had failed.
597 * We can delete the old symlink and try again...
599 if (dst_is_symlink && mc_unlink (dst_vpath) == 0
600 && mc_symlink (link_target_vpath, dst_vpath) == 0)
602 /* Success */
603 return_status = FILE_CONT;
604 goto ret;
607 if (ctx->ignore_all)
608 return_status = FILE_IGNORE_ALL;
609 else
611 return_status = file_error (TRUE, _("Cannot create target symlink \"%s\"\n%s"), dst_path);
612 if (return_status == FILE_IGNORE_ALL)
613 ctx->ignore_all = TRUE;
614 if (return_status == FILE_RETRY)
615 goto retry_dst_symlink;
618 ret:
619 vfs_path_free (link_target_vpath, TRUE);
620 return return_status;
623 /* --------------------------------------------------------------------------------------------- */
625 * do_compute_dir_size:
627 * Computes the number of bytes used by the files in a directory
630 static FileProgressStatus
631 do_compute_dir_size (const vfs_path_t *dirname_vpath, dirsize_status_msg_t *dsm,
632 size_t *dir_count, size_t *ret_marked, uintmax_t *ret_total,
633 mc_stat_fn stat_func)
635 static gint64 timestamp = 0;
636 /* update with 25 FPS rate */
637 static const gint64 delay = G_USEC_PER_SEC / 25;
639 status_msg_t *sm = STATUS_MSG (dsm);
640 int res;
641 struct stat s;
642 DIR *dir;
643 struct vfs_dirent *dirent;
644 FileProgressStatus ret = FILE_CONT;
646 (*dir_count)++;
648 dir = mc_opendir (dirname_vpath);
649 if (dir == NULL)
650 return ret;
652 while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL)
654 vfs_path_t *tmp_vpath;
656 if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name))
657 continue;
659 tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL);
661 res = stat_func (tmp_vpath, &s);
662 if (res == 0)
664 if (S_ISDIR (s.st_mode))
665 ret =
666 do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total,
667 stat_func);
668 else
670 ret = FILE_CONT;
672 (*ret_marked)++;
673 *ret_total += (uintmax_t) s.st_size;
676 if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (&timestamp, delay))
678 dsm->dirname_vpath = tmp_vpath;
679 dsm->dir_count = *dir_count;
680 dsm->total_size = *ret_total;
681 ret = sm->update (sm);
685 vfs_path_free (tmp_vpath, TRUE);
688 mc_closedir (dir);
689 return ret;
692 /* --------------------------------------------------------------------------------------------- */
694 * panel_compute_totals:
696 * compute the number of files and the number of bytes
697 * used up by the whole selection, recursing directories
698 * as required. In addition, it checks to see if it will
699 * overwrite any files by doing the copy.
702 static FileProgressStatus
703 panel_compute_totals (const WPanel *panel, dirsize_status_msg_t *sm, size_t *ret_count,
704 uintmax_t *ret_total, gboolean follow_symlinks)
706 int i;
707 size_t dir_count = 0;
708 mc_stat_fn stat_func = follow_symlinks ? mc_stat : mc_lstat;
710 for (i = 0; i < panel->dir.len; i++)
712 const file_entry_t *fe = &panel->dir.list[i];
713 const struct stat *s;
715 if (fe->f.marked == 0)
716 continue;
718 s = &fe->st;
720 if (S_ISDIR (s->st_mode) || (follow_symlinks && link_isdir (fe) && fe->f.stale_link == 0))
722 vfs_path_t *p;
723 FileProgressStatus status;
725 p = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
726 status = do_compute_dir_size (p, sm, &dir_count, ret_count, ret_total, stat_func);
727 vfs_path_free (p, TRUE);
729 if (status != FILE_CONT)
730 return status;
732 else
734 (*ret_count)++;
735 *ret_total += (uintmax_t) s->st_size;
739 return FILE_CONT;
742 /* --------------------------------------------------------------------------------------------- */
744 /** Initialize variables for progress bars */
745 static FileProgressStatus
746 panel_operate_init_totals (const WPanel *panel, const vfs_path_t *source,
747 const struct stat *source_stat, file_op_context_t *ctx,
748 gboolean compute_totals, filegui_dialog_type_t dialog_type)
750 FileProgressStatus status;
752 #ifdef ENABLE_BACKGROUND
753 if (mc_global.we_are_background)
754 return FILE_CONT;
755 #endif
757 if (verbose && compute_totals)
759 dirsize_status_msg_t dsm;
760 gboolean stale_link = FALSE;
762 memset (&dsm, 0, sizeof (dsm));
763 dsm.allow_skip = TRUE;
764 status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
765 dirsize_status_update_cb, dirsize_status_deinit_cb);
767 ctx->total_count = 0;
768 ctx->total_bytes = 0;
770 if (source == NULL)
771 status = panel_compute_totals (panel, &dsm, &ctx->total_count, &ctx->total_bytes,
772 ctx->follow_links);
773 else if (S_ISDIR (source_stat->st_mode)
774 || (ctx->follow_links
775 && file_is_symlink_to_dir (source, (struct stat *) source_stat, &stale_link)
776 && !stale_link))
778 size_t dir_count = 0;
780 status = do_compute_dir_size (source, &dsm, &dir_count, &ctx->total_count,
781 &ctx->total_bytes, ctx->stat_func);
783 else
785 ctx->total_count++;
786 ctx->total_bytes += (uintmax_t) source_stat->st_size;
787 status = FILE_CONT;
790 status_msg_deinit (STATUS_MSG (&dsm));
792 ctx->totals_computed = (status == FILE_CONT);
794 if (status == FILE_SKIP)
795 status = FILE_CONT;
797 else
799 status = FILE_CONT;
800 ctx->total_count = panel->marked;
801 ctx->total_bytes = panel->total;
802 ctx->totals_computed = verbose && dialog_type == FILEGUI_DIALOG_ONE_ITEM;
805 /* destroy already created UI for single file rename operation */
806 file_progress_ui_destroy (ctx);
808 file_progress_ui_create (ctx, TRUE, dialog_type);
810 return status;
813 /* --------------------------------------------------------------------------------------------- */
815 static FileProgressStatus
816 progress_update_one (file_op_context_t *ctx, off_t add)
818 gint64 tv_current;
819 static gint64 tv_start = -1;
821 ctx->total_progress_count++;
822 ctx->progress_bytes += (uintmax_t) add;
824 tv_current = g_get_monotonic_time ();
826 if (tv_start < 0)
827 tv_start = tv_current;
829 if (tv_current - tv_start > FILEOP_UPDATE_INTERVAL_US)
831 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
833 file_progress_show_count (ctx, ctx->total_progress_count, ctx->total_count);
834 file_progress_show_total (ctx, ctx->progress_bytes, TRUE);
837 tv_start = tv_current;
840 return file_progress_check_buttons (ctx);
843 /* --------------------------------------------------------------------------------------------- */
845 static FileProgressStatus
846 real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b)
848 char *msg;
849 int result = 0;
850 const char *head_msg;
851 int width_a, width_b, width;
853 head_msg = mode == Foreground ? MSG_ERROR : _("Background process error");
855 width_a = str_term_width1 (a);
856 width_b = str_term_width1 (b);
857 width = COLS - 8;
859 if (width_a > width)
861 if (width_b > width)
863 char *s;
865 s = g_strndup (str_trunc (a, width), width);
866 b = str_trunc (b, width);
867 msg = g_strdup_printf (fmt, s, b);
868 g_free (s);
870 else
872 a = str_trunc (a, width);
873 msg = g_strdup_printf (fmt, a, b);
876 else
878 if (width_b > width)
879 b = str_trunc (b, width);
881 msg = g_strdup_printf (fmt, a, b);
884 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
885 g_free (msg);
886 do_refresh ();
888 return (result == 1) ? FILE_ABORT : FILE_SKIP;
891 /* --------------------------------------------------------------------------------------------- */
893 static FileProgressStatus
894 warn_same_file (const char *fmt, const char *a, const char *b)
896 #ifdef ENABLE_BACKGROUND
897 /* *INDENT-OFF* */
898 union
900 void *p;
901 FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b);
902 } pntr;
903 /* *INDENT-ON* */
905 pntr.f = real_warn_same_file;
907 if (mc_global.we_are_background)
908 return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b);
909 #endif
910 return real_warn_same_file (Foreground, fmt, a, b);
913 /* --------------------------------------------------------------------------------------------- */
915 static gboolean
916 check_same_file (const char *a, const struct stat *ast, const char *b, const struct stat *bst,
917 FileProgressStatus *status)
919 if (ast->st_dev != bst->st_dev || ast->st_ino != bst->st_ino)
920 return FALSE;
922 if (S_ISDIR (ast->st_mode))
923 *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a, b);
924 else
925 *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a, b);
927 return TRUE;
930 /* --------------------------------------------------------------------------------------------- */
931 /* {{{ Query/status report routines */
933 static FileProgressStatus
934 real_do_file_error (enum OperationMode mode, gboolean allow_retry, const char *error)
936 int result;
937 const char *msg;
939 msg = mode == Foreground ? MSG_ERROR : _("Background process error");
941 if (allow_retry)
942 result =
943 query_dialog (msg, error, D_ERROR, 4, _("&Ignore"), _("Ignore a&ll"), _("&Retry"),
944 _("&Abort"));
945 else
946 result = query_dialog (msg, error, D_ERROR, 3, _("&Ignore"), _("Ignore a&ll"), _("&Abort"));
948 switch (result)
950 case 0:
951 do_refresh ();
952 return FILE_IGNORE;
954 case 1:
955 do_refresh ();
956 return FILE_IGNORE_ALL;
958 case 2:
959 if (allow_retry)
961 do_refresh ();
962 return FILE_RETRY;
964 MC_FALLTHROUGH;
966 case 3:
967 default:
968 return FILE_ABORT;
972 /* --------------------------------------------------------------------------------------------- */
974 static FileProgressStatus
975 real_query_recursive (file_op_context_t *ctx, enum OperationMode mode, const char *s)
977 if (ctx->recursive_result < RECURSIVE_ALWAYS)
979 const char *msg;
980 char *text;
982 msg = mode == Foreground
983 ? _("Directory \"%s\" not empty.\nDelete it recursively?")
984 : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
985 text = g_strdup_printf (msg, path_trunc (s, 30));
987 if (safe_delete)
988 query_set_sel (1);
990 ctx->recursive_result =
991 query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"),
992 _("Non&e"), _("&Abort"));
993 g_free (text);
995 if (ctx->recursive_result != RECURSIVE_ABORT)
996 do_refresh ();
999 switch (ctx->recursive_result)
1001 case RECURSIVE_YES:
1002 case RECURSIVE_ALWAYS:
1003 return FILE_CONT;
1005 case RECURSIVE_NO:
1006 case RECURSIVE_NEVER:
1007 return FILE_SKIP;
1009 case RECURSIVE_ABORT:
1010 default:
1011 return FILE_ABORT;
1015 /* --------------------------------------------------------------------------------------------- */
1017 #ifdef ENABLE_BACKGROUND
1018 static FileProgressStatus
1019 do_file_error (gboolean allow_retry, const char *str)
1021 /* *INDENT-OFF* */
1022 union
1024 void *p;
1025 FileProgressStatus (*f) (enum OperationMode, gboolean, const char *);
1026 } pntr;
1027 /* *INDENT-ON* */
1029 pntr.f = real_do_file_error;
1031 if (mc_global.we_are_background)
1032 return parent_call (pntr.p, NULL, 2, sizeof (allow_retry), allow_retry, strlen (str), str);
1033 else
1034 return real_do_file_error (Foreground, allow_retry, str);
1037 /* --------------------------------------------------------------------------------------------- */
1039 static FileProgressStatus
1040 query_recursive (file_op_context_t *ctx, const char *s)
1042 /* *INDENT-OFF* */
1043 union
1045 void *p;
1046 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *);
1047 } pntr;
1048 /* *INDENT-ON* */
1050 pntr.f = real_query_recursive;
1052 if (mc_global.we_are_background)
1053 return parent_call (pntr.p, ctx, 1, strlen (s), s);
1054 else
1055 return real_query_recursive (ctx, Foreground, s);
1058 /* --------------------------------------------------------------------------------------------- */
1060 static FileProgressStatus
1061 query_replace (file_op_context_t *ctx, const char *src, struct stat *src_stat, const char *dst,
1062 struct stat *dst_stat)
1064 /* *INDENT-OFF* */
1065 union
1067 void *p;
1068 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *,
1069 struct stat *, const char *, struct stat *);
1070 } pntr;
1071 /* *INDENT-ON* */
1073 pntr.f = file_progress_real_query_replace;
1075 if (mc_global.we_are_background)
1076 return parent_call (pntr.p, ctx, 4, strlen (src), src, sizeof (struct stat), src_stat,
1077 strlen (dst), dst, sizeof (struct stat), dst_stat);
1078 else
1079 return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
1082 #else
1083 /* --------------------------------------------------------------------------------------------- */
1085 static FileProgressStatus
1086 do_file_error (gboolean allow_retry, const char *str)
1088 return real_do_file_error (Foreground, allow_retry, str);
1091 /* --------------------------------------------------------------------------------------------- */
1093 static FileProgressStatus
1094 query_recursive (file_op_context_t *ctx, const char *s)
1096 return real_query_recursive (ctx, Foreground, s);
1099 /* --------------------------------------------------------------------------------------------- */
1101 static FileProgressStatus
1102 query_replace (file_op_context_t *ctx, const char *src, struct stat *src_stat, const char *dst,
1103 struct stat *dst_stat)
1105 return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
1108 #endif /* !ENABLE_BACKGROUND */
1110 /* --------------------------------------------------------------------------------------------- */
1111 /** Report error with two files */
1113 static FileProgressStatus
1114 files_error (const char *format, const char *file1, const char *file2)
1116 char buf[BUF_MEDIUM];
1117 char *nfile1, *nfile2;
1119 nfile1 = g_strdup (path_trunc (file1, 15));
1120 nfile2 = g_strdup (path_trunc (file2, 15));
1121 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno));
1122 g_free (nfile1);
1123 g_free (nfile2);
1125 return do_file_error (TRUE, buf);
1128 /* }}} */
1130 /* --------------------------------------------------------------------------------------------- */
1132 static void
1133 copy_file_file_display_progress (file_op_context_t *ctx, gint64 tv_current, off_t file_part,
1134 off_t file_size)
1136 gint64 dt;
1138 /* Update rotating dash after some time */
1139 rotate_dash (TRUE);
1141 /* Compute ETA */
1142 dt = (tv_current - ctx->transfer_start) / G_USEC_PER_SEC;
1144 if (file_part == 0)
1145 ctx->eta_secs = 0.0;
1146 else
1147 ctx->eta_secs = ((dt / (double) file_part) * file_size) - dt;
1149 /* Compute BPS rate */
1150 ctx->bps_time = MAX (1, dt);
1151 ctx->bps = file_part / ctx->bps_time;
1153 /* Compute total ETA and BPS */
1154 if (ctx->total_bytes != 0)
1156 gint64 total_secs;
1158 total_secs = (tv_current - ctx->total_transfer_start) / G_USEC_PER_SEC;
1159 total_secs = MAX (1, total_secs);
1161 ctx->total_bps = ctx->total_progress_bytes / total_secs;
1162 const uintmax_t remain_bytes = ctx->total_bytes - ctx->total_progress_bytes;
1163 ctx->total_eta_secs = ctx->total_bps != 0 ? remain_bytes / ctx->total_bps : 0;
1167 /* --------------------------------------------------------------------------------------------- */
1169 static gboolean
1170 try_remove_file (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status)
1172 while (mc_unlink (vpath) != 0 && !ctx->ignore_all)
1174 *status = file_error (TRUE, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath));
1175 if (*status == FILE_RETRY)
1176 continue;
1177 if (*status == FILE_IGNORE_ALL)
1178 ctx->ignore_all = TRUE;
1179 return FALSE;
1182 return TRUE;
1185 /* --------------------------------------------------------------------------------------------- */
1187 /* {{{ Move routines */
1190 * Move single file or one of many files from one location to another.
1192 * @panel pointer to panel in case of single file, NULL otherwise
1193 * @ctx file operation context object
1194 * @s source file name
1195 * @d destination file name
1197 * @return operation result
1199 static FileProgressStatus
1200 move_file_file (const WPanel *panel, file_op_context_t *ctx, const char *s, const char *d)
1202 struct stat src_stat, dst_stat;
1203 FileProgressStatus return_status = FILE_CONT;
1204 gboolean copy_done = FALSE;
1205 gboolean old_ask_overwrite;
1206 vfs_path_t *src_vpath, *dst_vpath;
1208 src_vpath = vfs_path_from_str (s);
1209 dst_vpath = vfs_path_from_str (d);
1211 file_progress_show_source (ctx, src_vpath);
1212 file_progress_show_target (ctx, dst_vpath);
1214 /* FIXME: do we really need to check buttons in case of single file? */
1215 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1217 return_status = FILE_ABORT;
1218 goto ret;
1221 mc_refresh ();
1223 while (mc_lstat (src_vpath, &src_stat) != 0)
1225 /* Source doesn't exist */
1226 if (ctx->ignore_all)
1227 return_status = FILE_IGNORE_ALL;
1228 else
1230 return_status = file_error (TRUE, _("Cannot stat file \"%s\"\n%s"), s);
1231 if (return_status == FILE_IGNORE_ALL)
1232 ctx->ignore_all = TRUE;
1235 if (return_status != FILE_RETRY)
1236 goto ret;
1239 if (mc_lstat (dst_vpath, &dst_stat) == 0)
1241 if (check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1242 goto ret;
1244 if (S_ISDIR (dst_stat.st_mode))
1246 message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
1247 do_refresh ();
1248 return_status = FILE_SKIP;
1249 goto ret;
1252 if (confirm_overwrite)
1254 return_status = query_replace (ctx, s, &src_stat, d, &dst_stat);
1255 if (return_status != FILE_CONT)
1256 goto ret;
1258 /* Ok to overwrite */
1261 if (!ctx->do_append)
1263 if (S_ISLNK (src_stat.st_mode) && ctx->stable_symlinks)
1265 return_status = make_symlink (ctx, src_vpath, dst_vpath);
1266 if (return_status == FILE_CONT)
1268 if (ctx->preserve)
1270 mc_timesbuf_t times;
1272 vfs_get_timesbuf_from_stat (&src_stat, &times);
1273 mc_utime (dst_vpath, &times);
1275 goto retry_src_remove;
1277 goto ret;
1280 if (mc_rename (src_vpath, dst_vpath) == 0)
1282 /* FIXME: do we really need to update progress in case of single file? */
1283 return_status = progress_update_one (ctx, src_stat.st_size);
1284 goto ret;
1287 #if 0
1288 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1289 one nfs to the same, but on the server it is on two different
1290 filesystems. Then nfs returns EIO instead of EXDEV.
1291 Hope it will not hurt if we always in case of error try to copy/delete. */
1292 else
1293 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1295 if (errno != EXDEV)
1297 if (ctx->ignore_all)
1298 return_status = FILE_IGNORE_ALL;
1299 else
1301 return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
1302 if (return_status == FILE_IGNORE_ALL)
1303 ctx->ignore_all = TRUE;
1304 if (return_status == FILE_RETRY)
1305 goto retry_rename;
1308 goto ret;
1310 #endif
1312 /* Failed rename -> copy the file instead */
1313 if (panel != NULL)
1315 /* In case of single file, calculate totals. In case of many files,
1316 totals are calculated already. */
1317 return_status =
1318 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1319 FILEGUI_DIALOG_ONE_ITEM);
1320 if (return_status != FILE_CONT)
1321 goto ret;
1324 old_ask_overwrite = ctx->ask_overwrite;
1325 ctx->ask_overwrite = FALSE;
1326 return_status = copy_file_file (ctx, s, d);
1327 ctx->ask_overwrite = old_ask_overwrite;
1328 if (return_status != FILE_CONT)
1329 goto ret;
1331 copy_done = TRUE;
1333 /* FIXME: there is no need to update progress and check buttons
1334 at the finish of single file operation. */
1335 if (panel == NULL)
1337 file_progress_show_source (ctx, NULL);
1338 if (verbose)
1339 file_progress_show (ctx, 0, 0, "", FALSE);
1341 return_status = file_progress_check_buttons (ctx);
1342 if (return_status != FILE_CONT)
1343 goto ret;
1346 mc_refresh ();
1348 retry_src_remove:
1349 if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL)
1350 goto ret;
1352 if (!copy_done)
1353 return_status = progress_update_one (ctx, src_stat.st_size);
1355 ret:
1356 vfs_path_free (src_vpath, TRUE);
1357 vfs_path_free (dst_vpath, TRUE);
1359 return return_status;
1362 /* }}} */
1364 /* --------------------------------------------------------------------------------------------- */
1365 /* {{{ Erase routines */
1366 /** Don't update progress status if progress_count==NULL */
1368 static FileProgressStatus
1369 erase_file (file_op_context_t *ctx, const vfs_path_t *vpath)
1371 struct stat buf;
1372 FileProgressStatus return_status;
1374 /* check buttons if deleting info was changed */
1375 if (file_progress_show_deleting (ctx, vpath, &ctx->total_progress_count))
1377 file_progress_show_count (ctx, ctx->total_progress_count, ctx->total_count);
1378 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1379 return FILE_ABORT;
1381 mc_refresh ();
1384 if (ctx->total_progress_count != 0 && mc_lstat (vpath, &buf) != 0)
1386 /* ignore, most likely the mc_unlink fails, too */
1387 buf.st_size = 0;
1390 if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT)
1391 return FILE_ABORT;
1393 if (ctx->total_progress_count == 0)
1394 return FILE_CONT;
1396 return file_progress_check_buttons (ctx);
1399 /* --------------------------------------------------------------------------------------------- */
1401 static FileProgressStatus
1402 try_erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
1404 const char *dir;
1405 FileProgressStatus return_status = FILE_CONT;
1407 dir = vfs_path_as_str (vpath);
1409 while (my_rmdir (dir) != 0 && !ctx->ignore_all)
1411 return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir);
1412 if (return_status == FILE_IGNORE_ALL)
1413 ctx->ignore_all = TRUE;
1414 if (return_status != FILE_RETRY)
1415 break;
1418 return return_status;
1421 /* --------------------------------------------------------------------------------------------- */
1424 Recursive removal of files
1425 abort -> cancel stack
1426 ignore -> warn every level, gets default
1427 ignore_all -> remove as much as possible
1429 static FileProgressStatus
1430 recursive_erase (file_op_context_t *ctx, const vfs_path_t *vpath)
1432 struct vfs_dirent *next;
1433 DIR *reading;
1434 FileProgressStatus return_status = FILE_CONT;
1436 reading = mc_opendir (vpath);
1437 if (reading == NULL)
1438 return FILE_RETRY;
1440 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
1442 vfs_path_t *tmp_vpath;
1443 struct stat buf;
1445 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
1446 continue;
1448 tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
1449 if (mc_lstat (tmp_vpath, &buf) != 0)
1451 mc_closedir (reading);
1452 vfs_path_free (tmp_vpath, TRUE);
1453 return FILE_RETRY;
1455 if (S_ISDIR (buf.st_mode))
1456 return_status = recursive_erase (ctx, tmp_vpath);
1457 else
1458 return_status = erase_file (ctx, tmp_vpath);
1459 vfs_path_free (tmp_vpath, TRUE);
1461 mc_closedir (reading);
1463 if (return_status == FILE_ABORT)
1464 return FILE_ABORT;
1466 file_progress_show_deleting (ctx, vpath, NULL);
1467 file_progress_show_count (ctx, ctx->total_progress_count, ctx->total_count);
1468 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1469 return FILE_ABORT;
1471 mc_refresh ();
1473 return try_erase_dir (ctx, vpath);
1476 /* --------------------------------------------------------------------------------------------- */
1478 * Check if directory is empty or not.
1480 * @param vpath directory handler
1482 * @returns -1 on error,
1483 * 1 if there are no entries besides "." and ".." in the directory path points to,
1484 * 0 else.
1486 * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
1487 * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
1488 * in SHELL) don't return "." and ".." entries.
1490 static int
1491 check_dir_is_empty (const vfs_path_t *vpath)
1493 DIR *dir;
1494 struct vfs_dirent *d;
1495 int i = 1;
1497 dir = mc_opendir (vpath);
1498 if (dir == NULL)
1499 return -1;
1501 for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1502 if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1504 i = 0;
1505 break;
1508 mc_closedir (dir);
1509 return i;
1512 /* --------------------------------------------------------------------------------------------- */
1514 static FileProgressStatus
1515 erase_dir_iff_empty (file_op_context_t *ctx, const vfs_path_t *vpath, size_t count)
1517 file_progress_show_deleting (ctx, vpath, NULL);
1518 file_progress_show_count (ctx, count, ctx->total_count);
1519 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1520 return FILE_ABORT;
1522 mc_refresh ();
1524 if (check_dir_is_empty (vpath) != 1)
1525 return FILE_CONT;
1527 /* not empty or error */
1528 return try_erase_dir (ctx, vpath);
1531 /* --------------------------------------------------------------------------------------------- */
1533 static void
1534 erase_dir_after_copy (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status)
1536 if (ctx->erase_at_end && erase_list != NULL)
1538 /* Reset progress count before delete to avoid counting files twice */
1539 ctx->total_progress_count = ctx->prev_total_progress_count;
1541 while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT)
1543 link_t *lp;
1545 lp = (link_t *) g_queue_pop_head (erase_list);
1547 if (S_ISDIR (lp->st_mode))
1548 *status = erase_dir_iff_empty (ctx, lp->src_vpath, ctx->total_progress_count);
1549 else
1550 *status = erase_file (ctx, lp->src_vpath);
1552 free_link (lp);
1555 /* Save progress counter before move next directory */
1556 ctx->prev_total_progress_count = ctx->total_progress_count;
1559 erase_dir_iff_empty (ctx, vpath, ctx->total_progress_count);
1562 /* }}} */
1564 /* --------------------------------------------------------------------------------------------- */
1567 * Move single directory or one of many directories from one location to another.
1569 * @panel pointer to panel in case of single directory, NULL otherwise
1570 * @ctx file operation context object
1571 * @s source directory name
1572 * @d destination directory name
1574 * @return operation result
1576 static FileProgressStatus
1577 do_move_dir_dir (const WPanel *panel, file_op_context_t *ctx, const char *s, const char *d)
1579 struct stat src_stat, dst_stat;
1580 FileProgressStatus return_status = FILE_CONT;
1581 gboolean move_over = FALSE;
1582 gboolean dstat_ok;
1583 vfs_path_t *src_vpath, *dst_vpath;
1585 src_vpath = vfs_path_from_str (s);
1586 dst_vpath = vfs_path_from_str (d);
1588 file_progress_show_source (ctx, src_vpath);
1589 file_progress_show_target (ctx, dst_vpath);
1591 /* FIXME: do we really need to check buttons in case of single directory? */
1592 if (panel != NULL && file_progress_check_buttons (ctx) == FILE_ABORT)
1594 return_status = FILE_ABORT;
1595 goto ret_fast;
1598 mc_refresh ();
1600 mc_stat (src_vpath, &src_stat);
1602 dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0);
1604 if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1605 goto ret_fast;
1607 if (!dstat_ok)
1608 ; /* destination doesn't exist */
1609 else if (!ctx->dive_into_subdirs)
1610 move_over = TRUE;
1611 else
1613 vfs_path_t *tmp;
1615 tmp = dst_vpath;
1616 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
1617 vfs_path_free (tmp, TRUE);
1620 d = vfs_path_as_str (dst_vpath);
1622 /* Check if the user inputted an existing dir */
1623 retry_dst_stat:
1624 if (mc_stat (dst_vpath, &dst_stat) == 0)
1626 if (move_over)
1628 if (panel != NULL)
1630 /* In case of single directory, calculate totals. In case of many directories,
1631 totals are calculated already. */
1632 return_status =
1633 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1634 FILEGUI_DIALOG_MULTI_ITEM);
1635 if (return_status != FILE_CONT)
1636 goto ret;
1639 return_status = copy_dir_dir (ctx, s, d, FALSE, TRUE, TRUE, NULL);
1641 if (return_status != FILE_CONT)
1642 goto ret;
1643 goto oktoret;
1645 else if (ctx->ignore_all)
1646 return_status = FILE_IGNORE_ALL;
1647 else
1649 if (S_ISDIR (dst_stat.st_mode))
1650 return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d);
1651 else
1652 return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d);
1653 if (return_status == FILE_IGNORE_ALL)
1654 ctx->ignore_all = TRUE;
1655 if (return_status == FILE_RETRY)
1656 goto retry_dst_stat;
1659 goto ret_fast;
1662 retry_rename:
1663 if (mc_rename (src_vpath, dst_vpath) == 0)
1665 return_status = FILE_CONT;
1666 goto ret;
1669 if (errno != EXDEV)
1671 if (!ctx->ignore_all)
1673 return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
1674 if (return_status == FILE_IGNORE_ALL)
1675 ctx->ignore_all = TRUE;
1676 if (return_status == FILE_RETRY)
1677 goto retry_rename;
1679 goto ret;
1682 /* Failed because of filesystem boundary -> copy dir instead */
1683 if (panel != NULL)
1685 /* In case of single directory, calculate totals. In case of many directories,
1686 totals are calculated already. */
1687 return_status =
1688 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1689 FILEGUI_DIALOG_MULTI_ITEM);
1690 if (return_status != FILE_CONT)
1691 goto ret;
1694 return_status = copy_dir_dir (ctx, s, d, FALSE, FALSE, TRUE, NULL);
1696 if (return_status != FILE_CONT)
1697 goto ret;
1699 oktoret:
1700 /* FIXME: there is no need to update progress and check buttons
1701 at the finish of single directory operation. */
1702 if (panel == NULL)
1704 file_progress_show_source (ctx, NULL);
1705 file_progress_show_target (ctx, NULL);
1706 if (verbose)
1707 file_progress_show (ctx, 0, 0, "", FALSE);
1709 return_status = file_progress_check_buttons (ctx);
1710 if (return_status != FILE_CONT)
1711 goto ret;
1714 mc_refresh ();
1716 erase_dir_after_copy (ctx, src_vpath, &return_status);
1718 ret:
1719 erase_list = free_erase_list (erase_list);
1720 ret_fast:
1721 vfs_path_free (src_vpath, TRUE);
1722 vfs_path_free (dst_vpath, TRUE);
1723 return return_status;
1726 /* --------------------------------------------------------------------------------------------- */
1728 /* {{{ Panel operate routines */
1731 * Return currently selected entry name or the name of the first marked
1732 * entry if there is one.
1735 static const char *
1736 panel_get_file (const WPanel *panel)
1738 const file_entry_t *fe;
1740 if (get_current_type () == view_tree)
1742 WTree *tree;
1743 const vfs_path_t *selected_name;
1745 tree = (WTree *) get_panel_widget (get_current_index ());
1746 selected_name = tree_selected_name (tree);
1747 return vfs_path_as_str (selected_name);
1750 if (panel->marked != 0)
1752 int i;
1754 for (i = 0; i < panel->dir.len; i++)
1755 if (panel->dir.list[i].f.marked != 0)
1756 return panel->dir.list[i].fname->str;
1759 fe = panel_current_entry (panel);
1761 return (fe == NULL ? NULL : fe->fname->str);
1764 /* --------------------------------------------------------------------------------------------- */
1766 static const char *
1767 check_single_entry (const WPanel *panel, gboolean force_single, struct stat *src_stat)
1769 const char *source;
1770 gboolean ok;
1772 if (force_single)
1774 const file_entry_t *fe;
1776 fe = panel_current_entry (panel);
1777 source = fe == NULL ? NULL : fe->fname->str;
1779 else
1780 source = panel_get_file (panel);
1782 if (source == NULL)
1783 return NULL;
1785 ok = !DIR_IS_DOTDOT (source);
1787 if (!ok)
1788 message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
1789 else
1791 vfs_path_t *source_vpath;
1793 source_vpath = vfs_path_from_str (source);
1795 /* Update stat to get actual info */
1796 ok = mc_lstat (source_vpath, src_stat) == 0;
1797 if (!ok)
1799 message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
1800 path_trunc (source, 30), unix_error_string (errno));
1802 /* Directory was changed outside MC. Reload it forced */
1803 if (!panel->is_panelized)
1805 panel_update_flags_t flags = UP_RELOAD;
1807 /* don't update panelized panel */
1808 if (get_other_type () == view_listing && other_panel->is_panelized)
1809 flags |= UP_ONLY_CURRENT;
1811 update_panels (flags, UP_KEEPSEL);
1815 vfs_path_free (source_vpath, TRUE);
1818 return ok ? source : NULL;
1821 /* --------------------------------------------------------------------------------------------- */
1823 * Generate user prompt for panel operation.
1824 * src_stat must be not NULL for single source, and NULL for multiple sources
1827 static char *
1828 panel_operate_generate_prompt (const WPanel *panel, FileOperation operation,
1829 const struct stat *src_stat)
1831 char *sp;
1832 char *format_string;
1833 const char *cp;
1835 static gboolean i18n_flag = FALSE;
1836 if (!i18n_flag)
1838 size_t i;
1840 for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1841 op_names1[i] = Q_ (op_names1[i]);
1843 #ifdef ENABLE_NLS
1844 for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1845 prompt_parts[i] = _(prompt_parts[i]);
1847 one_format = _(one_format);
1848 many_format = _(many_format);
1849 #endif /* ENABLE_NLS */
1850 i18n_flag = TRUE;
1853 /* Possible prompts:
1854 * OP_COPY:
1855 * "Copy file \"%s\" with source mask:"
1856 * "Copy %d files with source mask:"
1857 * "Copy directory \"%s\" with source mask:"
1858 * "Copy %d directories with source mask:"
1859 * "Copy %d files/directories with source mask:"
1860 * OP_MOVE:
1861 * "Move file \"%s\" with source mask:"
1862 * "Move %d files with source mask:"
1863 * "Move directory \"%s\" with source mask:"
1864 * "Move %d directories with source mask:"
1865 * "Move %d files/directories with source mask:"
1866 * OP_DELETE:
1867 * "Delete file \"%s\"?"
1868 * "Delete %d files?"
1869 * "Delete directory \"%s\"?"
1870 * "Delete %d directories?"
1871 * "Delete %d files/directories?"
1874 cp = (src_stat != NULL ? one_format : many_format);
1876 /* 1. Substitute %o */
1877 format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1879 /* 2. Substitute %n */
1880 cp = operation == OP_DELETE ? "\n" : " ";
1881 sp = format_string;
1882 format_string = str_replace_all (sp, "%n", cp);
1883 g_free (sp);
1885 /* 3. Substitute %f */
1886 if (src_stat != NULL)
1887 cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1888 else if (panel->marked == panel->dirs_marked)
1889 cp = prompt_parts[3];
1890 else
1891 cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1893 sp = format_string;
1894 format_string = str_replace_all (sp, "%f", cp);
1895 g_free (sp);
1897 /* 4. Substitute %m */
1898 cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1899 sp = format_string;
1900 format_string = str_replace_all (sp, "%m", cp);
1901 g_free (sp);
1903 return format_string;
1906 /* --------------------------------------------------------------------------------------------- */
1908 static char *
1909 do_confirm_copy_move (const WPanel *panel, gboolean force_single, const char *source,
1910 struct stat *src_stat, file_op_context_t *ctx, gboolean *do_bg)
1912 const char *tmp_dest_dir;
1913 char *dest_dir;
1914 char *format;
1915 char *ret;
1917 /* Forced single operations default to the original name */
1918 if (force_single)
1919 tmp_dest_dir = source;
1920 else if (get_other_type () == view_listing)
1921 tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
1922 else
1923 tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
1926 * Add trailing backslash only when do non-local ops.
1927 * It saves user from occasional file renames (when destination
1928 * dir is deleted)
1930 if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
1931 && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
1933 /* add trailing separator */
1934 dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
1936 else
1938 /* just copy */
1939 dest_dir = g_strdup (tmp_dest_dir);
1942 if (dest_dir == NULL)
1943 return NULL;
1945 if (source == NULL)
1946 src_stat = NULL;
1948 /* Generate confirmation prompt */
1949 format = panel_operate_generate_prompt (panel, ctx->operation, src_stat);
1951 ret = file_mask_dialog (ctx, source != NULL, format,
1952 source != NULL ? source : (const void *) &panel->marked, dest_dir,
1953 do_bg);
1955 g_free (format);
1956 g_free (dest_dir);
1958 return ret;
1961 /* --------------------------------------------------------------------------------------------- */
1963 static gboolean
1964 do_confirm_erase (const WPanel *panel, const char *source, struct stat *src_stat)
1966 int i;
1967 char *format;
1968 char fmd_buf[BUF_MEDIUM];
1970 if (source == NULL)
1971 src_stat = NULL;
1973 /* Generate confirmation prompt */
1974 format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat);
1976 if (source == NULL)
1977 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1978 else
1980 const int fmd_xlen = 64;
1982 i = fmd_xlen - str_term_width1 (format) - 4;
1983 g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
1986 g_free (format);
1988 if (safe_delete)
1989 query_set_sel (1);
1991 i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
1993 return (i == 0);
1996 /* --------------------------------------------------------------------------------------------- */
1998 static FileProgressStatus
1999 operate_single_file (const WPanel *panel, file_op_context_t *ctx, const char *src,
2000 struct stat *src_stat, const char *dest, filegui_dialog_type_t dialog_type)
2002 FileProgressStatus value;
2003 vfs_path_t *src_vpath;
2004 gboolean is_file;
2006 if (g_path_is_absolute (src))
2007 src_vpath = vfs_path_from_str (src);
2008 else
2009 src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2011 is_file = !S_ISDIR (src_stat->st_mode);
2012 /* Is link to directory? */
2013 if (is_file)
2015 gboolean is_link;
2017 is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2018 is_file = !(is_link && ctx->follow_links);
2021 if (ctx->operation == OP_DELETE)
2023 value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type);
2024 if (value == FILE_CONT)
2026 if (is_file)
2027 value = erase_file (ctx, src_vpath);
2028 else
2029 value = erase_dir (ctx, src_vpath);
2032 else
2034 char *temp;
2036 src = vfs_path_as_str (src_vpath);
2038 temp = build_dest (ctx, src, dest, &value);
2039 if (temp != NULL)
2041 dest = temp;
2043 switch (ctx->operation)
2045 case OP_COPY:
2046 /* we use file_mask_op_follow_links only with OP_COPY */
2047 ctx->stat_func (src_vpath, src_stat);
2049 value =
2050 panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file,
2051 dialog_type);
2052 if (value == FILE_CONT)
2054 is_file = !S_ISDIR (src_stat->st_mode);
2055 /* Is link to directory? */
2056 if (is_file)
2058 gboolean is_link;
2060 is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2061 is_file = !(is_link && ctx->follow_links);
2064 if (is_file)
2065 value = copy_file_file (ctx, src, dest);
2066 else
2067 value = copy_dir_dir (ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2069 break;
2071 case OP_MOVE:
2072 #ifdef ENABLE_BACKGROUND
2073 if (!mc_global.we_are_background)
2074 #endif
2075 /* create UI to show confirmation dialog */
2076 file_progress_ui_create (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM);
2078 if (is_file)
2079 value = move_file_file (panel, ctx, src, dest);
2080 else
2081 value = do_move_dir_dir (panel, ctx, src, dest);
2082 break;
2084 default:
2085 /* Unknown file operation */
2086 abort ();
2089 g_free (temp);
2093 vfs_path_free (src_vpath, TRUE);
2095 return value;
2098 /* --------------------------------------------------------------------------------------------- */
2100 static FileProgressStatus
2101 operate_one_file (const WPanel *panel, file_op_context_t *ctx, const char *src,
2102 struct stat *src_stat, const char *dest)
2104 FileProgressStatus value = FILE_CONT;
2105 vfs_path_t *src_vpath;
2106 gboolean is_file;
2108 if (g_path_is_absolute (src))
2109 src_vpath = vfs_path_from_str (src);
2110 else
2111 src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2113 is_file = !S_ISDIR (src_stat->st_mode);
2115 if (ctx->operation == OP_DELETE)
2117 if (is_file)
2118 value = erase_file (ctx, src_vpath);
2119 else
2120 value = erase_dir (ctx, src_vpath);
2122 else
2124 char *temp;
2126 src = vfs_path_as_str (src_vpath);
2128 temp = build_dest (ctx, src, dest, &value);
2129 if (temp != NULL)
2131 dest = temp;
2133 switch (ctx->operation)
2135 case OP_COPY:
2136 /* we use file_mask_op_follow_links only with OP_COPY */
2137 ctx->stat_func (src_vpath, src_stat);
2138 is_file = !S_ISDIR (src_stat->st_mode);
2140 if (is_file)
2141 value = copy_file_file (ctx, src, dest);
2142 else
2143 value = copy_dir_dir (ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2144 dest_dirs = free_linklist (dest_dirs);
2145 break;
2147 case OP_MOVE:
2148 if (is_file)
2149 value = move_file_file (NULL, ctx, src, dest);
2150 else
2151 value = do_move_dir_dir (NULL, ctx, src, dest);
2152 break;
2154 default:
2155 /* Unknown file operation */
2156 abort ();
2159 g_free (temp);
2163 vfs_path_free (src_vpath, TRUE);
2165 return value;
2168 /* --------------------------------------------------------------------------------------------- */
2170 #ifdef ENABLE_BACKGROUND
2171 static int
2172 end_bg_process (file_op_context_t *ctx, enum OperationMode mode)
2174 int pid = ctx->pid;
2176 (void) mode;
2177 ctx->pid = 0;
2179 unregister_task_with_pid (pid);
2180 /* file_op_context_destroy(ctx); */
2181 return 1;
2183 #endif
2184 /* }}} */
2186 /* --------------------------------------------------------------------------------------------- */
2189 * On Solaris, ENOTSUP != EOPNOTSUPP. Some FS also return ENOSYS or EINVAL as "not implemented".
2190 * On some Linux kernels (tested on 4.9, 5.4) there is ENOTTY on tmpfs.
2192 static inline gboolean
2193 attrs_ignore_error (const int e)
2195 return (e == ENOTSUP || e == EOPNOTSUPP || e == ENOSYS || e == EINVAL || e == ENOTTY
2196 || e == ELOOP || e == ENXIO);
2199 /* --------------------------------------------------------------------------------------------- */
2200 /*** public functions ****************************************************************************/
2201 /* --------------------------------------------------------------------------------------------- */
2203 /* Is file symlink to directory or not.
2205 * @param path file or directory
2206 * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2207 * @param stale_link TRUE if file is stale link to directory
2209 * @return TRUE if file symlink to directory, ELSE otherwise.
2211 gboolean
2212 file_is_symlink_to_dir (const vfs_path_t *vpath, struct stat *st, gboolean *stale_link)
2214 struct stat st2;
2215 gboolean stale = FALSE;
2216 gboolean res = FALSE;
2218 if (st == NULL)
2220 st = &st2;
2222 if (mc_lstat (vpath, st) != 0)
2223 goto ret;
2226 if (S_ISLNK (st->st_mode))
2228 struct stat st3;
2230 stale = (mc_stat (vpath, &st3) != 0);
2232 if (!stale)
2233 res = (S_ISDIR (st3.st_mode) != 0);
2236 ret:
2237 if (stale_link != NULL)
2238 *stale_link = stale;
2240 return res;
2243 /* --------------------------------------------------------------------------------------------- */
2245 FileProgressStatus
2246 copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_path)
2248 uid_t src_uid = (uid_t) (-1);
2249 gid_t src_gid = (gid_t) (-1);
2251 int src_desc, dest_desc = -1;
2252 mode_t src_mode = 0; /* The mode of the source file */
2253 struct stat src_stat, dst_stat;
2254 mc_timesbuf_t times;
2255 unsigned long attrs = 0;
2256 gboolean attrs_ok = ctx->preserve;
2257 gboolean dst_exists = FALSE, appending = FALSE;
2258 off_t file_size = -1;
2259 FileProgressStatus return_status, temp_status;
2260 dest_status_t dst_status = DEST_NONE;
2261 int open_flags;
2262 vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
2263 char *buf = NULL;
2265 /* Keep the non-default value applied in chain of calls:
2266 move_file_file() -> file_progress_real_query_replace()
2267 move_file_file() -> copy_file_file() */
2268 if (ctx->do_reget < 0)
2269 ctx->do_reget = 0;
2271 return_status = FILE_RETRY;
2273 dst_vpath = vfs_path_from_str (dst_path);
2274 src_vpath = vfs_path_from_str (src_path);
2276 file_progress_show_source (ctx, src_vpath);
2277 file_progress_show_target (ctx, dst_vpath);
2279 if (file_progress_check_buttons (ctx) == FILE_ABORT)
2281 return_status = FILE_ABORT;
2282 goto ret_fast;
2285 mc_refresh ();
2287 while (mc_stat (dst_vpath, &dst_stat) == 0)
2289 if (S_ISDIR (dst_stat.st_mode))
2291 if (ctx->ignore_all)
2292 return_status = FILE_IGNORE_ALL;
2293 else
2295 return_status =
2296 file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path);
2297 if (return_status == FILE_IGNORE_ALL)
2298 ctx->ignore_all = TRUE;
2299 if (return_status == FILE_RETRY)
2300 continue;
2302 goto ret_fast;
2305 dst_exists = TRUE;
2306 break;
2309 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2311 if (ctx->ignore_all)
2312 return_status = FILE_IGNORE_ALL;
2313 else
2315 return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path);
2316 if (return_status == FILE_IGNORE_ALL)
2317 ctx->ignore_all = TRUE;
2320 if (return_status != FILE_RETRY)
2321 goto ret_fast;
2324 while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2326 attrs_ok = FALSE;
2328 /* don't show an error message if attributes aren't supported in this FS */
2329 if (attrs_ignore_error (errno))
2330 return_status = FILE_CONT;
2331 else if (ctx->ignore_all)
2332 return_status = FILE_IGNORE_ALL;
2333 else
2335 return_status =
2336 file_error (TRUE, _("Cannot get ext2 attributes of source file \"%s\"\n%s"),
2337 src_path);
2338 if (return_status == FILE_IGNORE_ALL)
2339 ctx->ignore_all = TRUE;
2340 if (return_status == FILE_ABORT)
2341 goto ret_fast;
2344 if (return_status != FILE_RETRY)
2345 break;
2347 /* yet another attempt */
2348 attrs_ok = TRUE;
2351 if (dst_exists)
2353 /* Destination already exists */
2354 if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status))
2355 goto ret_fast;
2357 /* Should we replace destination? */
2358 if (ctx->ask_overwrite)
2360 ctx->do_reget = 0;
2361 return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
2362 if (return_status != FILE_CONT)
2363 goto ret_fast;
2367 vfs_get_timesbuf_from_stat (&src_stat, &times);
2369 if (!ctx->do_append)
2371 /* Check the hardlinks */
2372 if (!ctx->follow_links)
2374 switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
2376 case HARDLINK_OK:
2377 /* We have made a hardlink - no more processing is necessary */
2378 return_status = FILE_CONT;
2379 goto ret_fast;
2381 case HARDLINK_ABORT:
2382 return_status = FILE_ABORT;
2383 goto ret_fast;
2385 default:
2386 break;
2390 if (S_ISLNK (src_stat.st_mode))
2392 return_status = make_symlink (ctx, src_vpath, dst_vpath);
2393 if (return_status == FILE_CONT && ctx->preserve)
2395 mc_utime (dst_vpath, &times);
2397 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2399 attrs_ok = FALSE;
2401 /* don't show an error message if attributes aren't supported in this FS */
2402 if (attrs_ignore_error (errno))
2403 return_status = FILE_CONT;
2404 else if (return_status == FILE_IGNORE_ALL)
2405 ctx->ignore_all = TRUE;
2406 else
2407 return_status =
2408 file_error (TRUE,
2409 _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2410 dst_path);
2412 if (return_status != FILE_RETRY)
2413 break;
2415 /* yet another attempt */
2416 attrs_ok = TRUE;
2419 goto ret_fast;
2422 if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
2423 || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
2425 dev_t rdev = 0;
2427 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2428 rdev = src_stat.st_rdev;
2429 #endif
2431 while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
2432 && !ctx->ignore_all)
2434 return_status =
2435 file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path);
2436 if (return_status == FILE_RETRY)
2437 continue;
2438 if (return_status == FILE_IGNORE_ALL)
2439 ctx->ignore_all = TRUE;
2440 goto ret_fast;
2442 /* Success */
2444 while (ctx->preserve_uidgid
2445 && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0
2446 && !ctx->ignore_all)
2448 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2449 if (temp_status == FILE_IGNORE)
2450 break;
2451 if (temp_status == FILE_IGNORE_ALL)
2452 ctx->ignore_all = TRUE;
2453 if (temp_status != FILE_RETRY)
2455 return_status = temp_status;
2456 goto ret_fast;
2460 while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
2461 && !ctx->ignore_all)
2463 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2464 if (temp_status == FILE_IGNORE)
2465 break;
2466 if (temp_status == FILE_IGNORE_ALL)
2467 ctx->ignore_all = TRUE;
2468 if (temp_status != FILE_RETRY)
2470 return_status = temp_status;
2471 goto ret_fast;
2475 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2477 attrs_ok = FALSE;
2479 /* don't show an error message if attributes aren't supported in this FS */
2480 if (attrs_ignore_error (errno))
2481 break;
2483 temp_status =
2484 file_error (TRUE, _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2485 dst_path);
2486 if (temp_status == FILE_IGNORE)
2487 break;
2488 if (temp_status == FILE_IGNORE_ALL)
2489 ctx->ignore_all = TRUE;
2490 if (temp_status != FILE_RETRY)
2492 return_status = temp_status;
2493 goto ret_fast;
2496 /* yet another attempt */
2497 attrs_ok = TRUE;
2500 return_status = FILE_CONT;
2501 mc_utime (dst_vpath, &times);
2502 goto ret_fast;
2506 ctx->transfer_start = g_get_monotonic_time ();
2508 while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->ignore_all)
2510 return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path);
2511 if (return_status == FILE_RETRY)
2512 continue;
2513 if (return_status == FILE_IGNORE_ALL)
2514 ctx->ignore_all = TRUE;
2515 if (return_status == FILE_IGNORE)
2516 break;
2517 ctx->do_append = FALSE;
2518 goto ret_fast;
2521 if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
2523 message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
2524 ctx->do_reget = 0;
2525 ctx->do_append = FALSE;
2528 while (mc_fstat (src_desc, &src_stat) != 0)
2530 if (ctx->ignore_all)
2531 return_status = FILE_IGNORE_ALL;
2532 else
2534 return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
2535 if (return_status == FILE_RETRY)
2536 continue;
2537 if (return_status == FILE_IGNORE_ALL)
2538 ctx->ignore_all = TRUE;
2539 ctx->do_append = FALSE;
2541 goto ret;
2544 src_mode = src_stat.st_mode;
2545 src_uid = src_stat.st_uid;
2546 src_gid = src_stat.st_gid;
2547 file_size = src_stat.st_size;
2549 open_flags = O_WRONLY;
2550 if (!dst_exists)
2551 open_flags |= O_CREAT | O_EXCL;
2552 else if (ctx->do_append)
2553 open_flags |= O_APPEND;
2554 else
2555 open_flags |= O_CREAT | O_TRUNC;
2557 while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
2559 if (errno != EEXIST)
2561 if (ctx->ignore_all)
2562 return_status = FILE_IGNORE_ALL;
2563 else
2565 return_status =
2566 file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
2567 if (return_status == FILE_RETRY)
2568 continue;
2569 if (return_status == FILE_IGNORE_ALL)
2570 ctx->ignore_all = TRUE;
2571 ctx->do_append = FALSE;
2574 goto ret;
2577 /* file opened, but not fully copied */
2578 dst_status = DEST_SHORT_QUERY;
2580 appending = ctx->do_append;
2581 ctx->do_append = FALSE;
2583 /* Try clone the file first. */
2584 if (vfs_clone_file (dest_desc, src_desc) == 0)
2586 dst_status = DEST_FULL;
2587 return_status = FILE_CONT;
2588 goto ret;
2591 /* Find out the optimal buffer size. */
2592 while (mc_fstat (dest_desc, &dst_stat) != 0)
2594 if (ctx->ignore_all)
2595 return_status = FILE_IGNORE_ALL;
2596 else
2598 return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
2599 if (return_status == FILE_RETRY)
2600 continue;
2601 if (return_status == FILE_IGNORE_ALL)
2602 ctx->ignore_all = TRUE;
2604 goto ret;
2607 /* try preallocate space; if fail, try copy anyway */
2608 while (mc_global.vfs.preallocate_space &&
2609 vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
2611 if (ctx->ignore_all)
2613 /* cannot allocate, start the file copying anyway */
2614 return_status = FILE_CONT;
2615 break;
2618 return_status =
2619 file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
2621 if (return_status == FILE_IGNORE_ALL)
2622 ctx->ignore_all = TRUE;
2624 if (ctx->ignore_all || return_status == FILE_IGNORE)
2626 /* skip the space allocation error, start file copying */
2627 return_status = FILE_CONT;
2628 break;
2631 if (return_status == FILE_ABORT)
2633 mc_close (dest_desc);
2634 dest_desc = -1;
2635 mc_unlink (dst_vpath);
2636 dst_status = DEST_NONE;
2637 goto ret;
2640 /* return_status == FILE_RETRY -- try allocate space again */
2643 ctx->eta_secs = 0.0;
2644 ctx->bps = 0;
2646 if (verbose)
2648 if (ctx->total_bps == 0 || (file_size / ctx->total_bps) > FILEOP_UPDATE_INTERVAL)
2649 file_progress_show (ctx, 0, file_size, "", TRUE);
2650 else
2651 file_progress_show (ctx, 1, 1, "", TRUE);
2654 return_status = file_progress_check_buttons (ctx);
2655 mc_refresh ();
2657 if (return_status == FILE_CONT)
2659 off_t file_part = 0;
2660 gint64 tv_last_update = ctx->transfer_start;
2661 gint64 tv_last_input = 0;
2662 gboolean is_first_time = TRUE;
2664 const size_t bufsize = io_blksize (dst_stat);
2665 buf = g_malloc (bufsize);
2667 while (TRUE)
2669 ssize_t n_read = -1;
2671 /* src_read */
2672 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
2673 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->ignore_all)
2675 return_status =
2676 file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
2677 if (return_status == FILE_RETRY)
2678 continue;
2679 if (return_status == FILE_IGNORE_ALL)
2680 ctx->ignore_all = TRUE;
2681 goto ret;
2684 if (n_read == 0)
2685 break;
2687 const gint64 tv_current = g_get_monotonic_time ();
2689 if (n_read > 0)
2691 ssize_t n_written;
2692 char *t = buf;
2694 file_part += n_read;
2696 tv_last_input = tv_current;
2698 /* dst_write */
2699 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
2701 gboolean write_errno_nospace;
2703 if (n_written > 0)
2705 n_read -= n_written;
2706 t += n_written;
2707 continue;
2710 write_errno_nospace = (n_written < 0 && errno == ENOSPC);
2712 if (ctx->ignore_all)
2713 return_status = FILE_IGNORE_ALL;
2714 else
2715 return_status =
2716 file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
2718 if (return_status == FILE_IGNORE)
2720 if (write_errno_nospace)
2721 goto ret;
2722 break;
2724 if (return_status == FILE_IGNORE_ALL)
2726 ctx->ignore_all = TRUE;
2727 if (write_errno_nospace)
2728 goto ret;
2730 if (return_status != FILE_RETRY)
2731 goto ret;
2735 ctx->total_progress_bytes = ctx->progress_bytes + file_part + ctx->do_reget;
2737 const gint64 usecs = tv_current - tv_last_update;
2739 if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US)
2741 copy_file_file_display_progress (ctx, tv_current, file_part,
2742 file_size - ctx->do_reget);
2743 tv_last_update = tv_current;
2746 is_first_time = FALSE;
2748 if (verbose)
2750 const gint64 total_usecs = tv_current - ctx->total_transfer_start;
2751 const gboolean force_update = total_usecs > FILEOP_UPDATE_INTERVAL_US;
2753 const gint64 update_usecs = tv_current - tv_last_input;
2754 const char *stalled_msg =
2755 update_usecs > FILEOP_STALLING_INTERVAL_US ? _("(stalled)") : "";
2757 file_progress_show (ctx, file_part + ctx->do_reget, file_size, stalled_msg,
2758 force_update);
2759 if (ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
2761 file_progress_show_count (ctx, ctx->total_progress_count, ctx->total_count);
2762 file_progress_show_total (ctx, ctx->total_progress_bytes, force_update);
2765 mc_refresh ();
2768 return_status = file_progress_check_buttons (ctx);
2769 if (return_status != FILE_CONT)
2771 int query_res;
2773 query_res =
2774 query_dialog (Q_ ("DialogTitle|Copy"),
2775 _("Incomplete file was retrieved"), D_ERROR, 3,
2776 _("&Delete"), _("&Keep"), _("&Continue copy"));
2778 switch (query_res)
2780 case 0:
2781 /* delete */
2782 dst_status = DEST_SHORT_DELETE;
2783 goto ret;
2785 case 1:
2786 /* keep */
2787 dst_status = DEST_SHORT_KEEP;
2788 goto ret;
2790 default:
2791 /* continue copy */
2792 break;
2797 /* copy successful */
2798 dst_status = DEST_FULL;
2801 ret:
2802 g_free (buf);
2804 rotate_dash (FALSE);
2805 while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->ignore_all)
2807 temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
2808 if (temp_status == FILE_RETRY)
2809 continue;
2810 if (temp_status == FILE_ABORT)
2811 return_status = temp_status;
2812 if (temp_status == FILE_IGNORE_ALL)
2813 ctx->ignore_all = TRUE;
2814 break;
2817 while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->ignore_all)
2819 temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
2820 if (temp_status == FILE_RETRY)
2821 continue;
2822 if (temp_status == FILE_IGNORE_ALL)
2823 ctx->ignore_all = TRUE;
2824 return_status = temp_status;
2825 break;
2828 if (dst_status == DEST_SHORT_QUERY)
2830 /* Query to remove short file */
2831 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
2832 D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
2833 dst_status = DEST_SHORT_DELETE;
2834 else
2835 dst_status = DEST_SHORT_KEEP;
2838 if (dst_status == DEST_SHORT_DELETE)
2839 mc_unlink (dst_vpath);
2840 else if (dst_status == DEST_FULL && !appending)
2842 /* Copy has succeeded */
2844 while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
2845 && !ctx->ignore_all)
2847 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2848 if (temp_status == FILE_ABORT)
2850 return_status = FILE_ABORT;
2851 goto ret_fast;
2853 if (temp_status == FILE_RETRY)
2854 continue;
2855 if (temp_status == FILE_IGNORE_ALL)
2857 ctx->ignore_all = TRUE;
2858 return_status = FILE_CONT;
2860 if (temp_status == FILE_IGNORE)
2861 return_status = FILE_CONT;
2862 break;
2865 while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
2866 && !ctx->ignore_all)
2868 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2869 if (temp_status == FILE_ABORT)
2871 return_status = FILE_ABORT;
2872 goto ret_fast;
2874 if (temp_status == FILE_RETRY)
2875 continue;
2876 if (temp_status == FILE_IGNORE_ALL)
2878 ctx->ignore_all = TRUE;
2879 return_status = FILE_CONT;
2881 if (temp_status == FILE_IGNORE)
2882 return_status = FILE_CONT;
2883 break;
2886 if (!ctx->preserve && !dst_exists)
2888 src_mode = umask (-1);
2889 umask (src_mode);
2890 src_mode = 0100666 & ~src_mode;
2891 mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2895 if (dst_status == DEST_FULL || dst_status == DEST_SHORT_KEEP)
2897 /* Always sync timestamps */
2898 mc_utime (dst_vpath, &times);
2900 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2902 attrs_ok = FALSE;
2904 /* don't show an error message if attributes aren't supported in this FS */
2905 if (attrs_ignore_error (errno))
2907 return_status = FILE_CONT;
2908 break;
2911 temp_status =
2912 file_error (TRUE, _("Cannot set ext2 attributes for target file \"%s\"\n%s"),
2913 dst_path);
2914 if (temp_status == FILE_ABORT)
2915 return_status = FILE_ABORT;
2916 if (temp_status == FILE_RETRY)
2918 attrs_ok = TRUE;
2919 continue;
2921 if (temp_status == FILE_IGNORE_ALL)
2923 ctx->ignore_all = TRUE;
2924 return_status = FILE_CONT;
2926 if (temp_status == FILE_IGNORE)
2927 return_status = FILE_CONT;
2928 break;
2932 if (return_status == FILE_CONT)
2933 return_status = progress_update_one (ctx, file_size);
2935 ret_fast:
2936 vfs_path_free (src_vpath, TRUE);
2937 vfs_path_free (dst_vpath, TRUE);
2938 return return_status;
2941 /* --------------------------------------------------------------------------------------------- */
2943 * I think these copy_*_* functions should have a return type.
2944 * anyway, this function *must* have two directories as arguments.
2946 /* FIXME: This function needs to check the return values of the
2947 function calls */
2949 FileProgressStatus
2950 copy_dir_dir (file_op_context_t *ctx, const char *s, const char *d, gboolean toplevel,
2951 gboolean move_over, gboolean do_delete, GSList *parent_dirs)
2953 struct vfs_dirent *next;
2954 struct stat dst_stat, src_stat;
2955 unsigned long attrs = 0;
2956 gboolean attrs_ok = ctx->preserve;
2957 DIR *reading;
2958 FileProgressStatus return_status = FILE_CONT;
2959 link_t *lp;
2960 vfs_path_t *src_vpath, *dst_vpath;
2961 gboolean do_mkdir = TRUE;
2963 src_vpath = vfs_path_from_str (s);
2964 dst_vpath = vfs_path_from_str (d);
2966 /* First get the mode of the source dir */
2968 retry_src_stat:
2969 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2971 if (ctx->ignore_all)
2972 return_status = FILE_IGNORE_ALL;
2973 else
2975 return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
2976 if (return_status == FILE_IGNORE_ALL)
2977 ctx->ignore_all = TRUE;
2980 if (return_status != FILE_RETRY)
2981 goto ret_fast;
2984 while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2986 attrs_ok = FALSE;
2988 /* don't show an error message if attributes aren't supported in this FS */
2989 if (attrs_ignore_error (errno))
2990 return_status = FILE_CONT;
2991 else if (ctx->ignore_all)
2992 return_status = FILE_IGNORE_ALL;
2993 else
2995 return_status =
2996 file_error (TRUE, _("Cannot get ext2 attributes of source directory \"%s\"\n%s"),
2998 if (return_status == FILE_IGNORE_ALL)
2999 ctx->ignore_all = TRUE;
3000 if (return_status == FILE_ABORT)
3001 goto ret_fast;
3004 if (return_status != FILE_RETRY)
3005 break;
3007 /* yet another attempt */
3008 attrs_ok = TRUE;
3011 if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
3013 /* Don't copy a directory we created before (we don't want to copy
3014 infinitely if a directory is copied into itself) */
3015 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
3016 return_status = FILE_CONT;
3017 goto ret_fast;
3020 /* Hmm, hardlink to directory??? - Norbert */
3021 /* FIXME: In this step we should do something in case the destination already exist */
3022 /* Check the hardlinks */
3023 if (ctx->preserve)
3025 switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
3027 case HARDLINK_OK:
3028 /* We have made a hardlink - no more processing is necessary */
3029 goto ret_fast;
3031 case HARDLINK_ABORT:
3032 return_status = FILE_ABORT;
3033 goto ret_fast;
3035 default:
3036 break;
3040 if (!S_ISDIR (src_stat.st_mode))
3042 if (ctx->ignore_all)
3043 return_status = FILE_IGNORE_ALL;
3044 else
3046 return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
3047 if (return_status == FILE_RETRY)
3048 goto retry_src_stat;
3049 if (return_status == FILE_IGNORE_ALL)
3050 ctx->ignore_all = TRUE;
3052 goto ret_fast;
3055 if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
3057 /* we found a cyclic symbolic link */
3058 message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
3059 return_status = FILE_SKIP;
3060 goto ret_fast;
3063 lp = g_new0 (link_t, 1);
3064 lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
3065 lp->ino = src_stat.st_ino;
3066 lp->dev = src_stat.st_dev;
3067 parent_dirs = g_slist_prepend (parent_dirs, lp);
3069 retry_dst_stat:
3070 /* Now, check if the dest dir exists, if not, create it. */
3071 if (mc_stat (dst_vpath, &dst_stat) != 0)
3073 /* Here the dir doesn't exist : make it ! */
3074 if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
3076 return_status = FILE_CONT;
3077 goto ret;
3080 else
3083 * If the destination directory exists, we want to copy the whole
3084 * directory, but we only want this to happen once.
3086 * Escape sequences added to the * to compiler warnings.
3087 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3088 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
3090 if (!S_ISDIR (dst_stat.st_mode))
3092 if (ctx->ignore_all)
3093 return_status = FILE_IGNORE_ALL;
3094 else
3096 return_status =
3097 file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
3098 if (return_status == FILE_IGNORE_ALL)
3099 ctx->ignore_all = TRUE;
3100 if (return_status == FILE_RETRY)
3101 goto retry_dst_stat;
3103 goto ret;
3105 /* Dive into subdir if exists */
3106 if (toplevel && ctx->dive_into_subdirs)
3108 vfs_path_t *tmp;
3110 tmp = dst_vpath;
3111 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
3112 vfs_path_free (tmp, TRUE);
3115 else
3116 do_mkdir = FALSE;
3119 d = vfs_path_as_str (dst_vpath);
3121 if (do_mkdir)
3123 while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
3125 if (ctx->ignore_all)
3126 return_status = FILE_IGNORE_ALL;
3127 else
3129 return_status =
3130 file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
3131 if (return_status == FILE_IGNORE_ALL)
3132 ctx->ignore_all = TRUE;
3134 if (return_status != FILE_RETRY)
3135 goto ret;
3138 lp = g_new0 (link_t, 1);
3139 mc_stat (dst_vpath, &dst_stat);
3140 lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
3141 lp->ino = dst_stat.st_ino;
3142 lp->dev = dst_stat.st_dev;
3143 dest_dirs = g_slist_prepend (dest_dirs, lp);
3146 if (ctx->preserve_uidgid)
3148 while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
3150 if (ctx->ignore_all)
3151 return_status = FILE_IGNORE_ALL;
3152 else
3154 return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
3155 if (return_status == FILE_IGNORE_ALL)
3156 ctx->ignore_all = TRUE;
3158 if (return_status != FILE_RETRY)
3159 goto ret;
3163 /* open the source dir for reading */
3164 reading = mc_opendir (src_vpath);
3165 if (reading == NULL)
3166 goto ret;
3168 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
3170 char *path;
3171 vfs_path_t *tmp_vpath;
3174 * Now, we don't want '.' and '..' to be created / copied at any time
3176 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
3177 continue;
3179 /* get the filename and add it to the src directory */
3180 path = mc_build_filename (s, next->d_name, (char *) NULL);
3181 tmp_vpath = vfs_path_from_str (path);
3183 (*ctx->stat_func) (tmp_vpath, &dst_stat);
3184 if (S_ISDIR (dst_stat.st_mode))
3186 char *mdpath;
3188 mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
3190 * From here, we just intend to recursively copy subdirs, not
3191 * the double functionality of copying different when the target
3192 * dir already exists. So, we give the recursive call the flag 0
3193 * meaning no toplevel.
3195 return_status = copy_dir_dir (ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
3196 g_free (mdpath);
3198 else
3200 char *dest_file;
3202 dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
3203 return_status = copy_file_file (ctx, path, dest_file);
3204 g_free (dest_file);
3207 g_free (path);
3209 if (do_delete && return_status == FILE_CONT)
3211 if (ctx->erase_at_end)
3213 if (erase_list == NULL)
3214 erase_list = g_queue_new ();
3216 lp = g_new0 (link_t, 1);
3217 lp->src_vpath = tmp_vpath;
3218 lp->st_mode = dst_stat.st_mode;
3219 g_queue_push_tail (erase_list, lp);
3220 tmp_vpath = NULL;
3222 else if (S_ISDIR (dst_stat.st_mode))
3223 return_status = erase_dir_iff_empty (ctx, tmp_vpath, ctx->total_progress_count);
3224 else
3225 return_status = erase_file (ctx, tmp_vpath);
3227 vfs_path_free (tmp_vpath, TRUE);
3229 mc_closedir (reading);
3231 if (ctx->preserve)
3233 mc_timesbuf_t times;
3235 mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3237 if (attrs_ok)
3238 mc_fsetflags (dst_vpath, attrs);
3240 vfs_get_timesbuf_from_stat (&src_stat, &times);
3241 mc_utime (dst_vpath, &times);
3243 else
3245 src_stat.st_mode = umask (-1);
3246 umask (src_stat.st_mode);
3247 src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3248 mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3251 ret:
3252 free_link (parent_dirs->data);
3253 g_slist_free_1 (parent_dirs);
3254 ret_fast:
3255 vfs_path_free (src_vpath, TRUE);
3256 vfs_path_free (dst_vpath, TRUE);
3257 return return_status;
3260 /* }}} */
3262 /* --------------------------------------------------------------------------------------------- */
3263 /* {{{ Move routines */
3265 FileProgressStatus
3266 move_dir_dir (file_op_context_t *ctx, const char *s, const char *d)
3268 return do_move_dir_dir (NULL, ctx, s, d);
3271 /* }}} */
3273 /* --------------------------------------------------------------------------------------------- */
3274 /* {{{ Erase routines */
3276 FileProgressStatus
3277 erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
3279 file_progress_show_deleting (ctx, vpath, NULL);
3280 file_progress_show_count (ctx, ctx->total_progress_count, ctx->total_count);
3281 if (file_progress_check_buttons (ctx) == FILE_ABORT)
3282 return FILE_ABORT;
3284 mc_refresh ();
3286 /* The old way to detect a non empty directory was:
3287 error = my_rmdir (s);
3288 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3289 For the linux user space nfs server (nfs-server-2.2beta29-2)
3290 we would have to check also for EIO. I hope the new way is
3291 fool proof. (Norbert)
3293 if (check_dir_is_empty (vpath) == 0)
3294 { /* not empty */
3295 FileProgressStatus error;
3297 error = query_recursive (ctx, vfs_path_as_str (vpath));
3298 if (error == FILE_CONT)
3299 error = recursive_erase (ctx, vpath);
3300 return error;
3303 return try_erase_dir (ctx, vpath);
3306 /* }}} */
3308 /* --------------------------------------------------------------------------------------------- */
3309 /* {{{ Panel operate routines */
3311 void
3312 dirsize_status_init_cb (status_msg_t *sm)
3314 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3315 WGroup *gd = GROUP (sm->dlg);
3316 Widget *wd = WIDGET (sm->dlg);
3317 WRect r = wd->rect;
3319 const char *b1_name = N_("&Abort");
3320 const char *b2_name = N_("&Skip");
3321 int b_width, ui_width;
3323 #ifdef ENABLE_NLS
3324 b1_name = _(b1_name);
3325 b2_name = _(b2_name);
3326 #endif
3328 b_width = str_term_width1 (b1_name) + 4;
3329 if (dsm->allow_skip)
3330 b_width += str_term_width1 (b2_name) + 4 + 1;
3332 ui_width = MAX (COLS / 2, b_width + 6);
3333 dsm->dirname = label_new (2, 3, NULL);
3334 group_add_widget (gd, dsm->dirname);
3335 dsm->count_size = label_new (3, 3, NULL);
3336 group_add_widget (gd, dsm->count_size);
3337 group_add_widget (gd, hline_new (4, -1, -1));
3339 dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3340 group_add_widget (gd, dsm->abort_button);
3341 if (dsm->allow_skip)
3343 dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3344 group_add_widget (gd, dsm->skip_button);
3345 widget_select (dsm->skip_button);
3348 r.lines = 8;
3349 r.cols = ui_width;
3350 widget_set_size_rect (wd, &r);
3351 dirsize_status_locate_buttons (dsm);
3354 /* --------------------------------------------------------------------------------------------- */
3357 dirsize_status_update_cb (status_msg_t *sm)
3359 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3360 Widget *wd = WIDGET (sm->dlg);
3361 WRect r = wd->rect;
3363 /* update second (longer label) */
3364 label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
3365 dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3367 /* enlarge dialog if required */
3368 if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
3370 r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
3371 widget_set_size_rect (wd, &r);
3372 dirsize_status_locate_buttons (dsm);
3373 widget_draw (wd);
3374 /* TODO: ret rid of double redraw */
3377 /* adjust first label */
3378 label_set_text (dsm->dirname,
3379 str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
3381 switch (status_msg_common_update (sm))
3383 case B_CANCEL:
3384 case FILE_ABORT:
3385 return FILE_ABORT;
3386 case FILE_SKIP:
3387 return FILE_SKIP;
3388 default:
3389 return FILE_CONT;
3393 /* --------------------------------------------------------------------------------------------- */
3395 void
3396 dirsize_status_deinit_cb (status_msg_t *sm)
3398 (void) sm;
3400 /* schedule to update passive panel */
3401 if (get_other_type () == view_listing)
3402 other_panel->dirty = TRUE;
3405 /* --------------------------------------------------------------------------------------------- */
3407 * compute_dir_size:
3409 * Computes the number of bytes used by the files in a directory
3412 FileProgressStatus
3413 compute_dir_size (const vfs_path_t *dirname_vpath, dirsize_status_msg_t *sm,
3414 size_t *ret_dir_count, size_t *ret_marked_count, uintmax_t *ret_total,
3415 gboolean follow_symlinks)
3417 return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3418 follow_symlinks ? mc_stat : mc_lstat);
3421 /* --------------------------------------------------------------------------------------------- */
3423 * panel_operate:
3425 * Performs one of the operations on the current on the source_panel
3426 * (copy, delete, move).
3428 * Returns TRUE if did change the directory
3429 * structure, Returns FALSE if user aborted
3431 * force_single forces operation on the current entry and affects
3432 * default destination. Current filename is used as default.
3435 gboolean
3436 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
3438 WPanel *panel = PANEL (source_panel);
3439 const gboolean single_entry = force_single || (panel->marked <= 1)
3440 || (get_current_type () == view_tree);
3442 const char *source = NULL;
3443 char *dest = NULL;
3444 vfs_path_t *dest_vpath = NULL;
3445 vfs_path_t *save_cwd = NULL, *save_dest = NULL;
3446 struct stat src_stat;
3447 gboolean ret_val = TRUE;
3448 int i;
3449 FileProgressStatus value;
3450 file_op_context_t *ctx;
3451 filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3453 gboolean do_bg = FALSE; /* do background operation? */
3455 static gboolean i18n_flag = FALSE;
3456 if (!i18n_flag)
3458 for (i = G_N_ELEMENTS (op_names); i-- != 0;)
3459 op_names[i] = Q_ (op_names[i]);
3460 i18n_flag = TRUE;
3463 linklist = free_linklist (linklist);
3464 dest_dirs = free_linklist (dest_dirs);
3466 save_cwds_stat ();
3468 if (single_entry)
3470 source = check_single_entry (panel, force_single, &src_stat);
3472 if (source == NULL)
3473 return FALSE;
3476 ctx = file_op_context_new (operation);
3478 /* Show confirmation dialog */
3479 if (operation != OP_DELETE)
3481 dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
3482 if (dest == NULL)
3484 ret_val = FALSE;
3485 goto ret_fast;
3488 dest_vpath = vfs_path_from_str (dest);
3490 else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3492 ret_val = FALSE;
3493 goto ret_fast;
3496 ctx->total_transfer_start = g_get_monotonic_time ();
3498 #ifdef ENABLE_BACKGROUND
3499 /* Did the user select to do a background operation? */
3500 if (do_bg)
3502 int v;
3504 v = do_background (ctx,
3505 g_strconcat (op_names[operation], ": ",
3506 vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3507 if (v == -1)
3508 message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
3510 /* If we are the parent */
3511 if (v == 1)
3513 mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3515 mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3516 vfs_path_free (dest_vpath, TRUE);
3517 g_free (dest);
3518 /* file_op_context_destroy (ctx); */
3519 return FALSE;
3522 else
3523 #endif /* ENABLE_BACKGROUND */
3525 const file_entry_t *fe;
3527 if (operation == OP_DELETE)
3528 dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3529 else if (single_entry
3530 && ((fe = panel_current_entry (panel)) == NULL ? FALSE : S_ISDIR (fe->st.st_mode)))
3531 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3532 else if (single_entry || force_single)
3533 dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3534 else
3535 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3538 /* Initialize things */
3539 /* We do not want to trash cache every time file is
3540 created/touched. However, this will make our cache contain
3541 invalid data. */
3542 if ((dest != NULL)
3543 && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3544 save_dest = vfs_path_from_str (dest);
3546 if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3547 && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3548 save_cwd = vfs_path_clone (panel->cwd_vpath);
3550 /* Now, let's do the job */
3552 /* This code is only called by the tree and panel code */
3553 if (single_entry)
3555 /* We now have ETA in all cases */
3557 /* One file: FIXME mc_chdir will take user out of any vfs */
3558 if ((operation != OP_COPY) && (get_current_type () == view_tree))
3560 vfs_path_t *vpath;
3561 int chdir_retcode;
3563 vpath = vfs_path_from_str (PATH_SEP_STR);
3564 chdir_retcode = mc_chdir (vpath);
3565 vfs_path_free (vpath, TRUE);
3566 if (chdir_retcode < 0)
3568 ret_val = FALSE;
3569 goto clean_up;
3573 value = operate_single_file (panel, ctx, source, &src_stat, dest, dialog_type);
3574 if ((value == FILE_CONT) && !force_single)
3575 unmark_files (panel);
3577 else
3579 /* Many files */
3581 /* Check destination for copy or move operation */
3582 while (operation != OP_DELETE)
3584 int dst_result;
3585 struct stat dst_stat;
3587 dst_result = mc_stat (dest_vpath, &dst_stat);
3589 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3590 break;
3592 if (ctx->ignore_all
3593 || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
3594 dest) != FILE_RETRY)
3595 goto clean_up;
3598 /* TODO: the good way is required to skip directories scanning in case of rename/move
3599 * of several directories. Since reqular expression can be used for destination,
3600 * some directory movements can be a cross-filesystem and directory scanning is useful
3601 * for those directories only. */
3603 if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type)
3604 == FILE_CONT)
3606 /* Loop for every file, perform the actual copy operation */
3607 for (i = 0; i < panel->dir.len; i++)
3609 const char *source2;
3611 if (panel->dir.list[i].f.marked == 0)
3612 continue; /* Skip the unmarked ones */
3614 source2 = panel->dir.list[i].fname->str;
3615 src_stat = panel->dir.list[i].st;
3617 value = operate_one_file (panel, ctx, source2, &src_stat, dest);
3619 if (value == FILE_ABORT)
3620 break;
3622 if (value == FILE_CONT)
3623 do_file_mark (panel, i, 0);
3625 if (verbose)
3627 if (ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3629 file_progress_show_count (ctx, ctx->total_progress_count, ctx->total_count);
3630 file_progress_show_total (ctx, ctx->progress_bytes, FALSE);
3633 if (operation != OP_DELETE)
3634 file_progress_show (ctx, 0, 0, "", FALSE);
3637 if (file_progress_check_buttons (ctx) == FILE_ABORT)
3638 break;
3640 mc_refresh ();
3641 } /* Loop for every file */
3643 } /* Many entries */
3645 clean_up:
3646 /* Clean up */
3647 if (save_cwd != NULL)
3649 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
3650 vfs_path_free (save_cwd, TRUE);
3653 if (save_dest != NULL)
3655 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
3656 vfs_path_free (save_dest, TRUE);
3659 linklist = free_linklist (linklist);
3660 dest_dirs = free_linklist (dest_dirs);
3661 g_free (dest);
3662 vfs_path_free (dest_vpath, TRUE);
3663 MC_PTR_FREE (ctx->dest_mask);
3665 #ifdef ENABLE_BACKGROUND
3666 /* Let our parent know we are saying bye bye */
3667 if (mc_global.we_are_background)
3669 /* Send pid to parent with child context, it is fork and
3670 don't modify real parent ctx */
3671 ctx->pid = getpid ();
3672 parent_call ((void *) end_bg_process, ctx, 0);
3674 vfs_shut ();
3675 my_exit (EXIT_SUCCESS);
3677 #endif /* ENABLE_BACKGROUND */
3679 ret_fast:
3680 file_op_context_destroy (ctx);
3682 update_panels (UP_OPTIMIZE, UP_KEEPSEL);
3683 repaint_screen ();
3685 return ret_val;
3688 /* }}} */
3690 /* --------------------------------------------------------------------------------------------- */
3691 /* {{{ Query/status report routines */
3692 /** Report error with one file */
3693 FileProgressStatus
3694 file_error (gboolean allow_retry, const char *format, const char *file)
3696 char buf[BUF_MEDIUM];
3698 g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3700 return do_file_error (allow_retry, buf);
3703 /* --------------------------------------------------------------------------------------------- */
3706 Cause emacs to enter folding mode for this file:
3707 Local variables:
3708 end: