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 /* --------------------------------------------------------------------------------------------- */
815 static FileProgressStatus
816 progress_update_one (file_op_context_t
*ctx
, off_t add
)
819 static gint64 tv_start
= -1;
821 ctx
->total_progress_count
++;
822 ctx
->progress_bytes
+= (uintmax_t) add
;
824 tv_current
= g_get_monotonic_time ();
827 tv_start
= tv_current
;
829 if (tv_current
- tv_start
> FILEOP_UPDATE_INTERVAL_US
)
831 if (verbose
&& ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
833 file_progress_show_count (ctx
, ctx
->total_progress_count
, ctx
->total_count
);
834 file_progress_show_total (ctx
, ctx
->progress_bytes
, TRUE
);
837 tv_start
= tv_current
;
840 return file_progress_check_buttons (ctx
);
843 /* --------------------------------------------------------------------------------------------- */
845 static FileProgressStatus
846 real_warn_same_file (enum OperationMode mode
, const char *fmt
, const char *a
, const char *b
)
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 copy_file_file_display_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
) / G_USEC_PER_SEC
;
1145 ctx
->eta_secs
= 0.0;
1147 ctx
->eta_secs
= ((dt
/ (double) file_part
) * file_size
) - dt
;
1149 /* Compute BPS rate */
1150 ctx
->bps_time
= MAX (1, dt
);
1151 ctx
->bps
= file_part
/ ctx
->bps_time
;
1153 /* Compute total ETA and BPS */
1154 if (ctx
->total_bytes
!= 0)
1158 total_secs
= (tv_current
- ctx
->total_transfer_start
) / G_USEC_PER_SEC
;
1159 total_secs
= MAX (1, total_secs
);
1161 ctx
->total_bps
= ctx
->total_progress_bytes
/ total_secs
;
1162 const uintmax_t remain_bytes
= ctx
->total_bytes
- ctx
->total_progress_bytes
;
1163 ctx
->total_eta_secs
= ctx
->total_bps
!= 0 ? remain_bytes
/ ctx
->total_bps
: 0;
1167 /* --------------------------------------------------------------------------------------------- */
1170 try_remove_file (file_op_context_t
*ctx
, const vfs_path_t
*vpath
, FileProgressStatus
*status
)
1172 while (mc_unlink (vpath
) != 0 && !ctx
->ignore_all
)
1174 *status
= file_error (TRUE
, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath
));
1175 if (*status
== FILE_RETRY
)
1177 if (*status
== FILE_IGNORE_ALL
)
1178 ctx
->ignore_all
= TRUE
;
1185 /* --------------------------------------------------------------------------------------------- */
1187 /* {{{ Move routines */
1190 * Move single file or one of many files from one location to another.
1192 * @panel pointer to panel in case of single file, NULL otherwise
1193 * @ctx file operation context object
1194 * @s source file name
1195 * @d destination file name
1197 * @return operation result
1199 static FileProgressStatus
1200 move_file_file (const WPanel
*panel
, file_op_context_t
*ctx
, const char *s
, const char *d
)
1202 struct stat src_stat
, dst_stat
;
1203 FileProgressStatus return_status
= FILE_CONT
;
1204 gboolean copy_done
= FALSE
;
1205 gboolean old_ask_overwrite
;
1206 vfs_path_t
*src_vpath
, *dst_vpath
;
1208 src_vpath
= vfs_path_from_str (s
);
1209 dst_vpath
= vfs_path_from_str (d
);
1211 file_progress_show_source (ctx
, src_vpath
);
1212 file_progress_show_target (ctx
, dst_vpath
);
1214 /* FIXME: do we really need to check buttons in case of single file? */
1215 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
1217 return_status
= FILE_ABORT
;
1223 while (mc_lstat (src_vpath
, &src_stat
) != 0)
1225 /* Source doesn't exist */
1226 if (ctx
->ignore_all
)
1227 return_status
= FILE_IGNORE_ALL
;
1230 return_status
= file_error (TRUE
, _("Cannot stat file \"%s\"\n%s"), s
);
1231 if (return_status
== FILE_IGNORE_ALL
)
1232 ctx
->ignore_all
= TRUE
;
1235 if (return_status
!= FILE_RETRY
)
1239 if (mc_lstat (dst_vpath
, &dst_stat
) == 0)
1241 if (check_same_file (s
, &src_stat
, d
, &dst_stat
, &return_status
))
1244 if (S_ISDIR (dst_stat
.st_mode
))
1246 message (D_ERROR
, MSG_ERROR
, _("Cannot overwrite directory \"%s\""), d
);
1248 return_status
= FILE_SKIP
;
1252 if (confirm_overwrite
)
1254 return_status
= query_replace (ctx
, s
, &src_stat
, d
, &dst_stat
);
1255 if (return_status
!= FILE_CONT
)
1258 /* Ok to overwrite */
1261 if (!ctx
->do_append
)
1263 if (S_ISLNK (src_stat
.st_mode
) && ctx
->stable_symlinks
)
1265 return_status
= make_symlink (ctx
, src_vpath
, dst_vpath
);
1266 if (return_status
== FILE_CONT
)
1270 mc_timesbuf_t times
;
1272 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
1273 mc_utime (dst_vpath
, ×
);
1275 goto retry_src_remove
;
1280 if (mc_rename (src_vpath
, dst_vpath
) == 0)
1282 /* FIXME: do we really need to update progress in case of single file? */
1283 return_status
= progress_update_one (ctx
, src_stat
.st_size
);
1288 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1289 one nfs to the same, but on the server it is on two different
1290 filesystems. Then nfs returns EIO instead of EXDEV.
1291 Hope it will not hurt if we always in case of error try to copy/delete. */
1293 errno
= EXDEV
; /* Hack to copy (append) the file and then delete it */
1297 if (ctx
->ignore_all
)
1298 return_status
= FILE_IGNORE_ALL
;
1301 return_status
= files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s
, d
);
1302 if (return_status
== FILE_IGNORE_ALL
)
1303 ctx
->ignore_all
= TRUE
;
1304 if (return_status
== FILE_RETRY
)
1312 /* Failed rename -> copy the file instead */
1315 /* In case of single file, calculate totals. In case of many files,
1316 totals are calculated already. */
1318 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1319 FILEGUI_DIALOG_ONE_ITEM
);
1320 if (return_status
!= FILE_CONT
)
1324 old_ask_overwrite
= ctx
->ask_overwrite
;
1325 ctx
->ask_overwrite
= FALSE
;
1326 return_status
= copy_file_file (ctx
, s
, d
);
1327 ctx
->ask_overwrite
= old_ask_overwrite
;
1328 if (return_status
!= FILE_CONT
)
1333 /* FIXME: there is no need to update progress and check buttons
1334 at the finish of single file operation. */
1337 file_progress_show_source (ctx
, NULL
);
1339 file_progress_show (ctx
, 0, 0, "", FALSE
);
1341 return_status
= file_progress_check_buttons (ctx
);
1342 if (return_status
!= FILE_CONT
)
1349 if (!try_remove_file (ctx
, src_vpath
, &return_status
) && panel
== NULL
)
1353 return_status
= progress_update_one (ctx
, src_stat
.st_size
);
1356 vfs_path_free (src_vpath
, TRUE
);
1357 vfs_path_free (dst_vpath
, TRUE
);
1359 return return_status
;
1364 /* --------------------------------------------------------------------------------------------- */
1365 /* {{{ Erase routines */
1366 /** Don't update progress status if progress_count==NULL */
1368 static FileProgressStatus
1369 erase_file (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
1372 FileProgressStatus return_status
;
1374 /* check buttons if deleting info was changed */
1375 if (file_progress_show_deleting (ctx
, vpath
, &ctx
->total_progress_count
))
1377 file_progress_show_count (ctx
, ctx
->total_progress_count
, ctx
->total_count
);
1378 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
1384 if (ctx
->total_progress_count
!= 0 && mc_lstat (vpath
, &buf
) != 0)
1386 /* ignore, most likely the mc_unlink fails, too */
1390 if (!try_remove_file (ctx
, vpath
, &return_status
) && return_status
== FILE_ABORT
)
1393 if (ctx
->total_progress_count
== 0)
1396 return file_progress_check_buttons (ctx
);
1399 /* --------------------------------------------------------------------------------------------- */
1401 static FileProgressStatus
1402 try_erase_dir (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
1405 FileProgressStatus return_status
= FILE_CONT
;
1407 dir
= vfs_path_as_str (vpath
);
1409 while (my_rmdir (dir
) != 0 && !ctx
->ignore_all
)
1411 return_status
= file_error (TRUE
, _("Cannot remove directory \"%s\"\n%s"), dir
);
1412 if (return_status
== FILE_IGNORE_ALL
)
1413 ctx
->ignore_all
= TRUE
;
1414 if (return_status
!= FILE_RETRY
)
1418 return return_status
;
1421 /* --------------------------------------------------------------------------------------------- */
1424 Recursive removal of files
1425 abort -> cancel stack
1426 ignore -> warn every level, gets default
1427 ignore_all -> remove as much as possible
1429 static FileProgressStatus
1430 recursive_erase (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
1432 struct vfs_dirent
*next
;
1434 FileProgressStatus return_status
= FILE_CONT
;
1436 reading
= mc_opendir (vpath
);
1437 if (reading
== NULL
)
1440 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
1442 vfs_path_t
*tmp_vpath
;
1445 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
1448 tmp_vpath
= vfs_path_append_new (vpath
, next
->d_name
, (char *) NULL
);
1449 if (mc_lstat (tmp_vpath
, &buf
) != 0)
1451 mc_closedir (reading
);
1452 vfs_path_free (tmp_vpath
, TRUE
);
1455 if (S_ISDIR (buf
.st_mode
))
1456 return_status
= recursive_erase (ctx
, tmp_vpath
);
1458 return_status
= erase_file (ctx
, tmp_vpath
);
1459 vfs_path_free (tmp_vpath
, TRUE
);
1461 mc_closedir (reading
);
1463 if (return_status
== FILE_ABORT
)
1466 file_progress_show_deleting (ctx
, vpath
, NULL
);
1467 file_progress_show_count (ctx
, ctx
->total_progress_count
, ctx
->total_count
);
1468 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
1473 return try_erase_dir (ctx
, vpath
);
1476 /* --------------------------------------------------------------------------------------------- */
1478 * Check if directory is empty or not.
1480 * @param vpath directory handler
1482 * @returns -1 on error,
1483 * 1 if there are no entries besides "." and ".." in the directory path points to,
1486 * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
1487 * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
1488 * in SHELL) don't return "." and ".." entries.
1491 check_dir_is_empty (const vfs_path_t
*vpath
)
1494 struct vfs_dirent
*d
;
1497 dir
= mc_opendir (vpath
);
1501 for (d
= mc_readdir (dir
); d
!= NULL
; d
= mc_readdir (dir
))
1502 if (!DIR_IS_DOT (d
->d_name
) && !DIR_IS_DOTDOT (d
->d_name
))
1512 /* --------------------------------------------------------------------------------------------- */
1514 static FileProgressStatus
1515 erase_dir_iff_empty (file_op_context_t
*ctx
, const vfs_path_t
*vpath
, size_t count
)
1517 file_progress_show_deleting (ctx
, vpath
, NULL
);
1518 file_progress_show_count (ctx
, count
, ctx
->total_count
);
1519 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
1524 if (check_dir_is_empty (vpath
) != 1)
1527 /* not empty or error */
1528 return try_erase_dir (ctx
, vpath
);
1531 /* --------------------------------------------------------------------------------------------- */
1534 erase_dir_after_copy (file_op_context_t
*ctx
, const vfs_path_t
*vpath
, FileProgressStatus
*status
)
1536 if (ctx
->erase_at_end
&& erase_list
!= NULL
)
1538 /* Reset progress count before delete to avoid counting files twice */
1539 ctx
->total_progress_count
= ctx
->prev_total_progress_count
;
1541 while (!g_queue_is_empty (erase_list
) && *status
!= FILE_ABORT
)
1545 lp
= (link_t
*) g_queue_pop_head (erase_list
);
1547 if (S_ISDIR (lp
->st_mode
))
1548 *status
= erase_dir_iff_empty (ctx
, lp
->src_vpath
, ctx
->total_progress_count
);
1550 *status
= erase_file (ctx
, lp
->src_vpath
);
1555 /* Save progress counter before move next directory */
1556 ctx
->prev_total_progress_count
= ctx
->total_progress_count
;
1559 erase_dir_iff_empty (ctx
, vpath
, ctx
->total_progress_count
);
1564 /* --------------------------------------------------------------------------------------------- */
1567 * Move single directory or one of many directories from one location to another.
1569 * @panel pointer to panel in case of single directory, NULL otherwise
1570 * @ctx file operation context object
1571 * @s source directory name
1572 * @d destination directory name
1574 * @return operation result
1576 static FileProgressStatus
1577 do_move_dir_dir (const WPanel
*panel
, file_op_context_t
*ctx
, const char *s
, const char *d
)
1579 struct stat src_stat
, dst_stat
;
1580 FileProgressStatus return_status
= FILE_CONT
;
1581 gboolean move_over
= FALSE
;
1583 vfs_path_t
*src_vpath
, *dst_vpath
;
1585 src_vpath
= vfs_path_from_str (s
);
1586 dst_vpath
= vfs_path_from_str (d
);
1588 file_progress_show_source (ctx
, src_vpath
);
1589 file_progress_show_target (ctx
, dst_vpath
);
1591 /* FIXME: do we really need to check buttons in case of single directory? */
1592 if (panel
!= NULL
&& file_progress_check_buttons (ctx
) == FILE_ABORT
)
1594 return_status
= FILE_ABORT
;
1600 mc_stat (src_vpath
, &src_stat
);
1602 dstat_ok
= (mc_stat (dst_vpath
, &dst_stat
) == 0);
1604 if (dstat_ok
&& check_same_file (s
, &src_stat
, d
, &dst_stat
, &return_status
))
1608 ; /* destination doesn't exist */
1609 else if (!ctx
->dive_into_subdirs
)
1616 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
1617 vfs_path_free (tmp
, TRUE
);
1620 d
= vfs_path_as_str (dst_vpath
);
1622 /* Check if the user inputted an existing dir */
1624 if (mc_stat (dst_vpath
, &dst_stat
) == 0)
1630 /* In case of single directory, calculate totals. In case of many directories,
1631 totals are calculated already. */
1633 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1634 FILEGUI_DIALOG_MULTI_ITEM
);
1635 if (return_status
!= FILE_CONT
)
1639 return_status
= copy_dir_dir (ctx
, s
, d
, FALSE
, TRUE
, TRUE
, NULL
);
1641 if (return_status
!= FILE_CONT
)
1645 else if (ctx
->ignore_all
)
1646 return_status
= FILE_IGNORE_ALL
;
1649 if (S_ISDIR (dst_stat
.st_mode
))
1650 return_status
= file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), d
);
1652 return_status
= file_error (TRUE
, _("Cannot overwrite file \"%s\"\n%s"), d
);
1653 if (return_status
== FILE_IGNORE_ALL
)
1654 ctx
->ignore_all
= TRUE
;
1655 if (return_status
== FILE_RETRY
)
1656 goto retry_dst_stat
;
1663 if (mc_rename (src_vpath
, dst_vpath
) == 0)
1665 return_status
= FILE_CONT
;
1671 if (!ctx
->ignore_all
)
1673 return_status
= files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s
, d
);
1674 if (return_status
== FILE_IGNORE_ALL
)
1675 ctx
->ignore_all
= TRUE
;
1676 if (return_status
== FILE_RETRY
)
1682 /* Failed because of filesystem boundary -> copy dir instead */
1685 /* In case of single directory, calculate totals. In case of many directories,
1686 totals are calculated already. */
1688 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1689 FILEGUI_DIALOG_MULTI_ITEM
);
1690 if (return_status
!= FILE_CONT
)
1694 return_status
= copy_dir_dir (ctx
, s
, d
, FALSE
, FALSE
, TRUE
, NULL
);
1696 if (return_status
!= FILE_CONT
)
1700 /* FIXME: there is no need to update progress and check buttons
1701 at the finish of single directory operation. */
1704 file_progress_show_source (ctx
, NULL
);
1705 file_progress_show_target (ctx
, NULL
);
1707 file_progress_show (ctx
, 0, 0, "", FALSE
);
1709 return_status
= file_progress_check_buttons (ctx
);
1710 if (return_status
!= FILE_CONT
)
1716 erase_dir_after_copy (ctx
, src_vpath
, &return_status
);
1719 erase_list
= free_erase_list (erase_list
);
1721 vfs_path_free (src_vpath
, TRUE
);
1722 vfs_path_free (dst_vpath
, TRUE
);
1723 return return_status
;
1726 /* --------------------------------------------------------------------------------------------- */
1728 /* {{{ Panel operate routines */
1731 * Return currently selected entry name or the name of the first marked
1732 * entry if there is one.
1736 panel_get_file (const WPanel
*panel
)
1738 const file_entry_t
*fe
;
1740 if (get_current_type () == view_tree
)
1743 const vfs_path_t
*selected_name
;
1745 tree
= (WTree
*) get_panel_widget (get_current_index ());
1746 selected_name
= tree_selected_name (tree
);
1747 return vfs_path_as_str (selected_name
);
1750 if (panel
->marked
!= 0)
1754 for (i
= 0; i
< panel
->dir
.len
; i
++)
1755 if (panel
->dir
.list
[i
].f
.marked
!= 0)
1756 return panel
->dir
.list
[i
].fname
->str
;
1759 fe
= panel_current_entry (panel
);
1761 return (fe
== NULL
? NULL
: fe
->fname
->str
);
1764 /* --------------------------------------------------------------------------------------------- */
1767 check_single_entry (const WPanel
*panel
, gboolean force_single
, struct stat
*src_stat
)
1774 const file_entry_t
*fe
;
1776 fe
= panel_current_entry (panel
);
1777 source
= fe
== NULL
? NULL
: fe
->fname
->str
;
1780 source
= panel_get_file (panel
);
1785 ok
= !DIR_IS_DOTDOT (source
);
1788 message (D_ERROR
, MSG_ERROR
, _("Cannot operate on \"..\"!"));
1791 vfs_path_t
*source_vpath
;
1793 source_vpath
= vfs_path_from_str (source
);
1795 /* Update stat to get actual info */
1796 ok
= mc_lstat (source_vpath
, src_stat
) == 0;
1799 message (D_ERROR
, MSG_ERROR
, _("Cannot stat \"%s\"\n%s"),
1800 path_trunc (source
, 30), unix_error_string (errno
));
1802 /* Directory was changed outside MC. Reload it forced */
1803 if (!panel
->is_panelized
)
1805 panel_update_flags_t flags
= UP_RELOAD
;
1807 /* don't update panelized panel */
1808 if (get_other_type () == view_listing
&& other_panel
->is_panelized
)
1809 flags
|= UP_ONLY_CURRENT
;
1811 update_panels (flags
, UP_KEEPSEL
);
1815 vfs_path_free (source_vpath
, TRUE
);
1818 return ok
? source
: NULL
;
1821 /* --------------------------------------------------------------------------------------------- */
1823 * Generate user prompt for panel operation.
1824 * src_stat must be not NULL for single source, and NULL for multiple sources
1828 panel_operate_generate_prompt (const WPanel
*panel
, FileOperation operation
,
1829 const struct stat
*src_stat
)
1832 char *format_string
;
1835 static gboolean i18n_flag
= FALSE
;
1840 for (i
= G_N_ELEMENTS (op_names1
); i
-- != 0;)
1841 op_names1
[i
] = Q_ (op_names1
[i
]);
1844 for (i
= G_N_ELEMENTS (prompt_parts
); i
-- != 0;)
1845 prompt_parts
[i
] = _(prompt_parts
[i
]);
1847 one_format
= _(one_format
);
1848 many_format
= _(many_format
);
1849 #endif /* ENABLE_NLS */
1853 /* Possible prompts:
1855 * "Copy file \"%s\" with source mask:"
1856 * "Copy %d files with source mask:"
1857 * "Copy directory \"%s\" with source mask:"
1858 * "Copy %d directories with source mask:"
1859 * "Copy %d files/directories with source mask:"
1861 * "Move file \"%s\" with source mask:"
1862 * "Move %d files with source mask:"
1863 * "Move directory \"%s\" with source mask:"
1864 * "Move %d directories with source mask:"
1865 * "Move %d files/directories with source mask:"
1867 * "Delete file \"%s\"?"
1868 * "Delete %d files?"
1869 * "Delete directory \"%s\"?"
1870 * "Delete %d directories?"
1871 * "Delete %d files/directories?"
1874 cp
= (src_stat
!= NULL
? one_format
: many_format
);
1876 /* 1. Substitute %o */
1877 format_string
= str_replace_all (cp
, "%o", op_names1
[(int) operation
]);
1879 /* 2. Substitute %n */
1880 cp
= operation
== OP_DELETE
? "\n" : " ";
1882 format_string
= str_replace_all (sp
, "%n", cp
);
1885 /* 3. Substitute %f */
1886 if (src_stat
!= NULL
)
1887 cp
= S_ISDIR (src_stat
->st_mode
) ? prompt_parts
[2] : prompt_parts
[0];
1888 else if (panel
->marked
== panel
->dirs_marked
)
1889 cp
= prompt_parts
[3];
1891 cp
= panel
->dirs_marked
!= 0 ? prompt_parts
[4] : prompt_parts
[1];
1894 format_string
= str_replace_all (sp
, "%f", cp
);
1897 /* 4. Substitute %m */
1898 cp
= operation
== OP_DELETE
? "?" : prompt_parts
[5];
1900 format_string
= str_replace_all (sp
, "%m", cp
);
1903 return format_string
;
1906 /* --------------------------------------------------------------------------------------------- */
1909 do_confirm_copy_move (const WPanel
*panel
, gboolean force_single
, const char *source
,
1910 struct stat
*src_stat
, file_op_context_t
*ctx
, gboolean
*do_bg
)
1912 const char *tmp_dest_dir
;
1917 /* Forced single operations default to the original name */
1919 tmp_dest_dir
= source
;
1920 else if (get_other_type () == view_listing
)
1921 tmp_dest_dir
= vfs_path_as_str (other_panel
->cwd_vpath
);
1923 tmp_dest_dir
= vfs_path_as_str (panel
->cwd_vpath
);
1926 * Add trailing backslash only when do non-local ops.
1927 * It saves user from occasional file renames (when destination
1930 if (!force_single
&& tmp_dest_dir
!= NULL
&& tmp_dest_dir
[0] != '\0'
1931 && !IS_PATH_SEP (tmp_dest_dir
[strlen (tmp_dest_dir
) - 1]))
1933 /* add trailing separator */
1934 dest_dir
= g_strconcat (tmp_dest_dir
, PATH_SEP_STR
, (char *) NULL
);
1939 dest_dir
= g_strdup (tmp_dest_dir
);
1942 if (dest_dir
== NULL
)
1948 /* Generate confirmation prompt */
1949 format
= panel_operate_generate_prompt (panel
, ctx
->operation
, src_stat
);
1951 ret
= file_mask_dialog (ctx
, source
!= NULL
, format
,
1952 source
!= NULL
? source
: (const void *) &panel
->marked
, dest_dir
,
1961 /* --------------------------------------------------------------------------------------------- */
1964 do_confirm_erase (const WPanel
*panel
, const char *source
, struct stat
*src_stat
)
1968 char fmd_buf
[BUF_MEDIUM
];
1973 /* Generate confirmation prompt */
1974 format
= panel_operate_generate_prompt (panel
, OP_DELETE
, src_stat
);
1977 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, panel
->marked
);
1980 const int fmd_xlen
= 64;
1982 i
= fmd_xlen
- str_term_width1 (format
) - 4;
1983 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, str_trunc (source
, i
));
1991 i
= query_dialog (op_names
[OP_DELETE
], fmd_buf
, D_ERROR
, 2, _("&Yes"), _("&No"));
1996 /* --------------------------------------------------------------------------------------------- */
1998 static FileProgressStatus
1999 operate_single_file (const WPanel
*panel
, file_op_context_t
*ctx
, const char *src
,
2000 struct stat
*src_stat
, const char *dest
, filegui_dialog_type_t dialog_type
)
2002 FileProgressStatus value
;
2003 vfs_path_t
*src_vpath
;
2006 if (g_path_is_absolute (src
))
2007 src_vpath
= vfs_path_from_str (src
);
2009 src_vpath
= vfs_path_append_new (panel
->cwd_vpath
, src
, (char *) NULL
);
2011 is_file
= !S_ISDIR (src_stat
->st_mode
);
2012 /* Is link to directory? */
2017 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
2018 is_file
= !(is_link
&& ctx
->follow_links
);
2021 if (ctx
->operation
== OP_DELETE
)
2023 value
= panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
, dialog_type
);
2024 if (value
== FILE_CONT
)
2027 value
= erase_file (ctx
, src_vpath
);
2029 value
= erase_dir (ctx
, src_vpath
);
2036 src
= vfs_path_as_str (src_vpath
);
2038 temp
= build_dest (ctx
, src
, dest
, &value
);
2043 switch (ctx
->operation
)
2046 /* we use file_mask_op_follow_links only with OP_COPY */
2047 ctx
->stat_func (src_vpath
, src_stat
);
2050 panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
,
2052 if (value
== FILE_CONT
)
2054 is_file
= !S_ISDIR (src_stat
->st_mode
);
2055 /* Is link to directory? */
2060 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
2061 is_file
= !(is_link
&& ctx
->follow_links
);
2065 value
= copy_file_file (ctx
, src
, dest
);
2067 value
= copy_dir_dir (ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2072 #ifdef ENABLE_BACKGROUND
2073 if (!mc_global
.we_are_background
)
2075 /* create UI to show confirmation dialog */
2076 file_progress_ui_create (ctx
, TRUE
, FILEGUI_DIALOG_ONE_ITEM
);
2079 value
= move_file_file (panel
, ctx
, src
, dest
);
2081 value
= do_move_dir_dir (panel
, ctx
, src
, dest
);
2085 /* Unknown file operation */
2093 vfs_path_free (src_vpath
, TRUE
);
2098 /* --------------------------------------------------------------------------------------------- */
2100 static FileProgressStatus
2101 operate_one_file (const WPanel
*panel
, file_op_context_t
*ctx
, const char *src
,
2102 struct stat
*src_stat
, const char *dest
)
2104 FileProgressStatus value
= FILE_CONT
;
2105 vfs_path_t
*src_vpath
;
2108 if (g_path_is_absolute (src
))
2109 src_vpath
= vfs_path_from_str (src
);
2111 src_vpath
= vfs_path_append_new (panel
->cwd_vpath
, src
, (char *) NULL
);
2113 is_file
= !S_ISDIR (src_stat
->st_mode
);
2115 if (ctx
->operation
== OP_DELETE
)
2118 value
= erase_file (ctx
, src_vpath
);
2120 value
= erase_dir (ctx
, src_vpath
);
2126 src
= vfs_path_as_str (src_vpath
);
2128 temp
= build_dest (ctx
, src
, dest
, &value
);
2133 switch (ctx
->operation
)
2136 /* we use file_mask_op_follow_links only with OP_COPY */
2137 ctx
->stat_func (src_vpath
, src_stat
);
2138 is_file
= !S_ISDIR (src_stat
->st_mode
);
2141 value
= copy_file_file (ctx
, src
, dest
);
2143 value
= copy_dir_dir (ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2144 dest_dirs
= free_linklist (dest_dirs
);
2149 value
= move_file_file (NULL
, ctx
, src
, dest
);
2151 value
= do_move_dir_dir (NULL
, ctx
, src
, dest
);
2155 /* Unknown file operation */
2163 vfs_path_free (src_vpath
, TRUE
);
2168 /* --------------------------------------------------------------------------------------------- */
2170 #ifdef ENABLE_BACKGROUND
2172 end_bg_process (file_op_context_t
*ctx
, enum OperationMode mode
)
2179 unregister_task_with_pid (pid
);
2180 /* file_op_context_destroy(ctx); */
2186 /* --------------------------------------------------------------------------------------------- */
2189 * On Solaris, ENOTSUP != EOPNOTSUPP. Some FS also return ENOSYS or EINVAL as "not implemented".
2190 * On some Linux kernels (tested on 4.9, 5.4) there is ENOTTY on tmpfs.
2192 static inline gboolean
2193 attrs_ignore_error (const int e
)
2195 return (e
== ENOTSUP
|| e
== EOPNOTSUPP
|| e
== ENOSYS
|| e
== EINVAL
|| e
== ENOTTY
2196 || e
== ELOOP
|| e
== ENXIO
);
2199 /* --------------------------------------------------------------------------------------------- */
2200 /*** public functions ****************************************************************************/
2201 /* --------------------------------------------------------------------------------------------- */
2203 /* Is file symlink to directory or not.
2205 * @param path file or directory
2206 * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2207 * @param stale_link TRUE if file is stale link to directory
2209 * @return TRUE if file symlink to directory, ELSE otherwise.
2212 file_is_symlink_to_dir (const vfs_path_t
*vpath
, struct stat
*st
, gboolean
*stale_link
)
2215 gboolean stale
= FALSE
;
2216 gboolean res
= FALSE
;
2222 if (mc_lstat (vpath
, st
) != 0)
2226 if (S_ISLNK (st
->st_mode
))
2230 stale
= (mc_stat (vpath
, &st3
) != 0);
2233 res
= (S_ISDIR (st3
.st_mode
) != 0);
2237 if (stale_link
!= NULL
)
2238 *stale_link
= stale
;
2243 /* --------------------------------------------------------------------------------------------- */
2246 copy_file_file (file_op_context_t
*ctx
, const char *src_path
, const char *dst_path
)
2248 uid_t src_uid
= (uid_t
) (-1);
2249 gid_t src_gid
= (gid_t
) (-1);
2251 int src_desc
, dest_desc
= -1;
2252 mode_t src_mode
= 0; /* The mode of the source file */
2253 struct stat src_stat
, dst_stat
;
2254 mc_timesbuf_t times
;
2255 unsigned long attrs
= 0;
2256 gboolean attrs_ok
= ctx
->preserve
;
2257 gboolean dst_exists
= FALSE
, appending
= FALSE
;
2258 off_t file_size
= -1;
2259 FileProgressStatus return_status
, temp_status
;
2260 dest_status_t dst_status
= DEST_NONE
;
2262 vfs_path_t
*src_vpath
= NULL
, *dst_vpath
= NULL
;
2265 /* Keep the non-default value applied in chain of calls:
2266 move_file_file() -> file_progress_real_query_replace()
2267 move_file_file() -> copy_file_file() */
2268 if (ctx
->do_reget
< 0)
2271 return_status
= FILE_RETRY
;
2273 dst_vpath
= vfs_path_from_str (dst_path
);
2274 src_vpath
= vfs_path_from_str (src_path
);
2276 file_progress_show_source (ctx
, src_vpath
);
2277 file_progress_show_target (ctx
, dst_vpath
);
2279 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
2281 return_status
= FILE_ABORT
;
2287 while (mc_stat (dst_vpath
, &dst_stat
) == 0)
2289 if (S_ISDIR (dst_stat
.st_mode
))
2291 if (ctx
->ignore_all
)
2292 return_status
= FILE_IGNORE_ALL
;
2296 file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), dst_path
);
2297 if (return_status
== FILE_IGNORE_ALL
)
2298 ctx
->ignore_all
= TRUE
;
2299 if (return_status
== FILE_RETRY
)
2309 while ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2311 if (ctx
->ignore_all
)
2312 return_status
= FILE_IGNORE_ALL
;
2315 return_status
= file_error (TRUE
, _("Cannot stat source file \"%s\"\n%s"), src_path
);
2316 if (return_status
== FILE_IGNORE_ALL
)
2317 ctx
->ignore_all
= TRUE
;
2320 if (return_status
!= FILE_RETRY
)
2324 while (attrs_ok
&& mc_fgetflags (src_vpath
, &attrs
) != 0)
2328 /* don't show an error message if attributes aren't supported in this FS */
2329 if (attrs_ignore_error (errno
))
2330 return_status
= FILE_CONT
;
2331 else if (ctx
->ignore_all
)
2332 return_status
= FILE_IGNORE_ALL
;
2336 file_error (TRUE
, _("Cannot get ext2 attributes of source file \"%s\"\n%s"),
2338 if (return_status
== FILE_IGNORE_ALL
)
2339 ctx
->ignore_all
= TRUE
;
2340 if (return_status
== FILE_ABORT
)
2344 if (return_status
!= FILE_RETRY
)
2347 /* yet another attempt */
2353 /* Destination already exists */
2354 if (check_same_file (src_path
, &src_stat
, dst_path
, &dst_stat
, &return_status
))
2357 /* Should we replace destination? */
2358 if (ctx
->ask_overwrite
)
2361 return_status
= query_replace (ctx
, src_path
, &src_stat
, dst_path
, &dst_stat
);
2362 if (return_status
!= FILE_CONT
)
2367 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
2369 if (!ctx
->do_append
)
2371 /* Check the hardlinks */
2372 if (!ctx
->follow_links
)
2374 switch (check_hardlinks (src_vpath
, &src_stat
, dst_vpath
, &ctx
->ignore_all
))
2377 /* We have made a hardlink - no more processing is necessary */
2378 return_status
= FILE_CONT
;
2381 case HARDLINK_ABORT
:
2382 return_status
= FILE_ABORT
;
2390 if (S_ISLNK (src_stat
.st_mode
))
2392 return_status
= make_symlink (ctx
, src_vpath
, dst_vpath
);
2393 if (return_status
== FILE_CONT
&& ctx
->preserve
)
2395 mc_utime (dst_vpath
, ×
);
2397 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
2401 /* don't show an error message if attributes aren't supported in this FS */
2402 if (attrs_ignore_error (errno
))
2403 return_status
= FILE_CONT
;
2404 else if (return_status
== FILE_IGNORE_ALL
)
2405 ctx
->ignore_all
= TRUE
;
2409 _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2412 if (return_status
!= FILE_RETRY
)
2415 /* yet another attempt */
2422 if (S_ISCHR (src_stat
.st_mode
) || S_ISBLK (src_stat
.st_mode
) || S_ISFIFO (src_stat
.st_mode
)
2423 || S_ISNAM (src_stat
.st_mode
) || S_ISSOCK (src_stat
.st_mode
))
2427 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2428 rdev
= src_stat
.st_rdev
;
2431 while (mc_mknod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
, rdev
) < 0
2432 && !ctx
->ignore_all
)
2435 file_error (TRUE
, _("Cannot create special file \"%s\"\n%s"), dst_path
);
2436 if (return_status
== FILE_RETRY
)
2438 if (return_status
== FILE_IGNORE_ALL
)
2439 ctx
->ignore_all
= TRUE
;
2444 while (ctx
->preserve_uidgid
2445 && mc_chown (dst_vpath
, src_stat
.st_uid
, src_stat
.st_gid
) != 0
2446 && !ctx
->ignore_all
)
2448 temp_status
= file_error (TRUE
, _("Cannot chown target file \"%s\"\n%s"), dst_path
);
2449 if (temp_status
== FILE_IGNORE
)
2451 if (temp_status
== FILE_IGNORE_ALL
)
2452 ctx
->ignore_all
= TRUE
;
2453 if (temp_status
!= FILE_RETRY
)
2455 return_status
= temp_status
;
2460 while (ctx
->preserve
&& mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
) != 0
2461 && !ctx
->ignore_all
)
2463 temp_status
= file_error (TRUE
, _("Cannot chmod target file \"%s\"\n%s"), dst_path
);
2464 if (temp_status
== FILE_IGNORE
)
2466 if (temp_status
== FILE_IGNORE_ALL
)
2467 ctx
->ignore_all
= TRUE
;
2468 if (temp_status
!= FILE_RETRY
)
2470 return_status
= temp_status
;
2475 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
2479 /* don't show an error message if attributes aren't supported in this FS */
2480 if (attrs_ignore_error (errno
))
2484 file_error (TRUE
, _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2486 if (temp_status
== FILE_IGNORE
)
2488 if (temp_status
== FILE_IGNORE_ALL
)
2489 ctx
->ignore_all
= TRUE
;
2490 if (temp_status
!= FILE_RETRY
)
2492 return_status
= temp_status
;
2496 /* yet another attempt */
2500 return_status
= FILE_CONT
;
2501 mc_utime (dst_vpath
, ×
);
2506 ctx
->transfer_start
= g_get_monotonic_time ();
2508 while ((src_desc
= mc_open (src_vpath
, O_RDONLY
| O_LINEAR
)) < 0 && !ctx
->ignore_all
)
2510 return_status
= file_error (TRUE
, _("Cannot open source file \"%s\"\n%s"), src_path
);
2511 if (return_status
== FILE_RETRY
)
2513 if (return_status
== FILE_IGNORE_ALL
)
2514 ctx
->ignore_all
= TRUE
;
2515 if (return_status
== FILE_IGNORE
)
2517 ctx
->do_append
= FALSE
;
2521 if (ctx
->do_reget
!= 0 && mc_lseek (src_desc
, ctx
->do_reget
, SEEK_SET
) != ctx
->do_reget
)
2523 message (D_ERROR
, _("Warning"), _("Reget failed, about to overwrite file"));
2525 ctx
->do_append
= FALSE
;
2528 while (mc_fstat (src_desc
, &src_stat
) != 0)
2530 if (ctx
->ignore_all
)
2531 return_status
= FILE_IGNORE_ALL
;
2534 return_status
= file_error (TRUE
, _("Cannot fstat source file \"%s\"\n%s"), src_path
);
2535 if (return_status
== FILE_RETRY
)
2537 if (return_status
== FILE_IGNORE_ALL
)
2538 ctx
->ignore_all
= TRUE
;
2539 ctx
->do_append
= FALSE
;
2544 src_mode
= src_stat
.st_mode
;
2545 src_uid
= src_stat
.st_uid
;
2546 src_gid
= src_stat
.st_gid
;
2547 file_size
= src_stat
.st_size
;
2549 open_flags
= O_WRONLY
;
2551 open_flags
|= O_CREAT
| O_EXCL
;
2552 else if (ctx
->do_append
)
2553 open_flags
|= O_APPEND
;
2555 open_flags
|= O_CREAT
| O_TRUNC
;
2557 while ((dest_desc
= mc_open (dst_vpath
, open_flags
, src_mode
)) < 0)
2559 if (errno
!= EEXIST
)
2561 if (ctx
->ignore_all
)
2562 return_status
= FILE_IGNORE_ALL
;
2566 file_error (TRUE
, _("Cannot create target file \"%s\"\n%s"), dst_path
);
2567 if (return_status
== FILE_RETRY
)
2569 if (return_status
== FILE_IGNORE_ALL
)
2570 ctx
->ignore_all
= TRUE
;
2571 ctx
->do_append
= FALSE
;
2577 /* file opened, but not fully copied */
2578 dst_status
= DEST_SHORT_QUERY
;
2580 appending
= ctx
->do_append
;
2581 ctx
->do_append
= FALSE
;
2583 /* Try clone the file first. */
2584 if (vfs_clone_file (dest_desc
, src_desc
) == 0)
2586 dst_status
= DEST_FULL
;
2587 return_status
= FILE_CONT
;
2591 /* Find out the optimal buffer size. */
2592 while (mc_fstat (dest_desc
, &dst_stat
) != 0)
2594 if (ctx
->ignore_all
)
2595 return_status
= FILE_IGNORE_ALL
;
2598 return_status
= file_error (TRUE
, _("Cannot fstat target file \"%s\"\n%s"), dst_path
);
2599 if (return_status
== FILE_RETRY
)
2601 if (return_status
== FILE_IGNORE_ALL
)
2602 ctx
->ignore_all
= TRUE
;
2607 /* try preallocate space; if fail, try copy anyway */
2608 while (mc_global
.vfs
.preallocate_space
&&
2609 vfs_preallocate (dest_desc
, file_size
, appending
? dst_stat
.st_size
: 0) != 0)
2611 if (ctx
->ignore_all
)
2613 /* cannot allocate, start the file copying anyway */
2614 return_status
= FILE_CONT
;
2619 file_error (TRUE
, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path
);
2621 if (return_status
== FILE_IGNORE_ALL
)
2622 ctx
->ignore_all
= TRUE
;
2624 if (ctx
->ignore_all
|| return_status
== FILE_IGNORE
)
2626 /* skip the space allocation error, start file copying */
2627 return_status
= FILE_CONT
;
2631 if (return_status
== FILE_ABORT
)
2633 mc_close (dest_desc
);
2635 mc_unlink (dst_vpath
);
2636 dst_status
= DEST_NONE
;
2640 /* return_status == FILE_RETRY -- try allocate space again */
2643 ctx
->eta_secs
= 0.0;
2648 if (ctx
->total_bps
== 0 || (file_size
/ ctx
->total_bps
) > FILEOP_UPDATE_INTERVAL
)
2649 file_progress_show (ctx
, 0, file_size
, "", TRUE
);
2651 file_progress_show (ctx
, 1, 1, "", TRUE
);
2654 return_status
= file_progress_check_buttons (ctx
);
2657 if (return_status
== FILE_CONT
)
2659 off_t file_part
= 0;
2660 gint64 tv_last_update
= ctx
->transfer_start
;
2661 gint64 tv_last_input
= 0;
2662 gboolean is_first_time
= TRUE
;
2664 const size_t bufsize
= io_blksize (dst_stat
);
2665 buf
= g_malloc (bufsize
);
2669 ssize_t n_read
= -1;
2672 if (mc_ctl (src_desc
, VFS_CTL_IS_NOTREADY
, 0) == 0)
2673 while ((n_read
= mc_read (src_desc
, buf
, bufsize
)) < 0 && !ctx
->ignore_all
)
2676 file_error (TRUE
, _("Cannot read source file \"%s\"\n%s"), src_path
);
2677 if (return_status
== FILE_RETRY
)
2679 if (return_status
== FILE_IGNORE_ALL
)
2680 ctx
->ignore_all
= TRUE
;
2687 const gint64 tv_current
= g_get_monotonic_time ();
2694 file_part
+= n_read
;
2696 tv_last_input
= tv_current
;
2699 while ((n_written
= mc_write (dest_desc
, t
, (size_t) n_read
)) < n_read
)
2701 gboolean write_errno_nospace
;
2705 n_read
-= n_written
;
2710 write_errno_nospace
= (n_written
< 0 && errno
== ENOSPC
);
2712 if (ctx
->ignore_all
)
2713 return_status
= FILE_IGNORE_ALL
;
2716 file_error (TRUE
, _("Cannot write target file \"%s\"\n%s"), dst_path
);
2718 if (return_status
== FILE_IGNORE
)
2720 if (write_errno_nospace
)
2724 if (return_status
== FILE_IGNORE_ALL
)
2726 ctx
->ignore_all
= TRUE
;
2727 if (write_errno_nospace
)
2730 if (return_status
!= FILE_RETRY
)
2735 ctx
->total_progress_bytes
= ctx
->progress_bytes
+ file_part
+ ctx
->do_reget
;
2737 const gint64 usecs
= tv_current
- tv_last_update
;
2739 if (is_first_time
|| usecs
> FILEOP_UPDATE_INTERVAL_US
)
2741 copy_file_file_display_progress (ctx
, tv_current
, file_part
,
2742 file_size
- ctx
->do_reget
);
2743 tv_last_update
= tv_current
;
2746 is_first_time
= FALSE
;
2750 const gint64 total_usecs
= tv_current
- ctx
->total_transfer_start
;
2751 const gboolean force_update
= total_usecs
> FILEOP_UPDATE_INTERVAL_US
;
2753 const gint64 update_usecs
= tv_current
- tv_last_input
;
2754 const char *stalled_msg
=
2755 update_usecs
> FILEOP_STALLING_INTERVAL_US
? _("(stalled)") : "";
2757 file_progress_show (ctx
, file_part
+ ctx
->do_reget
, file_size
, stalled_msg
,
2759 if (ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
2761 file_progress_show_count (ctx
, ctx
->total_progress_count
, ctx
->total_count
);
2762 file_progress_show_total (ctx
, ctx
->total_progress_bytes
, force_update
);
2768 return_status
= file_progress_check_buttons (ctx
);
2769 if (return_status
!= FILE_CONT
)
2774 query_dialog (Q_ ("DialogTitle|Copy"),
2775 _("Incomplete file was retrieved"), D_ERROR
, 3,
2776 _("&Delete"), _("&Keep"), _("&Continue copy"));
2782 dst_status
= DEST_SHORT_DELETE
;
2787 dst_status
= DEST_SHORT_KEEP
;
2797 /* copy successful */
2798 dst_status
= DEST_FULL
;
2804 rotate_dash (FALSE
);
2805 while (src_desc
!= -1 && mc_close (src_desc
) < 0 && !ctx
->ignore_all
)
2807 temp_status
= file_error (TRUE
, _("Cannot close source file \"%s\"\n%s"), src_path
);
2808 if (temp_status
== FILE_RETRY
)
2810 if (temp_status
== FILE_ABORT
)
2811 return_status
= temp_status
;
2812 if (temp_status
== FILE_IGNORE_ALL
)
2813 ctx
->ignore_all
= TRUE
;
2817 while (dest_desc
!= -1 && mc_close (dest_desc
) < 0 && !ctx
->ignore_all
)
2819 temp_status
= file_error (TRUE
, _("Cannot close target file \"%s\"\n%s"), dst_path
);
2820 if (temp_status
== FILE_RETRY
)
2822 if (temp_status
== FILE_IGNORE_ALL
)
2823 ctx
->ignore_all
= TRUE
;
2824 return_status
= temp_status
;
2828 if (dst_status
== DEST_SHORT_QUERY
)
2830 /* Query to remove short file */
2831 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
2832 D_ERROR
, 2, _("&Delete"), _("&Keep")) == 0)
2833 dst_status
= DEST_SHORT_DELETE
;
2835 dst_status
= DEST_SHORT_KEEP
;
2838 if (dst_status
== DEST_SHORT_DELETE
)
2839 mc_unlink (dst_vpath
);
2840 else if (dst_status
== DEST_FULL
&& !appending
)
2842 /* Copy has succeeded */
2844 while (ctx
->preserve_uidgid
&& mc_chown (dst_vpath
, src_uid
, src_gid
) != 0
2845 && !ctx
->ignore_all
)
2847 temp_status
= file_error (TRUE
, _("Cannot chown target file \"%s\"\n%s"), dst_path
);
2848 if (temp_status
== FILE_ABORT
)
2850 return_status
= FILE_ABORT
;
2853 if (temp_status
== FILE_RETRY
)
2855 if (temp_status
== FILE_IGNORE_ALL
)
2857 ctx
->ignore_all
= TRUE
;
2858 return_status
= FILE_CONT
;
2860 if (temp_status
== FILE_IGNORE
)
2861 return_status
= FILE_CONT
;
2865 while (ctx
->preserve
&& mc_chmod (dst_vpath
, (src_mode
& ctx
->umask_kill
)) != 0
2866 && !ctx
->ignore_all
)
2868 temp_status
= file_error (TRUE
, _("Cannot chmod target file \"%s\"\n%s"), dst_path
);
2869 if (temp_status
== FILE_ABORT
)
2871 return_status
= FILE_ABORT
;
2874 if (temp_status
== FILE_RETRY
)
2876 if (temp_status
== FILE_IGNORE_ALL
)
2878 ctx
->ignore_all
= TRUE
;
2879 return_status
= FILE_CONT
;
2881 if (temp_status
== FILE_IGNORE
)
2882 return_status
= FILE_CONT
;
2886 if (!ctx
->preserve
&& !dst_exists
)
2888 src_mode
= umask (-1);
2890 src_mode
= 0100666 & ~src_mode
;
2891 mc_chmod (dst_vpath
, (src_mode
& ctx
->umask_kill
));
2895 if (dst_status
== DEST_FULL
|| dst_status
== DEST_SHORT_KEEP
)
2897 /* Always sync timestamps */
2898 mc_utime (dst_vpath
, ×
);
2900 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
2904 /* don't show an error message if attributes aren't supported in this FS */
2905 if (attrs_ignore_error (errno
))
2907 return_status
= FILE_CONT
;
2912 file_error (TRUE
, _("Cannot set ext2 attributes for target file \"%s\"\n%s"),
2914 if (temp_status
== FILE_ABORT
)
2915 return_status
= FILE_ABORT
;
2916 if (temp_status
== FILE_RETRY
)
2921 if (temp_status
== FILE_IGNORE_ALL
)
2923 ctx
->ignore_all
= TRUE
;
2924 return_status
= FILE_CONT
;
2926 if (temp_status
== FILE_IGNORE
)
2927 return_status
= FILE_CONT
;
2932 if (return_status
== FILE_CONT
)
2933 return_status
= progress_update_one (ctx
, file_size
);
2936 vfs_path_free (src_vpath
, TRUE
);
2937 vfs_path_free (dst_vpath
, TRUE
);
2938 return return_status
;
2941 /* --------------------------------------------------------------------------------------------- */
2943 * I think these copy_*_* functions should have a return type.
2944 * anyway, this function *must* have two directories as arguments.
2946 /* FIXME: This function needs to check the return values of the
2950 copy_dir_dir (file_op_context_t
*ctx
, const char *s
, const char *d
, gboolean toplevel
,
2951 gboolean move_over
, gboolean do_delete
, GSList
*parent_dirs
)
2953 struct vfs_dirent
*next
;
2954 struct stat dst_stat
, src_stat
;
2955 unsigned long attrs
= 0;
2956 gboolean attrs_ok
= ctx
->preserve
;
2958 FileProgressStatus return_status
= FILE_CONT
;
2960 vfs_path_t
*src_vpath
, *dst_vpath
;
2961 gboolean do_mkdir
= TRUE
;
2963 src_vpath
= vfs_path_from_str (s
);
2964 dst_vpath
= vfs_path_from_str (d
);
2966 /* First get the mode of the source dir */
2969 while ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2971 if (ctx
->ignore_all
)
2972 return_status
= FILE_IGNORE_ALL
;
2975 return_status
= file_error (TRUE
, _("Cannot stat source directory \"%s\"\n%s"), s
);
2976 if (return_status
== FILE_IGNORE_ALL
)
2977 ctx
->ignore_all
= TRUE
;
2980 if (return_status
!= FILE_RETRY
)
2984 while (attrs_ok
&& mc_fgetflags (src_vpath
, &attrs
) != 0)
2988 /* don't show an error message if attributes aren't supported in this FS */
2989 if (attrs_ignore_error (errno
))
2990 return_status
= FILE_CONT
;
2991 else if (ctx
->ignore_all
)
2992 return_status
= FILE_IGNORE_ALL
;
2996 file_error (TRUE
, _("Cannot get ext2 attributes of source directory \"%s\"\n%s"),
2998 if (return_status
== FILE_IGNORE_ALL
)
2999 ctx
->ignore_all
= TRUE
;
3000 if (return_status
== FILE_ABORT
)
3004 if (return_status
!= FILE_RETRY
)
3007 /* yet another attempt */
3011 if (is_in_linklist (dest_dirs
, src_vpath
, &src_stat
) != NULL
)
3013 /* Don't copy a directory we created before (we don't want to copy
3014 infinitely if a directory is copied into itself) */
3015 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
3016 return_status
= FILE_CONT
;
3020 /* Hmm, hardlink to directory??? - Norbert */
3021 /* FIXME: In this step we should do something in case the destination already exist */
3022 /* Check the hardlinks */
3025 switch (check_hardlinks (src_vpath
, &src_stat
, dst_vpath
, &ctx
->ignore_all
))
3028 /* We have made a hardlink - no more processing is necessary */
3031 case HARDLINK_ABORT
:
3032 return_status
= FILE_ABORT
;
3040 if (!S_ISDIR (src_stat
.st_mode
))
3042 if (ctx
->ignore_all
)
3043 return_status
= FILE_IGNORE_ALL
;
3046 return_status
= file_error (TRUE
, _("Source \"%s\" is not a directory\n%s"), s
);
3047 if (return_status
== FILE_RETRY
)
3048 goto retry_src_stat
;
3049 if (return_status
== FILE_IGNORE_ALL
)
3050 ctx
->ignore_all
= TRUE
;
3055 if (is_in_linklist (parent_dirs
, src_vpath
, &src_stat
) != NULL
)
3057 /* we found a cyclic symbolic link */
3058 message (D_ERROR
, MSG_ERROR
, _("Cannot copy cyclic symbolic link\n\"%s\""), s
);
3059 return_status
= FILE_SKIP
;
3063 lp
= g_new0 (link_t
, 1);
3064 lp
->vfs
= vfs_path_get_last_path_vfs (src_vpath
);
3065 lp
->ino
= src_stat
.st_ino
;
3066 lp
->dev
= src_stat
.st_dev
;
3067 parent_dirs
= g_slist_prepend (parent_dirs
, lp
);
3070 /* Now, check if the dest dir exists, if not, create it. */
3071 if (mc_stat (dst_vpath
, &dst_stat
) != 0)
3073 /* Here the dir doesn't exist : make it ! */
3074 if (move_over
&& mc_rename (src_vpath
, dst_vpath
) == 0)
3076 return_status
= FILE_CONT
;
3083 * If the destination directory exists, we want to copy the whole
3084 * directory, but we only want this to happen once.
3086 * Escape sequences added to the * to compiler warnings.
3087 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3088 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
3090 if (!S_ISDIR (dst_stat
.st_mode
))
3092 if (ctx
->ignore_all
)
3093 return_status
= FILE_IGNORE_ALL
;
3097 file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"), d
);
3098 if (return_status
== FILE_IGNORE_ALL
)
3099 ctx
->ignore_all
= TRUE
;
3100 if (return_status
== FILE_RETRY
)
3101 goto retry_dst_stat
;
3105 /* Dive into subdir if exists */
3106 if (toplevel
&& ctx
->dive_into_subdirs
)
3111 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
3112 vfs_path_free (tmp
, TRUE
);
3119 d
= vfs_path_as_str (dst_vpath
);
3123 while (my_mkdir (dst_vpath
, (src_stat
.st_mode
& ctx
->umask_kill
) | S_IRWXU
) != 0)
3125 if (ctx
->ignore_all
)
3126 return_status
= FILE_IGNORE_ALL
;
3130 file_error (TRUE
, _("Cannot create target directory \"%s\"\n%s"), d
);
3131 if (return_status
== FILE_IGNORE_ALL
)
3132 ctx
->ignore_all
= TRUE
;
3134 if (return_status
!= FILE_RETRY
)
3138 lp
= g_new0 (link_t
, 1);
3139 mc_stat (dst_vpath
, &dst_stat
);
3140 lp
->vfs
= vfs_path_get_last_path_vfs (dst_vpath
);
3141 lp
->ino
= dst_stat
.st_ino
;
3142 lp
->dev
= dst_stat
.st_dev
;
3143 dest_dirs
= g_slist_prepend (dest_dirs
, lp
);
3146 if (ctx
->preserve_uidgid
)
3148 while (mc_chown (dst_vpath
, src_stat
.st_uid
, src_stat
.st_gid
) != 0)
3150 if (ctx
->ignore_all
)
3151 return_status
= FILE_IGNORE_ALL
;
3154 return_status
= file_error (TRUE
, _("Cannot chown target directory \"%s\"\n%s"), d
);
3155 if (return_status
== FILE_IGNORE_ALL
)
3156 ctx
->ignore_all
= TRUE
;
3158 if (return_status
!= FILE_RETRY
)
3163 /* open the source dir for reading */
3164 reading
= mc_opendir (src_vpath
);
3165 if (reading
== NULL
)
3168 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
3171 vfs_path_t
*tmp_vpath
;
3174 * Now, we don't want '.' and '..' to be created / copied at any time
3176 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
3179 /* get the filename and add it to the src directory */
3180 path
= mc_build_filename (s
, next
->d_name
, (char *) NULL
);
3181 tmp_vpath
= vfs_path_from_str (path
);
3183 (*ctx
->stat_func
) (tmp_vpath
, &dst_stat
);
3184 if (S_ISDIR (dst_stat
.st_mode
))
3188 mdpath
= mc_build_filename (d
, next
->d_name
, (char *) NULL
);
3190 * From here, we just intend to recursively copy subdirs, not
3191 * the double functionality of copying different when the target
3192 * dir already exists. So, we give the recursive call the flag 0
3193 * meaning no toplevel.
3195 return_status
= copy_dir_dir (ctx
, path
, mdpath
, FALSE
, FALSE
, do_delete
, parent_dirs
);
3202 dest_file
= mc_build_filename (d
, x_basename (path
), (char *) NULL
);
3203 return_status
= copy_file_file (ctx
, path
, dest_file
);
3209 if (do_delete
&& return_status
== FILE_CONT
)
3211 if (ctx
->erase_at_end
)
3213 if (erase_list
== NULL
)
3214 erase_list
= g_queue_new ();
3216 lp
= g_new0 (link_t
, 1);
3217 lp
->src_vpath
= tmp_vpath
;
3218 lp
->st_mode
= dst_stat
.st_mode
;
3219 g_queue_push_tail (erase_list
, lp
);
3222 else if (S_ISDIR (dst_stat
.st_mode
))
3223 return_status
= erase_dir_iff_empty (ctx
, tmp_vpath
, ctx
->total_progress_count
);
3225 return_status
= erase_file (ctx
, tmp_vpath
);
3227 vfs_path_free (tmp_vpath
, TRUE
);
3229 mc_closedir (reading
);
3233 mc_timesbuf_t times
;
3235 mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
);
3238 mc_fsetflags (dst_vpath
, attrs
);
3240 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
3241 mc_utime (dst_vpath
, ×
);
3245 src_stat
.st_mode
= umask (-1);
3246 umask (src_stat
.st_mode
);
3247 src_stat
.st_mode
= 0100777 & ~src_stat
.st_mode
;
3248 mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
);
3252 free_link (parent_dirs
->data
);
3253 g_slist_free_1 (parent_dirs
);
3255 vfs_path_free (src_vpath
, TRUE
);
3256 vfs_path_free (dst_vpath
, TRUE
);
3257 return return_status
;
3262 /* --------------------------------------------------------------------------------------------- */
3263 /* {{{ Move routines */
3266 move_dir_dir (file_op_context_t
*ctx
, const char *s
, const char *d
)
3268 return do_move_dir_dir (NULL
, ctx
, s
, d
);
3273 /* --------------------------------------------------------------------------------------------- */
3274 /* {{{ Erase routines */
3277 erase_dir (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
3279 file_progress_show_deleting (ctx
, vpath
, NULL
);
3280 file_progress_show_count (ctx
, ctx
->total_progress_count
, ctx
->total_count
);
3281 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
3286 /* The old way to detect a non empty directory was:
3287 error = my_rmdir (s);
3288 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3289 For the linux user space nfs server (nfs-server-2.2beta29-2)
3290 we would have to check also for EIO. I hope the new way is
3291 fool proof. (Norbert)
3293 if (check_dir_is_empty (vpath
) == 0)
3295 FileProgressStatus error
;
3297 error
= query_recursive (ctx
, vfs_path_as_str (vpath
));
3298 if (error
== FILE_CONT
)
3299 error
= recursive_erase (ctx
, vpath
);
3303 return try_erase_dir (ctx
, vpath
);
3308 /* --------------------------------------------------------------------------------------------- */
3309 /* {{{ Panel operate routines */
3312 dirsize_status_init_cb (status_msg_t
*sm
)
3314 dirsize_status_msg_t
*dsm
= (dirsize_status_msg_t
*) sm
;
3315 WGroup
*gd
= GROUP (sm
->dlg
);
3316 Widget
*wd
= WIDGET (sm
->dlg
);
3319 const char *b1_name
= N_("&Abort");
3320 const char *b2_name
= N_("&Skip");
3321 int b_width
, ui_width
;
3324 b1_name
= _(b1_name
);
3325 b2_name
= _(b2_name
);
3328 b_width
= str_term_width1 (b1_name
) + 4;
3329 if (dsm
->allow_skip
)
3330 b_width
+= str_term_width1 (b2_name
) + 4 + 1;
3332 ui_width
= MAX (COLS
/ 2, b_width
+ 6);
3333 dsm
->dirname
= label_new (2, 3, NULL
);
3334 group_add_widget (gd
, dsm
->dirname
);
3335 dsm
->count_size
= label_new (3, 3, NULL
);
3336 group_add_widget (gd
, dsm
->count_size
);
3337 group_add_widget (gd
, hline_new (4, -1, -1));
3339 dsm
->abort_button
= WIDGET (button_new (5, 3, FILE_ABORT
, NORMAL_BUTTON
, b1_name
, NULL
));
3340 group_add_widget (gd
, dsm
->abort_button
);
3341 if (dsm
->allow_skip
)
3343 dsm
->skip_button
= WIDGET (button_new (5, 3, FILE_SKIP
, NORMAL_BUTTON
, b2_name
, NULL
));
3344 group_add_widget (gd
, dsm
->skip_button
);
3345 widget_select (dsm
->skip_button
);
3350 widget_set_size_rect (wd
, &r
);
3351 dirsize_status_locate_buttons (dsm
);
3354 /* --------------------------------------------------------------------------------------------- */
3357 dirsize_status_update_cb (status_msg_t
*sm
)
3359 dirsize_status_msg_t
*dsm
= (dirsize_status_msg_t
*) sm
;
3360 Widget
*wd
= WIDGET (sm
->dlg
);
3363 /* update second (longer label) */
3364 label_set_textv (dsm
->count_size
, _("Directories: %zu, total size: %s"),
3365 dsm
->dir_count
, size_trunc_sep (dsm
->total_size
, panels_options
.kilobyte_si
));
3367 /* enlarge dialog if required */
3368 if (WIDGET (dsm
->count_size
)->rect
.cols
+ 6 > r
.cols
)
3370 r
.cols
= WIDGET (dsm
->count_size
)->rect
.cols
+ 6;
3371 widget_set_size_rect (wd
, &r
);
3372 dirsize_status_locate_buttons (dsm
);
3374 /* TODO: ret rid of double redraw */
3377 /* adjust first label */
3378 label_set_text (dsm
->dirname
,
3379 str_trunc (vfs_path_as_str (dsm
->dirname_vpath
), wd
->rect
.cols
- 6));
3381 switch (status_msg_common_update (sm
))
3393 /* --------------------------------------------------------------------------------------------- */
3396 dirsize_status_deinit_cb (status_msg_t
*sm
)
3400 /* schedule to update passive panel */
3401 if (get_other_type () == view_listing
)
3402 other_panel
->dirty
= TRUE
;
3405 /* --------------------------------------------------------------------------------------------- */
3409 * Computes the number of bytes used by the files in a directory
3413 compute_dir_size (const vfs_path_t
*dirname_vpath
, dirsize_status_msg_t
*sm
,
3414 size_t *ret_dir_count
, size_t *ret_marked_count
, uintmax_t *ret_total
,
3415 gboolean follow_symlinks
)
3417 return do_compute_dir_size (dirname_vpath
, sm
, ret_dir_count
, ret_marked_count
, ret_total
,
3418 follow_symlinks
? mc_stat
: mc_lstat
);
3421 /* --------------------------------------------------------------------------------------------- */
3425 * Performs one of the operations on the current on the source_panel
3426 * (copy, delete, move).
3428 * Returns TRUE if did change the directory
3429 * structure, Returns FALSE if user aborted
3431 * force_single forces operation on the current entry and affects
3432 * default destination. Current filename is used as default.
3436 panel_operate (void *source_panel
, FileOperation operation
, gboolean force_single
)
3438 WPanel
*panel
= PANEL (source_panel
);
3439 const gboolean single_entry
= force_single
|| (panel
->marked
<= 1)
3440 || (get_current_type () == view_tree
);
3442 const char *source
= NULL
;
3444 vfs_path_t
*dest_vpath
= NULL
;
3445 vfs_path_t
*save_cwd
= NULL
, *save_dest
= NULL
;
3446 struct stat src_stat
;
3447 gboolean ret_val
= TRUE
;
3449 FileProgressStatus value
;
3450 file_op_context_t
*ctx
;
3451 filegui_dialog_type_t dialog_type
= FILEGUI_DIALOG_ONE_ITEM
;
3453 gboolean do_bg
= FALSE
; /* do background operation? */
3455 static gboolean i18n_flag
= FALSE
;
3458 for (i
= G_N_ELEMENTS (op_names
); i
-- != 0;)
3459 op_names
[i
] = Q_ (op_names
[i
]);
3463 linklist
= free_linklist (linklist
);
3464 dest_dirs
= free_linklist (dest_dirs
);
3470 source
= check_single_entry (panel
, force_single
, &src_stat
);
3476 ctx
= file_op_context_new (operation
);
3478 /* Show confirmation dialog */
3479 if (operation
!= OP_DELETE
)
3481 dest
= do_confirm_copy_move (panel
, force_single
, source
, &src_stat
, ctx
, &do_bg
);
3488 dest_vpath
= vfs_path_from_str (dest
);
3490 else if (confirm_delete
&& !do_confirm_erase (panel
, source
, &src_stat
))
3496 ctx
->total_transfer_start
= g_get_monotonic_time ();
3498 #ifdef ENABLE_BACKGROUND
3499 /* Did the user select to do a background operation? */
3504 v
= do_background (ctx
,
3505 g_strconcat (op_names
[operation
], ": ",
3506 vfs_path_as_str (panel
->cwd_vpath
), (char *) NULL
));
3508 message (D_ERROR
, MSG_ERROR
, _("Sorry, I could not put the job in background"));
3510 /* If we are the parent */
3513 mc_setctl (panel
->cwd_vpath
, VFS_SETCTL_FORGET
, NULL
);
3515 mc_setctl (dest_vpath
, VFS_SETCTL_FORGET
, NULL
);
3516 vfs_path_free (dest_vpath
, TRUE
);
3518 /* file_op_context_destroy (ctx); */
3523 #endif /* ENABLE_BACKGROUND */
3525 const file_entry_t
*fe
;
3527 if (operation
== OP_DELETE
)
3528 dialog_type
= FILEGUI_DIALOG_DELETE_ITEM
;
3529 else if (single_entry
3530 && ((fe
= panel_current_entry (panel
)) == NULL
? FALSE
: S_ISDIR (fe
->st
.st_mode
)))
3531 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
3532 else if (single_entry
|| force_single
)
3533 dialog_type
= FILEGUI_DIALOG_ONE_ITEM
;
3535 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
3538 /* Initialize things */
3539 /* We do not want to trash cache every time file is
3540 created/touched. However, this will make our cache contain
3543 && (mc_setctl (dest_vpath
, VFS_SETCTL_STALE_DATA
, GUINT_TO_POINTER (1)) != 0))
3544 save_dest
= vfs_path_from_str (dest
);
3546 if ((vfs_path_tokens_count (panel
->cwd_vpath
) != 0)
3547 && (mc_setctl (panel
->cwd_vpath
, VFS_SETCTL_STALE_DATA
, GUINT_TO_POINTER (1)) != 0))
3548 save_cwd
= vfs_path_clone (panel
->cwd_vpath
);
3550 /* Now, let's do the job */
3552 /* This code is only called by the tree and panel code */
3555 /* We now have ETA in all cases */
3557 /* One file: FIXME mc_chdir will take user out of any vfs */
3558 if ((operation
!= OP_COPY
) && (get_current_type () == view_tree
))
3563 vpath
= vfs_path_from_str (PATH_SEP_STR
);
3564 chdir_retcode
= mc_chdir (vpath
);
3565 vfs_path_free (vpath
, TRUE
);
3566 if (chdir_retcode
< 0)
3573 value
= operate_single_file (panel
, ctx
, source
, &src_stat
, dest
, dialog_type
);
3574 if ((value
== FILE_CONT
) && !force_single
)
3575 unmark_files (panel
);
3581 /* Check destination for copy or move operation */
3582 while (operation
!= OP_DELETE
)
3585 struct stat dst_stat
;
3587 dst_result
= mc_stat (dest_vpath
, &dst_stat
);
3589 if ((dst_result
!= 0) || S_ISDIR (dst_stat
.st_mode
))
3593 || file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"),
3594 dest
) != FILE_RETRY
)
3598 /* TODO: the good way is required to skip directories scanning in case of rename/move
3599 * of several directories. Since reqular expression can be used for destination,
3600 * some directory movements can be a cross-filesystem and directory scanning is useful
3601 * for those directories only. */
3603 if (panel_operate_init_totals (panel
, NULL
, NULL
, ctx
, file_op_compute_totals
, dialog_type
)
3606 /* Loop for every file, perform the actual copy operation */
3607 for (i
= 0; i
< panel
->dir
.len
; i
++)
3609 const char *source2
;
3611 if (panel
->dir
.list
[i
].f
.marked
== 0)
3612 continue; /* Skip the unmarked ones */
3614 source2
= panel
->dir
.list
[i
].fname
->str
;
3615 src_stat
= panel
->dir
.list
[i
].st
;
3617 value
= operate_one_file (panel
, ctx
, source2
, &src_stat
, dest
);
3619 if (value
== FILE_ABORT
)
3622 if (value
== FILE_CONT
)
3623 do_file_mark (panel
, i
, 0);
3627 if (ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
3629 file_progress_show_count (ctx
, ctx
->total_progress_count
, ctx
->total_count
);
3630 file_progress_show_total (ctx
, ctx
->progress_bytes
, FALSE
);
3633 if (operation
!= OP_DELETE
)
3634 file_progress_show (ctx
, 0, 0, "", FALSE
);
3637 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
3641 } /* 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: