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 */
83 #include "filemanager.h" /* other_panel */
84 #include "layout.h" /* rotate_dash() */
85 #include "ioblksize.h" /* io_blksize() */
91 /*** global variables ****************************************************************************/
93 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
94 const char *op_names
[3] = {
95 N_("DialogTitle|Copy"),
96 N_("DialogTitle|Move"),
97 N_("DialogTitle|Delete")
100 /*** file scope macro definitions ****************************************************************/
102 #define FILEOP_UPDATE_INTERVAL 2
103 #define FILEOP_STALLING_INTERVAL 4
104 #define FILEOP_UPDATE_INTERVAL_US (FILEOP_UPDATE_INTERVAL * G_USEC_PER_SEC)
105 #define FILEOP_STALLING_INTERVAL_US (FILEOP_STALLING_INTERVAL * G_USEC_PER_SEC)
107 /*** file scope type declarations ****************************************************************/
109 /* This is a hard link cache */
112 const struct vfs_class
*vfs
;
116 vfs_path_t
*src_vpath
;
117 vfs_path_t
*dst_vpath
;
120 /* Status of the destination file */
123 DEST_NONE
= 0, /**< Not created */
124 DEST_SHORT_QUERY
, /**< Created, not fully copied, query to do */
125 DEST_SHORT_KEEP
, /**< Created, not fully copied, keep it */
126 DEST_SHORT_DELETE
, /**< Created, not fully copied, delete it */
127 DEST_FULL
/**< Created, fully copied */
130 /* Status of hard link creation */
133 HARDLINK_OK
= 0, /**< Hardlink was created successfully */
134 HARDLINK_CACHED
, /**< Hardlink was added to the cache */
135 HARDLINK_NOTLINK
, /**< This is not a hard link */
136 HARDLINK_UNSUPPORTED
, /**< VFS doesn't support hard links */
137 HARDLINK_ERROR
, /**< Hard link creation error */
138 HARDLINK_ABORT
/**< Stop file operation after hardlink creation error */
142 * This array introduced to avoid translation problems. The former (op_names)
143 * is assumed to be nouns, suitable in dialog box titles; this one should
144 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
145 * (I don't use spaces around the words, because someday they could be
146 * dropped, when widgets get smarter)
149 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
150 static const char *op_names1
[] = {
151 N_("FileOperation|Copy"),
152 N_("FileOperation|Move"),
153 N_("FileOperation|Delete")
157 * These are formats for building a prompt. Parts encoded as follows:
158 * %o - operation from op_names1
159 * %f - file/files or files/directories, as appropriate
160 * %m - "with source mask" or question mark for delete
161 * %s - source name (truncated)
162 * %d - number of marked files
163 * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
165 /* xgettext:no-c-format */
166 static const char *one_format
= N_("%o %f%n\"%s\"%m");
167 /* xgettext:no-c-format */
168 static const char *many_format
= N_("%o %d %f%m");
170 static const char *prompt_parts
[] = {
175 N_("files/directories"),
176 /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
177 N_(" with source mask:")
180 /*** forward declarations (file scope functions) *************************************************/
182 /*** file scope variables ************************************************************************/
184 /* the hard link cache */
185 static GSList
*linklist
= NULL
;
187 /* the files-to-be-erased list */
188 static GQueue
*erase_list
= NULL
;
191 * This list holds information about just created target directories and is used to detect
192 * when an directory is copied into itself (we don't want to copy infinitely).
194 static GSList
*dest_dirs
= NULL
;
196 /* --------------------------------------------------------------------------------------------- */
197 /*** file scope functions ************************************************************************/
198 /* --------------------------------------------------------------------------------------------- */
201 dirsize_status_locate_buttons (dirsize_status_msg_t
*dsm
)
203 status_msg_t
*sm
= STATUS_MSG (dsm
);
204 Widget
*wd
= WIDGET (sm
->dlg
);
211 if (!dsm
->allow_skip
)
213 /* single button: "Abort" */
214 x
+= (wd
->rect
.cols
- dsm
->abort_button
->rect
.cols
) / 2;
215 r
= dsm
->abort_button
->rect
;
218 widget_set_size_rect (dsm
->abort_button
, &r
);
222 /* two buttons: "Abort" and "Skip" */
225 cols
= dsm
->abort_button
->rect
.cols
+ dsm
->skip_button
->rect
.cols
+ 1;
226 x
+= (wd
->rect
.cols
- cols
) / 2;
227 r
= dsm
->abort_button
->rect
;
230 widget_set_size_rect (dsm
->abort_button
, &r
);
231 x
+= dsm
->abort_button
->rect
.cols
+ 1;
232 r
= dsm
->skip_button
->rect
;
235 widget_set_size_rect (dsm
->skip_button
, &r
);
239 /* --------------------------------------------------------------------------------------------- */
242 build_dest (file_op_context_t
*ctx
, const char *src
, const char *dest
, FileProgressStatus
*status
)
245 const char *fnsource
;
251 /* We remove \n from the filename since regex routines would use \n as an anchor */
252 /* this is just to be allowed to maniupulate file names with \n on it */
253 for (q
= s
; *q
!= '\0'; q
++)
257 fnsource
= x_basename (s
);
259 if (!mc_search_run (ctx
->search_handle
, fnsource
, 0, strlen (fnsource
), NULL
))
266 q
= mc_search_prepare_replace_str2 (ctx
->search_handle
, ctx
->dest_mask
);
267 if (ctx
->search_handle
->error
!= MC_SEARCH_E_OK
)
269 if (ctx
->search_handle
->error_str
!= NULL
)
270 message (D_ERROR
, MSG_ERROR
, "%s", ctx
->search_handle
->error_str
);
272 *status
= FILE_ABORT
;
278 if (*status
== FILE_CONT
)
282 repl_dest
= mc_search_prepare_replace_str2 (ctx
->search_handle
, dest
);
283 if (ctx
->search_handle
->error
== MC_SEARCH_E_OK
)
284 s
= mc_build_filename (repl_dest
, q
, (char *) NULL
);
287 if (ctx
->search_handle
->error_str
!= NULL
)
288 message (D_ERROR
, MSG_ERROR
, "%s", ctx
->search_handle
->error_str
);
290 *status
= FILE_ABORT
;
301 /* --------------------------------------------------------------------------------------------- */
304 free_link (void *data
)
306 link_t
*lp
= (link_t
*) data
;
308 vfs_path_free (lp
->src_vpath
, TRUE
);
309 vfs_path_free (lp
->dst_vpath
, TRUE
);
313 /* --------------------------------------------------------------------------------------------- */
316 free_erase_list (GQueue
*lp
)
319 g_queue_free_full (lp
, free_link
);
324 /* --------------------------------------------------------------------------------------------- */
327 free_linklist (GSList
*lp
)
329 g_slist_free_full (lp
, free_link
);
334 /* --------------------------------------------------------------------------------------------- */
336 static const link_t
*
337 is_in_linklist (const GSList
*lp
, const vfs_path_t
*vpath
, const struct stat
*sb
)
339 const struct vfs_class
*class;
340 ino_t ino
= sb
->st_ino
;
341 dev_t dev
= sb
->st_dev
;
343 class = vfs_path_get_last_path_vfs (vpath
);
345 for (; lp
!= NULL
; lp
= (const GSList
*) g_slist_next (lp
))
347 const link_t
*lnk
= (const link_t
*) lp
->data
;
349 if (lnk
->vfs
== class && lnk
->ino
== ino
&& lnk
->dev
== dev
)
356 /* --------------------------------------------------------------------------------------------- */
358 * Check and made hardlink
360 * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
361 * and a hardlink was successfully made
364 static hardlink_status_t
365 check_hardlinks (const vfs_path_t
*src_vpath
, const struct stat
*src_stat
,
366 const vfs_path_t
*dst_vpath
, gboolean
*ignore_all
)
369 ino_t ino
= src_stat
->st_ino
;
370 dev_t dev
= src_stat
->st_dev
;
372 if (src_stat
->st_nlink
< 2)
373 return HARDLINK_NOTLINK
;
374 if ((vfs_file_class_flags (src_vpath
) & VFSF_NOLINKS
) != 0)
375 return HARDLINK_UNSUPPORTED
;
377 lnk
= (link_t
*) is_in_linklist (linklist
, src_vpath
, src_stat
);
381 struct stat link_stat
;
383 stat_result
= mc_stat (lnk
->src_vpath
, &link_stat
);
385 if (stat_result
== 0 && link_stat
.st_ino
== ino
&& link_stat
.st_dev
== dev
)
387 const struct vfs_class
*lp_name_class
;
388 const struct vfs_class
*my_vfs
;
390 lp_name_class
= vfs_path_get_last_path_vfs (lnk
->src_vpath
);
391 my_vfs
= vfs_path_get_last_path_vfs (src_vpath
);
393 if (lp_name_class
== my_vfs
)
395 const struct vfs_class
*p_class
, *dst_name_class
;
397 dst_name_class
= vfs_path_get_last_path_vfs (dst_vpath
);
398 p_class
= vfs_path_get_last_path_vfs (lnk
->dst_vpath
);
400 if (dst_name_class
== p_class
)
404 while (!(ok
= (mc_stat (lnk
->dst_vpath
, &link_stat
) == 0)) && !*ignore_all
)
406 FileProgressStatus status
;
409 file_error (TRUE
, _("Cannot stat hardlink source file \"%s\"\n%s"),
410 vfs_path_as_str (lnk
->dst_vpath
));
411 if (status
== FILE_ABORT
)
412 return HARDLINK_ABORT
;
413 if (status
== FILE_RETRY
)
415 if (status
== FILE_IGNORE_ALL
)
420 /* if stat() finished unsuccessfully, don't try to create link */
422 return HARDLINK_ERROR
;
424 while (!(ok
= (mc_link (lnk
->dst_vpath
, dst_vpath
) == 0)) && !*ignore_all
)
426 FileProgressStatus status
;
429 file_error (TRUE
, _("Cannot create target hardlink \"%s\"\n%s"),
430 vfs_path_as_str (dst_vpath
));
431 if (status
== FILE_ABORT
)
432 return HARDLINK_ABORT
;
433 if (status
== FILE_RETRY
)
435 if (status
== FILE_IGNORE_ALL
)
441 return (ok
? HARDLINK_OK
: HARDLINK_ERROR
);
448 FileProgressStatus status
;
450 /* Message w/o "Retry" action.
452 * FIXME: Can't say what errno is here. Define it and don't display.
454 * file_error() displays a message with text representation of errno
455 * and the string passed to file_error() should provide the format "%s"
456 * for that at end (see previous file_error() call for the reference).
457 * But if format for errno isn't provided, it is safe, because C standard says:
458 * "If the format is exhausted while arguments remain, the excess arguments
459 * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999,
460 * section 7.19.6.1, paragraph 2).
465 file_error (FALSE
, _("Cannot create target hardlink \"%s\""),
466 vfs_path_as_str (dst_vpath
));
468 if (status
== FILE_ABORT
)
469 return HARDLINK_ABORT
;
471 if (status
== FILE_IGNORE_ALL
)
475 return HARDLINK_ERROR
;
478 lnk
= g_try_new (link_t
, 1);
481 lnk
->vfs
= vfs_path_get_last_path_vfs (src_vpath
);
485 lnk
->src_vpath
= vfs_path_clone (src_vpath
);
486 lnk
->dst_vpath
= vfs_path_clone (dst_vpath
);
488 linklist
= g_slist_prepend (linklist
, lnk
);
491 return HARDLINK_CACHED
;
494 /* --------------------------------------------------------------------------------------------- */
496 * Duplicate the contents of the symbolic link src_vpath in dst_vpath.
497 * Try to make a stable symlink if the option "stable symlink" was
498 * set in the file mask dialog.
499 * If dst_path is an existing symlink it will be deleted silently
500 * (upper levels take already care of existing files at dst_vpath).
503 static FileProgressStatus
504 make_symlink (file_op_context_t
*ctx
, const vfs_path_t
*src_vpath
, const vfs_path_t
*dst_vpath
)
506 const char *src_path
;
507 const char *dst_path
;
508 char link_target
[MC_MAXPATHLEN
];
510 FileProgressStatus return_status
;
511 struct stat dst_stat
;
512 gboolean dst_is_symlink
;
513 vfs_path_t
*link_target_vpath
= NULL
;
515 src_path
= vfs_path_as_str (src_vpath
);
516 dst_path
= vfs_path_as_str (dst_vpath
);
518 dst_is_symlink
= (mc_lstat (dst_vpath
, &dst_stat
) == 0) && S_ISLNK (dst_stat
.st_mode
);
521 len
= mc_readlink (src_vpath
, link_target
, sizeof (link_target
) - 1);
525 return_status
= FILE_IGNORE_ALL
;
528 return_status
= file_error (TRUE
, _("Cannot read source link \"%s\"\n%s"), src_path
);
529 if (return_status
== FILE_IGNORE_ALL
)
530 ctx
->ignore_all
= TRUE
;
531 if (return_status
== FILE_RETRY
)
532 goto retry_src_readlink
;
537 link_target
[len
] = '\0';
539 if (ctx
->stable_symlinks
&& !(vfs_file_is_local (src_vpath
) && vfs_file_is_local (dst_vpath
)))
541 message (D_ERROR
, MSG_ERROR
,
542 _("Cannot make stable symlinks across "
543 "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
544 ctx
->stable_symlinks
= FALSE
;
547 if (ctx
->stable_symlinks
&& !g_path_is_absolute (link_target
))
551 r
= strrchr (src_path
, PATH_SEP
);
558 slen
= r
- src_path
+ 1;
560 p
= g_string_sized_new (slen
+ len
);
561 g_string_append_len (p
, src_path
, slen
);
563 if (g_path_is_absolute (dst_path
))
564 q
= vfs_path_from_str_flags (dst_path
, VPF_NO_CANON
);
566 q
= vfs_path_build_filename (p
->str
, dst_path
, (char *) NULL
);
568 if (vfs_path_tokens_count (q
) > 1)
571 vfs_path_t
*tmp_vpath1
, *tmp_vpath2
;
573 g_string_append_len (p
, link_target
, len
);
574 tmp_vpath1
= vfs_path_vtokens_get (q
, -1, 1);
575 tmp_vpath2
= vfs_path_from_str (p
->str
);
576 s
= diff_two_paths (tmp_vpath1
, tmp_vpath2
);
577 vfs_path_free (tmp_vpath2
, TRUE
);
578 vfs_path_free (tmp_vpath1
, TRUE
);
579 g_strlcpy (link_target
, s
!= NULL
? s
: p
->str
, sizeof (link_target
));
583 g_string_free (p
, TRUE
);
584 vfs_path_free (q
, TRUE
);
587 link_target_vpath
= vfs_path_from_str_flags (link_target
, VPF_NO_CANON
);
590 if (mc_symlink (link_target_vpath
, dst_vpath
) == 0)
593 return_status
= FILE_CONT
;
597 * if dst_exists, it is obvious that this had failed.
598 * We can delete the old symlink and try again...
600 if (dst_is_symlink
&& mc_unlink (dst_vpath
) == 0
601 && mc_symlink (link_target_vpath
, dst_vpath
) == 0)
604 return_status
= FILE_CONT
;
609 return_status
= FILE_IGNORE_ALL
;
612 return_status
= file_error (TRUE
, _("Cannot create target symlink \"%s\"\n%s"), dst_path
);
613 if (return_status
== FILE_IGNORE_ALL
)
614 ctx
->ignore_all
= TRUE
;
615 if (return_status
== FILE_RETRY
)
616 goto retry_dst_symlink
;
620 vfs_path_free (link_target_vpath
, TRUE
);
621 return return_status
;
624 /* --------------------------------------------------------------------------------------------- */
626 * do_compute_dir_size:
628 * Computes the number of bytes used by the files in a directory
631 static FileProgressStatus
632 do_compute_dir_size (const vfs_path_t
*dirname_vpath
, dirsize_status_msg_t
*dsm
,
633 size_t *dir_count
, size_t *ret_marked
, uintmax_t *ret_total
,
634 mc_stat_fn stat_func
)
636 static gint64 timestamp
= 0;
637 /* update with 25 FPS rate */
638 static const gint64 delay
= G_USEC_PER_SEC
/ 25;
640 status_msg_t
*sm
= STATUS_MSG (dsm
);
644 struct vfs_dirent
*dirent
;
645 FileProgressStatus ret
= FILE_CONT
;
649 dir
= mc_opendir (dirname_vpath
);
653 while (ret
== FILE_CONT
&& (dirent
= mc_readdir (dir
)) != NULL
)
655 vfs_path_t
*tmp_vpath
;
657 if (DIR_IS_DOT (dirent
->d_name
) || DIR_IS_DOTDOT (dirent
->d_name
))
660 tmp_vpath
= vfs_path_append_new (dirname_vpath
, dirent
->d_name
, (char *) NULL
);
662 res
= stat_func (tmp_vpath
, &s
);
665 if (S_ISDIR (s
.st_mode
))
667 do_compute_dir_size (tmp_vpath
, dsm
, dir_count
, ret_marked
, ret_total
,
674 *ret_total
+= (uintmax_t) s
.st_size
;
677 if (ret
== FILE_CONT
&& sm
->update
!= NULL
&& mc_time_elapsed (×tamp
, delay
))
679 dsm
->dirname_vpath
= tmp_vpath
;
680 dsm
->dir_count
= *dir_count
;
681 dsm
->total_size
= *ret_total
;
682 ret
= sm
->update (sm
);
686 vfs_path_free (tmp_vpath
, TRUE
);
693 /* --------------------------------------------------------------------------------------------- */
695 * panel_compute_totals:
697 * compute the number of files and the number of bytes
698 * used up by the whole selection, recursing directories
699 * as required. In addition, it checks to see if it will
700 * overwrite any files by doing the copy.
703 static FileProgressStatus
704 panel_compute_totals (const WPanel
*panel
, dirsize_status_msg_t
*sm
, size_t *ret_count
,
705 uintmax_t *ret_total
, gboolean follow_symlinks
)
708 size_t dir_count
= 0;
709 mc_stat_fn stat_func
= follow_symlinks
? mc_stat
: mc_lstat
;
711 for (i
= 0; i
< panel
->dir
.len
; i
++)
713 const file_entry_t
*fe
= &panel
->dir
.list
[i
];
714 const struct stat
*s
;
716 if (fe
->f
.marked
== 0)
721 if (S_ISDIR (s
->st_mode
) || (follow_symlinks
&& link_isdir (fe
) && fe
->f
.stale_link
== 0))
724 FileProgressStatus status
;
726 p
= vfs_path_append_new (panel
->cwd_vpath
, fe
->fname
->str
, (char *) NULL
);
727 status
= do_compute_dir_size (p
, sm
, &dir_count
, ret_count
, ret_total
, stat_func
);
728 vfs_path_free (p
, TRUE
);
730 if (status
!= FILE_CONT
)
736 *ret_total
+= (uintmax_t) s
->st_size
;
743 /* --------------------------------------------------------------------------------------------- */
745 /** Initialize variables for progress bars */
746 static FileProgressStatus
747 panel_operate_init_totals (const WPanel
*panel
, const vfs_path_t
*source
,
748 const struct stat
*source_stat
, file_op_context_t
*ctx
,
749 gboolean compute_totals
, filegui_dialog_type_t dialog_type
)
751 FileProgressStatus status
;
753 #ifdef ENABLE_BACKGROUND
754 if (mc_global
.we_are_background
)
758 if (verbose
&& compute_totals
)
760 dirsize_status_msg_t dsm
;
761 gboolean stale_link
= FALSE
;
763 memset (&dsm
, 0, sizeof (dsm
));
764 dsm
.allow_skip
= TRUE
;
765 status_msg_init (STATUS_MSG (&dsm
), _("Directory scanning"), 0, dirsize_status_init_cb
,
766 dirsize_status_update_cb
, dirsize_status_deinit_cb
);
768 ctx
->total_count
= 0;
769 ctx
->total_bytes
= 0;
772 status
= panel_compute_totals (panel
, &dsm
, &ctx
->total_count
, &ctx
->total_bytes
,
774 else if (S_ISDIR (source_stat
->st_mode
)
775 || (ctx
->follow_links
776 && file_is_symlink_to_dir (source
, (struct stat
*) source_stat
, &stale_link
)
779 size_t dir_count
= 0;
781 status
= do_compute_dir_size (source
, &dsm
, &dir_count
, &ctx
->total_count
,
782 &ctx
->total_bytes
, ctx
->stat_func
);
787 ctx
->total_bytes
+= (uintmax_t) source_stat
->st_size
;
791 status_msg_deinit (STATUS_MSG (&dsm
));
793 ctx
->totals_computed
= (status
== FILE_CONT
);
795 if (status
== FILE_SKIP
)
801 ctx
->total_count
= panel
->marked
;
802 ctx
->total_bytes
= panel
->total
;
803 ctx
->totals_computed
= verbose
&& dialog_type
== FILEGUI_DIALOG_ONE_ITEM
;
806 /* destroy already created UI for single file rename operation */
807 file_progress_ui_destroy (ctx
);
809 file_progress_ui_create (ctx
, TRUE
, dialog_type
);
814 /* --------------------------------------------------------------------------------------------- */
816 static FileProgressStatus
817 progress_update_one (file_op_context_t
*ctx
, off_t add
)
820 static gint64 tv_start
= -1;
822 ctx
->progress_count
++;
823 ctx
->progress_bytes
+= (uintmax_t) add
;
825 tv_current
= g_get_monotonic_time ();
828 tv_start
= tv_current
;
830 if (tv_current
- tv_start
> FILEOP_UPDATE_INTERVAL_US
)
832 if (verbose
&& ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
834 file_progress_show_count (ctx
, ctx
->progress_count
, ctx
->total_count
);
835 file_progress_show_total (ctx
, ctx
->progress_bytes
, TRUE
);
838 tv_start
= tv_current
;
841 return file_progress_check_buttons (ctx
);
844 /* --------------------------------------------------------------------------------------------- */
846 static FileProgressStatus
847 real_warn_same_file (enum OperationMode mode
, const char *fmt
, const char *a
, const char *b
)
851 const char *head_msg
;
852 int width_a
, width_b
, width
;
854 head_msg
= mode
== Foreground
? MSG_ERROR
: _("Background process error");
856 width_a
= str_term_width1 (a
);
857 width_b
= str_term_width1 (b
);
866 s
= g_strndup (str_trunc (a
, width
), width
);
867 b
= str_trunc (b
, width
);
868 msg
= g_strdup_printf (fmt
, s
, b
);
873 a
= str_trunc (a
, width
);
874 msg
= g_strdup_printf (fmt
, a
, b
);
880 b
= str_trunc (b
, width
);
882 msg
= g_strdup_printf (fmt
, a
, b
);
885 result
= query_dialog (head_msg
, msg
, D_ERROR
, 2, _("&Skip"), _("&Abort"));
889 return (result
== 1) ? FILE_ABORT
: FILE_SKIP
;
892 /* --------------------------------------------------------------------------------------------- */
894 static FileProgressStatus
895 warn_same_file (const char *fmt
, const char *a
, const char *b
)
897 #ifdef ENABLE_BACKGROUND
902 FileProgressStatus (*f
) (enum OperationMode
, const char *fmt
, const char *a
, const char *b
);
906 pntr
.f
= real_warn_same_file
;
908 if (mc_global
.we_are_background
)
909 return parent_call (pntr
.p
, NULL
, 3, strlen (fmt
), fmt
, strlen (a
), a
, strlen (b
), b
);
911 return real_warn_same_file (Foreground
, fmt
, a
, b
);
914 /* --------------------------------------------------------------------------------------------- */
917 check_same_file (const char *a
, const struct stat
*ast
, const char *b
, const struct stat
*bst
,
918 FileProgressStatus
*status
)
920 if (ast
->st_dev
!= bst
->st_dev
|| ast
->st_ino
!= bst
->st_ino
)
923 if (S_ISDIR (ast
->st_mode
))
924 *status
= warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a
, b
);
926 *status
= warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a
, b
);
931 /* --------------------------------------------------------------------------------------------- */
932 /* {{{ Query/status report routines */
934 static FileProgressStatus
935 real_do_file_error (enum OperationMode mode
, gboolean allow_retry
, const char *error
)
940 msg
= mode
== Foreground
? MSG_ERROR
: _("Background process error");
944 query_dialog (msg
, error
, D_ERROR
, 4, _("&Ignore"), _("Ignore a&ll"), _("&Retry"),
947 result
= query_dialog (msg
, error
, D_ERROR
, 3, _("&Ignore"), _("Ignore a&ll"), _("&Abort"));
957 return FILE_IGNORE_ALL
;
973 /* --------------------------------------------------------------------------------------------- */
975 static FileProgressStatus
976 real_query_recursive (file_op_context_t
*ctx
, enum OperationMode mode
, const char *s
)
978 if (ctx
->recursive_result
< RECURSIVE_ALWAYS
)
983 msg
= mode
== Foreground
984 ? _("Directory \"%s\" not empty.\nDelete it recursively?")
985 : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
986 text
= g_strdup_printf (msg
, path_trunc (s
, 30));
991 ctx
->recursive_result
=
992 query_dialog (op_names
[OP_DELETE
], text
, D_ERROR
, 5, _("&Yes"), _("&No"), _("A&ll"),
993 _("Non&e"), _("&Abort"));
996 if (ctx
->recursive_result
!= RECURSIVE_ABORT
)
1000 switch (ctx
->recursive_result
)
1003 case RECURSIVE_ALWAYS
:
1007 case RECURSIVE_NEVER
:
1010 case RECURSIVE_ABORT
:
1016 /* --------------------------------------------------------------------------------------------- */
1018 #ifdef ENABLE_BACKGROUND
1019 static FileProgressStatus
1020 do_file_error (gboolean allow_retry
, const char *str
)
1026 FileProgressStatus (*f
) (enum OperationMode
, gboolean
, const char *);
1030 pntr
.f
= real_do_file_error
;
1032 if (mc_global
.we_are_background
)
1033 return parent_call (pntr
.p
, NULL
, 2, sizeof (allow_retry
), allow_retry
, strlen (str
), str
);
1035 return real_do_file_error (Foreground
, allow_retry
, str
);
1038 /* --------------------------------------------------------------------------------------------- */
1040 static FileProgressStatus
1041 query_recursive (file_op_context_t
*ctx
, const char *s
)
1047 FileProgressStatus (*f
) (file_op_context_t
*, enum OperationMode
, const char *);
1051 pntr
.f
= real_query_recursive
;
1053 if (mc_global
.we_are_background
)
1054 return parent_call (pntr
.p
, ctx
, 1, strlen (s
), s
);
1056 return real_query_recursive (ctx
, Foreground
, s
);
1059 /* --------------------------------------------------------------------------------------------- */
1061 static FileProgressStatus
1062 query_replace (file_op_context_t
*ctx
, const char *src
, struct stat
*src_stat
, const char *dst
,
1063 struct stat
*dst_stat
)
1069 FileProgressStatus (*f
) (file_op_context_t
*, enum OperationMode
, const char *,
1070 struct stat
*, const char *, struct stat
*);
1074 pntr
.f
= file_progress_real_query_replace
;
1076 if (mc_global
.we_are_background
)
1077 return parent_call (pntr
.p
, ctx
, 4, strlen (src
), src
, sizeof (struct stat
), src_stat
,
1078 strlen (dst
), dst
, sizeof (struct stat
), dst_stat
);
1080 return file_progress_real_query_replace (ctx
, Foreground
, src
, src_stat
, dst
, dst_stat
);
1084 /* --------------------------------------------------------------------------------------------- */
1086 static FileProgressStatus
1087 do_file_error (gboolean allow_retry
, const char *str
)
1089 return real_do_file_error (Foreground
, allow_retry
, str
);
1092 /* --------------------------------------------------------------------------------------------- */
1094 static FileProgressStatus
1095 query_recursive (file_op_context_t
*ctx
, const char *s
)
1097 return real_query_recursive (ctx
, Foreground
, s
);
1100 /* --------------------------------------------------------------------------------------------- */
1102 static FileProgressStatus
1103 query_replace (file_op_context_t
*ctx
, const char *src
, struct stat
*src_stat
, const char *dst
,
1104 struct stat
*dst_stat
)
1106 return file_progress_real_query_replace (ctx
, Foreground
, src
, src_stat
, dst
, dst_stat
);
1109 #endif /* !ENABLE_BACKGROUND */
1111 /* --------------------------------------------------------------------------------------------- */
1112 /** Report error with two files */
1114 static FileProgressStatus
1115 files_error (const char *format
, const char *file1
, const char *file2
)
1117 char buf
[BUF_MEDIUM
];
1118 char *nfile1
, *nfile2
;
1120 nfile1
= g_strdup (path_trunc (file1
, 15));
1121 nfile2
= g_strdup (path_trunc (file2
, 15));
1122 g_snprintf (buf
, sizeof (buf
), format
, nfile1
, nfile2
, unix_error_string (errno
));
1126 return do_file_error (TRUE
, buf
);
1131 /* --------------------------------------------------------------------------------------------- */
1134 copy_file_file_display_progress (file_op_context_t
*ctx
, gint64 tv_current
, off_t file_part
,
1139 /* Update rotating dash after some time */
1143 dt
= (tv_current
- ctx
->transfer_start
) / G_USEC_PER_SEC
;
1146 ctx
->eta_secs
= 0.0;
1148 ctx
->eta_secs
= ((dt
/ (double) file_part
) * file_size
) - dt
;
1150 /* Compute BPS rate */
1151 ctx
->bps_time
= MAX (1, dt
);
1152 ctx
->bps
= file_part
/ ctx
->bps_time
;
1154 /* Compute total ETA and BPS */
1155 if (ctx
->total_bytes
!= 0)
1159 total_secs
= (tv_current
- ctx
->total_transfer_start
) / G_USEC_PER_SEC
;
1160 total_secs
= MAX (1, total_secs
);
1162 ctx
->total_bps
= ctx
->copied_bytes
/ total_secs
;
1163 const uintmax_t remain_bytes
= ctx
->total_bytes
- ctx
->copied_bytes
;
1164 ctx
->total_eta_secs
= ctx
->total_bps
!= 0 ? remain_bytes
/ ctx
->total_bps
: 0;
1168 /* --------------------------------------------------------------------------------------------- */
1171 try_remove_file (file_op_context_t
*ctx
, const vfs_path_t
*vpath
, FileProgressStatus
*status
)
1173 while (mc_unlink (vpath
) != 0 && !ctx
->ignore_all
)
1175 *status
= file_error (TRUE
, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath
));
1176 if (*status
== FILE_RETRY
)
1178 if (*status
== FILE_IGNORE_ALL
)
1179 ctx
->ignore_all
= TRUE
;
1186 /* --------------------------------------------------------------------------------------------- */
1188 /* {{{ Move routines */
1191 * Move single file or one of many files from one location to another.
1193 * @panel pointer to panel in case of single file, NULL otherwise
1194 * @ctx file operation context object
1195 * @s source file name
1196 * @d destination file name
1198 * @return operation result
1200 static FileProgressStatus
1201 move_file_file (const WPanel
*panel
, file_op_context_t
*ctx
, const char *s
, const char *d
)
1203 struct stat src_stat
, dst_stat
;
1204 FileProgressStatus return_status
= FILE_CONT
;
1205 gboolean copy_done
= FALSE
;
1206 gboolean old_ask_overwrite
;
1207 vfs_path_t
*src_vpath
, *dst_vpath
;
1209 src_vpath
= vfs_path_from_str (s
);
1210 dst_vpath
= vfs_path_from_str (d
);
1212 file_progress_show_source (ctx
, src_vpath
);
1213 file_progress_show_target (ctx
, dst_vpath
);
1215 /* FIXME: do we really need to check buttons in case of single file? */
1216 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
1218 return_status
= FILE_ABORT
;
1224 while (mc_lstat (src_vpath
, &src_stat
) != 0)
1226 /* Source doesn't exist */
1227 if (ctx
->ignore_all
)
1228 return_status
= FILE_IGNORE_ALL
;
1231 return_status
= file_error (TRUE
, _("Cannot stat file \"%s\"\n%s"), s
);
1232 if (return_status
== FILE_IGNORE_ALL
)
1233 ctx
->ignore_all
= TRUE
;
1236 if (return_status
!= FILE_RETRY
)
1240 if (mc_lstat (dst_vpath
, &dst_stat
) == 0)
1242 if (check_same_file (s
, &src_stat
, d
, &dst_stat
, &return_status
))
1245 if (S_ISDIR (dst_stat
.st_mode
))
1247 message (D_ERROR
, MSG_ERROR
, _("Cannot overwrite directory \"%s\""), d
);
1249 return_status
= FILE_SKIP
;
1253 if (confirm_overwrite
)
1255 return_status
= query_replace (ctx
, s
, &src_stat
, d
, &dst_stat
);
1256 if (return_status
!= FILE_CONT
)
1259 /* Ok to overwrite */
1262 if (!ctx
->do_append
)
1264 if (S_ISLNK (src_stat
.st_mode
) && ctx
->stable_symlinks
)
1266 return_status
= make_symlink (ctx
, src_vpath
, dst_vpath
);
1267 if (return_status
== FILE_CONT
)
1271 mc_timesbuf_t times
;
1273 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
1274 mc_utime (dst_vpath
, ×
);
1276 goto retry_src_remove
;
1281 if (mc_rename (src_vpath
, dst_vpath
) == 0)
1283 /* FIXME: do we really need to update progress in case of single file? */
1284 return_status
= progress_update_one (ctx
, src_stat
.st_size
);
1289 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1290 one nfs to the same, but on the server it is on two different
1291 filesystems. Then nfs returns EIO instead of EXDEV.
1292 Hope it will not hurt if we always in case of error try to copy/delete. */
1294 errno
= EXDEV
; /* Hack to copy (append) the file and then delete it */
1298 if (ctx
->ignore_all
)
1299 return_status
= FILE_IGNORE_ALL
;
1302 return_status
= files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s
, d
);
1303 if (return_status
== FILE_IGNORE_ALL
)
1304 ctx
->ignore_all
= TRUE
;
1305 if (return_status
== FILE_RETRY
)
1313 /* Failed rename -> copy the file instead */
1316 /* In case of single file, calculate totals. In case of many files,
1317 totals are calculated already. */
1319 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1320 FILEGUI_DIALOG_ONE_ITEM
);
1321 if (return_status
!= FILE_CONT
)
1325 old_ask_overwrite
= ctx
->ask_overwrite
;
1326 ctx
->ask_overwrite
= FALSE
;
1327 return_status
= copy_file_file (ctx
, s
, d
);
1328 ctx
->ask_overwrite
= old_ask_overwrite
;
1329 if (return_status
!= FILE_CONT
)
1334 /* FIXME: there is no need to update progress and check buttons
1335 at the finish of single file operation. */
1338 file_progress_show_source (ctx
, NULL
);
1340 file_progress_show (ctx
, 0, 0, "", FALSE
);
1342 return_status
= file_progress_check_buttons (ctx
);
1343 if (return_status
!= FILE_CONT
)
1350 if (!try_remove_file (ctx
, src_vpath
, &return_status
) && panel
== NULL
)
1354 return_status
= progress_update_one (ctx
, src_stat
.st_size
);
1357 vfs_path_free (src_vpath
, TRUE
);
1358 vfs_path_free (dst_vpath
, TRUE
);
1360 return return_status
;
1365 /* --------------------------------------------------------------------------------------------- */
1366 /* {{{ Erase routines */
1367 /** Don't update progress status if progress_count==NULL */
1369 static FileProgressStatus
1370 erase_file (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
1373 FileProgressStatus return_status
;
1375 /* check buttons if deleting info was changed */
1376 if (file_progress_show_deleting (ctx
, vpath
, &ctx
->progress_count
))
1378 file_progress_show_count (ctx
, ctx
->progress_count
, ctx
->total_count
);
1379 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
1385 if (ctx
->progress_count
!= 0 && mc_lstat (vpath
, &buf
) != 0)
1387 /* ignore, most likely the mc_unlink fails, too */
1391 if (!try_remove_file (ctx
, vpath
, &return_status
) && return_status
== FILE_ABORT
)
1394 if (ctx
->progress_count
== 0)
1397 return file_progress_check_buttons (ctx
);
1400 /* --------------------------------------------------------------------------------------------- */
1402 static FileProgressStatus
1403 try_erase_dir (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
1406 FileProgressStatus return_status
= FILE_CONT
;
1408 dir
= vfs_path_as_str (vpath
);
1410 while (my_rmdir (dir
) != 0 && !ctx
->ignore_all
)
1412 return_status
= file_error (TRUE
, _("Cannot remove directory \"%s\"\n%s"), dir
);
1413 if (return_status
== FILE_IGNORE_ALL
)
1414 ctx
->ignore_all
= TRUE
;
1415 if (return_status
!= FILE_RETRY
)
1419 return return_status
;
1422 /* --------------------------------------------------------------------------------------------- */
1425 Recursive removal of files
1426 abort -> cancel stack
1427 ignore -> warn every level, gets default
1428 ignore_all -> remove as much as possible
1430 static FileProgressStatus
1431 recursive_erase (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
1433 struct vfs_dirent
*next
;
1435 FileProgressStatus return_status
= FILE_CONT
;
1437 reading
= mc_opendir (vpath
);
1438 if (reading
== NULL
)
1441 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
1443 vfs_path_t
*tmp_vpath
;
1446 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
1449 tmp_vpath
= vfs_path_append_new (vpath
, next
->d_name
, (char *) NULL
);
1450 if (mc_lstat (tmp_vpath
, &buf
) != 0)
1452 mc_closedir (reading
);
1453 vfs_path_free (tmp_vpath
, TRUE
);
1456 if (S_ISDIR (buf
.st_mode
))
1457 return_status
= recursive_erase (ctx
, tmp_vpath
);
1459 return_status
= erase_file (ctx
, tmp_vpath
);
1460 vfs_path_free (tmp_vpath
, TRUE
);
1462 mc_closedir (reading
);
1464 if (return_status
== FILE_ABORT
)
1467 file_progress_show_deleting (ctx
, vpath
, NULL
);
1468 file_progress_show_count (ctx
, ctx
->progress_count
, ctx
->total_count
);
1469 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
1474 return try_erase_dir (ctx
, vpath
);
1477 /* --------------------------------------------------------------------------------------------- */
1479 * Check if directory is empty or not.
1481 * @param vpath directory handler
1483 * @returns -1 on error,
1484 * 1 if there are no entries besides "." and ".." in the directory path points to,
1487 * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
1488 * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
1489 * in SHELL) don't return "." and ".." entries.
1492 check_dir_is_empty (const vfs_path_t
*vpath
)
1495 struct vfs_dirent
*d
;
1498 dir
= mc_opendir (vpath
);
1502 for (d
= mc_readdir (dir
); d
!= NULL
; d
= mc_readdir (dir
))
1503 if (!DIR_IS_DOT (d
->d_name
) && !DIR_IS_DOTDOT (d
->d_name
))
1513 /* --------------------------------------------------------------------------------------------- */
1515 static FileProgressStatus
1516 erase_dir_iff_empty (file_op_context_t
*ctx
, const vfs_path_t
*vpath
, size_t count
)
1518 file_progress_show_deleting (ctx
, vpath
, NULL
);
1519 file_progress_show_count (ctx
, count
, ctx
->total_count
);
1520 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
1525 if (check_dir_is_empty (vpath
) != 1)
1528 /* not empty or error */
1529 return try_erase_dir (ctx
, vpath
);
1532 /* --------------------------------------------------------------------------------------------- */
1535 erase_dir_after_copy (file_op_context_t
*ctx
, const vfs_path_t
*vpath
, FileProgressStatus
*status
)
1537 if (ctx
->erase_at_end
&& erase_list
!= NULL
)
1539 /* Reset progress count before delete to avoid counting files twice */
1540 ctx
->progress_count
= ctx
->prev_progress_count
;
1542 while (!g_queue_is_empty (erase_list
) && *status
!= FILE_ABORT
)
1546 lp
= (link_t
*) g_queue_pop_head (erase_list
);
1548 if (S_ISDIR (lp
->st_mode
))
1549 *status
= erase_dir_iff_empty (ctx
, lp
->src_vpath
, ctx
->progress_count
);
1551 *status
= erase_file (ctx
, lp
->src_vpath
);
1556 /* Save progress counter before move next directory */
1557 ctx
->prev_progress_count
= ctx
->progress_count
;
1560 erase_dir_iff_empty (ctx
, vpath
, ctx
->progress_count
);
1565 /* --------------------------------------------------------------------------------------------- */
1568 * Move single directory or one of many directories from one location to another.
1570 * @panel pointer to panel in case of single directory, NULL otherwise
1571 * @ctx file operation context object
1572 * @s source directory name
1573 * @d destination directory name
1575 * @return operation result
1577 static FileProgressStatus
1578 do_move_dir_dir (const WPanel
*panel
, file_op_context_t
*ctx
, const char *s
, const char *d
)
1580 struct stat src_stat
, dst_stat
;
1581 FileProgressStatus return_status
= FILE_CONT
;
1582 gboolean move_over
= FALSE
;
1584 vfs_path_t
*src_vpath
, *dst_vpath
;
1586 src_vpath
= vfs_path_from_str (s
);
1587 dst_vpath
= vfs_path_from_str (d
);
1589 file_progress_show_source (ctx
, src_vpath
);
1590 file_progress_show_target (ctx
, dst_vpath
);
1592 /* FIXME: do we really need to check buttons in case of single directory? */
1593 if (panel
!= NULL
&& file_progress_check_buttons (ctx
) == FILE_ABORT
)
1595 return_status
= FILE_ABORT
;
1601 mc_stat (src_vpath
, &src_stat
);
1603 dstat_ok
= (mc_stat (dst_vpath
, &dst_stat
) == 0);
1605 if (dstat_ok
&& check_same_file (s
, &src_stat
, d
, &dst_stat
, &return_status
))
1609 ; /* destination doesn't exist */
1610 else if (!ctx
->dive_into_subdirs
)
1617 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
1618 vfs_path_free (tmp
, TRUE
);
1621 d
= vfs_path_as_str (dst_vpath
);
1623 /* Check if the user inputted an existing dir */
1625 if (mc_stat (dst_vpath
, &dst_stat
) == 0)
1631 /* In case of single directory, calculate totals. In case of many directories,
1632 totals are calculated already. */
1634 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1635 FILEGUI_DIALOG_MULTI_ITEM
);
1636 if (return_status
!= FILE_CONT
)
1640 return_status
= copy_dir_dir (ctx
, s
, d
, FALSE
, TRUE
, TRUE
, NULL
);
1642 if (return_status
!= FILE_CONT
)
1646 else if (ctx
->ignore_all
)
1647 return_status
= FILE_IGNORE_ALL
;
1650 if (S_ISDIR (dst_stat
.st_mode
))
1651 return_status
= file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), d
);
1653 return_status
= file_error (TRUE
, _("Cannot overwrite file \"%s\"\n%s"), d
);
1654 if (return_status
== FILE_IGNORE_ALL
)
1655 ctx
->ignore_all
= TRUE
;
1656 if (return_status
== FILE_RETRY
)
1657 goto retry_dst_stat
;
1664 if (mc_rename (src_vpath
, dst_vpath
) == 0)
1666 return_status
= FILE_CONT
;
1672 if (!ctx
->ignore_all
)
1674 return_status
= files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s
, d
);
1675 if (return_status
== FILE_IGNORE_ALL
)
1676 ctx
->ignore_all
= TRUE
;
1677 if (return_status
== FILE_RETRY
)
1683 /* Failed because of filesystem boundary -> copy dir instead */
1686 /* In case of single directory, calculate totals. In case of many directories,
1687 totals are calculated already. */
1689 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1690 FILEGUI_DIALOG_MULTI_ITEM
);
1691 if (return_status
!= FILE_CONT
)
1695 return_status
= copy_dir_dir (ctx
, s
, d
, FALSE
, FALSE
, TRUE
, NULL
);
1697 if (return_status
!= FILE_CONT
)
1701 /* FIXME: there is no need to update progress and check buttons
1702 at the finish of single directory operation. */
1705 file_progress_show_source (ctx
, NULL
);
1706 file_progress_show_target (ctx
, NULL
);
1708 file_progress_show (ctx
, 0, 0, "", FALSE
);
1710 return_status
= file_progress_check_buttons (ctx
);
1711 if (return_status
!= FILE_CONT
)
1717 erase_dir_after_copy (ctx
, src_vpath
, &return_status
);
1720 erase_list
= free_erase_list (erase_list
);
1722 vfs_path_free (src_vpath
, TRUE
);
1723 vfs_path_free (dst_vpath
, TRUE
);
1724 return return_status
;
1727 /* --------------------------------------------------------------------------------------------- */
1729 /* {{{ Panel operate routines */
1732 * Return currently selected entry name or the name of the first marked
1733 * entry if there is one.
1737 panel_get_file (const WPanel
*panel
)
1739 const file_entry_t
*fe
;
1741 if (get_current_type () == view_tree
)
1744 const vfs_path_t
*selected_name
;
1746 tree
= (WTree
*) get_panel_widget (get_current_index ());
1747 selected_name
= tree_selected_name (tree
);
1748 return vfs_path_as_str (selected_name
);
1751 if (panel
->marked
!= 0)
1755 for (i
= 0; i
< panel
->dir
.len
; i
++)
1756 if (panel
->dir
.list
[i
].f
.marked
!= 0)
1757 return panel
->dir
.list
[i
].fname
->str
;
1760 fe
= panel_current_entry (panel
);
1762 return (fe
== NULL
? NULL
: fe
->fname
->str
);
1765 /* --------------------------------------------------------------------------------------------- */
1768 check_single_entry (const WPanel
*panel
, gboolean force_single
, struct stat
*src_stat
)
1775 const file_entry_t
*fe
;
1777 fe
= panel_current_entry (panel
);
1778 source
= fe
== NULL
? NULL
: fe
->fname
->str
;
1781 source
= panel_get_file (panel
);
1786 ok
= !DIR_IS_DOTDOT (source
);
1789 message (D_ERROR
, MSG_ERROR
, _("Cannot operate on \"..\"!"));
1792 vfs_path_t
*source_vpath
;
1794 source_vpath
= vfs_path_from_str (source
);
1796 /* Update stat to get actual info */
1797 ok
= mc_lstat (source_vpath
, src_stat
) == 0;
1800 message (D_ERROR
, MSG_ERROR
, _("Cannot stat \"%s\"\n%s"),
1801 path_trunc (source
, 30), unix_error_string (errno
));
1803 /* Directory was changed outside MC. Reload it forced */
1804 if (!panel
->is_panelized
)
1806 panel_update_flags_t flags
= UP_RELOAD
;
1808 /* don't update panelized panel */
1809 if (get_other_type () == view_listing
&& other_panel
->is_panelized
)
1810 flags
|= UP_ONLY_CURRENT
;
1812 update_panels (flags
, UP_KEEPSEL
);
1816 vfs_path_free (source_vpath
, TRUE
);
1819 return ok
? source
: NULL
;
1822 /* --------------------------------------------------------------------------------------------- */
1824 * Generate user prompt for panel operation.
1825 * src_stat must be not NULL for single source, and NULL for multiple sources
1829 panel_operate_generate_prompt (const WPanel
*panel
, FileOperation operation
,
1830 const struct stat
*src_stat
)
1833 char *format_string
;
1836 static gboolean i18n_flag
= FALSE
;
1841 for (i
= G_N_ELEMENTS (op_names1
); i
-- != 0;)
1842 op_names1
[i
] = Q_ (op_names1
[i
]);
1845 for (i
= G_N_ELEMENTS (prompt_parts
); i
-- != 0;)
1846 prompt_parts
[i
] = _(prompt_parts
[i
]);
1848 one_format
= _(one_format
);
1849 many_format
= _(many_format
);
1850 #endif /* ENABLE_NLS */
1854 /* Possible prompts:
1856 * "Copy file \"%s\" with source mask:"
1857 * "Copy %d files with source mask:"
1858 * "Copy directory \"%s\" with source mask:"
1859 * "Copy %d directories with source mask:"
1860 * "Copy %d files/directories with source mask:"
1862 * "Move file \"%s\" with source mask:"
1863 * "Move %d files with source mask:"
1864 * "Move directory \"%s\" with source mask:"
1865 * "Move %d directories with source mask:"
1866 * "Move %d files/directories with source mask:"
1868 * "Delete file \"%s\"?"
1869 * "Delete %d files?"
1870 * "Delete directory \"%s\"?"
1871 * "Delete %d directories?"
1872 * "Delete %d files/directories?"
1875 cp
= (src_stat
!= NULL
? one_format
: many_format
);
1877 /* 1. Substitute %o */
1878 format_string
= str_replace_all (cp
, "%o", op_names1
[(int) operation
]);
1880 /* 2. Substitute %n */
1881 cp
= operation
== OP_DELETE
? "\n" : " ";
1883 format_string
= str_replace_all (sp
, "%n", cp
);
1886 /* 3. Substitute %f */
1887 if (src_stat
!= NULL
)
1888 cp
= S_ISDIR (src_stat
->st_mode
) ? prompt_parts
[2] : prompt_parts
[0];
1889 else if (panel
->marked
== panel
->dirs_marked
)
1890 cp
= prompt_parts
[3];
1892 cp
= panel
->dirs_marked
!= 0 ? prompt_parts
[4] : prompt_parts
[1];
1895 format_string
= str_replace_all (sp
, "%f", cp
);
1898 /* 4. Substitute %m */
1899 cp
= operation
== OP_DELETE
? "?" : prompt_parts
[5];
1901 format_string
= str_replace_all (sp
, "%m", cp
);
1904 return format_string
;
1907 /* --------------------------------------------------------------------------------------------- */
1910 do_confirm_copy_move (const WPanel
*panel
, gboolean force_single
, const char *source
,
1911 struct stat
*src_stat
, file_op_context_t
*ctx
, gboolean
*do_bg
)
1913 const char *tmp_dest_dir
;
1918 /* Forced single operations default to the original name */
1920 tmp_dest_dir
= source
;
1921 else if (get_other_type () == view_listing
)
1922 tmp_dest_dir
= vfs_path_as_str (other_panel
->cwd_vpath
);
1924 tmp_dest_dir
= vfs_path_as_str (panel
->cwd_vpath
);
1927 * Add trailing backslash only when do non-local ops.
1928 * It saves user from occasional file renames (when destination
1931 if (!force_single
&& tmp_dest_dir
!= NULL
&& tmp_dest_dir
[0] != '\0'
1932 && !IS_PATH_SEP (tmp_dest_dir
[strlen (tmp_dest_dir
) - 1]))
1934 /* add trailing separator */
1935 dest_dir
= g_strconcat (tmp_dest_dir
, PATH_SEP_STR
, (char *) NULL
);
1940 dest_dir
= g_strdup (tmp_dest_dir
);
1943 if (dest_dir
== NULL
)
1949 /* Generate confirmation prompt */
1950 format
= panel_operate_generate_prompt (panel
, ctx
->operation
, src_stat
);
1952 ret
= file_mask_dialog (ctx
, source
!= NULL
, format
,
1953 source
!= NULL
? source
: (const void *) &panel
->marked
, dest_dir
,
1962 /* --------------------------------------------------------------------------------------------- */
1965 do_confirm_erase (const WPanel
*panel
, const char *source
, struct stat
*src_stat
)
1969 char fmd_buf
[BUF_MEDIUM
];
1974 /* Generate confirmation prompt */
1975 format
= panel_operate_generate_prompt (panel
, OP_DELETE
, src_stat
);
1978 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, panel
->marked
);
1981 const int fmd_xlen
= 64;
1983 i
= fmd_xlen
- str_term_width1 (format
) - 4;
1984 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, str_trunc (source
, i
));
1992 i
= query_dialog (op_names
[OP_DELETE
], fmd_buf
, D_ERROR
, 2, _("&Yes"), _("&No"));
1997 /* --------------------------------------------------------------------------------------------- */
1999 static FileProgressStatus
2000 operate_single_file (const WPanel
*panel
, file_op_context_t
*ctx
, const char *src
,
2001 struct stat
*src_stat
, const char *dest
, filegui_dialog_type_t dialog_type
)
2003 FileProgressStatus value
;
2004 vfs_path_t
*src_vpath
;
2007 if (g_path_is_absolute (src
))
2008 src_vpath
= vfs_path_from_str (src
);
2010 src_vpath
= vfs_path_append_new (panel
->cwd_vpath
, src
, (char *) NULL
);
2012 is_file
= !S_ISDIR (src_stat
->st_mode
);
2013 /* Is link to directory? */
2018 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
2019 is_file
= !(is_link
&& ctx
->follow_links
);
2022 if (ctx
->operation
== OP_DELETE
)
2024 value
= panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
, dialog_type
);
2025 if (value
== FILE_CONT
)
2028 value
= erase_file (ctx
, src_vpath
);
2030 value
= erase_dir (ctx
, src_vpath
);
2037 src
= vfs_path_as_str (src_vpath
);
2039 temp
= build_dest (ctx
, src
, dest
, &value
);
2044 switch (ctx
->operation
)
2047 /* we use file_mask_op_follow_links only with OP_COPY */
2048 ctx
->stat_func (src_vpath
, src_stat
);
2051 panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
,
2053 if (value
== FILE_CONT
)
2055 is_file
= !S_ISDIR (src_stat
->st_mode
);
2056 /* Is link to directory? */
2061 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
2062 is_file
= !(is_link
&& ctx
->follow_links
);
2066 value
= copy_file_file (ctx
, src
, dest
);
2068 value
= copy_dir_dir (ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2073 #ifdef ENABLE_BACKGROUND
2074 if (!mc_global
.we_are_background
)
2076 /* create UI to show confirmation dialog */
2077 file_progress_ui_create (ctx
, TRUE
, FILEGUI_DIALOG_ONE_ITEM
);
2080 value
= move_file_file (panel
, ctx
, src
, dest
);
2082 value
= do_move_dir_dir (panel
, ctx
, src
, dest
);
2086 /* Unknown file operation */
2094 vfs_path_free (src_vpath
, TRUE
);
2099 /* --------------------------------------------------------------------------------------------- */
2101 static FileProgressStatus
2102 operate_one_file (const WPanel
*panel
, file_op_context_t
*ctx
, const char *src
,
2103 struct stat
*src_stat
, const char *dest
)
2105 FileProgressStatus value
= FILE_CONT
;
2106 vfs_path_t
*src_vpath
;
2109 if (g_path_is_absolute (src
))
2110 src_vpath
= vfs_path_from_str (src
);
2112 src_vpath
= vfs_path_append_new (panel
->cwd_vpath
, src
, (char *) NULL
);
2114 is_file
= !S_ISDIR (src_stat
->st_mode
);
2116 if (ctx
->operation
== OP_DELETE
)
2119 value
= erase_file (ctx
, src_vpath
);
2121 value
= erase_dir (ctx
, src_vpath
);
2127 src
= vfs_path_as_str (src_vpath
);
2129 temp
= build_dest (ctx
, src
, dest
, &value
);
2134 switch (ctx
->operation
)
2137 /* we use file_mask_op_follow_links only with OP_COPY */
2138 ctx
->stat_func (src_vpath
, src_stat
);
2139 is_file
= !S_ISDIR (src_stat
->st_mode
);
2142 value
= copy_file_file (ctx
, src
, dest
);
2144 value
= copy_dir_dir (ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2145 dest_dirs
= free_linklist (dest_dirs
);
2150 value
= move_file_file (NULL
, ctx
, src
, dest
);
2152 value
= do_move_dir_dir (NULL
, ctx
, src
, dest
);
2156 /* Unknown file operation */
2164 vfs_path_free (src_vpath
, TRUE
);
2169 /* --------------------------------------------------------------------------------------------- */
2171 #ifdef ENABLE_BACKGROUND
2173 end_bg_process (file_op_context_t
*ctx
, enum OperationMode mode
)
2180 unregister_task_with_pid (pid
);
2181 /* file_op_context_destroy(ctx); */
2187 /* --------------------------------------------------------------------------------------------- */
2190 * On Solaris, ENOTSUP != EOPNOTSUPP. Some FS also return ENOSYS or EINVAL as "not implemented".
2191 * On some Linux kernels (tested on 4.9, 5.4) there is ENOTTY on tmpfs.
2193 static inline gboolean
2194 attrs_ignore_error (const int e
)
2196 return (e
== ENOTSUP
|| e
== EOPNOTSUPP
|| e
== ENOSYS
|| e
== EINVAL
|| e
== ENOTTY
2197 || e
== ELOOP
|| e
== ENXIO
);
2200 /* --------------------------------------------------------------------------------------------- */
2201 /*** public functions ****************************************************************************/
2202 /* --------------------------------------------------------------------------------------------- */
2204 /* Is file symlink to directory or not.
2206 * @param path file or directory
2207 * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2208 * @param stale_link TRUE if file is stale link to directory
2210 * @return TRUE if file symlink to directory, ELSE otherwise.
2213 file_is_symlink_to_dir (const vfs_path_t
*vpath
, struct stat
*st
, gboolean
*stale_link
)
2216 gboolean stale
= FALSE
;
2217 gboolean res
= FALSE
;
2223 if (mc_lstat (vpath
, st
) != 0)
2227 if (S_ISLNK (st
->st_mode
))
2231 stale
= (mc_stat (vpath
, &st3
) != 0);
2234 res
= (S_ISDIR (st3
.st_mode
) != 0);
2238 if (stale_link
!= NULL
)
2239 *stale_link
= stale
;
2244 /* --------------------------------------------------------------------------------------------- */
2247 copy_file_file (file_op_context_t
*ctx
, const char *src_path
, const char *dst_path
)
2249 uid_t src_uid
= (uid_t
) (-1);
2250 gid_t src_gid
= (gid_t
) (-1);
2252 int src_desc
, dest_desc
= -1;
2253 mode_t src_mode
= 0; /* The mode of the source file */
2254 struct stat src_stat
, dst_stat
;
2255 mc_timesbuf_t times
;
2256 unsigned long attrs
= 0;
2257 gboolean attrs_ok
= ctx
->preserve
;
2258 gboolean dst_exists
= FALSE
, appending
= FALSE
;
2259 off_t file_size
= -1;
2260 FileProgressStatus return_status
, temp_status
;
2261 dest_status_t dst_status
= DEST_NONE
;
2263 vfs_path_t
*src_vpath
= NULL
, *dst_vpath
= NULL
;
2266 /* Keep the non-default value applied in chain of calls:
2267 move_file_file() -> file_progress_real_query_replace()
2268 move_file_file() -> copy_file_file() */
2269 if (ctx
->do_reget
< 0)
2272 return_status
= FILE_RETRY
;
2274 dst_vpath
= vfs_path_from_str (dst_path
);
2275 src_vpath
= vfs_path_from_str (src_path
);
2277 file_progress_show_source (ctx
, src_vpath
);
2278 file_progress_show_target (ctx
, dst_vpath
);
2280 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
2282 return_status
= FILE_ABORT
;
2288 while (mc_stat (dst_vpath
, &dst_stat
) == 0)
2290 if (S_ISDIR (dst_stat
.st_mode
))
2292 if (ctx
->ignore_all
)
2293 return_status
= FILE_IGNORE_ALL
;
2297 file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), dst_path
);
2298 if (return_status
== FILE_IGNORE_ALL
)
2299 ctx
->ignore_all
= TRUE
;
2300 if (return_status
== FILE_RETRY
)
2310 while ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2312 if (ctx
->ignore_all
)
2313 return_status
= FILE_IGNORE_ALL
;
2316 return_status
= file_error (TRUE
, _("Cannot stat source file \"%s\"\n%s"), src_path
);
2317 if (return_status
== FILE_IGNORE_ALL
)
2318 ctx
->ignore_all
= TRUE
;
2321 if (return_status
!= FILE_RETRY
)
2325 while (attrs_ok
&& mc_fgetflags (src_vpath
, &attrs
) != 0)
2329 /* don't show an error message if attributes aren't supported in this FS */
2330 if (attrs_ignore_error (errno
))
2331 return_status
= FILE_CONT
;
2332 else if (ctx
->ignore_all
)
2333 return_status
= FILE_IGNORE_ALL
;
2337 file_error (TRUE
, _("Cannot get ext2 attributes of source file \"%s\"\n%s"),
2339 if (return_status
== FILE_IGNORE_ALL
)
2340 ctx
->ignore_all
= TRUE
;
2341 if (return_status
== FILE_ABORT
)
2345 if (return_status
!= FILE_RETRY
)
2348 /* yet another attempt */
2354 /* Destination already exists */
2355 if (check_same_file (src_path
, &src_stat
, dst_path
, &dst_stat
, &return_status
))
2358 /* Should we replace destination? */
2359 if (ctx
->ask_overwrite
)
2362 return_status
= query_replace (ctx
, src_path
, &src_stat
, dst_path
, &dst_stat
);
2363 if (return_status
!= FILE_CONT
)
2368 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
2370 if (!ctx
->do_append
)
2372 /* Check the hardlinks */
2373 if (!ctx
->follow_links
)
2375 switch (check_hardlinks (src_vpath
, &src_stat
, dst_vpath
, &ctx
->ignore_all
))
2378 /* We have made a hardlink - no more processing is necessary */
2379 return_status
= FILE_CONT
;
2382 case HARDLINK_ABORT
:
2383 return_status
= FILE_ABORT
;
2391 if (S_ISLNK (src_stat
.st_mode
))
2393 return_status
= make_symlink (ctx
, src_vpath
, dst_vpath
);
2394 if (return_status
== FILE_CONT
&& ctx
->preserve
)
2396 mc_utime (dst_vpath
, ×
);
2398 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
2402 /* don't show an error message if attributes aren't supported in this FS */
2403 if (attrs_ignore_error (errno
))
2404 return_status
= FILE_CONT
;
2405 else if (return_status
== FILE_IGNORE_ALL
)
2406 ctx
->ignore_all
= TRUE
;
2410 _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2413 if (return_status
!= FILE_RETRY
)
2416 /* yet another attempt */
2423 if (S_ISCHR (src_stat
.st_mode
) || S_ISBLK (src_stat
.st_mode
) || S_ISFIFO (src_stat
.st_mode
)
2424 || S_ISNAM (src_stat
.st_mode
) || S_ISSOCK (src_stat
.st_mode
))
2428 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2429 rdev
= src_stat
.st_rdev
;
2432 while (mc_mknod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
, rdev
) < 0
2433 && !ctx
->ignore_all
)
2436 file_error (TRUE
, _("Cannot create special file \"%s\"\n%s"), dst_path
);
2437 if (return_status
== FILE_RETRY
)
2439 if (return_status
== FILE_IGNORE_ALL
)
2440 ctx
->ignore_all
= TRUE
;
2445 while (ctx
->preserve_uidgid
2446 && mc_chown (dst_vpath
, src_stat
.st_uid
, src_stat
.st_gid
) != 0
2447 && !ctx
->ignore_all
)
2449 temp_status
= file_error (TRUE
, _("Cannot chown target file \"%s\"\n%s"), dst_path
);
2450 if (temp_status
== FILE_IGNORE
)
2452 if (temp_status
== FILE_IGNORE_ALL
)
2453 ctx
->ignore_all
= TRUE
;
2454 if (temp_status
!= FILE_RETRY
)
2456 return_status
= temp_status
;
2461 while (ctx
->preserve
&& mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
) != 0
2462 && !ctx
->ignore_all
)
2464 temp_status
= file_error (TRUE
, _("Cannot chmod target file \"%s\"\n%s"), dst_path
);
2465 if (temp_status
== FILE_IGNORE
)
2467 if (temp_status
== FILE_IGNORE_ALL
)
2468 ctx
->ignore_all
= TRUE
;
2469 if (temp_status
!= FILE_RETRY
)
2471 return_status
= temp_status
;
2476 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
2480 /* don't show an error message if attributes aren't supported in this FS */
2481 if (attrs_ignore_error (errno
))
2485 file_error (TRUE
, _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2487 if (temp_status
== FILE_IGNORE
)
2489 if (temp_status
== FILE_IGNORE_ALL
)
2490 ctx
->ignore_all
= TRUE
;
2491 if (temp_status
!= FILE_RETRY
)
2493 return_status
= temp_status
;
2497 /* yet another attempt */
2501 return_status
= FILE_CONT
;
2502 mc_utime (dst_vpath
, ×
);
2507 ctx
->transfer_start
= g_get_monotonic_time ();
2509 while ((src_desc
= mc_open (src_vpath
, O_RDONLY
| O_LINEAR
)) < 0 && !ctx
->ignore_all
)
2511 return_status
= file_error (TRUE
, _("Cannot open source file \"%s\"\n%s"), src_path
);
2512 if (return_status
== FILE_RETRY
)
2514 if (return_status
== FILE_IGNORE_ALL
)
2515 ctx
->ignore_all
= TRUE
;
2516 if (return_status
== FILE_IGNORE
)
2518 ctx
->do_append
= FALSE
;
2522 if (ctx
->do_reget
!= 0 && mc_lseek (src_desc
, ctx
->do_reget
, SEEK_SET
) != ctx
->do_reget
)
2524 message (D_ERROR
, _("Warning"), _("Reget failed, about to overwrite file"));
2526 ctx
->do_append
= FALSE
;
2529 while (mc_fstat (src_desc
, &src_stat
) != 0)
2531 if (ctx
->ignore_all
)
2532 return_status
= FILE_IGNORE_ALL
;
2535 return_status
= file_error (TRUE
, _("Cannot fstat source file \"%s\"\n%s"), src_path
);
2536 if (return_status
== FILE_RETRY
)
2538 if (return_status
== FILE_IGNORE_ALL
)
2539 ctx
->ignore_all
= TRUE
;
2540 ctx
->do_append
= FALSE
;
2545 src_mode
= src_stat
.st_mode
;
2546 src_uid
= src_stat
.st_uid
;
2547 src_gid
= src_stat
.st_gid
;
2548 file_size
= src_stat
.st_size
;
2550 open_flags
= O_WRONLY
;
2552 open_flags
|= O_CREAT
| O_EXCL
;
2553 else if (ctx
->do_append
)
2554 open_flags
|= O_APPEND
;
2556 open_flags
|= O_CREAT
| O_TRUNC
;
2558 while ((dest_desc
= mc_open (dst_vpath
, open_flags
, src_mode
)) < 0)
2560 if (errno
!= EEXIST
)
2562 if (ctx
->ignore_all
)
2563 return_status
= FILE_IGNORE_ALL
;
2567 file_error (TRUE
, _("Cannot create target file \"%s\"\n%s"), dst_path
);
2568 if (return_status
== FILE_RETRY
)
2570 if (return_status
== FILE_IGNORE_ALL
)
2571 ctx
->ignore_all
= TRUE
;
2572 ctx
->do_append
= FALSE
;
2578 /* file opened, but not fully copied */
2579 dst_status
= DEST_SHORT_QUERY
;
2581 appending
= ctx
->do_append
;
2582 ctx
->do_append
= FALSE
;
2584 /* Try clone the file first. */
2585 if (vfs_clone_file (dest_desc
, src_desc
) == 0)
2587 dst_status
= DEST_FULL
;
2588 return_status
= FILE_CONT
;
2592 /* Find out the optimal buffer size. */
2593 while (mc_fstat (dest_desc
, &dst_stat
) != 0)
2595 if (ctx
->ignore_all
)
2596 return_status
= FILE_IGNORE_ALL
;
2599 return_status
= file_error (TRUE
, _("Cannot fstat target file \"%s\"\n%s"), dst_path
);
2600 if (return_status
== FILE_RETRY
)
2602 if (return_status
== FILE_IGNORE_ALL
)
2603 ctx
->ignore_all
= TRUE
;
2608 /* try preallocate space; if fail, try copy anyway */
2609 while (mc_global
.vfs
.preallocate_space
&&
2610 vfs_preallocate (dest_desc
, file_size
, appending
? dst_stat
.st_size
: 0) != 0)
2612 if (ctx
->ignore_all
)
2614 /* cannot allocate, start the file copying anyway */
2615 return_status
= FILE_CONT
;
2620 file_error (TRUE
, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path
);
2622 if (return_status
== FILE_IGNORE_ALL
)
2623 ctx
->ignore_all
= TRUE
;
2625 if (ctx
->ignore_all
|| return_status
== FILE_IGNORE
)
2627 /* skip the space allocation error, start file copying */
2628 return_status
= FILE_CONT
;
2632 if (return_status
== FILE_ABORT
)
2634 mc_close (dest_desc
);
2636 mc_unlink (dst_vpath
);
2637 dst_status
= DEST_NONE
;
2641 /* return_status == FILE_RETRY -- try allocate space again */
2644 ctx
->eta_secs
= 0.0;
2649 if (ctx
->total_bps
== 0 || (file_size
/ ctx
->total_bps
) > FILEOP_UPDATE_INTERVAL
)
2650 file_progress_show (ctx
, 0, file_size
, "", TRUE
);
2652 file_progress_show (ctx
, 1, 1, "", TRUE
);
2655 return_status
= file_progress_check_buttons (ctx
);
2658 if (return_status
== FILE_CONT
)
2661 off_t file_part
= 0;
2662 gint64 tv_current
, tv_last_update
;
2663 gint64 tv_last_input
= 0;
2664 gint64 usecs
, update_usecs
;
2665 const char *stalled_msg
= "";
2666 gboolean is_first_time
= TRUE
;
2668 tv_last_update
= ctx
->transfer_start
;
2670 bufsize
= io_blksize (dst_stat
);
2671 buf
= g_malloc (bufsize
);
2675 ssize_t n_read
= -1, n_written
;
2676 gboolean force_update
;
2679 if (mc_ctl (src_desc
, VFS_CTL_IS_NOTREADY
, 0) == 0)
2680 while ((n_read
= mc_read (src_desc
, buf
, bufsize
)) < 0 && !ctx
->ignore_all
)
2683 file_error (TRUE
, _("Cannot read source file \"%s\"\n%s"), src_path
);
2684 if (return_status
== FILE_RETRY
)
2686 if (return_status
== FILE_IGNORE_ALL
)
2687 ctx
->ignore_all
= TRUE
;
2694 tv_current
= g_get_monotonic_time ();
2700 file_part
+= n_read
;
2702 tv_last_input
= tv_current
;
2705 while ((n_written
= mc_write (dest_desc
, t
, (size_t) n_read
)) < n_read
)
2707 gboolean write_errno_nospace
;
2711 n_read
-= n_written
;
2716 write_errno_nospace
= (n_written
< 0 && errno
== ENOSPC
);
2718 if (ctx
->ignore_all
)
2719 return_status
= FILE_IGNORE_ALL
;
2722 file_error (TRUE
, _("Cannot write target file \"%s\"\n%s"), dst_path
);
2724 if (return_status
== FILE_IGNORE
)
2726 if (write_errno_nospace
)
2730 if (return_status
== FILE_IGNORE_ALL
)
2732 ctx
->ignore_all
= TRUE
;
2733 if (write_errno_nospace
)
2736 if (return_status
!= FILE_RETRY
)
2741 ctx
->copied_bytes
= ctx
->progress_bytes
+ file_part
+ ctx
->do_reget
;
2743 usecs
= tv_current
- tv_last_update
;
2744 update_usecs
= tv_current
- tv_last_input
;
2746 if (is_first_time
|| usecs
> FILEOP_UPDATE_INTERVAL_US
)
2748 copy_file_file_display_progress (ctx
, tv_current
, file_part
,
2749 file_size
- ctx
->do_reget
);
2750 tv_last_update
= tv_current
;
2753 is_first_time
= FALSE
;
2755 if (update_usecs
> FILEOP_STALLING_INTERVAL_US
)
2756 stalled_msg
= _("(stalled)");
2758 force_update
= (tv_current
- ctx
->total_transfer_start
) > FILEOP_UPDATE_INTERVAL_US
;
2762 if (ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
2764 file_progress_show_count (ctx
, ctx
->progress_count
, ctx
->total_count
);
2765 file_progress_show_total (ctx
, ctx
->copied_bytes
, force_update
);
2768 file_progress_show (ctx
, file_part
+ ctx
->do_reget
, file_size
, stalled_msg
,
2774 return_status
= file_progress_check_buttons (ctx
);
2775 if (return_status
!= FILE_CONT
)
2780 query_dialog (Q_ ("DialogTitle|Copy"),
2781 _("Incomplete file was retrieved"), D_ERROR
, 3,
2782 _("&Delete"), _("&Keep"), _("&Continue copy"));
2788 dst_status
= DEST_SHORT_DELETE
;
2793 dst_status
= DEST_SHORT_KEEP
;
2803 /* copy successful */
2804 dst_status
= DEST_FULL
;
2810 rotate_dash (FALSE
);
2811 while (src_desc
!= -1 && mc_close (src_desc
) < 0 && !ctx
->ignore_all
)
2813 temp_status
= file_error (TRUE
, _("Cannot close source file \"%s\"\n%s"), src_path
);
2814 if (temp_status
== FILE_RETRY
)
2816 if (temp_status
== FILE_ABORT
)
2817 return_status
= temp_status
;
2818 if (temp_status
== FILE_IGNORE_ALL
)
2819 ctx
->ignore_all
= TRUE
;
2823 while (dest_desc
!= -1 && mc_close (dest_desc
) < 0 && !ctx
->ignore_all
)
2825 temp_status
= file_error (TRUE
, _("Cannot close target file \"%s\"\n%s"), dst_path
);
2826 if (temp_status
== FILE_RETRY
)
2828 if (temp_status
== FILE_IGNORE_ALL
)
2829 ctx
->ignore_all
= TRUE
;
2830 return_status
= temp_status
;
2834 if (dst_status
== DEST_SHORT_QUERY
)
2836 /* Query to remove short file */
2837 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
2838 D_ERROR
, 2, _("&Delete"), _("&Keep")) == 0)
2839 dst_status
= DEST_SHORT_DELETE
;
2841 dst_status
= DEST_SHORT_KEEP
;
2844 if (dst_status
== DEST_SHORT_DELETE
)
2845 mc_unlink (dst_vpath
);
2846 else if (dst_status
== DEST_FULL
&& !appending
)
2848 /* Copy has succeeded */
2850 while (ctx
->preserve_uidgid
&& mc_chown (dst_vpath
, src_uid
, src_gid
) != 0
2851 && !ctx
->ignore_all
)
2853 temp_status
= file_error (TRUE
, _("Cannot chown target file \"%s\"\n%s"), dst_path
);
2854 if (temp_status
== FILE_ABORT
)
2856 return_status
= FILE_ABORT
;
2859 if (temp_status
== FILE_RETRY
)
2861 if (temp_status
== FILE_IGNORE_ALL
)
2863 ctx
->ignore_all
= TRUE
;
2864 return_status
= FILE_CONT
;
2866 if (temp_status
== FILE_IGNORE
)
2867 return_status
= FILE_CONT
;
2871 while (ctx
->preserve
&& mc_chmod (dst_vpath
, (src_mode
& ctx
->umask_kill
)) != 0
2872 && !ctx
->ignore_all
)
2874 temp_status
= file_error (TRUE
, _("Cannot chmod target file \"%s\"\n%s"), dst_path
);
2875 if (temp_status
== FILE_ABORT
)
2877 return_status
= FILE_ABORT
;
2880 if (temp_status
== FILE_RETRY
)
2882 if (temp_status
== FILE_IGNORE_ALL
)
2884 ctx
->ignore_all
= TRUE
;
2885 return_status
= FILE_CONT
;
2887 if (temp_status
== FILE_IGNORE
)
2888 return_status
= FILE_CONT
;
2892 if (!ctx
->preserve
&& !dst_exists
)
2894 src_mode
= umask (-1);
2896 src_mode
= 0100666 & ~src_mode
;
2897 mc_chmod (dst_vpath
, (src_mode
& ctx
->umask_kill
));
2901 if (dst_status
== DEST_FULL
|| dst_status
== DEST_SHORT_KEEP
)
2903 /* Always sync timestamps */
2904 mc_utime (dst_vpath
, ×
);
2906 while (attrs_ok
&& mc_fsetflags (dst_vpath
, attrs
) != 0 && !ctx
->ignore_all
)
2910 /* don't show an error message if attributes aren't supported in this FS */
2911 if (attrs_ignore_error (errno
))
2913 return_status
= FILE_CONT
;
2918 file_error (TRUE
, _("Cannot set ext2 attributes for target file \"%s\"\n%s"),
2920 if (temp_status
== FILE_ABORT
)
2921 return_status
= FILE_ABORT
;
2922 if (temp_status
== FILE_RETRY
)
2927 if (temp_status
== FILE_IGNORE_ALL
)
2929 ctx
->ignore_all
= TRUE
;
2930 return_status
= FILE_CONT
;
2932 if (temp_status
== FILE_IGNORE
)
2933 return_status
= FILE_CONT
;
2938 if (return_status
== FILE_CONT
)
2939 return_status
= progress_update_one (ctx
, file_size
);
2942 vfs_path_free (src_vpath
, TRUE
);
2943 vfs_path_free (dst_vpath
, TRUE
);
2944 return return_status
;
2947 /* --------------------------------------------------------------------------------------------- */
2949 * I think these copy_*_* functions should have a return type.
2950 * anyway, this function *must* have two directories as arguments.
2952 /* FIXME: This function needs to check the return values of the
2956 copy_dir_dir (file_op_context_t
*ctx
, const char *s
, const char *d
, gboolean toplevel
,
2957 gboolean move_over
, gboolean do_delete
, GSList
*parent_dirs
)
2959 struct vfs_dirent
*next
;
2960 struct stat dst_stat
, src_stat
;
2961 unsigned long attrs
= 0;
2962 gboolean attrs_ok
= ctx
->preserve
;
2964 FileProgressStatus return_status
= FILE_CONT
;
2966 vfs_path_t
*src_vpath
, *dst_vpath
;
2967 gboolean do_mkdir
= TRUE
;
2969 src_vpath
= vfs_path_from_str (s
);
2970 dst_vpath
= vfs_path_from_str (d
);
2972 /* First get the mode of the source dir */
2975 while ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2977 if (ctx
->ignore_all
)
2978 return_status
= FILE_IGNORE_ALL
;
2981 return_status
= file_error (TRUE
, _("Cannot stat source directory \"%s\"\n%s"), s
);
2982 if (return_status
== FILE_IGNORE_ALL
)
2983 ctx
->ignore_all
= TRUE
;
2986 if (return_status
!= FILE_RETRY
)
2990 while (attrs_ok
&& mc_fgetflags (src_vpath
, &attrs
) != 0)
2994 /* don't show an error message if attributes aren't supported in this FS */
2995 if (attrs_ignore_error (errno
))
2996 return_status
= FILE_CONT
;
2997 else if (ctx
->ignore_all
)
2998 return_status
= FILE_IGNORE_ALL
;
3002 file_error (TRUE
, _("Cannot get ext2 attributes of source directory \"%s\"\n%s"),
3004 if (return_status
== FILE_IGNORE_ALL
)
3005 ctx
->ignore_all
= TRUE
;
3006 if (return_status
== FILE_ABORT
)
3010 if (return_status
!= FILE_RETRY
)
3013 /* yet another attempt */
3017 if (is_in_linklist (dest_dirs
, src_vpath
, &src_stat
) != NULL
)
3019 /* Don't copy a directory we created before (we don't want to copy
3020 infinitely if a directory is copied into itself) */
3021 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
3022 return_status
= FILE_CONT
;
3026 /* Hmm, hardlink to directory??? - Norbert */
3027 /* FIXME: In this step we should do something in case the destination already exist */
3028 /* Check the hardlinks */
3031 switch (check_hardlinks (src_vpath
, &src_stat
, dst_vpath
, &ctx
->ignore_all
))
3034 /* We have made a hardlink - no more processing is necessary */
3037 case HARDLINK_ABORT
:
3038 return_status
= FILE_ABORT
;
3046 if (!S_ISDIR (src_stat
.st_mode
))
3048 if (ctx
->ignore_all
)
3049 return_status
= FILE_IGNORE_ALL
;
3052 return_status
= file_error (TRUE
, _("Source \"%s\" is not a directory\n%s"), s
);
3053 if (return_status
== FILE_RETRY
)
3054 goto retry_src_stat
;
3055 if (return_status
== FILE_IGNORE_ALL
)
3056 ctx
->ignore_all
= TRUE
;
3061 if (is_in_linklist (parent_dirs
, src_vpath
, &src_stat
) != NULL
)
3063 /* we found a cyclic symbolic link */
3064 message (D_ERROR
, MSG_ERROR
, _("Cannot copy cyclic symbolic link\n\"%s\""), s
);
3065 return_status
= FILE_SKIP
;
3069 lp
= g_new0 (link_t
, 1);
3070 lp
->vfs
= vfs_path_get_last_path_vfs (src_vpath
);
3071 lp
->ino
= src_stat
.st_ino
;
3072 lp
->dev
= src_stat
.st_dev
;
3073 parent_dirs
= g_slist_prepend (parent_dirs
, lp
);
3076 /* Now, check if the dest dir exists, if not, create it. */
3077 if (mc_stat (dst_vpath
, &dst_stat
) != 0)
3079 /* Here the dir doesn't exist : make it ! */
3080 if (move_over
&& mc_rename (src_vpath
, dst_vpath
) == 0)
3082 return_status
= FILE_CONT
;
3089 * If the destination directory exists, we want to copy the whole
3090 * directory, but we only want this to happen once.
3092 * Escape sequences added to the * to compiler warnings.
3093 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3094 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
3096 if (!S_ISDIR (dst_stat
.st_mode
))
3098 if (ctx
->ignore_all
)
3099 return_status
= FILE_IGNORE_ALL
;
3103 file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"), d
);
3104 if (return_status
== FILE_IGNORE_ALL
)
3105 ctx
->ignore_all
= TRUE
;
3106 if (return_status
== FILE_RETRY
)
3107 goto retry_dst_stat
;
3111 /* Dive into subdir if exists */
3112 if (toplevel
&& ctx
->dive_into_subdirs
)
3117 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
3118 vfs_path_free (tmp
, TRUE
);
3125 d
= vfs_path_as_str (dst_vpath
);
3129 while (my_mkdir (dst_vpath
, (src_stat
.st_mode
& ctx
->umask_kill
) | S_IRWXU
) != 0)
3131 if (ctx
->ignore_all
)
3132 return_status
= FILE_IGNORE_ALL
;
3136 file_error (TRUE
, _("Cannot create target directory \"%s\"\n%s"), d
);
3137 if (return_status
== FILE_IGNORE_ALL
)
3138 ctx
->ignore_all
= TRUE
;
3140 if (return_status
!= FILE_RETRY
)
3144 lp
= g_new0 (link_t
, 1);
3145 mc_stat (dst_vpath
, &dst_stat
);
3146 lp
->vfs
= vfs_path_get_last_path_vfs (dst_vpath
);
3147 lp
->ino
= dst_stat
.st_ino
;
3148 lp
->dev
= dst_stat
.st_dev
;
3149 dest_dirs
= g_slist_prepend (dest_dirs
, lp
);
3152 if (ctx
->preserve_uidgid
)
3154 while (mc_chown (dst_vpath
, src_stat
.st_uid
, src_stat
.st_gid
) != 0)
3156 if (ctx
->ignore_all
)
3157 return_status
= FILE_IGNORE_ALL
;
3160 return_status
= file_error (TRUE
, _("Cannot chown target directory \"%s\"\n%s"), d
);
3161 if (return_status
== FILE_IGNORE_ALL
)
3162 ctx
->ignore_all
= TRUE
;
3164 if (return_status
!= FILE_RETRY
)
3169 /* open the source dir for reading */
3170 reading
= mc_opendir (src_vpath
);
3171 if (reading
== NULL
)
3174 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
3177 vfs_path_t
*tmp_vpath
;
3180 * Now, we don't want '.' and '..' to be created / copied at any time
3182 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
3185 /* get the filename and add it to the src directory */
3186 path
= mc_build_filename (s
, next
->d_name
, (char *) NULL
);
3187 tmp_vpath
= vfs_path_from_str (path
);
3189 (*ctx
->stat_func
) (tmp_vpath
, &dst_stat
);
3190 if (S_ISDIR (dst_stat
.st_mode
))
3194 mdpath
= mc_build_filename (d
, next
->d_name
, (char *) NULL
);
3196 * From here, we just intend to recursively copy subdirs, not
3197 * the double functionality of copying different when the target
3198 * dir already exists. So, we give the recursive call the flag 0
3199 * meaning no toplevel.
3201 return_status
= copy_dir_dir (ctx
, path
, mdpath
, FALSE
, FALSE
, do_delete
, parent_dirs
);
3208 dest_file
= mc_build_filename (d
, x_basename (path
), (char *) NULL
);
3209 return_status
= copy_file_file (ctx
, path
, dest_file
);
3215 if (do_delete
&& return_status
== FILE_CONT
)
3217 if (ctx
->erase_at_end
)
3219 if (erase_list
== NULL
)
3220 erase_list
= g_queue_new ();
3222 lp
= g_new0 (link_t
, 1);
3223 lp
->src_vpath
= tmp_vpath
;
3224 lp
->st_mode
= dst_stat
.st_mode
;
3225 g_queue_push_tail (erase_list
, lp
);
3228 else if (S_ISDIR (dst_stat
.st_mode
))
3229 return_status
= erase_dir_iff_empty (ctx
, tmp_vpath
, ctx
->progress_count
);
3231 return_status
= erase_file (ctx
, tmp_vpath
);
3233 vfs_path_free (tmp_vpath
, TRUE
);
3235 mc_closedir (reading
);
3239 mc_timesbuf_t times
;
3241 mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
);
3244 mc_fsetflags (dst_vpath
, attrs
);
3246 vfs_get_timesbuf_from_stat (&src_stat
, ×
);
3247 mc_utime (dst_vpath
, ×
);
3251 src_stat
.st_mode
= umask (-1);
3252 umask (src_stat
.st_mode
);
3253 src_stat
.st_mode
= 0100777 & ~src_stat
.st_mode
;
3254 mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
);
3258 free_link (parent_dirs
->data
);
3259 g_slist_free_1 (parent_dirs
);
3261 vfs_path_free (src_vpath
, TRUE
);
3262 vfs_path_free (dst_vpath
, TRUE
);
3263 return return_status
;
3268 /* --------------------------------------------------------------------------------------------- */
3269 /* {{{ Move routines */
3272 move_dir_dir (file_op_context_t
*ctx
, const char *s
, const char *d
)
3274 return do_move_dir_dir (NULL
, ctx
, s
, d
);
3279 /* --------------------------------------------------------------------------------------------- */
3280 /* {{{ Erase routines */
3283 erase_dir (file_op_context_t
*ctx
, const vfs_path_t
*vpath
)
3285 file_progress_show_deleting (ctx
, vpath
, NULL
);
3286 file_progress_show_count (ctx
, ctx
->progress_count
, ctx
->total_count
);
3287 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
3292 /* The old way to detect a non empty directory was:
3293 error = my_rmdir (s);
3294 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3295 For the linux user space nfs server (nfs-server-2.2beta29-2)
3296 we would have to check also for EIO. I hope the new way is
3297 fool proof. (Norbert)
3299 if (check_dir_is_empty (vpath
) == 0)
3301 FileProgressStatus error
;
3303 error
= query_recursive (ctx
, vfs_path_as_str (vpath
));
3304 if (error
== FILE_CONT
)
3305 error
= recursive_erase (ctx
, vpath
);
3309 return try_erase_dir (ctx
, vpath
);
3314 /* --------------------------------------------------------------------------------------------- */
3315 /* {{{ Panel operate routines */
3318 dirsize_status_init_cb (status_msg_t
*sm
)
3320 dirsize_status_msg_t
*dsm
= (dirsize_status_msg_t
*) sm
;
3321 WGroup
*gd
= GROUP (sm
->dlg
);
3322 Widget
*wd
= WIDGET (sm
->dlg
);
3325 const char *b1_name
= N_("&Abort");
3326 const char *b2_name
= N_("&Skip");
3327 int b_width
, ui_width
;
3330 b1_name
= _(b1_name
);
3331 b2_name
= _(b2_name
);
3334 b_width
= str_term_width1 (b1_name
) + 4;
3335 if (dsm
->allow_skip
)
3336 b_width
+= str_term_width1 (b2_name
) + 4 + 1;
3338 ui_width
= MAX (COLS
/ 2, b_width
+ 6);
3339 dsm
->dirname
= label_new (2, 3, NULL
);
3340 group_add_widget (gd
, dsm
->dirname
);
3341 dsm
->count_size
= label_new (3, 3, NULL
);
3342 group_add_widget (gd
, dsm
->count_size
);
3343 group_add_widget (gd
, hline_new (4, -1, -1));
3345 dsm
->abort_button
= WIDGET (button_new (5, 3, FILE_ABORT
, NORMAL_BUTTON
, b1_name
, NULL
));
3346 group_add_widget (gd
, dsm
->abort_button
);
3347 if (dsm
->allow_skip
)
3349 dsm
->skip_button
= WIDGET (button_new (5, 3, FILE_SKIP
, NORMAL_BUTTON
, b2_name
, NULL
));
3350 group_add_widget (gd
, dsm
->skip_button
);
3351 widget_select (dsm
->skip_button
);
3356 widget_set_size_rect (wd
, &r
);
3357 dirsize_status_locate_buttons (dsm
);
3360 /* --------------------------------------------------------------------------------------------- */
3363 dirsize_status_update_cb (status_msg_t
*sm
)
3365 dirsize_status_msg_t
*dsm
= (dirsize_status_msg_t
*) sm
;
3366 Widget
*wd
= WIDGET (sm
->dlg
);
3369 /* update second (longer label) */
3370 label_set_textv (dsm
->count_size
, _("Directories: %zu, total size: %s"),
3371 dsm
->dir_count
, size_trunc_sep (dsm
->total_size
, panels_options
.kilobyte_si
));
3373 /* enlarge dialog if required */
3374 if (WIDGET (dsm
->count_size
)->rect
.cols
+ 6 > r
.cols
)
3376 r
.cols
= WIDGET (dsm
->count_size
)->rect
.cols
+ 6;
3377 widget_set_size_rect (wd
, &r
);
3378 dirsize_status_locate_buttons (dsm
);
3380 /* TODO: ret rid of double redraw */
3383 /* adjust first label */
3384 label_set_text (dsm
->dirname
,
3385 str_trunc (vfs_path_as_str (dsm
->dirname_vpath
), wd
->rect
.cols
- 6));
3387 switch (status_msg_common_update (sm
))
3399 /* --------------------------------------------------------------------------------------------- */
3402 dirsize_status_deinit_cb (status_msg_t
*sm
)
3406 /* schedule to update passive panel */
3407 if (get_other_type () == view_listing
)
3408 other_panel
->dirty
= TRUE
;
3411 /* --------------------------------------------------------------------------------------------- */
3415 * Computes the number of bytes used by the files in a directory
3419 compute_dir_size (const vfs_path_t
*dirname_vpath
, dirsize_status_msg_t
*sm
,
3420 size_t *ret_dir_count
, size_t *ret_marked_count
, uintmax_t *ret_total
,
3421 gboolean follow_symlinks
)
3423 return do_compute_dir_size (dirname_vpath
, sm
, ret_dir_count
, ret_marked_count
, ret_total
,
3424 follow_symlinks
? mc_stat
: mc_lstat
);
3427 /* --------------------------------------------------------------------------------------------- */
3431 * Performs one of the operations on the current on the source_panel
3432 * (copy, delete, move).
3434 * Returns TRUE if did change the directory
3435 * structure, Returns FALSE if user aborted
3437 * force_single forces operation on the current entry and affects
3438 * default destination. Current filename is used as default.
3442 panel_operate (void *source_panel
, FileOperation operation
, gboolean force_single
)
3444 WPanel
*panel
= PANEL (source_panel
);
3445 const gboolean single_entry
= force_single
|| (panel
->marked
<= 1)
3446 || (get_current_type () == view_tree
);
3448 const char *source
= NULL
;
3450 vfs_path_t
*dest_vpath
= NULL
;
3451 vfs_path_t
*save_cwd
= NULL
, *save_dest
= NULL
;
3452 struct stat src_stat
;
3453 gboolean ret_val
= TRUE
;
3455 FileProgressStatus value
;
3456 file_op_context_t
*ctx
;
3457 filegui_dialog_type_t dialog_type
= FILEGUI_DIALOG_ONE_ITEM
;
3459 gboolean do_bg
= FALSE
; /* do background operation? */
3461 static gboolean i18n_flag
= FALSE
;
3464 for (i
= G_N_ELEMENTS (op_names
); i
-- != 0;)
3465 op_names
[i
] = Q_ (op_names
[i
]);
3469 linklist
= free_linklist (linklist
);
3470 dest_dirs
= free_linklist (dest_dirs
);
3476 source
= check_single_entry (panel
, force_single
, &src_stat
);
3482 ctx
= file_op_context_new (operation
);
3484 /* Show confirmation dialog */
3485 if (operation
!= OP_DELETE
)
3487 dest
= do_confirm_copy_move (panel
, force_single
, source
, &src_stat
, ctx
, &do_bg
);
3494 dest_vpath
= vfs_path_from_str (dest
);
3496 else if (confirm_delete
&& !do_confirm_erase (panel
, source
, &src_stat
))
3502 ctx
->total_transfer_start
= g_get_monotonic_time ();
3504 #ifdef ENABLE_BACKGROUND
3505 /* Did the user select to do a background operation? */
3510 v
= do_background (ctx
,
3511 g_strconcat (op_names
[operation
], ": ",
3512 vfs_path_as_str (panel
->cwd_vpath
), (char *) NULL
));
3514 message (D_ERROR
, MSG_ERROR
, _("Sorry, I could not put the job in background"));
3516 /* If we are the parent */
3519 mc_setctl (panel
->cwd_vpath
, VFS_SETCTL_FORGET
, NULL
);
3521 mc_setctl (dest_vpath
, VFS_SETCTL_FORGET
, NULL
);
3522 vfs_path_free (dest_vpath
, TRUE
);
3524 /* file_op_context_destroy (ctx); */
3529 #endif /* ENABLE_BACKGROUND */
3531 const file_entry_t
*fe
;
3533 if (operation
== OP_DELETE
)
3534 dialog_type
= FILEGUI_DIALOG_DELETE_ITEM
;
3535 else if (single_entry
3536 && ((fe
= panel_current_entry (panel
)) == NULL
? FALSE
: S_ISDIR (fe
->st
.st_mode
)))
3537 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
3538 else if (single_entry
|| force_single
)
3539 dialog_type
= FILEGUI_DIALOG_ONE_ITEM
;
3541 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
3544 /* Initialize things */
3545 /* We do not want to trash cache every time file is
3546 created/touched. However, this will make our cache contain
3549 && (mc_setctl (dest_vpath
, VFS_SETCTL_STALE_DATA
, GUINT_TO_POINTER (1)) != 0))
3550 save_dest
= vfs_path_from_str (dest
);
3552 if ((vfs_path_tokens_count (panel
->cwd_vpath
) != 0)
3553 && (mc_setctl (panel
->cwd_vpath
, VFS_SETCTL_STALE_DATA
, GUINT_TO_POINTER (1)) != 0))
3554 save_cwd
= vfs_path_clone (panel
->cwd_vpath
);
3556 /* Now, let's do the job */
3558 /* This code is only called by the tree and panel code */
3561 /* We now have ETA in all cases */
3563 /* One file: FIXME mc_chdir will take user out of any vfs */
3564 if ((operation
!= OP_COPY
) && (get_current_type () == view_tree
))
3569 vpath
= vfs_path_from_str (PATH_SEP_STR
);
3570 chdir_retcode
= mc_chdir (vpath
);
3571 vfs_path_free (vpath
, TRUE
);
3572 if (chdir_retcode
< 0)
3579 value
= operate_single_file (panel
, ctx
, source
, &src_stat
, dest
, dialog_type
);
3580 if ((value
== FILE_CONT
) && !force_single
)
3581 unmark_files (panel
);
3587 /* Check destination for copy or move operation */
3588 while (operation
!= OP_DELETE
)
3591 struct stat dst_stat
;
3593 dst_result
= mc_stat (dest_vpath
, &dst_stat
);
3595 if ((dst_result
!= 0) || S_ISDIR (dst_stat
.st_mode
))
3599 || file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"),
3600 dest
) != FILE_RETRY
)
3604 /* TODO: the good way is required to skip directories scanning in case of rename/move
3605 * of several directories. Since reqular expression can be used for destination,
3606 * some directory movements can be a cross-filesystem and directory scanning is useful
3607 * for those directories only. */
3609 if (panel_operate_init_totals (panel
, NULL
, NULL
, ctx
, file_op_compute_totals
, dialog_type
)
3612 /* Loop for every file, perform the actual copy operation */
3613 for (i
= 0; i
< panel
->dir
.len
; i
++)
3615 const char *source2
;
3617 if (panel
->dir
.list
[i
].f
.marked
== 0)
3618 continue; /* Skip the unmarked ones */
3620 source2
= panel
->dir
.list
[i
].fname
->str
;
3621 src_stat
= panel
->dir
.list
[i
].st
;
3623 value
= operate_one_file (panel
, ctx
, source2
, &src_stat
, dest
);
3625 if (value
== FILE_ABORT
)
3628 if (value
== FILE_CONT
)
3629 do_file_mark (panel
, i
, 0);
3633 if (ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
3635 file_progress_show_count (ctx
, ctx
->progress_count
, ctx
->total_count
);
3636 file_progress_show_total (ctx
, ctx
->progress_bytes
, FALSE
);
3639 if (operation
!= OP_DELETE
)
3640 file_progress_show (ctx
, 0, 0, "", FALSE
);
3643 if (file_progress_check_buttons (ctx
) == FILE_ABORT
)
3647 } /* Loop for every file */
3649 } /* Many entries */
3653 if (save_cwd
!= NULL
)
3655 mc_setctl (save_cwd
, VFS_SETCTL_STALE_DATA
, NULL
);
3656 vfs_path_free (save_cwd
, TRUE
);
3659 if (save_dest
!= NULL
)
3661 mc_setctl (save_dest
, VFS_SETCTL_STALE_DATA
, NULL
);
3662 vfs_path_free (save_dest
, TRUE
);
3665 linklist
= free_linklist (linklist
);
3666 dest_dirs
= free_linklist (dest_dirs
);
3668 vfs_path_free (dest_vpath
, TRUE
);
3669 MC_PTR_FREE (ctx
->dest_mask
);
3671 #ifdef ENABLE_BACKGROUND
3672 /* Let our parent know we are saying bye bye */
3673 if (mc_global
.we_are_background
)
3675 /* Send pid to parent with child context, it is fork and
3676 don't modify real parent ctx */
3677 ctx
->pid
= getpid ();
3678 parent_call ((void *) end_bg_process
, ctx
, 0);
3681 my_exit (EXIT_SUCCESS
);
3683 #endif /* ENABLE_BACKGROUND */
3686 file_op_context_destroy (ctx
);
3688 update_panels (UP_OPTIMIZE
, UP_KEEPSEL
);
3696 /* --------------------------------------------------------------------------------------------- */
3697 /* {{{ Query/status report routines */
3698 /** Report error with one file */
3700 file_error (gboolean allow_retry
, const char *format
, const char *file
)
3702 char buf
[BUF_MEDIUM
];
3704 g_snprintf (buf
, sizeof (buf
), format
, path_trunc (file
, 30), unix_error_string (errno
));
3706 return do_file_error (allow_retry
, buf
);
3709 /* --------------------------------------------------------------------------------------------- */
3712 Cause emacs to enter folding mode for this file: