4 Copyright (C) 1994-2024
5 Free Software Foundation, Inc.
8 Janne Kukonlehto, 1994, 1995
9 Fred Leeflang, 1994, 1995
10 Miguel de Icaza, 1994, 1995, 1996
11 Jakub Jelinek, 1995, 1996
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
46 /** \file src/filemanager/file.c
47 * \brief Source: file management
50 /* {{{ Include files */
59 #include <sys/types.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"
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() */
78 /* Needed for other_panel and WTree */
82 #include "filemanager.h" /* other_panel */
83 #include "layout.h" /* rotate_dash() */
84 #include "ioblksize.h" /* io_blksize() */
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 */
111 const struct vfs_class
*vfs
;
115 vfs_path_t
*src_vpath
;
116 vfs_path_t
*dst_vpath
;
119 /* Status of the destination file */
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 */
129 /* Status of hard link creation */
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 */
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
[] = {
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 /* --------------------------------------------------------------------------------------------- */
200 dirsize_status_locate_buttons (dirsize_status_msg_t
*dsm
)
202 status_msg_t
*sm
= STATUS_MSG (dsm
);
203 Widget
*wd
= WIDGET (sm
->dlg
);
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
;
217 widget_set_size_rect (dsm
->abort_button
, &r
);
221 /* two buttons: "Abort" and "Skip" */
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
;
229 widget_set_size_rect (dsm
->abort_button
, &r
);
230 x
+= dsm
->abort_button
->rect
.cols
+ 1;
231 r
= dsm
->skip_button
->rect
;
234 widget_set_size_rect (dsm
->skip_button
, &r
);
238 /* --------------------------------------------------------------------------------------------- */
241 build_dest (file_op_context_t
*ctx
, const char *src
, const char *dest
, FileProgressStatus
*status
)
244 const char *fnsource
;
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
++)
256 fnsource
= x_basename (s
);
258 if (!mc_search_run (ctx
->search_handle
, fnsource
, 0, strlen (fnsource
), NULL
))
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
;
277 if (*status
== FILE_CONT
)
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
);
286 if (ctx
->search_handle
->error_str
!= NULL
)
287 message (D_ERROR
, MSG_ERROR
, "%s", ctx
->search_handle
->error_str
);
289 *status
= FILE_ABORT
;
300 /* --------------------------------------------------------------------------------------------- */
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
);
312 /* --------------------------------------------------------------------------------------------- */
315 free_erase_list (GQueue
*lp
)
318 g_queue_free_full (lp
, free_link
);
323 /* --------------------------------------------------------------------------------------------- */
326 free_linklist (GSList
*lp
)
328 g_slist_free_full (lp
, free_link
);
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
)
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
)
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
);
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
)
403 while (!(ok
= (mc_stat (lnk
->dst_vpath
, &link_stat
) == 0)) && !*ignore_all
)
405 FileProgressStatus 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
)
414 if (status
== FILE_IGNORE_ALL
)
419 /* if stat() finished unsuccessfully, don't try to create link */
421 return HARDLINK_ERROR
;
423 while (!(ok
= (mc_link (lnk
->dst_vpath
, dst_vpath
) == 0)) && !*ignore_all
)
425 FileProgressStatus 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
)
434 if (status
== FILE_IGNORE_ALL
)
440 return (ok
? HARDLINK_OK
: HARDLINK_ERROR
);
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).
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
)
474 return HARDLINK_ERROR
;
477 lnk
= g_try_new (link_t
, 1);
480 lnk
->vfs
= vfs_path_get_last_path_vfs (src_vpath
);
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
];
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
);
520 len
= mc_readlink (src_vpath
, link_target
, sizeof (link_target
) - 1);
524 return_status
= FILE_IGNORE_ALL
;
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
;
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
))
550 r
= strrchr (src_path
, PATH_SEP
);
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
);
565 q
= vfs_path_build_filename (p
->str
, dst_path
, (char *) NULL
);
567 if (vfs_path_tokens_count (q
) > 1)
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
));
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
);
589 if (mc_symlink (link_target_vpath
, dst_vpath
) == 0)
592 return_status
= FILE_CONT
;
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)
603 return_status
= FILE_CONT
;
608 return_status
= FILE_IGNORE_ALL
;
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
;
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
);
643 struct vfs_dirent
*dirent
;
644 FileProgressStatus ret
= FILE_CONT
;
648 dir
= mc_opendir (dirname_vpath
);
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
))
659 tmp_vpath
= vfs_path_append_new (dirname_vpath
, dirent
->d_name
, (char *) NULL
);
661 res
= stat_func (tmp_vpath
, &s
);
664 if (S_ISDIR (s
.st_mode
))
666 do_compute_dir_size (tmp_vpath
, dsm
, dir_count
, ret_marked
, ret_total
,
673 *ret_total
+= (uintmax_t) s
.st_size
;
676 if (ret
== FILE_CONT
&& sm
->update
!= NULL
&& mc_time_elapsed (×tamp
, 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
);
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
)
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)
720 if (S_ISDIR (s
->st_mode
) || (follow_symlinks
&& link_isdir (fe
) && fe
->f
.stale_link
== 0))
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
)
735 *ret_total
+= (uintmax_t) s
->st_size
;
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
)
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;
771 status
= panel_compute_totals (panel
, &dsm
, &ctx
->total_count
, &ctx
->total_bytes
,
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
)
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
);
786 ctx
->total_bytes
+= (uintmax_t) source_stat
->st_size
;
790 status_msg_deinit (STATUS_MSG (&dsm
));
792 ctx
->totals_computed
= (status
== FILE_CONT
);
794 if (status
== FILE_SKIP
)
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
);
813 /* --------------------------------------------------------------------------------------------- */
816 progress_update_one (gboolean success
, file_op_context_t
*ctx
, off_t add
)
819 static gint64 tv_start
= -1;
821 ctx
->total_progress_count
++;
822 ctx
->total_progress_bytes
+= (uintmax_t) add
;
827 tv_current
= g_get_monotonic_time ();
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
)
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
);
865 s
= g_strndup (str_trunc (a
, width
), width
);
866 b
= str_trunc (b
, width
);
867 msg
= g_strdup_printf (fmt
, s
, b
);
872 a
= str_trunc (a
, width
);
873 msg
= g_strdup_printf (fmt
, a
, b
);
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"));
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
901 FileProgressStatus (*f
) (enum OperationMode
, const char *fmt
, const char *a
, const char *b
);
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
);
910 return real_warn_same_file (Foreground
, fmt
, a
, b
);
913 /* --------------------------------------------------------------------------------------------- */
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
)
922 if (S_ISDIR (ast
->st_mode
))
923 *status
= warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a
, b
);
925 *status
= warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a
, b
);
930 /* --------------------------------------------------------------------------------------------- */
931 /* {{{ Query/status report routines */
933 static FileProgressStatus
934 real_do_file_error (enum OperationMode mode
, gboolean allow_retry
, const char *error
)
939 msg
= mode
== Foreground
? MSG_ERROR
: _("Background process error");
943 query_dialog (msg
, error
, D_ERROR
, 4, _("&Ignore"), _("Ignore a&ll"), _("&Retry"),
946 result
= query_dialog (msg
, error
, D_ERROR
, 3, _("&Ignore"), _("Ignore a&ll"), _("&Abort"));
956 return FILE_IGNORE_ALL
;
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
)
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));
990 ctx
->recursive_result
=
991 query_dialog (op_names
[OP_DELETE
], text
, D_ERROR
, 5, _("&Yes"), _("&No"), _("A&ll"),
992 _("Non&e"), _("&Abort"));
995 if (ctx
->recursive_result
!= RECURSIVE_ABORT
)
999 switch (ctx
->recursive_result
)
1002 case RECURSIVE_ALWAYS
:
1006 case RECURSIVE_NEVER
:
1009 case RECURSIVE_ABORT
:
1015 /* --------------------------------------------------------------------------------------------- */
1017 #ifdef ENABLE_BACKGROUND
1018 static FileProgressStatus
1019 do_file_error (gboolean allow_retry
, const char *str
)
1025 FileProgressStatus (*f
) (enum OperationMode
, gboolean
, const char *);
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
);
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
)
1046 FileProgressStatus (*f
) (file_op_context_t
*, enum OperationMode
, const char *);
1050 pntr
.f
= real_query_recursive
;
1052 if (mc_global
.we_are_background
)
1053 return parent_call (pntr
.p
, ctx
, 1, strlen (s
), s
);
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
)
1068 FileProgressStatus (*f
) (file_op_context_t
*, enum OperationMode
, const char *,
1069 struct stat
*, const char *, struct stat
*);
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
);
1079 return file_progress_real_query_replace (ctx
, Foreground
, src
, src_stat
, dst
, dst_stat
);
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
));
1125 return do_file_error (TRUE
, buf
);
1130 /* --------------------------------------------------------------------------------------------- */
1133 calc_copy_file_progress (file_op_context_t
*ctx
, gint64 tv_current
, off_t file_part
,
1138 /* Update rotating dash after some time */
1142 dt
= (tv_current
- ctx
->transfer_start
) / (double) G_USEC_PER_SEC
;
1145 ctx
->eta_secs
= 0.0;
1147 ctx
->eta_secs
= ((double) file_size
/ file_part
- 1) * dt
;
1149 /* Compute BPS rate */
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;
1162 ctx
->total_eta_secs
= ((double) ctx
->total_bytes
/ copied_bytes
- 1) * dt
;
1165 ctx
->total_bps
= (long) (copied_bytes
/ dt
);
1169 /* --------------------------------------------------------------------------------------------- */
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
)
1179 if (*status
== FILE_IGNORE_ALL
)
1180 ctx
->ignore_all
= 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
;
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
;
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
)
1241 if (mc_lstat (dst_vpath
, &dst_stat
) == 0)
1243 if (check_same_file (s
, &src_stat
, d
, &dst_stat
, &return_status
))
1246 if (S_ISDIR (dst_stat
.st_mode
))
1248 message (D_ERROR
, MSG_ERROR
, _("Cannot overwrite directory \"%s\""), d
);
1250 return_status
= FILE_SKIP
;
1254 if (confirm_overwrite
)
1256 return_status
= query_replace (ctx
, s
, &src_stat
, d
, &dst_stat
);
1257 if (return_status
!= FILE_CONT
)
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
)
1272 mc_timesbuf_t times
;
1274 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
1275 mc_utime (dst_vpath
, ×
);
1277 goto retry_src_remove
;
1282 if (mc_rename (src_vpath
, dst_vpath
) == 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. */
1291 errno
= EXDEV
; /* Hack to copy (append) the file and then delete it */
1295 if (ctx
->ignore_all
)
1296 return_status
= FILE_IGNORE_ALL
;
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
)
1310 /* Failed rename -> copy the file instead */
1313 /* In case of single file, calculate totals. In case of many files,
1314 totals are calculated already. */
1316 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1317 FILEGUI_DIALOG_ONE_ITEM
);
1318 if (return_status
!= FILE_CONT
)
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
)
1331 /* FIXME: there is no need to update progress and check buttons
1332 at the finish of single file operation. */
1335 file_progress_show_source (ctx
, NULL
);
1337 file_progress_show (ctx
, 0, 0, "", FALSE
);
1339 return_status
= file_progress_check_buttons (ctx
);
1340 if (return_status
!= FILE_CONT
)
1347 if (!try_remove_file (ctx
, src_vpath
, &return_status
) && panel
== NULL
)
1351 if (return_status
!= FILE_ABORT
)
1353 /* if copy_done == TRUE, progress_update_one() was called in copy_file_file() */
1355 progress_update_one (TRUE
, ctx
, src_stat
.st_size
);
1356 return_status
= file_progress_check_buttons (ctx
);
1359 vfs_path_free (src_vpath
, TRUE
);
1360 vfs_path_free (dst_vpath
, TRUE
);
1362 return return_status
;
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
)
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
)
1387 if (ctx
->total_progress_count
!= 0 && mc_lstat (vpath
, &buf
) != 0)
1389 /* ignore, most likely the mc_unlink fails, too */
1393 if (!try_remove_file (ctx
, vpath
, &return_status
) && return_status
== FILE_ABORT
)
1396 if (ctx
->total_progress_count
== 0)
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
)
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
)
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
;
1437 FileProgressStatus return_status
= FILE_CONT
;
1439 reading
= mc_opendir (vpath
);
1440 if (reading
== NULL
)
1443 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
1445 vfs_path_t
*tmp_vpath
;
1448 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
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
);
1458 if (S_ISDIR (buf
.st_mode
))
1459 return_status
= recursive_erase (ctx
, tmp_vpath
);
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
)
1469 file_progress_show_deleting (ctx
, vpath
, NULL
);
1470 file_progress_show_count (ctx
);
1471 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
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,
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.
1494 check_dir_is_empty (const vfs_path_t
*vpath
)
1497 struct vfs_dirent
*d
;
1500 dir
= mc_opendir (vpath
);
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
))
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
)
1527 if (check_dir_is_empty (vpath
) != 1)
1530 /* not empty or error */
1531 return try_erase_dir (ctx
, vpath
);
1534 /* --------------------------------------------------------------------------------------------- */
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
)
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
);
1553 *status
= erase_file (ctx
, lp
->src_vpath
);
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
);
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
;
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
;
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
))
1611 ; /* destination doesn't exist */
1612 else if (!ctx
->dive_into_subdirs
)
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 */
1627 if (mc_stat (dst_vpath
, &dst_stat
) == 0)
1633 /* In case of single directory, calculate totals. In case of many directories,
1634 totals are calculated already. */
1636 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1637 FILEGUI_DIALOG_MULTI_ITEM
);
1638 if (return_status
!= FILE_CONT
)
1642 return_status
= copy_dir_dir (ctx
, s
, d
, FALSE
, TRUE
, TRUE
, NULL
);
1644 if (return_status
!= FILE_CONT
)
1648 else if (ctx
->ignore_all
)
1649 return_status
= FILE_IGNORE_ALL
;
1652 if (S_ISDIR (dst_stat
.st_mode
))
1653 return_status
= file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), d
);
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
;
1666 if (mc_rename (src_vpath
, dst_vpath
) == 0)
1668 return_status
= FILE_CONT
;
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
)
1685 /* Failed because of filesystem boundary -> copy dir instead */
1688 /* In case of single directory, calculate totals. In case of many directories,
1689 totals are calculated already. */
1691 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1692 FILEGUI_DIALOG_MULTI_ITEM
);
1693 if (return_status
!= FILE_CONT
)
1697 return_status
= copy_dir_dir (ctx
, s
, d
, FALSE
, FALSE
, TRUE
, NULL
);
1699 if (return_status
!= FILE_CONT
)
1703 /* FIXME: there is no need to update progress and check buttons
1704 at the finish of single directory operation. */
1707 file_progress_show_source (ctx
, NULL
);
1708 file_progress_show_target (ctx
, NULL
);
1710 file_progress_show (ctx
, 0, 0, "", FALSE
);
1712 return_status
= file_progress_check_buttons (ctx
);
1713 if (return_status
!= FILE_CONT
)
1719 erase_dir_after_copy (ctx
, src_vpath
, &return_status
);
1722 erase_list
= free_erase_list (erase_list
);
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.
1739 panel_get_file (const WPanel
*panel
)
1741 const file_entry_t
*fe
;
1743 if (get_current_type () == view_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)
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 /* --------------------------------------------------------------------------------------------- */
1770 check_single_entry (const WPanel
*panel
, gboolean force_single
, struct stat
*src_stat
)
1777 const file_entry_t
*fe
;
1779 fe
= panel_current_entry (panel
);
1780 source
= fe
== NULL
? NULL
: fe
->fname
->str
;
1783 source
= panel_get_file (panel
);
1788 ok
= !DIR_IS_DOTDOT (source
);
1791 message (D_ERROR
, MSG_ERROR
, _("Cannot operate on \"..\"!"));
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;
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
1831 panel_operate_generate_prompt (const WPanel
*panel
, FileOperation operation
,
1832 const struct stat
*src_stat
)
1835 char *format_string
;
1838 static gboolean i18n_flag
= FALSE
;
1843 for (i
= G_N_ELEMENTS (op_names1
); i
-- != 0;)
1844 op_names1
[i
] = Q_ (op_names1
[i
]);
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 */
1856 /* Possible prompts:
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:"
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:"
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" : " ";
1885 format_string
= str_replace_all (sp
, "%n", cp
);
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];
1894 cp
= panel
->dirs_marked
!= 0 ? prompt_parts
[4] : prompt_parts
[1];
1897 format_string
= str_replace_all (sp
, "%f", cp
);
1900 /* 4. Substitute %m */
1901 cp
= operation
== OP_DELETE
? "?" : prompt_parts
[5];
1903 format_string
= str_replace_all (sp
, "%m", cp
);
1906 return format_string
;
1909 /* --------------------------------------------------------------------------------------------- */
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
;
1920 /* Forced single operations default to the original name */
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
);
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
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
);
1942 dest_dir
= g_strdup (tmp_dest_dir
);
1945 if (dest_dir
== 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
,
1964 /* --------------------------------------------------------------------------------------------- */
1967 do_confirm_erase (const WPanel
*panel
, const char *source
, struct stat
*src_stat
)
1971 char fmd_buf
[BUF_MEDIUM
];
1976 /* Generate confirmation prompt */
1977 format
= panel_operate_generate_prompt (panel
, OP_DELETE
, src_stat
);
1980 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, panel
->marked
);
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
));
1994 i
= query_dialog (op_names
[OP_DELETE
], fmd_buf
, D_ERROR
, 2, _("&Yes"), _("&No"));
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
;
2009 if (g_path_is_absolute (src
))
2010 src_vpath
= vfs_path_from_str (src
);
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? */
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
)
2030 value
= erase_file (ctx
, src_vpath
);
2032 value
= erase_dir (ctx
, src_vpath
);
2039 src
= vfs_path_as_str (src_vpath
);
2041 temp
= build_dest (ctx
, src
, dest
, &value
);
2046 switch (ctx
->operation
)
2049 /* we use file_mask_op_follow_links only with OP_COPY */
2050 ctx
->stat_func (src_vpath
, src_stat
);
2053 panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
,
2055 if (value
== FILE_CONT
)
2057 is_file
= !S_ISDIR (src_stat
->st_mode
);
2058 /* Is link to directory? */
2063 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
2064 is_file
= !(is_link
&& ctx
->follow_links
);
2068 value
= copy_file_file (ctx
, src
, dest
);
2070 value
= copy_dir_dir (ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2075 #ifdef ENABLE_BACKGROUND
2076 if (!mc_global
.we_are_background
)
2078 /* create UI to show confirmation dialog */
2079 file_progress_ui_create (ctx
, TRUE
, FILEGUI_DIALOG_ONE_ITEM
);
2082 value
= move_file_file (panel
, ctx
, src
, dest
);
2084 value
= do_move_dir_dir (panel
, ctx
, src
, dest
);
2088 /* Unknown file operation */
2096 vfs_path_free (src_vpath
, TRUE
);
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
;
2111 if (g_path_is_absolute (src
))
2112 src_vpath
= vfs_path_from_str (src
);
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
)
2121 value
= erase_file (ctx
, src_vpath
);
2123 value
= erase_dir (ctx
, src_vpath
);
2129 src
= vfs_path_as_str (src_vpath
);
2131 temp
= build_dest (ctx
, src
, dest
, &value
);
2136 switch (ctx
->operation
)
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
);
2144 value
= copy_file_file (ctx
, src
, dest
);
2146 value
= copy_dir_dir (ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2147 dest_dirs
= free_linklist (dest_dirs
);
2152 value
= move_file_file (NULL
, ctx
, src
, dest
);
2154 value
= do_move_dir_dir (NULL
, ctx
, src
, dest
);
2158 /* Unknown file operation */
2166 vfs_path_free (src_vpath
, TRUE
);
2171 /* --------------------------------------------------------------------------------------------- */
2173 #ifdef ENABLE_BACKGROUND
2175 end_bg_process (file_op_context_t
*ctx
, enum OperationMode mode
)
2182 unregister_task_with_pid (pid
);
2183 /* file_op_context_destroy(ctx); */
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.
2215 file_is_symlink_to_dir (const vfs_path_t
*vpath
, struct stat
*st
, gboolean
*stale_link
)
2218 gboolean stale
= FALSE
;
2219 gboolean res
= FALSE
;
2225 if (mc_lstat (vpath
, st
) != 0)
2229 if (S_ISLNK (st
->st_mode
))
2233 stale
= (mc_stat (vpath
, &st3
) != 0);
2236 res
= (S_ISDIR (st3
.st_mode
) != 0);
2240 if (stale_link
!= NULL
)
2241 *stale_link
= stale
;
2246 /* --------------------------------------------------------------------------------------------- */
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
;
2265 vfs_path_t
*src_vpath
= NULL
, *dst_vpath
= 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)
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
;
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
;
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
)
2312 while ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2314 if (ctx
->ignore_all
)
2315 return_status
= FILE_IGNORE_ALL
;
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
)
2326 progress_update_one (FALSE
, ctx
, 0);
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)
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
;
2349 file_error (TRUE
, _("Cannot get ext2 attributes of source file \"%s\"\n%s"),
2351 if (return_status
== FILE_IGNORE_ALL
)
2352 ctx
->ignore_all
= TRUE
;
2353 if (return_status
== FILE_ABORT
)
2357 if (return_status
!= FILE_RETRY
)
2360 /* yet another attempt */
2366 /* Destination already exists */
2367 if (check_same_file (src_path
, &src_stat
, dst_path
, &dst_stat
, &return_status
))
2370 /* Should we replace destination? */
2371 if (ctx
->ask_overwrite
)
2374 return_status
= query_replace (ctx
, src_path
, &src_stat
, dst_path
, &dst_stat
);
2375 if (return_status
!= FILE_CONT
)
2380 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
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
))
2390 /* We have made a hardlink - no more processing is necessary */
2391 return_status
= FILE_CONT
;
2394 case HARDLINK_ABORT
:
2395 return_status
= FILE_ABORT
;
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
, ×
);
2410 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
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
;
2422 _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2425 if (return_status
!= FILE_RETRY
)
2428 /* yet another attempt */
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
))
2440 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2441 rdev
= src_stat
.st_rdev
;
2444 while (mc_mknod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
, rdev
) < 0
2445 && !ctx
->ignore_all
)
2448 file_error (TRUE
, _("Cannot create special file \"%s\"\n%s"), dst_path
);
2449 if (return_status
== FILE_RETRY
)
2451 if (return_status
== FILE_IGNORE_ALL
)
2452 ctx
->ignore_all
= TRUE
;
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
)
2464 if (temp_status
== FILE_IGNORE_ALL
)
2465 ctx
->ignore_all
= TRUE
;
2466 if (temp_status
!= FILE_RETRY
)
2468 return_status
= temp_status
;
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
)
2479 if (temp_status
== FILE_IGNORE_ALL
)
2480 ctx
->ignore_all
= TRUE
;
2481 if (temp_status
!= FILE_RETRY
)
2483 return_status
= temp_status
;
2488 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
2492 /* don't show an error message if attributes aren't supported in this FS */
2493 if (attrs_ignore_error (errno
))
2497 file_error (TRUE
, _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2499 if (temp_status
== FILE_IGNORE
)
2501 if (temp_status
== FILE_IGNORE_ALL
)
2502 ctx
->ignore_all
= TRUE
;
2503 if (temp_status
!= FILE_RETRY
)
2505 return_status
= temp_status
;
2509 /* yet another attempt */
2513 return_status
= FILE_CONT
;
2514 mc_utime (dst_vpath
, ×
);
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
;
2527 return_status
= file_error (TRUE
, _("Cannot open source file \"%s\"\n%s"), src_path
);
2528 if (return_status
== FILE_RETRY
)
2530 if (return_status
== FILE_IGNORE_ALL
)
2531 ctx
->ignore_all
= TRUE
;
2532 ctx
->do_append
= FALSE
;
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"));
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
;
2550 return_status
= file_error (TRUE
, _("Cannot fstat source file \"%s\"\n%s"), src_path
);
2551 if (return_status
== FILE_RETRY
)
2553 if (return_status
== FILE_IGNORE_ALL
)
2554 ctx
->ignore_all
= TRUE
;
2555 ctx
->do_append
= FALSE
;
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
;
2568 open_flags
|= O_CREAT
| O_EXCL
;
2569 else if (ctx
->do_append
)
2570 open_flags
|= O_APPEND
;
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
;
2583 file_error (TRUE
, _("Cannot create target file \"%s\"\n%s"), dst_path
);
2584 if (return_status
== FILE_RETRY
)
2586 if (return_status
== FILE_IGNORE_ALL
)
2587 ctx
->ignore_all
= TRUE
;
2588 ctx
->do_append
= FALSE
;
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
;
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
;
2615 return_status
= file_error (TRUE
, _("Cannot fstat target file \"%s\"\n%s"), dst_path
);
2616 if (return_status
== FILE_RETRY
)
2618 if (return_status
== FILE_IGNORE_ALL
)
2619 ctx
->ignore_all
= TRUE
;
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
;
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
;
2648 if (return_status
== FILE_ABORT
)
2650 mc_close (dest_desc
);
2652 mc_unlink (dst_vpath
);
2653 dst_status
= DEST_NONE
;
2657 /* return_status == FILE_RETRY -- try allocate space again */
2660 ctx
->eta_secs
= 0.0;
2665 if (ctx
->total_bps
== 0 || (file_size
/ ctx
->total_bps
) > FILEOP_UPDATE_INTERVAL
)
2666 file_progress_show (ctx
, 0, file_size
, "", TRUE
);
2668 file_progress_show (ctx
, 1, 1, "", TRUE
);
2671 return_status
= file_progress_check_buttons (ctx
);
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
);
2686 ssize_t n_read
= -1;
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
)
2693 file_error (TRUE
, _("Cannot read source file \"%s\"\n%s"), src_path
);
2694 if (return_status
== FILE_RETRY
)
2696 if (return_status
== FILE_IGNORE_ALL
)
2697 ctx
->ignore_all
= TRUE
;
2704 const gint64 tv_current
= g_get_monotonic_time ();
2711 file_part
+= n_read
;
2713 tv_last_input
= tv_current
;
2716 while ((n_written
= mc_write (dest_desc
, t
, (size_t) n_read
)) < n_read
)
2718 gboolean write_errno_nospace
;
2722 n_read
-= n_written
;
2727 write_errno_nospace
= (n_written
< 0 && errno
== ENOSPC
);
2729 if (ctx
->ignore_all
)
2730 return_status
= FILE_IGNORE_ALL
;
2733 file_error (TRUE
, _("Cannot write target file \"%s\"\n%s"), dst_path
);
2735 if (return_status
== FILE_IGNORE
)
2737 if (write_errno_nospace
)
2741 if (return_status
== FILE_IGNORE_ALL
)
2743 ctx
->ignore_all
= TRUE
;
2744 if (write_errno_nospace
)
2747 if (return_status
!= FILE_RETRY
)
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
;
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
);
2784 return_status
= file_progress_check_buttons (ctx
);
2785 if (return_status
!= FILE_CONT
)
2790 query_dialog (Q_ ("DialogTitle|Copy"),
2791 _("Incomplete file was retrieved"), D_ERROR
, 3,
2792 _("&Delete"), _("&Keep"), _("&Continue copy"));
2798 dst_status
= DEST_SHORT_DELETE
;
2803 dst_status
= DEST_SHORT_KEEP
;
2813 /* copy successful */
2814 dst_status
= DEST_FULL
;
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
)
2826 if (temp_status
== FILE_ABORT
)
2827 return_status
= temp_status
;
2828 if (temp_status
== FILE_IGNORE_ALL
)
2829 ctx
->ignore_all
= TRUE
;
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
)
2838 if (temp_status
== FILE_IGNORE_ALL
)
2839 ctx
->ignore_all
= TRUE
;
2840 return_status
= temp_status
;
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
;
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
;
2869 if (temp_status
== FILE_RETRY
)
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
;
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
;
2890 if (temp_status
== FILE_RETRY
)
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
;
2902 if (!ctx
->preserve
&& !dst_exists
)
2904 src_mode
= umask (-1);
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
, ×
);
2916 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
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
;
2928 file_error (TRUE
, _("Cannot set ext2 attributes for target file \"%s\"\n%s"),
2930 if (temp_status
== FILE_ABORT
)
2931 return_status
= FILE_ABORT
;
2932 if (temp_status
== FILE_RETRY
)
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
;
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
);
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
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
;
2975 FileProgressStatus return_status
= FILE_CONT
;
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 */
2986 while ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2988 if (ctx
->ignore_all
)
2989 return_status
= FILE_IGNORE_ALL
;
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
)
3001 while (attrs_ok
&& mc_fgetflags (src_vpath
, &attrs
) != 0)
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
;
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
)
3021 if (return_status
!= FILE_RETRY
)
3024 /* yet another attempt */
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
;
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 */
3042 switch (check_hardlinks (src_vpath
, &src_stat
, dst_vpath
, &ctx
->ignore_all
))
3045 /* We have made a hardlink - no more processing is necessary */
3048 case HARDLINK_ABORT
:
3049 return_status
= FILE_ABORT
;
3057 if (!S_ISDIR (src_stat
.st_mode
))
3059 if (ctx
->ignore_all
)
3060 return_status
= FILE_IGNORE_ALL
;
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
;
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
;
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
);
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
;
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
;
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
;
3122 /* Dive into subdir if exists */
3123 if (toplevel
&& ctx
->dive_into_subdirs
)
3128 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
3129 vfs_path_free (tmp
, TRUE
);
3136 d
= vfs_path_as_str (dst_vpath
);
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
;
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
)
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
;
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
)
3180 /* open the source dir for reading */
3181 reading
= mc_opendir (src_vpath
);
3182 if (reading
== NULL
)
3185 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
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
))
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
))
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
);
3219 dest_file
= mc_build_filename (d
, x_basename (path
), (char *) NULL
);
3220 return_status
= copy_file_file (ctx
, path
, dest_file
);
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
);
3239 else if (S_ISDIR (dst_stat
.st_mode
))
3240 return_status
= erase_dir_iff_empty (ctx
, tmp_vpath
);
3242 return_status
= erase_file (ctx
, tmp_vpath
);
3244 vfs_path_free (tmp_vpath
, TRUE
);
3246 mc_closedir (reading
);
3250 mc_timesbuf_t times
;
3252 mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
);
3255 mc_fsetflags (dst_vpath
, attrs
);
3257 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
3258 mc_utime (dst_vpath
, ×
);
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
);
3269 free_link (parent_dirs
->data
);
3270 g_slist_free_1 (parent_dirs
);
3272 vfs_path_free (src_vpath
, TRUE
);
3273 vfs_path_free (dst_vpath
, TRUE
);
3274 return return_status
;
3279 /* --------------------------------------------------------------------------------------------- */
3280 /* {{{ Move routines */
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
);
3290 /* --------------------------------------------------------------------------------------------- */
3291 /* {{{ Erase routines */
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
)
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)
3312 FileProgressStatus error
;
3314 error
= query_recursive (ctx
, vfs_path_as_str (vpath
));
3315 if (error
== FILE_CONT
)
3316 error
= recursive_erase (ctx
, vpath
);
3320 return try_erase_dir (ctx
, vpath
);
3325 /* --------------------------------------------------------------------------------------------- */
3326 /* {{{ Panel operate routines */
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
);
3336 const char *b1_name
= N_("&Abort");
3337 const char *b2_name
= N_("&Skip");
3338 int b_width
, ui_width
;
3341 b1_name
= _(b1_name
);
3342 b2_name
= _(b2_name
);
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
);
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
);
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
);
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
))
3410 /* --------------------------------------------------------------------------------------------- */
3413 dirsize_status_deinit_cb (status_msg_t
*sm
)
3417 /* schedule to update passive panel */
3418 if (get_other_type () == view_listing
)
3419 other_panel
->dirty
= TRUE
;
3422 /* --------------------------------------------------------------------------------------------- */
3426 * Computes the number of bytes used by the files in a directory
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 /* --------------------------------------------------------------------------------------------- */
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.
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
;
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
;
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
;
3475 for (i
= G_N_ELEMENTS (op_names
); i
-- != 0;)
3476 op_names
[i
] = Q_ (op_names
[i
]);
3480 linklist
= free_linklist (linklist
);
3481 dest_dirs
= free_linklist (dest_dirs
);
3487 source
= check_single_entry (panel
, force_single
, &src_stat
);
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
);
3505 dest_vpath
= vfs_path_from_str (dest
);
3507 else if (confirm_delete
&& !do_confirm_erase (panel
, source
, &src_stat
))
3513 ctx
->total_transfer_start
= g_get_monotonic_time ();
3515 #ifdef ENABLE_BACKGROUND
3516 /* Did the user select to do a background operation? */
3521 v
= do_background (ctx
,
3522 g_strconcat (op_names
[operation
], ": ",
3523 vfs_path_as_str (panel
->cwd_vpath
), (char *) NULL
));
3525 message (D_ERROR
, MSG_ERROR
, _("Sorry, I could not put the job in background"));
3527 /* If we are the parent */
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
);
3535 /* file_op_context_destroy (ctx); */
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
;
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
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 */
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
))
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)
3590 value
= operate_single_file (panel
, ctx
, source
, &src_stat
, dest
, dialog_type
);
3591 if ((value
== FILE_CONT
) && !force_single
)
3592 unmark_files (panel
);
3598 /* Check destination for copy or move operation */
3599 while (operation
!= OP_DELETE
)
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
))
3610 || file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"),
3611 dest
) != FILE_RETRY
)
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. */
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
)
3638 if (value
== FILE_CONT
)
3639 do_file_mark (panel
, i
, 0);
3642 } /* Loop for every file */
3643 } /* Many entries */
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
);
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);
3675 my_exit (EXIT_SUCCESS
);
3677 #endif /* ENABLE_BACKGROUND */
3680 file_op_context_destroy (ctx
);
3682 update_panels (UP_OPTIMIZE
, UP_KEEPSEL
);
3690 /* --------------------------------------------------------------------------------------------- */
3691 /* {{{ Query/status report routines */
3692 /** Report error with one file */
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: