File copy/move: make ETA accurate.
[midnight-commander.git] / src / filemanager / file.c
blob9cd5962f4d2886476f66faada771475cf0762bfd
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 void
816 progress_update_one (gboolean success, 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->total_progress_bytes += (uintmax_t) add;
824 if (!success)
825 return;
827 tv_current = g_get_monotonic_time ();
829 if (tv_start < 0)
830 tv_start = tv_current;
831 else if (tv_current - tv_start > FILEOP_UPDATE_INTERVAL_US)
833 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
835 file_progress_show_count (ctx);
836 file_progress_show_total (ctx, ctx->total_progress_bytes, tv_current, TRUE);
839 tv_start = tv_current;
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 calc_copy_file_progress (file_op_context_t *ctx, gint64 tv_current, off_t file_part,
1134 off_t file_size)
1136 double dt;
1138 /* Update rotating dash after some time */
1139 rotate_dash (TRUE);
1141 /* Compute ETA */
1142 dt = (tv_current - ctx->transfer_start) / (double) G_USEC_PER_SEC;
1144 if (file_part == 0)
1145 ctx->eta_secs = 0.0;
1146 else
1147 ctx->eta_secs = ((double) file_size / file_part - 1) * dt;
1149 /* Compute BPS rate */
1150 dt = MAX (1.0, dt);
1151 ctx->bps = (long) (file_part / dt);
1153 /* Compute total ETA and BPS */
1154 if (ctx->total_bytes != 0)
1156 dt = (tv_current - ctx->total_transfer_start) / (double) G_USEC_PER_SEC;
1158 const uintmax_t copied_bytes = ctx->total_progress_bytes + file_part;
1159 if (copied_bytes == 0)
1160 ctx->total_eta_secs = 0;
1161 else
1162 ctx->total_eta_secs = ((double) ctx->total_bytes / copied_bytes - 1) * dt;
1164 dt = MAX (1.0, dt);
1165 ctx->total_bps = (long) (copied_bytes / dt);
1169 /* --------------------------------------------------------------------------------------------- */
1171 static gboolean
1172 try_remove_file (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status)
1174 while (mc_unlink (vpath) != 0 && !ctx->ignore_all)
1176 *status = file_error (TRUE, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath));
1177 if (*status == FILE_RETRY)
1178 continue;
1179 if (*status == FILE_IGNORE_ALL)
1180 ctx->ignore_all = TRUE;
1181 return FALSE;
1184 return TRUE;
1187 /* --------------------------------------------------------------------------------------------- */
1189 /* {{{ Move routines */
1192 * Move single file or one of many files from one location to another.
1194 * @panel pointer to panel in case of single file, NULL otherwise
1195 * @ctx file operation context object
1196 * @s source file name
1197 * @d destination file name
1199 * @return operation result
1201 static FileProgressStatus
1202 move_file_file (const WPanel *panel, file_op_context_t *ctx, const char *s, const char *d)
1204 struct stat src_stat, dst_stat;
1205 FileProgressStatus return_status = FILE_CONT;
1206 gboolean copy_done = FALSE;
1207 gboolean old_ask_overwrite;
1208 vfs_path_t *src_vpath, *dst_vpath;
1210 src_vpath = vfs_path_from_str (s);
1211 dst_vpath = vfs_path_from_str (d);
1213 file_progress_show_source (ctx, src_vpath);
1214 file_progress_show_target (ctx, dst_vpath);
1216 /* FIXME: do we really need to check buttons in case of single file? */
1217 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1219 return_status = FILE_ABORT;
1220 goto ret_fast;
1223 mc_refresh ();
1225 while (mc_lstat (src_vpath, &src_stat) != 0)
1227 /* Source doesn't exist */
1228 if (ctx->ignore_all)
1229 return_status = FILE_IGNORE_ALL;
1230 else
1232 return_status = file_error (TRUE, _("Cannot stat file \"%s\"\n%s"), s);
1233 if (return_status == FILE_IGNORE_ALL)
1234 ctx->ignore_all = TRUE;
1237 if (return_status != FILE_RETRY)
1238 goto ret;
1241 if (mc_lstat (dst_vpath, &dst_stat) == 0)
1243 if (check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1244 goto ret;
1246 if (S_ISDIR (dst_stat.st_mode))
1248 message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
1249 do_refresh ();
1250 return_status = FILE_SKIP;
1251 goto ret;
1254 if (confirm_overwrite)
1256 return_status = query_replace (ctx, s, &src_stat, d, &dst_stat);
1257 if (return_status != FILE_CONT)
1258 goto ret;
1260 /* Ok to overwrite */
1263 if (!ctx->do_append)
1265 if (S_ISLNK (src_stat.st_mode) && ctx->stable_symlinks)
1267 return_status = make_symlink (ctx, src_vpath, dst_vpath);
1268 if (return_status == FILE_CONT)
1270 if (ctx->preserve)
1272 mc_timesbuf_t times;
1274 vfs_get_timesbuf_from_stat (&src_stat, &times);
1275 mc_utime (dst_vpath, &times);
1277 goto retry_src_remove;
1279 goto ret;
1282 if (mc_rename (src_vpath, dst_vpath) == 0)
1283 goto ret;
1285 #if 0
1286 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1287 one nfs to the same, but on the server it is on two different
1288 filesystems. Then nfs returns EIO instead of EXDEV.
1289 Hope it will not hurt if we always in case of error try to copy/delete. */
1290 else
1291 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1293 if (errno != EXDEV)
1295 if (ctx->ignore_all)
1296 return_status = FILE_IGNORE_ALL;
1297 else
1299 return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
1300 if (return_status == FILE_IGNORE_ALL)
1301 ctx->ignore_all = TRUE;
1302 if (return_status == FILE_RETRY)
1303 goto retry_rename;
1306 goto ret;
1308 #endif
1310 /* Failed rename -> copy the file instead */
1311 if (panel != NULL)
1313 /* In case of single file, calculate totals. In case of many files,
1314 totals are calculated already. */
1315 return_status =
1316 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1317 FILEGUI_DIALOG_ONE_ITEM);
1318 if (return_status != FILE_CONT)
1319 goto ret_fast;
1322 old_ask_overwrite = ctx->ask_overwrite;
1323 ctx->ask_overwrite = FALSE;
1324 return_status = copy_file_file (ctx, s, d);
1325 ctx->ask_overwrite = old_ask_overwrite;
1326 if (return_status != FILE_CONT)
1327 goto ret;
1329 copy_done = TRUE;
1331 /* FIXME: there is no need to update progress and check buttons
1332 at the finish of single file operation. */
1333 if (panel == NULL)
1335 file_progress_show_source (ctx, NULL);
1336 if (verbose)
1337 file_progress_show (ctx, 0, 0, "", FALSE);
1339 return_status = file_progress_check_buttons (ctx);
1340 if (return_status != FILE_CONT)
1341 goto ret_fast;
1344 mc_refresh ();
1346 retry_src_remove:
1347 if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL)
1348 goto ret_fast;
1350 ret:
1351 if (return_status != FILE_ABORT)
1353 /* if copy_done == TRUE, progress_update_one() was called in copy_file_file() */
1354 if (!copy_done)
1355 progress_update_one (TRUE, ctx, src_stat.st_size);
1356 return_status = file_progress_check_buttons (ctx);
1358 ret_fast:
1359 vfs_path_free (src_vpath, TRUE);
1360 vfs_path_free (dst_vpath, TRUE);
1362 return return_status;
1365 /* }}} */
1367 /* --------------------------------------------------------------------------------------------- */
1368 /* {{{ Erase routines */
1369 /** Don't update progress status if progress_count==NULL */
1371 static FileProgressStatus
1372 erase_file (file_op_context_t *ctx, const vfs_path_t *vpath)
1374 struct stat buf;
1375 FileProgressStatus return_status;
1377 /* check buttons if deleting info was changed */
1378 if (file_progress_show_deleting (ctx, vpath, &ctx->total_progress_count))
1380 file_progress_show_count (ctx);
1381 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1382 return FILE_ABORT;
1384 mc_refresh ();
1387 if (ctx->total_progress_count != 0 && mc_lstat (vpath, &buf) != 0)
1389 /* ignore, most likely the mc_unlink fails, too */
1390 buf.st_size = 0;
1393 if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT)
1394 return FILE_ABORT;
1396 if (ctx->total_progress_count == 0)
1397 return FILE_CONT;
1399 return file_progress_check_buttons (ctx);
1402 /* --------------------------------------------------------------------------------------------- */
1404 static FileProgressStatus
1405 try_erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
1407 const char *dir;
1408 FileProgressStatus return_status = FILE_CONT;
1410 dir = vfs_path_as_str (vpath);
1412 while (my_rmdir (dir) != 0 && !ctx->ignore_all)
1414 return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir);
1415 if (return_status == FILE_IGNORE_ALL)
1416 ctx->ignore_all = TRUE;
1417 if (return_status != FILE_RETRY)
1418 break;
1421 return return_status;
1424 /* --------------------------------------------------------------------------------------------- */
1427 Recursive removal of files
1428 abort -> cancel stack
1429 ignore -> warn every level, gets default
1430 ignore_all -> remove as much as possible
1432 static FileProgressStatus
1433 recursive_erase (file_op_context_t *ctx, const vfs_path_t *vpath)
1435 struct vfs_dirent *next;
1436 DIR *reading;
1437 FileProgressStatus return_status = FILE_CONT;
1439 reading = mc_opendir (vpath);
1440 if (reading == NULL)
1441 return FILE_RETRY;
1443 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
1445 vfs_path_t *tmp_vpath;
1446 struct stat buf;
1448 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
1449 continue;
1451 tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
1452 if (mc_lstat (tmp_vpath, &buf) != 0)
1454 mc_closedir (reading);
1455 vfs_path_free (tmp_vpath, TRUE);
1456 return FILE_RETRY;
1458 if (S_ISDIR (buf.st_mode))
1459 return_status = recursive_erase (ctx, tmp_vpath);
1460 else
1461 return_status = erase_file (ctx, tmp_vpath);
1462 vfs_path_free (tmp_vpath, TRUE);
1464 mc_closedir (reading);
1466 if (return_status == FILE_ABORT)
1467 return FILE_ABORT;
1469 file_progress_show_deleting (ctx, vpath, NULL);
1470 file_progress_show_count (ctx);
1471 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1472 return FILE_ABORT;
1474 mc_refresh ();
1476 return try_erase_dir (ctx, vpath);
1479 /* --------------------------------------------------------------------------------------------- */
1481 * Check if directory is empty or not.
1483 * @param vpath directory handler
1485 * @returns -1 on error,
1486 * 1 if there are no entries besides "." and ".." in the directory path points to,
1487 * 0 else.
1489 * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
1490 * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
1491 * in SHELL) don't return "." and ".." entries.
1493 static int
1494 check_dir_is_empty (const vfs_path_t *vpath)
1496 DIR *dir;
1497 struct vfs_dirent *d;
1498 int i = 1;
1500 dir = mc_opendir (vpath);
1501 if (dir == NULL)
1502 return -1;
1504 for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1505 if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1507 i = 0;
1508 break;
1511 mc_closedir (dir);
1512 return i;
1515 /* --------------------------------------------------------------------------------------------- */
1517 static FileProgressStatus
1518 erase_dir_iff_empty (file_op_context_t *ctx, const vfs_path_t *vpath)
1520 file_progress_show_deleting (ctx, vpath, NULL);
1521 file_progress_show_count (ctx);
1522 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1523 return FILE_ABORT;
1525 mc_refresh ();
1527 if (check_dir_is_empty (vpath) != 1)
1528 return FILE_CONT;
1530 /* not empty or error */
1531 return try_erase_dir (ctx, vpath);
1534 /* --------------------------------------------------------------------------------------------- */
1536 static void
1537 erase_dir_after_copy (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status)
1539 if (ctx->erase_at_end && erase_list != NULL)
1541 /* Reset progress count before delete to avoid counting files twice */
1542 ctx->total_progress_count = ctx->prev_total_progress_count;
1544 while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT)
1546 link_t *lp;
1548 lp = (link_t *) g_queue_pop_head (erase_list);
1550 if (S_ISDIR (lp->st_mode))
1551 *status = erase_dir_iff_empty (ctx, lp->src_vpath);
1552 else
1553 *status = erase_file (ctx, lp->src_vpath);
1555 free_link (lp);
1558 /* Save progress counter before move next directory */
1559 ctx->prev_total_progress_count = ctx->total_progress_count;
1562 erase_dir_iff_empty (ctx, vpath);
1565 /* }}} */
1567 /* --------------------------------------------------------------------------------------------- */
1570 * Move single directory or one of many directories from one location to another.
1572 * @panel pointer to panel in case of single directory, NULL otherwise
1573 * @ctx file operation context object
1574 * @s source directory name
1575 * @d destination directory name
1577 * @return operation result
1579 static FileProgressStatus
1580 do_move_dir_dir (const WPanel *panel, file_op_context_t *ctx, const char *s, const char *d)
1582 struct stat src_stat, dst_stat;
1583 FileProgressStatus return_status = FILE_CONT;
1584 gboolean move_over = FALSE;
1585 gboolean dstat_ok;
1586 vfs_path_t *src_vpath, *dst_vpath;
1588 src_vpath = vfs_path_from_str (s);
1589 dst_vpath = vfs_path_from_str (d);
1591 file_progress_show_source (ctx, src_vpath);
1592 file_progress_show_target (ctx, dst_vpath);
1594 /* FIXME: do we really need to check buttons in case of single directory? */
1595 if (panel != NULL && file_progress_check_buttons (ctx) == FILE_ABORT)
1597 return_status = FILE_ABORT;
1598 goto ret_fast;
1601 mc_refresh ();
1603 mc_stat (src_vpath, &src_stat);
1605 dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0);
1607 if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1608 goto ret_fast;
1610 if (!dstat_ok)
1611 ; /* destination doesn't exist */
1612 else if (!ctx->dive_into_subdirs)
1613 move_over = TRUE;
1614 else
1616 vfs_path_t *tmp;
1618 tmp = dst_vpath;
1619 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
1620 vfs_path_free (tmp, TRUE);
1623 d = vfs_path_as_str (dst_vpath);
1625 /* Check if the user inputted an existing dir */
1626 retry_dst_stat:
1627 if (mc_stat (dst_vpath, &dst_stat) == 0)
1629 if (move_over)
1631 if (panel != NULL)
1633 /* In case of single directory, calculate totals. In case of many directories,
1634 totals are calculated already. */
1635 return_status =
1636 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1637 FILEGUI_DIALOG_MULTI_ITEM);
1638 if (return_status != FILE_CONT)
1639 goto ret;
1642 return_status = copy_dir_dir (ctx, s, d, FALSE, TRUE, TRUE, NULL);
1644 if (return_status != FILE_CONT)
1645 goto ret;
1646 goto oktoret;
1648 else if (ctx->ignore_all)
1649 return_status = FILE_IGNORE_ALL;
1650 else
1652 if (S_ISDIR (dst_stat.st_mode))
1653 return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d);
1654 else
1655 return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d);
1656 if (return_status == FILE_IGNORE_ALL)
1657 ctx->ignore_all = TRUE;
1658 if (return_status == FILE_RETRY)
1659 goto retry_dst_stat;
1662 goto ret_fast;
1665 retry_rename:
1666 if (mc_rename (src_vpath, dst_vpath) == 0)
1668 return_status = FILE_CONT;
1669 goto ret;
1672 if (errno != EXDEV)
1674 if (!ctx->ignore_all)
1676 return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
1677 if (return_status == FILE_IGNORE_ALL)
1678 ctx->ignore_all = TRUE;
1679 if (return_status == FILE_RETRY)
1680 goto retry_rename;
1682 goto ret;
1685 /* Failed because of filesystem boundary -> copy dir instead */
1686 if (panel != NULL)
1688 /* In case of single directory, calculate totals. In case of many directories,
1689 totals are calculated already. */
1690 return_status =
1691 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1692 FILEGUI_DIALOG_MULTI_ITEM);
1693 if (return_status != FILE_CONT)
1694 goto ret;
1697 return_status = copy_dir_dir (ctx, s, d, FALSE, FALSE, TRUE, NULL);
1699 if (return_status != FILE_CONT)
1700 goto ret;
1702 oktoret:
1703 /* FIXME: there is no need to update progress and check buttons
1704 at the finish of single directory operation. */
1705 if (panel == NULL)
1707 file_progress_show_source (ctx, NULL);
1708 file_progress_show_target (ctx, NULL);
1709 if (verbose)
1710 file_progress_show (ctx, 0, 0, "", FALSE);
1712 return_status = file_progress_check_buttons (ctx);
1713 if (return_status != FILE_CONT)
1714 goto ret;
1717 mc_refresh ();
1719 erase_dir_after_copy (ctx, src_vpath, &return_status);
1721 ret:
1722 erase_list = free_erase_list (erase_list);
1723 ret_fast:
1724 vfs_path_free (src_vpath, TRUE);
1725 vfs_path_free (dst_vpath, TRUE);
1726 return return_status;
1729 /* --------------------------------------------------------------------------------------------- */
1731 /* {{{ Panel operate routines */
1734 * Return currently selected entry name or the name of the first marked
1735 * entry if there is one.
1738 static const char *
1739 panel_get_file (const WPanel *panel)
1741 const file_entry_t *fe;
1743 if (get_current_type () == view_tree)
1745 WTree *tree;
1746 const vfs_path_t *selected_name;
1748 tree = (WTree *) get_panel_widget (get_current_index ());
1749 selected_name = tree_selected_name (tree);
1750 return vfs_path_as_str (selected_name);
1753 if (panel->marked != 0)
1755 int i;
1757 for (i = 0; i < panel->dir.len; i++)
1758 if (panel->dir.list[i].f.marked != 0)
1759 return panel->dir.list[i].fname->str;
1762 fe = panel_current_entry (panel);
1764 return (fe == NULL ? NULL : fe->fname->str);
1767 /* --------------------------------------------------------------------------------------------- */
1769 static const char *
1770 check_single_entry (const WPanel *panel, gboolean force_single, struct stat *src_stat)
1772 const char *source;
1773 gboolean ok;
1775 if (force_single)
1777 const file_entry_t *fe;
1779 fe = panel_current_entry (panel);
1780 source = fe == NULL ? NULL : fe->fname->str;
1782 else
1783 source = panel_get_file (panel);
1785 if (source == NULL)
1786 return NULL;
1788 ok = !DIR_IS_DOTDOT (source);
1790 if (!ok)
1791 message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
1792 else
1794 vfs_path_t *source_vpath;
1796 source_vpath = vfs_path_from_str (source);
1798 /* Update stat to get actual info */
1799 ok = mc_lstat (source_vpath, src_stat) == 0;
1800 if (!ok)
1802 message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
1803 path_trunc (source, 30), unix_error_string (errno));
1805 /* Directory was changed outside MC. Reload it forced */
1806 if (!panel->is_panelized)
1808 panel_update_flags_t flags = UP_RELOAD;
1810 /* don't update panelized panel */
1811 if (get_other_type () == view_listing && other_panel->is_panelized)
1812 flags |= UP_ONLY_CURRENT;
1814 update_panels (flags, UP_KEEPSEL);
1818 vfs_path_free (source_vpath, TRUE);
1821 return ok ? source : NULL;
1824 /* --------------------------------------------------------------------------------------------- */
1826 * Generate user prompt for panel operation.
1827 * src_stat must be not NULL for single source, and NULL for multiple sources
1830 static char *
1831 panel_operate_generate_prompt (const WPanel *panel, FileOperation operation,
1832 const struct stat *src_stat)
1834 char *sp;
1835 char *format_string;
1836 const char *cp;
1838 static gboolean i18n_flag = FALSE;
1839 if (!i18n_flag)
1841 size_t i;
1843 for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1844 op_names1[i] = Q_ (op_names1[i]);
1846 #ifdef ENABLE_NLS
1847 for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1848 prompt_parts[i] = _(prompt_parts[i]);
1850 one_format = _(one_format);
1851 many_format = _(many_format);
1852 #endif /* ENABLE_NLS */
1853 i18n_flag = TRUE;
1856 /* Possible prompts:
1857 * OP_COPY:
1858 * "Copy file \"%s\" with source mask:"
1859 * "Copy %d files with source mask:"
1860 * "Copy directory \"%s\" with source mask:"
1861 * "Copy %d directories with source mask:"
1862 * "Copy %d files/directories with source mask:"
1863 * OP_MOVE:
1864 * "Move file \"%s\" with source mask:"
1865 * "Move %d files with source mask:"
1866 * "Move directory \"%s\" with source mask:"
1867 * "Move %d directories with source mask:"
1868 * "Move %d files/directories with source mask:"
1869 * OP_DELETE:
1870 * "Delete file \"%s\"?"
1871 * "Delete %d files?"
1872 * "Delete directory \"%s\"?"
1873 * "Delete %d directories?"
1874 * "Delete %d files/directories?"
1877 cp = (src_stat != NULL ? one_format : many_format);
1879 /* 1. Substitute %o */
1880 format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1882 /* 2. Substitute %n */
1883 cp = operation == OP_DELETE ? "\n" : " ";
1884 sp = format_string;
1885 format_string = str_replace_all (sp, "%n", cp);
1886 g_free (sp);
1888 /* 3. Substitute %f */
1889 if (src_stat != NULL)
1890 cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1891 else if (panel->marked == panel->dirs_marked)
1892 cp = prompt_parts[3];
1893 else
1894 cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1896 sp = format_string;
1897 format_string = str_replace_all (sp, "%f", cp);
1898 g_free (sp);
1900 /* 4. Substitute %m */
1901 cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1902 sp = format_string;
1903 format_string = str_replace_all (sp, "%m", cp);
1904 g_free (sp);
1906 return format_string;
1909 /* --------------------------------------------------------------------------------------------- */
1911 static char *
1912 do_confirm_copy_move (const WPanel *panel, gboolean force_single, const char *source,
1913 struct stat *src_stat, file_op_context_t *ctx, gboolean *do_bg)
1915 const char *tmp_dest_dir;
1916 char *dest_dir;
1917 char *format;
1918 char *ret;
1920 /* Forced single operations default to the original name */
1921 if (force_single)
1922 tmp_dest_dir = source;
1923 else if (get_other_type () == view_listing)
1924 tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
1925 else
1926 tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
1929 * Add trailing backslash only when do non-local ops.
1930 * It saves user from occasional file renames (when destination
1931 * dir is deleted)
1933 if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
1934 && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
1936 /* add trailing separator */
1937 dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
1939 else
1941 /* just copy */
1942 dest_dir = g_strdup (tmp_dest_dir);
1945 if (dest_dir == NULL)
1946 return NULL;
1948 if (source == NULL)
1949 src_stat = NULL;
1951 /* Generate confirmation prompt */
1952 format = panel_operate_generate_prompt (panel, ctx->operation, src_stat);
1954 ret = file_mask_dialog (ctx, source != NULL, format,
1955 source != NULL ? source : (const void *) &panel->marked, dest_dir,
1956 do_bg);
1958 g_free (format);
1959 g_free (dest_dir);
1961 return ret;
1964 /* --------------------------------------------------------------------------------------------- */
1966 static gboolean
1967 do_confirm_erase (const WPanel *panel, const char *source, struct stat *src_stat)
1969 int i;
1970 char *format;
1971 char fmd_buf[BUF_MEDIUM];
1973 if (source == NULL)
1974 src_stat = NULL;
1976 /* Generate confirmation prompt */
1977 format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat);
1979 if (source == NULL)
1980 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1981 else
1983 const int fmd_xlen = 64;
1985 i = fmd_xlen - str_term_width1 (format) - 4;
1986 g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
1989 g_free (format);
1991 if (safe_delete)
1992 query_set_sel (1);
1994 i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
1996 return (i == 0);
1999 /* --------------------------------------------------------------------------------------------- */
2001 static FileProgressStatus
2002 operate_single_file (const WPanel *panel, file_op_context_t *ctx, const char *src,
2003 struct stat *src_stat, const char *dest, filegui_dialog_type_t dialog_type)
2005 FileProgressStatus value;
2006 vfs_path_t *src_vpath;
2007 gboolean is_file;
2009 if (g_path_is_absolute (src))
2010 src_vpath = vfs_path_from_str (src);
2011 else
2012 src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2014 is_file = !S_ISDIR (src_stat->st_mode);
2015 /* Is link to directory? */
2016 if (is_file)
2018 gboolean is_link;
2020 is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2021 is_file = !(is_link && ctx->follow_links);
2024 if (ctx->operation == OP_DELETE)
2026 value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type);
2027 if (value == FILE_CONT)
2029 if (is_file)
2030 value = erase_file (ctx, src_vpath);
2031 else
2032 value = erase_dir (ctx, src_vpath);
2035 else
2037 char *temp;
2039 src = vfs_path_as_str (src_vpath);
2041 temp = build_dest (ctx, src, dest, &value);
2042 if (temp != NULL)
2044 dest = temp;
2046 switch (ctx->operation)
2048 case OP_COPY:
2049 /* we use file_mask_op_follow_links only with OP_COPY */
2050 ctx->stat_func (src_vpath, src_stat);
2052 value =
2053 panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file,
2054 dialog_type);
2055 if (value == FILE_CONT)
2057 is_file = !S_ISDIR (src_stat->st_mode);
2058 /* Is link to directory? */
2059 if (is_file)
2061 gboolean is_link;
2063 is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2064 is_file = !(is_link && ctx->follow_links);
2067 if (is_file)
2068 value = copy_file_file (ctx, src, dest);
2069 else
2070 value = copy_dir_dir (ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2072 break;
2074 case OP_MOVE:
2075 #ifdef ENABLE_BACKGROUND
2076 if (!mc_global.we_are_background)
2077 #endif
2078 /* create UI to show confirmation dialog */
2079 file_progress_ui_create (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM);
2081 if (is_file)
2082 value = move_file_file (panel, ctx, src, dest);
2083 else
2084 value = do_move_dir_dir (panel, ctx, src, dest);
2085 break;
2087 default:
2088 /* Unknown file operation */
2089 abort ();
2092 g_free (temp);
2096 vfs_path_free (src_vpath, TRUE);
2098 return value;
2101 /* --------------------------------------------------------------------------------------------- */
2103 static FileProgressStatus
2104 operate_one_file (const WPanel *panel, file_op_context_t *ctx, const char *src,
2105 struct stat *src_stat, const char *dest)
2107 FileProgressStatus value = FILE_CONT;
2108 vfs_path_t *src_vpath;
2109 gboolean is_file;
2111 if (g_path_is_absolute (src))
2112 src_vpath = vfs_path_from_str (src);
2113 else
2114 src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2116 is_file = !S_ISDIR (src_stat->st_mode);
2118 if (ctx->operation == OP_DELETE)
2120 if (is_file)
2121 value = erase_file (ctx, src_vpath);
2122 else
2123 value = erase_dir (ctx, src_vpath);
2125 else
2127 char *temp;
2129 src = vfs_path_as_str (src_vpath);
2131 temp = build_dest (ctx, src, dest, &value);
2132 if (temp != NULL)
2134 dest = temp;
2136 switch (ctx->operation)
2138 case OP_COPY:
2139 /* we use file_mask_op_follow_links only with OP_COPY */
2140 ctx->stat_func (src_vpath, src_stat);
2141 is_file = !S_ISDIR (src_stat->st_mode);
2143 if (is_file)
2144 value = copy_file_file (ctx, src, dest);
2145 else
2146 value = copy_dir_dir (ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2147 dest_dirs = free_linklist (dest_dirs);
2148 break;
2150 case OP_MOVE:
2151 if (is_file)
2152 value = move_file_file (NULL, ctx, src, dest);
2153 else
2154 value = do_move_dir_dir (NULL, ctx, src, dest);
2155 break;
2157 default:
2158 /* Unknown file operation */
2159 abort ();
2162 g_free (temp);
2166 vfs_path_free (src_vpath, TRUE);
2168 return value;
2171 /* --------------------------------------------------------------------------------------------- */
2173 #ifdef ENABLE_BACKGROUND
2174 static int
2175 end_bg_process (file_op_context_t *ctx, enum OperationMode mode)
2177 int pid = ctx->pid;
2179 (void) mode;
2180 ctx->pid = 0;
2182 unregister_task_with_pid (pid);
2183 /* file_op_context_destroy(ctx); */
2184 return 1;
2186 #endif
2187 /* }}} */
2189 /* --------------------------------------------------------------------------------------------- */
2192 * On Solaris, ENOTSUP != EOPNOTSUPP. Some FS also return ENOSYS or EINVAL as "not implemented".
2193 * On some Linux kernels (tested on 4.9, 5.4) there is ENOTTY on tmpfs.
2195 static inline gboolean
2196 attrs_ignore_error (const int e)
2198 return (e == ENOTSUP || e == EOPNOTSUPP || e == ENOSYS || e == EINVAL || e == ENOTTY
2199 || e == ELOOP || e == ENXIO);
2202 /* --------------------------------------------------------------------------------------------- */
2203 /*** public functions ****************************************************************************/
2204 /* --------------------------------------------------------------------------------------------- */
2206 /* Is file symlink to directory or not.
2208 * @param path file or directory
2209 * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2210 * @param stale_link TRUE if file is stale link to directory
2212 * @return TRUE if file symlink to directory, ELSE otherwise.
2214 gboolean
2215 file_is_symlink_to_dir (const vfs_path_t *vpath, struct stat *st, gboolean *stale_link)
2217 struct stat st2;
2218 gboolean stale = FALSE;
2219 gboolean res = FALSE;
2221 if (st == NULL)
2223 st = &st2;
2225 if (mc_lstat (vpath, st) != 0)
2226 goto ret;
2229 if (S_ISLNK (st->st_mode))
2231 struct stat st3;
2233 stale = (mc_stat (vpath, &st3) != 0);
2235 if (!stale)
2236 res = (S_ISDIR (st3.st_mode) != 0);
2239 ret:
2240 if (stale_link != NULL)
2241 *stale_link = stale;
2243 return res;
2246 /* --------------------------------------------------------------------------------------------- */
2248 FileProgressStatus
2249 copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_path)
2251 uid_t src_uid = (uid_t) (-1);
2252 gid_t src_gid = (gid_t) (-1);
2254 int src_desc, dest_desc = -1;
2255 mode_t src_mode = 0; /* The mode of the source file */
2256 struct stat src_stat, dst_stat;
2257 mc_timesbuf_t times;
2258 unsigned long attrs = 0;
2259 gboolean attrs_ok = ctx->preserve;
2260 gboolean dst_exists = FALSE, appending = FALSE;
2261 off_t file_size = -1;
2262 FileProgressStatus return_status, temp_status;
2263 dest_status_t dst_status = DEST_NONE;
2264 int open_flags;
2265 vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
2266 char *buf = NULL;
2268 /* Keep the non-default value applied in chain of calls:
2269 move_file_file() -> file_progress_real_query_replace()
2270 move_file_file() -> copy_file_file() */
2271 if (ctx->do_reget < 0)
2272 ctx->do_reget = 0;
2274 return_status = FILE_RETRY;
2276 dst_vpath = vfs_path_from_str (dst_path);
2277 src_vpath = vfs_path_from_str (src_path);
2279 file_progress_show_source (ctx, src_vpath);
2280 file_progress_show_target (ctx, dst_vpath);
2282 if (file_progress_check_buttons (ctx) == FILE_ABORT)
2284 return_status = FILE_ABORT;
2285 goto ret_fast;
2288 mc_refresh ();
2290 while (mc_stat (dst_vpath, &dst_stat) == 0)
2292 if (S_ISDIR (dst_stat.st_mode))
2294 if (ctx->ignore_all)
2295 return_status = FILE_IGNORE_ALL;
2296 else
2298 return_status =
2299 file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path);
2300 if (return_status == FILE_IGNORE_ALL)
2301 ctx->ignore_all = TRUE;
2302 if (return_status == FILE_RETRY)
2303 continue;
2305 goto ret_fast;
2308 dst_exists = TRUE;
2309 break;
2312 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2314 if (ctx->ignore_all)
2315 return_status = FILE_IGNORE_ALL;
2316 else
2318 return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path);
2319 if (return_status == FILE_IGNORE_ALL)
2320 ctx->ignore_all = TRUE;
2323 if (return_status != FILE_RETRY)
2325 /* unknown size */
2326 progress_update_one (FALSE, ctx, 0);
2327 goto ret_fast;
2331 /* After ctx->stat_func() */
2332 src_mode = src_stat.st_mode;
2333 src_uid = src_stat.st_uid;
2334 src_gid = src_stat.st_gid;
2335 file_size = src_stat.st_size;
2337 while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2339 attrs_ok = FALSE;
2341 /* don't show an error message if attributes aren't supported in this FS */
2342 if (attrs_ignore_error (errno))
2343 return_status = FILE_CONT;
2344 else if (ctx->ignore_all)
2345 return_status = FILE_IGNORE_ALL;
2346 else
2348 return_status =
2349 file_error (TRUE, _("Cannot get ext2 attributes of source file \"%s\"\n%s"),
2350 src_path);
2351 if (return_status == FILE_IGNORE_ALL)
2352 ctx->ignore_all = TRUE;
2353 if (return_status == FILE_ABORT)
2354 goto ret_fast;
2357 if (return_status != FILE_RETRY)
2358 break;
2360 /* yet another attempt */
2361 attrs_ok = TRUE;
2364 if (dst_exists)
2366 /* Destination already exists */
2367 if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status))
2368 goto ret_fast;
2370 /* Should we replace destination? */
2371 if (ctx->ask_overwrite)
2373 ctx->do_reget = 0;
2374 return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
2375 if (return_status != FILE_CONT)
2376 goto ret_fast;
2380 vfs_get_timesbuf_from_stat (&src_stat, &times);
2382 if (!ctx->do_append)
2384 /* Check the hardlinks */
2385 if (!ctx->follow_links)
2387 switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
2389 case HARDLINK_OK:
2390 /* We have made a hardlink - no more processing is necessary */
2391 return_status = FILE_CONT;
2392 goto ret_fast;
2394 case HARDLINK_ABORT:
2395 return_status = FILE_ABORT;
2396 goto ret_fast;
2398 default:
2399 break;
2403 if (S_ISLNK (src_stat.st_mode))
2405 return_status = make_symlink (ctx, src_vpath, dst_vpath);
2406 if (return_status == FILE_CONT && ctx->preserve)
2408 mc_utime (dst_vpath, &times);
2410 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2412 attrs_ok = FALSE;
2414 /* don't show an error message if attributes aren't supported in this FS */
2415 if (attrs_ignore_error (errno))
2416 return_status = FILE_CONT;
2417 else if (return_status == FILE_IGNORE_ALL)
2418 ctx->ignore_all = TRUE;
2419 else
2420 return_status =
2421 file_error (TRUE,
2422 _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2423 dst_path);
2425 if (return_status != FILE_RETRY)
2426 break;
2428 /* yet another attempt */
2429 attrs_ok = TRUE;
2432 goto ret_fast;
2435 if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
2436 || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
2438 dev_t rdev = 0;
2440 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2441 rdev = src_stat.st_rdev;
2442 #endif
2444 while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
2445 && !ctx->ignore_all)
2447 return_status =
2448 file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path);
2449 if (return_status == FILE_RETRY)
2450 continue;
2451 if (return_status == FILE_IGNORE_ALL)
2452 ctx->ignore_all = TRUE;
2453 goto ret_fast;
2455 /* Success */
2457 while (ctx->preserve_uidgid
2458 && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0
2459 && !ctx->ignore_all)
2461 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2462 if (temp_status == FILE_IGNORE)
2463 break;
2464 if (temp_status == FILE_IGNORE_ALL)
2465 ctx->ignore_all = TRUE;
2466 if (temp_status != FILE_RETRY)
2468 return_status = temp_status;
2469 goto ret_fast;
2473 while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
2474 && !ctx->ignore_all)
2476 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2477 if (temp_status == FILE_IGNORE)
2478 break;
2479 if (temp_status == FILE_IGNORE_ALL)
2480 ctx->ignore_all = TRUE;
2481 if (temp_status != FILE_RETRY)
2483 return_status = temp_status;
2484 goto ret_fast;
2488 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2490 attrs_ok = FALSE;
2492 /* don't show an error message if attributes aren't supported in this FS */
2493 if (attrs_ignore_error (errno))
2494 break;
2496 temp_status =
2497 file_error (TRUE, _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2498 dst_path);
2499 if (temp_status == FILE_IGNORE)
2500 break;
2501 if (temp_status == FILE_IGNORE_ALL)
2502 ctx->ignore_all = TRUE;
2503 if (temp_status != FILE_RETRY)
2505 return_status = temp_status;
2506 goto ret_fast;
2509 /* yet another attempt */
2510 attrs_ok = TRUE;
2513 return_status = FILE_CONT;
2514 mc_utime (dst_vpath, &times);
2515 goto ret_fast;
2519 ctx->transfer_start = g_get_monotonic_time ();
2521 while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0)
2523 if (ctx->ignore_all)
2524 return_status = FILE_IGNORE_ALL;
2525 else
2527 return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path);
2528 if (return_status == FILE_RETRY)
2529 continue;
2530 if (return_status == FILE_IGNORE_ALL)
2531 ctx->ignore_all = TRUE;
2532 ctx->do_append = FALSE;
2534 goto ret;
2537 if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
2539 message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
2540 ctx->do_reget = 0;
2541 ctx->do_append = FALSE;
2544 while (mc_fstat (src_desc, &src_stat) != 0)
2546 if (ctx->ignore_all)
2547 return_status = FILE_IGNORE_ALL;
2548 else
2550 return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
2551 if (return_status == FILE_RETRY)
2552 continue;
2553 if (return_status == FILE_IGNORE_ALL)
2554 ctx->ignore_all = TRUE;
2555 ctx->do_append = FALSE;
2557 goto ret;
2560 /* After mc_fstat() */
2561 src_mode = src_stat.st_mode;
2562 src_uid = src_stat.st_uid;
2563 src_gid = src_stat.st_gid;
2564 file_size = src_stat.st_size;
2566 open_flags = O_WRONLY;
2567 if (!dst_exists)
2568 open_flags |= O_CREAT | O_EXCL;
2569 else if (ctx->do_append)
2570 open_flags |= O_APPEND;
2571 else
2572 open_flags |= O_CREAT | O_TRUNC;
2574 while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
2576 if (errno != EEXIST)
2578 if (ctx->ignore_all)
2579 return_status = FILE_IGNORE_ALL;
2580 else
2582 return_status =
2583 file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
2584 if (return_status == FILE_RETRY)
2585 continue;
2586 if (return_status == FILE_IGNORE_ALL)
2587 ctx->ignore_all = TRUE;
2588 ctx->do_append = FALSE;
2591 goto ret;
2594 /* file opened, but not fully copied */
2595 dst_status = DEST_SHORT_QUERY;
2597 appending = ctx->do_append;
2598 ctx->do_append = FALSE;
2600 /* Try clone the file first. */
2601 if (vfs_clone_file (dest_desc, src_desc) == 0)
2603 dst_status = DEST_FULL;
2604 return_status = FILE_CONT;
2605 goto ret;
2608 /* Find out the optimal buffer size. */
2609 while (mc_fstat (dest_desc, &dst_stat) != 0)
2611 if (ctx->ignore_all)
2612 return_status = FILE_IGNORE_ALL;
2613 else
2615 return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
2616 if (return_status == FILE_RETRY)
2617 continue;
2618 if (return_status == FILE_IGNORE_ALL)
2619 ctx->ignore_all = TRUE;
2621 goto ret;
2624 /* try preallocate space; if fail, try copy anyway */
2625 while (mc_global.vfs.preallocate_space &&
2626 vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
2628 if (ctx->ignore_all)
2630 /* cannot allocate, start the file copying anyway */
2631 return_status = FILE_CONT;
2632 break;
2635 return_status =
2636 file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
2638 if (return_status == FILE_IGNORE_ALL)
2639 ctx->ignore_all = TRUE;
2641 if (ctx->ignore_all || return_status == FILE_IGNORE)
2643 /* skip the space allocation error, start file copying */
2644 return_status = FILE_CONT;
2645 break;
2648 if (return_status == FILE_ABORT)
2650 mc_close (dest_desc);
2651 dest_desc = -1;
2652 mc_unlink (dst_vpath);
2653 dst_status = DEST_NONE;
2654 goto ret;
2657 /* return_status == FILE_RETRY -- try allocate space again */
2660 ctx->eta_secs = 0.0;
2661 ctx->bps = 0;
2663 if (verbose)
2665 if (ctx->total_bps == 0 || (file_size / ctx->total_bps) > FILEOP_UPDATE_INTERVAL)
2666 file_progress_show (ctx, 0, file_size, "", TRUE);
2667 else
2668 file_progress_show (ctx, 1, 1, "", TRUE);
2671 return_status = file_progress_check_buttons (ctx);
2672 mc_refresh ();
2674 if (return_status == FILE_CONT)
2676 off_t file_part = 0;
2677 gint64 tv_last_update = ctx->transfer_start;
2678 gint64 tv_last_input = 0;
2679 gboolean is_first_time = TRUE;
2681 const size_t bufsize = io_blksize (dst_stat);
2682 buf = g_malloc (bufsize);
2684 while (TRUE)
2686 ssize_t n_read = -1;
2688 /* src_read */
2689 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
2690 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->ignore_all)
2692 return_status =
2693 file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
2694 if (return_status == FILE_RETRY)
2695 continue;
2696 if (return_status == FILE_IGNORE_ALL)
2697 ctx->ignore_all = TRUE;
2698 goto ret;
2701 if (n_read == 0)
2702 break;
2704 const gint64 tv_current = g_get_monotonic_time ();
2706 if (n_read > 0)
2708 ssize_t n_written;
2709 char *t = buf;
2711 file_part += n_read;
2713 tv_last_input = tv_current;
2715 /* dst_write */
2716 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
2718 gboolean write_errno_nospace;
2720 if (n_written > 0)
2722 n_read -= n_written;
2723 t += n_written;
2724 continue;
2727 write_errno_nospace = (n_written < 0 && errno == ENOSPC);
2729 if (ctx->ignore_all)
2730 return_status = FILE_IGNORE_ALL;
2731 else
2732 return_status =
2733 file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
2735 if (return_status == FILE_IGNORE)
2737 if (write_errno_nospace)
2738 goto ret;
2739 break;
2741 if (return_status == FILE_IGNORE_ALL)
2743 ctx->ignore_all = TRUE;
2744 if (write_errno_nospace)
2745 goto ret;
2747 if (return_status != FILE_RETRY)
2748 goto ret;
2752 ctx->progress_bytes = file_part + ctx->do_reget;
2754 const gint64 usecs = tv_current - tv_last_update;
2756 if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US)
2758 calc_copy_file_progress (ctx, tv_current, file_part, file_size - ctx->do_reget);
2759 tv_last_update = tv_current;
2762 is_first_time = FALSE;
2764 if (verbose)
2766 const gint64 total_usecs = tv_current - ctx->total_transfer_start;
2767 const gboolean force_update = total_usecs > FILEOP_UPDATE_INTERVAL_US;
2769 const gint64 update_usecs = tv_current - tv_last_input;
2770 const char *stalled_msg =
2771 update_usecs > FILEOP_STALLING_INTERVAL_US ? _("(stalled)") : "";
2773 file_progress_show (ctx, ctx->progress_bytes, file_size, stalled_msg, force_update);
2774 if (ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
2776 file_progress_show_count (ctx);
2777 file_progress_show_total (ctx, ctx->total_progress_bytes + ctx->progress_bytes,
2778 tv_current, force_update);
2781 mc_refresh ();
2784 return_status = file_progress_check_buttons (ctx);
2785 if (return_status != FILE_CONT)
2787 int query_res;
2789 query_res =
2790 query_dialog (Q_ ("DialogTitle|Copy"),
2791 _("Incomplete file was retrieved"), D_ERROR, 3,
2792 _("&Delete"), _("&Keep"), _("&Continue copy"));
2794 switch (query_res)
2796 case 0:
2797 /* delete */
2798 dst_status = DEST_SHORT_DELETE;
2799 goto ret;
2801 case 1:
2802 /* keep */
2803 dst_status = DEST_SHORT_KEEP;
2804 goto ret;
2806 default:
2807 /* continue copy */
2808 break;
2813 /* copy successful */
2814 dst_status = DEST_FULL;
2817 ret:
2818 g_free (buf);
2820 rotate_dash (FALSE);
2821 while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->ignore_all)
2823 temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
2824 if (temp_status == FILE_RETRY)
2825 continue;
2826 if (temp_status == FILE_ABORT)
2827 return_status = temp_status;
2828 if (temp_status == FILE_IGNORE_ALL)
2829 ctx->ignore_all = TRUE;
2830 break;
2833 while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->ignore_all)
2835 temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
2836 if (temp_status == FILE_RETRY)
2837 continue;
2838 if (temp_status == FILE_IGNORE_ALL)
2839 ctx->ignore_all = TRUE;
2840 return_status = temp_status;
2841 break;
2844 if (dst_status == DEST_SHORT_QUERY)
2846 /* Query to remove short file */
2847 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
2848 D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
2849 dst_status = DEST_SHORT_DELETE;
2850 else
2851 dst_status = DEST_SHORT_KEEP;
2854 if (dst_status == DEST_SHORT_DELETE)
2855 mc_unlink (dst_vpath);
2856 else if (dst_status == DEST_FULL && !appending)
2858 /* Copy has succeeded */
2860 while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
2861 && !ctx->ignore_all)
2863 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2864 if (temp_status == FILE_ABORT)
2866 return_status = FILE_ABORT;
2867 goto ret_fast;
2869 if (temp_status == FILE_RETRY)
2870 continue;
2871 if (temp_status == FILE_IGNORE_ALL)
2873 ctx->ignore_all = TRUE;
2874 return_status = FILE_CONT;
2876 if (temp_status == FILE_IGNORE)
2877 return_status = FILE_CONT;
2878 break;
2881 while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
2882 && !ctx->ignore_all)
2884 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2885 if (temp_status == FILE_ABORT)
2887 return_status = FILE_ABORT;
2888 goto ret_fast;
2890 if (temp_status == FILE_RETRY)
2891 continue;
2892 if (temp_status == FILE_IGNORE_ALL)
2894 ctx->ignore_all = TRUE;
2895 return_status = FILE_CONT;
2897 if (temp_status == FILE_IGNORE)
2898 return_status = FILE_CONT;
2899 break;
2902 if (!ctx->preserve && !dst_exists)
2904 src_mode = umask (-1);
2905 umask (src_mode);
2906 src_mode = 0100666 & ~src_mode;
2907 mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2911 if (dst_status == DEST_FULL || dst_status == DEST_SHORT_KEEP)
2913 /* Always sync timestamps */
2914 mc_utime (dst_vpath, &times);
2916 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2918 attrs_ok = FALSE;
2920 /* don't show an error message if attributes aren't supported in this FS */
2921 if (attrs_ignore_error (errno))
2923 return_status = FILE_CONT;
2924 break;
2927 temp_status =
2928 file_error (TRUE, _("Cannot set ext2 attributes for target file \"%s\"\n%s"),
2929 dst_path);
2930 if (temp_status == FILE_ABORT)
2931 return_status = FILE_ABORT;
2932 if (temp_status == FILE_RETRY)
2934 attrs_ok = TRUE;
2935 continue;
2937 if (temp_status == FILE_IGNORE_ALL)
2939 ctx->ignore_all = TRUE;
2940 return_status = FILE_CONT;
2942 if (temp_status == FILE_IGNORE)
2943 return_status = FILE_CONT;
2944 break;
2948 progress_update_one (return_status == FILE_CONT, ctx, file_size);
2949 if (return_status == FILE_CONT)
2950 return_status = file_progress_check_buttons (ctx);
2952 ret_fast:
2953 vfs_path_free (src_vpath, TRUE);
2954 vfs_path_free (dst_vpath, TRUE);
2955 return return_status;
2958 /* --------------------------------------------------------------------------------------------- */
2960 * I think these copy_*_* functions should have a return type.
2961 * anyway, this function *must* have two directories as arguments.
2963 /* FIXME: This function needs to check the return values of the
2964 function calls */
2966 FileProgressStatus
2967 copy_dir_dir (file_op_context_t *ctx, const char *s, const char *d, gboolean toplevel,
2968 gboolean move_over, gboolean do_delete, GSList *parent_dirs)
2970 struct vfs_dirent *next;
2971 struct stat dst_stat, src_stat;
2972 unsigned long attrs = 0;
2973 gboolean attrs_ok = ctx->preserve;
2974 DIR *reading;
2975 FileProgressStatus return_status = FILE_CONT;
2976 link_t *lp;
2977 vfs_path_t *src_vpath, *dst_vpath;
2978 gboolean do_mkdir = TRUE;
2980 src_vpath = vfs_path_from_str (s);
2981 dst_vpath = vfs_path_from_str (d);
2983 /* First get the mode of the source dir */
2985 retry_src_stat:
2986 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2988 if (ctx->ignore_all)
2989 return_status = FILE_IGNORE_ALL;
2990 else
2992 return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
2993 if (return_status == FILE_IGNORE_ALL)
2994 ctx->ignore_all = TRUE;
2997 if (return_status != FILE_RETRY)
2998 goto ret_fast;
3001 while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
3003 attrs_ok = FALSE;
3005 /* don't show an error message if attributes aren't supported in this FS */
3006 if (attrs_ignore_error (errno))
3007 return_status = FILE_CONT;
3008 else if (ctx->ignore_all)
3009 return_status = FILE_IGNORE_ALL;
3010 else
3012 return_status =
3013 file_error (TRUE, _("Cannot get ext2 attributes of source directory \"%s\"\n%s"),
3015 if (return_status == FILE_IGNORE_ALL)
3016 ctx->ignore_all = TRUE;
3017 if (return_status == FILE_ABORT)
3018 goto ret_fast;
3021 if (return_status != FILE_RETRY)
3022 break;
3024 /* yet another attempt */
3025 attrs_ok = TRUE;
3028 if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
3030 /* Don't copy a directory we created before (we don't want to copy
3031 infinitely if a directory is copied into itself) */
3032 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
3033 return_status = FILE_CONT;
3034 goto ret_fast;
3037 /* Hmm, hardlink to directory??? - Norbert */
3038 /* FIXME: In this step we should do something in case the destination already exist */
3039 /* Check the hardlinks */
3040 if (ctx->preserve)
3042 switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
3044 case HARDLINK_OK:
3045 /* We have made a hardlink - no more processing is necessary */
3046 goto ret_fast;
3048 case HARDLINK_ABORT:
3049 return_status = FILE_ABORT;
3050 goto ret_fast;
3052 default:
3053 break;
3057 if (!S_ISDIR (src_stat.st_mode))
3059 if (ctx->ignore_all)
3060 return_status = FILE_IGNORE_ALL;
3061 else
3063 return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
3064 if (return_status == FILE_RETRY)
3065 goto retry_src_stat;
3066 if (return_status == FILE_IGNORE_ALL)
3067 ctx->ignore_all = TRUE;
3069 goto ret_fast;
3072 if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
3074 /* we found a cyclic symbolic link */
3075 message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
3076 return_status = FILE_SKIP;
3077 goto ret_fast;
3080 lp = g_new0 (link_t, 1);
3081 lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
3082 lp->ino = src_stat.st_ino;
3083 lp->dev = src_stat.st_dev;
3084 parent_dirs = g_slist_prepend (parent_dirs, lp);
3086 retry_dst_stat:
3087 /* Now, check if the dest dir exists, if not, create it. */
3088 if (mc_stat (dst_vpath, &dst_stat) != 0)
3090 /* Here the dir doesn't exist : make it ! */
3091 if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
3093 return_status = FILE_CONT;
3094 goto ret;
3097 else
3100 * If the destination directory exists, we want to copy the whole
3101 * directory, but we only want this to happen once.
3103 * Escape sequences added to the * to compiler warnings.
3104 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3105 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
3107 if (!S_ISDIR (dst_stat.st_mode))
3109 if (ctx->ignore_all)
3110 return_status = FILE_IGNORE_ALL;
3111 else
3113 return_status =
3114 file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
3115 if (return_status == FILE_IGNORE_ALL)
3116 ctx->ignore_all = TRUE;
3117 if (return_status == FILE_RETRY)
3118 goto retry_dst_stat;
3120 goto ret;
3122 /* Dive into subdir if exists */
3123 if (toplevel && ctx->dive_into_subdirs)
3125 vfs_path_t *tmp;
3127 tmp = dst_vpath;
3128 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
3129 vfs_path_free (tmp, TRUE);
3132 else
3133 do_mkdir = FALSE;
3136 d = vfs_path_as_str (dst_vpath);
3138 if (do_mkdir)
3140 while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
3142 if (ctx->ignore_all)
3143 return_status = FILE_IGNORE_ALL;
3144 else
3146 return_status =
3147 file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
3148 if (return_status == FILE_IGNORE_ALL)
3149 ctx->ignore_all = TRUE;
3151 if (return_status != FILE_RETRY)
3152 goto ret;
3155 lp = g_new0 (link_t, 1);
3156 mc_stat (dst_vpath, &dst_stat);
3157 lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
3158 lp->ino = dst_stat.st_ino;
3159 lp->dev = dst_stat.st_dev;
3160 dest_dirs = g_slist_prepend (dest_dirs, lp);
3163 if (ctx->preserve_uidgid)
3165 while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
3167 if (ctx->ignore_all)
3168 return_status = FILE_IGNORE_ALL;
3169 else
3171 return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
3172 if (return_status == FILE_IGNORE_ALL)
3173 ctx->ignore_all = TRUE;
3175 if (return_status != FILE_RETRY)
3176 goto ret;
3180 /* open the source dir for reading */
3181 reading = mc_opendir (src_vpath);
3182 if (reading == NULL)
3183 goto ret;
3185 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
3187 char *path;
3188 vfs_path_t *tmp_vpath;
3191 * Now, we don't want '.' and '..' to be created / copied at any time
3193 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
3194 continue;
3196 /* get the filename and add it to the src directory */
3197 path = mc_build_filename (s, next->d_name, (char *) NULL);
3198 tmp_vpath = vfs_path_from_str (path);
3200 (*ctx->stat_func) (tmp_vpath, &dst_stat);
3201 if (S_ISDIR (dst_stat.st_mode))
3203 char *mdpath;
3205 mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
3207 * From here, we just intend to recursively copy subdirs, not
3208 * the double functionality of copying different when the target
3209 * dir already exists. So, we give the recursive call the flag 0
3210 * meaning no toplevel.
3212 return_status = copy_dir_dir (ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
3213 g_free (mdpath);
3215 else
3217 char *dest_file;
3219 dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
3220 return_status = copy_file_file (ctx, path, dest_file);
3221 g_free (dest_file);
3224 g_free (path);
3226 if (do_delete && return_status == FILE_CONT)
3228 if (ctx->erase_at_end)
3230 if (erase_list == NULL)
3231 erase_list = g_queue_new ();
3233 lp = g_new0 (link_t, 1);
3234 lp->src_vpath = tmp_vpath;
3235 lp->st_mode = dst_stat.st_mode;
3236 g_queue_push_tail (erase_list, lp);
3237 tmp_vpath = NULL;
3239 else if (S_ISDIR (dst_stat.st_mode))
3240 return_status = erase_dir_iff_empty (ctx, tmp_vpath);
3241 else
3242 return_status = erase_file (ctx, tmp_vpath);
3244 vfs_path_free (tmp_vpath, TRUE);
3246 mc_closedir (reading);
3248 if (ctx->preserve)
3250 mc_timesbuf_t times;
3252 mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3254 if (attrs_ok)
3255 mc_fsetflags (dst_vpath, attrs);
3257 vfs_get_timesbuf_from_stat (&src_stat, &times);
3258 mc_utime (dst_vpath, &times);
3260 else
3262 src_stat.st_mode = umask (-1);
3263 umask (src_stat.st_mode);
3264 src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3265 mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3268 ret:
3269 free_link (parent_dirs->data);
3270 g_slist_free_1 (parent_dirs);
3271 ret_fast:
3272 vfs_path_free (src_vpath, TRUE);
3273 vfs_path_free (dst_vpath, TRUE);
3274 return return_status;
3277 /* }}} */
3279 /* --------------------------------------------------------------------------------------------- */
3280 /* {{{ Move routines */
3282 FileProgressStatus
3283 move_dir_dir (file_op_context_t *ctx, const char *s, const char *d)
3285 return do_move_dir_dir (NULL, ctx, s, d);
3288 /* }}} */
3290 /* --------------------------------------------------------------------------------------------- */
3291 /* {{{ Erase routines */
3293 FileProgressStatus
3294 erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
3296 file_progress_show_deleting (ctx, vpath, NULL);
3297 file_progress_show_count (ctx);
3298 if (file_progress_check_buttons (ctx) == FILE_ABORT)
3299 return FILE_ABORT;
3301 mc_refresh ();
3303 /* The old way to detect a non empty directory was:
3304 error = my_rmdir (s);
3305 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3306 For the linux user space nfs server (nfs-server-2.2beta29-2)
3307 we would have to check also for EIO. I hope the new way is
3308 fool proof. (Norbert)
3310 if (check_dir_is_empty (vpath) == 0)
3311 { /* not empty */
3312 FileProgressStatus error;
3314 error = query_recursive (ctx, vfs_path_as_str (vpath));
3315 if (error == FILE_CONT)
3316 error = recursive_erase (ctx, vpath);
3317 return error;
3320 return try_erase_dir (ctx, vpath);
3323 /* }}} */
3325 /* --------------------------------------------------------------------------------------------- */
3326 /* {{{ Panel operate routines */
3328 void
3329 dirsize_status_init_cb (status_msg_t *sm)
3331 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3332 WGroup *gd = GROUP (sm->dlg);
3333 Widget *wd = WIDGET (sm->dlg);
3334 WRect r = wd->rect;
3336 const char *b1_name = N_("&Abort");
3337 const char *b2_name = N_("&Skip");
3338 int b_width, ui_width;
3340 #ifdef ENABLE_NLS
3341 b1_name = _(b1_name);
3342 b2_name = _(b2_name);
3343 #endif
3345 b_width = str_term_width1 (b1_name) + 4;
3346 if (dsm->allow_skip)
3347 b_width += str_term_width1 (b2_name) + 4 + 1;
3349 ui_width = MAX (COLS / 2, b_width + 6);
3350 dsm->dirname = label_new (2, 3, NULL);
3351 group_add_widget (gd, dsm->dirname);
3352 dsm->count_size = label_new (3, 3, NULL);
3353 group_add_widget (gd, dsm->count_size);
3354 group_add_widget (gd, hline_new (4, -1, -1));
3356 dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3357 group_add_widget (gd, dsm->abort_button);
3358 if (dsm->allow_skip)
3360 dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3361 group_add_widget (gd, dsm->skip_button);
3362 widget_select (dsm->skip_button);
3365 r.lines = 8;
3366 r.cols = ui_width;
3367 widget_set_size_rect (wd, &r);
3368 dirsize_status_locate_buttons (dsm);
3371 /* --------------------------------------------------------------------------------------------- */
3374 dirsize_status_update_cb (status_msg_t *sm)
3376 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3377 Widget *wd = WIDGET (sm->dlg);
3378 WRect r = wd->rect;
3380 /* update second (longer label) */
3381 label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
3382 dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3384 /* enlarge dialog if required */
3385 if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
3387 r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
3388 widget_set_size_rect (wd, &r);
3389 dirsize_status_locate_buttons (dsm);
3390 widget_draw (wd);
3391 /* TODO: ret rid of double redraw */
3394 /* adjust first label */
3395 label_set_text (dsm->dirname,
3396 str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
3398 switch (status_msg_common_update (sm))
3400 case B_CANCEL:
3401 case FILE_ABORT:
3402 return FILE_ABORT;
3403 case FILE_SKIP:
3404 return FILE_SKIP;
3405 default:
3406 return FILE_CONT;
3410 /* --------------------------------------------------------------------------------------------- */
3412 void
3413 dirsize_status_deinit_cb (status_msg_t *sm)
3415 (void) sm;
3417 /* schedule to update passive panel */
3418 if (get_other_type () == view_listing)
3419 other_panel->dirty = TRUE;
3422 /* --------------------------------------------------------------------------------------------- */
3424 * compute_dir_size:
3426 * Computes the number of bytes used by the files in a directory
3429 FileProgressStatus
3430 compute_dir_size (const vfs_path_t *dirname_vpath, dirsize_status_msg_t *sm,
3431 size_t *ret_dir_count, size_t *ret_marked_count, uintmax_t *ret_total,
3432 gboolean follow_symlinks)
3434 return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3435 follow_symlinks ? mc_stat : mc_lstat);
3438 /* --------------------------------------------------------------------------------------------- */
3440 * panel_operate:
3442 * Performs one of the operations on the current on the source_panel
3443 * (copy, delete, move).
3445 * Returns TRUE if did change the directory
3446 * structure, Returns FALSE if user aborted
3448 * force_single forces operation on the current entry and affects
3449 * default destination. Current filename is used as default.
3452 gboolean
3453 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
3455 WPanel *panel = PANEL (source_panel);
3456 const gboolean single_entry = force_single || (panel->marked <= 1)
3457 || (get_current_type () == view_tree);
3459 const char *source = NULL;
3460 char *dest = NULL;
3461 vfs_path_t *dest_vpath = NULL;
3462 vfs_path_t *save_cwd = NULL, *save_dest = NULL;
3463 struct stat src_stat;
3464 gboolean ret_val = TRUE;
3465 int i;
3466 FileProgressStatus value;
3467 file_op_context_t *ctx;
3468 filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3470 gboolean do_bg = FALSE; /* do background operation? */
3472 static gboolean i18n_flag = FALSE;
3473 if (!i18n_flag)
3475 for (i = G_N_ELEMENTS (op_names); i-- != 0;)
3476 op_names[i] = Q_ (op_names[i]);
3477 i18n_flag = TRUE;
3480 linklist = free_linklist (linklist);
3481 dest_dirs = free_linklist (dest_dirs);
3483 save_cwds_stat ();
3485 if (single_entry)
3487 source = check_single_entry (panel, force_single, &src_stat);
3489 if (source == NULL)
3490 return FALSE;
3493 ctx = file_op_context_new (operation);
3495 /* Show confirmation dialog */
3496 if (operation != OP_DELETE)
3498 dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
3499 if (dest == NULL)
3501 ret_val = FALSE;
3502 goto ret_fast;
3505 dest_vpath = vfs_path_from_str (dest);
3507 else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3509 ret_val = FALSE;
3510 goto ret_fast;
3513 ctx->total_transfer_start = g_get_monotonic_time ();
3515 #ifdef ENABLE_BACKGROUND
3516 /* Did the user select to do a background operation? */
3517 if (do_bg)
3519 int v;
3521 v = do_background (ctx,
3522 g_strconcat (op_names[operation], ": ",
3523 vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3524 if (v == -1)
3525 message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
3527 /* If we are the parent */
3528 if (v == 1)
3530 mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3532 mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3533 vfs_path_free (dest_vpath, TRUE);
3534 g_free (dest);
3535 /* file_op_context_destroy (ctx); */
3536 return FALSE;
3539 else
3540 #endif /* ENABLE_BACKGROUND */
3542 const file_entry_t *fe;
3544 if (operation == OP_DELETE)
3545 dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3546 else if (single_entry
3547 && ((fe = panel_current_entry (panel)) == NULL ? FALSE : S_ISDIR (fe->st.st_mode)))
3548 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3549 else if (single_entry || force_single)
3550 dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3551 else
3552 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3555 /* Initialize things */
3556 /* We do not want to trash cache every time file is
3557 created/touched. However, this will make our cache contain
3558 invalid data. */
3559 if ((dest != NULL)
3560 && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3561 save_dest = vfs_path_from_str (dest);
3563 if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3564 && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3565 save_cwd = vfs_path_clone (panel->cwd_vpath);
3567 /* Now, let's do the job */
3569 /* This code is only called by the tree and panel code */
3570 if (single_entry)
3572 /* We now have ETA in all cases */
3574 /* One file: FIXME mc_chdir will take user out of any vfs */
3575 if ((operation != OP_COPY) && (get_current_type () == view_tree))
3577 vfs_path_t *vpath;
3578 int chdir_retcode;
3580 vpath = vfs_path_from_str (PATH_SEP_STR);
3581 chdir_retcode = mc_chdir (vpath);
3582 vfs_path_free (vpath, TRUE);
3583 if (chdir_retcode < 0)
3585 ret_val = FALSE;
3586 goto clean_up;
3590 value = operate_single_file (panel, ctx, source, &src_stat, dest, dialog_type);
3591 if ((value == FILE_CONT) && !force_single)
3592 unmark_files (panel);
3594 else
3596 /* Many files */
3598 /* Check destination for copy or move operation */
3599 while (operation != OP_DELETE)
3601 int dst_result;
3602 struct stat dst_stat;
3604 dst_result = mc_stat (dest_vpath, &dst_stat);
3606 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3607 break;
3609 if (ctx->ignore_all
3610 || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
3611 dest) != FILE_RETRY)
3612 goto clean_up;
3615 /* TODO: the good way is required to skip directories scanning in case of rename/move
3616 * of several directories. Since reqular expression can be used for destination,
3617 * some directory movements can be a cross-filesystem and directory scanning is useful
3618 * for those directories only. */
3620 value =
3621 panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type);
3622 if (value == FILE_CONT)
3623 /* Loop for every file, perform the actual copy operation */
3624 for (i = 0; i < panel->dir.len; i++)
3626 const char *source2;
3628 if (panel->dir.list[i].f.marked == 0)
3629 continue; /* Skip the unmarked ones */
3631 source2 = panel->dir.list[i].fname->str;
3632 src_stat = panel->dir.list[i].st;
3634 value = operate_one_file (panel, ctx, source2, &src_stat, dest);
3635 if (value == FILE_ABORT)
3636 break;
3638 if (value == FILE_CONT)
3639 do_file_mark (panel, i, 0);
3641 mc_refresh ();
3642 } /* 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: