4 Copyright (C) 1994-2020
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-2014
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/strescape.h"
68 #include "lib/strutil.h"
70 #include "lib/vfs/vfs.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 current_panel, other_panel and WTree */
83 #include "midnight.h" /* current_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
105 /*** file scope type declarations ****************************************************************/
107 /* This is a hard link cache */
110 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
= 1, /* Created, not fully copied */
124 DEST_FULL
= 2 /* Created, fully copied */
127 /* Status of hard link creation */
130 HARDLINK_OK
= 0, /**< Hardlink was created successfully */
131 HARDLINK_CACHED
, /**< Hardlink was added to the cache */
132 HARDLINK_NOTLINK
, /**< This is not a hard link */
133 HARDLINK_UNSUPPORTED
, /**< VFS doesn't support hard links */
134 HARDLINK_ERROR
, /**< Hard link creation error */
135 HARDLINK_ABORT
/**< Stop file operation after hardlink creation error */
139 * This array introduced to avoid translation problems. The former (op_names)
140 * is assumed to be nouns, suitable in dialog box titles; this one should
141 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
142 * (I don't use spaces around the words, because someday they could be
143 * dropped, when widgets get smarter)
146 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
147 static const char *op_names1
[] = {
148 N_("FileOperation|Copy"),
149 N_("FileOperation|Move"),
150 N_("FileOperation|Delete")
154 * These are formats for building a prompt. Parts encoded as follows:
155 * %o - operation from op_names1
156 * %f - file/files or files/directories, as appropriate
157 * %m - "with source mask" or question mark for delete
158 * %s - source name (truncated)
159 * %d - number of marked files
160 * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
162 /* xgettext:no-c-format */
163 static const char *one_format
= N_("%o %f%n\"%s\"%m");
164 /* xgettext:no-c-format */
165 static const char *many_format
= N_("%o %d %f%m");
167 static const char *prompt_parts
[] = {
172 N_("files/directories"),
173 /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
174 N_(" with source mask:")
177 /*** file scope variables ************************************************************************/
179 /* the hard link cache */
180 static GSList
*linklist
= NULL
;
182 /* the files-to-be-erased list */
183 static GSList
*erase_list
= NULL
;
186 * In copy_dir_dir we use two additional single linked lists: The first -
187 * variable name 'parent_dirs' - holds information about already copied
188 * directories and is used to detect cyclic symbolic links.
189 * The second ('dest_dirs' below) holds information about just created
190 * target directories and is used to detect when an directory is copied
191 * into itself (we don't want to copy infinitly).
192 * Both lists don't use the linkcount and name structure members of struct
195 static GSList
*dest_dirs
= NULL
;
197 /* --------------------------------------------------------------------------------------------- */
198 /*** file scope functions ************************************************************************/
199 /* --------------------------------------------------------------------------------------------- */
202 dirsize_status_locate_buttons (dirsize_status_msg_t
* dsm
)
204 status_msg_t
*sm
= STATUS_MSG (dsm
);
205 Widget
*wd
= WIDGET (sm
->dlg
);
211 if (!dsm
->allow_skip
)
213 /* single button: "Abort" */
214 x
+= (wd
->cols
- dsm
->abort_button
->cols
) / 2;
215 widget_set_size (dsm
->abort_button
, y
, x
,
216 dsm
->abort_button
->lines
, dsm
->abort_button
->cols
);
220 /* two buttons: "Abort" and "Skip" */
223 cols
= dsm
->abort_button
->cols
+ dsm
->skip_button
->cols
+ 1;
224 x
+= (wd
->cols
- cols
) / 2;
225 widget_set_size (dsm
->abort_button
, y
, x
, dsm
->abort_button
->lines
,
226 dsm
->abort_button
->cols
);
227 x
+= dsm
->abort_button
->cols
+ 1;
228 widget_set_size (dsm
->skip_button
, y
, x
, dsm
->skip_button
->lines
, dsm
->skip_button
->cols
);
232 /* --------------------------------------------------------------------------------------------- */
235 build_dest (file_op_context_t
* ctx
, const char *src
, const char *dest
, FileProgressStatus
* status
)
238 const char *fnsource
;
244 /* We remove \n from the filename since regex routines would use \n as an anchor */
245 /* this is just to be allowed to maniupulate file names with \n on it */
246 for (q
= s
; *q
!= '\0'; q
++)
250 fnsource
= x_basename (s
);
252 if (!mc_search_run (ctx
->search_handle
, fnsource
, 0, strlen (fnsource
), NULL
))
259 q
= mc_search_prepare_replace_str2 (ctx
->search_handle
, ctx
->dest_mask
);
260 if (ctx
->search_handle
->error
!= MC_SEARCH_E_OK
)
262 if (ctx
->search_handle
->error_str
!= NULL
)
263 message (D_ERROR
, MSG_ERROR
, "%s", ctx
->search_handle
->error_str
);
265 *status
= FILE_ABORT
;
271 if (*status
== FILE_CONT
)
275 repl_dest
= mc_search_prepare_replace_str2 (ctx
->search_handle
, dest
);
276 if (ctx
->search_handle
->error
== MC_SEARCH_E_OK
)
277 s
= mc_build_filename (repl_dest
, q
, (char *) NULL
);
280 if (ctx
->search_handle
->error_str
!= NULL
)
281 message (D_ERROR
, MSG_ERROR
, "%s", ctx
->search_handle
->error_str
);
283 *status
= FILE_ABORT
;
294 /* --------------------------------------------------------------------------------------------- */
297 free_link (void *data
)
299 struct link
*lp
= (struct link
*) data
;
301 vfs_path_free (lp
->src_vpath
);
302 vfs_path_free (lp
->dst_vpath
);
306 /* --------------------------------------------------------------------------------------------- */
309 free_linklist (GSList
* lp
)
311 g_slist_free_full (lp
, free_link
);
316 /* --------------------------------------------------------------------------------------------- */
318 static const struct link
*
319 is_in_linklist (const GSList
* lp
, const vfs_path_t
* vpath
, const struct stat
*sb
)
321 const struct vfs_class
*class;
322 ino_t ino
= sb
->st_ino
;
323 dev_t dev
= sb
->st_dev
;
325 class = vfs_path_get_last_path_vfs (vpath
);
327 for (; lp
!= NULL
; lp
= (const GSList
*) g_slist_next (lp
))
329 const struct link
*lnk
= (const struct link
*) lp
->data
;
331 if (lnk
->vfs
== class && lnk
->ino
== ino
&& lnk
->dev
== dev
)
338 /* --------------------------------------------------------------------------------------------- */
340 * Check and made hardlink
342 * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
343 * and a hardlink was successfully made
346 static hardlink_status_t
347 check_hardlinks (const vfs_path_t
* src_vpath
, const struct stat
*src_stat
,
348 const vfs_path_t
* dst_vpath
, gboolean
* skip_all
)
351 ino_t ino
= src_stat
->st_ino
;
352 dev_t dev
= src_stat
->st_dev
;
354 if (src_stat
->st_nlink
< 2)
355 return HARDLINK_NOTLINK
;
356 if ((vfs_file_class_flags (src_vpath
) & VFSF_NOLINKS
) != 0)
357 return HARDLINK_UNSUPPORTED
;
359 lnk
= (struct link
*) is_in_linklist (linklist
, src_vpath
, src_stat
);
363 struct stat link_stat
;
365 stat_result
= mc_stat (lnk
->src_vpath
, &link_stat
);
367 if (stat_result
== 0 && link_stat
.st_ino
== ino
&& link_stat
.st_dev
== dev
)
369 const struct vfs_class
*lp_name_class
;
370 const struct vfs_class
*my_vfs
;
372 lp_name_class
= vfs_path_get_last_path_vfs (lnk
->src_vpath
);
373 my_vfs
= vfs_path_get_last_path_vfs (src_vpath
);
375 if (lp_name_class
== my_vfs
)
377 const struct vfs_class
*p_class
, *dst_name_class
;
379 dst_name_class
= vfs_path_get_last_path_vfs (dst_vpath
);
380 p_class
= vfs_path_get_last_path_vfs (lnk
->dst_vpath
);
382 if (dst_name_class
== p_class
)
386 while (!(ok
= (mc_stat (lnk
->dst_vpath
, &link_stat
) == 0)) && !*skip_all
)
388 FileProgressStatus status
;
391 file_error (TRUE
, _("Cannot stat hardlink source file \"%s\"\n%s"),
392 vfs_path_as_str (lnk
->dst_vpath
));
393 if (status
== FILE_ABORT
)
394 return HARDLINK_ABORT
;
395 if (status
== FILE_RETRY
)
397 if (status
== FILE_SKIPALL
)
402 /* if stat() finished unsuccessfully, don't try to create link */
404 return HARDLINK_ERROR
;
406 while (!(ok
= (mc_link (lnk
->dst_vpath
, dst_vpath
) == 0)) && !*skip_all
)
408 FileProgressStatus status
;
411 file_error (TRUE
, _("Cannot create target hardlink \"%s\"\n%s"),
412 vfs_path_as_str (dst_vpath
));
413 if (status
== FILE_ABORT
)
414 return HARDLINK_ABORT
;
415 if (status
== FILE_RETRY
)
417 if (status
== FILE_SKIPALL
)
423 return (ok
? HARDLINK_OK
: HARDLINK_ERROR
);
430 FileProgressStatus status
;
432 /* Message w/o "Retry" action.
434 * FIXME: Can't say what errno is here. Define it and don't display.
436 * file_error() displays a message with text representation of errno
437 * and the string passed to file_error() should provide the format "%s"
438 * for that at end (see previous file_error() call for the reference).
439 * But if format for errno isn't provided, it is safe, because C standard says:
440 * "If the format is exhausted while arguments remain, the excess arguments
441 * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999,
442 * section 7.19.6.1, paragraph 2).
447 file_error (FALSE
, _("Cannot create target hardlink \"%s\""),
448 vfs_path_as_str (dst_vpath
));
450 if (status
== FILE_ABORT
)
451 return HARDLINK_ABORT
;
453 if (status
== FILE_SKIPALL
)
457 return HARDLINK_ERROR
;
460 lnk
= g_try_new (struct link
, 1);
463 lnk
->vfs
= vfs_path_get_last_path_vfs (src_vpath
);
468 lnk
->src_vpath
= vfs_path_clone (src_vpath
);
469 lnk
->dst_vpath
= vfs_path_clone (dst_vpath
);
471 linklist
= g_slist_prepend (linklist
, lnk
);
474 return HARDLINK_CACHED
;
477 /* --------------------------------------------------------------------------------------------- */
479 * Duplicate the contents of the symbolic link src_path in dst_path.
480 * Try to make a stable symlink if the option "stable symlink" was
481 * set in the file mask dialog.
482 * If dst_path is an existing symlink it will be deleted silently
483 * (upper levels take already care of existing files at dst_path).
486 static FileProgressStatus
487 make_symlink (file_op_context_t
* ctx
, const char *src_path
, const char *dst_path
)
489 char link_target
[MC_MAXPATHLEN
];
491 FileProgressStatus return_status
;
492 struct stat dst_stat
;
493 vfs_path_t
*src_vpath
;
494 vfs_path_t
*dst_vpath
;
495 gboolean dst_is_symlink
;
496 vfs_path_t
*link_target_vpath
= NULL
;
498 src_vpath
= vfs_path_from_str (src_path
);
499 dst_vpath
= vfs_path_from_str (dst_path
);
500 dst_is_symlink
= (mc_lstat (dst_vpath
, &dst_stat
) == 0) && S_ISLNK (dst_stat
.st_mode
);
503 len
= mc_readlink (src_vpath
, link_target
, sizeof (link_target
) - 1);
507 return_status
= FILE_SKIPALL
;
510 return_status
= file_error (TRUE
, _("Cannot read source link \"%s\"\n%s"), src_path
);
511 if (return_status
== FILE_SKIPALL
)
512 ctx
->skip_all
= TRUE
;
513 if (return_status
== FILE_RETRY
)
514 goto retry_src_readlink
;
519 link_target
[len
] = '\0';
521 if (ctx
->stable_symlinks
&& !(vfs_file_is_local (src_vpath
) && vfs_file_is_local (dst_vpath
)))
523 message (D_ERROR
, MSG_ERROR
,
524 _("Cannot make stable symlinks across "
525 "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
526 ctx
->stable_symlinks
= FALSE
;
529 if (ctx
->stable_symlinks
&& !g_path_is_absolute (link_target
))
533 r
= strrchr (src_path
, PATH_SEP
);
539 p
= g_strndup (src_path
, r
- src_path
+ 1);
540 if (g_path_is_absolute (dst_path
))
541 q
= vfs_path_from_str_flags (dst_path
, VPF_NO_CANON
);
543 q
= vfs_path_build_filename (p
, dst_path
, (char *) NULL
);
545 if (vfs_path_tokens_count (q
) > 1)
548 vfs_path_t
*tmp_vpath1
, *tmp_vpath2
;
550 tmp_vpath1
= vfs_path_vtokens_get (q
, -1, 1);
551 s
= g_strconcat (p
, link_target
, (char *) NULL
);
552 g_strlcpy (link_target
, s
, sizeof (link_target
));
554 tmp_vpath2
= vfs_path_from_str (link_target
);
555 s
= diff_two_paths (tmp_vpath1
, tmp_vpath2
);
556 vfs_path_free (tmp_vpath1
);
557 vfs_path_free (tmp_vpath2
);
560 g_strlcpy (link_target
, s
, sizeof (link_target
));
568 link_target_vpath
= vfs_path_from_str_flags (link_target
, VPF_NO_CANON
);
571 if (mc_symlink (link_target_vpath
, dst_vpath
) == 0)
574 return_status
= FILE_CONT
;
578 * if dst_exists, it is obvious that this had failed.
579 * We can delete the old symlink and try again...
581 if (dst_is_symlink
&& mc_unlink (dst_vpath
) == 0
582 && mc_symlink (link_target_vpath
, dst_vpath
) == 0)
585 return_status
= FILE_CONT
;
590 return_status
= FILE_SKIPALL
;
593 return_status
= file_error (TRUE
, _("Cannot create target symlink \"%s\"\n%s"), dst_path
);
594 if (return_status
== FILE_SKIPALL
)
595 ctx
->skip_all
= TRUE
;
596 if (return_status
== FILE_RETRY
)
597 goto retry_dst_symlink
;
601 vfs_path_free (src_vpath
);
602 vfs_path_free (dst_vpath
);
603 vfs_path_free (link_target_vpath
);
604 return return_status
;
607 /* --------------------------------------------------------------------------------------------- */
609 * do_compute_dir_size:
611 * Computes the number of bytes used by the files in a directory
614 static FileProgressStatus
615 do_compute_dir_size (const vfs_path_t
* dirname_vpath
, dirsize_status_msg_t
* dsm
,
616 size_t * dir_count
, size_t * ret_marked
, uintmax_t * ret_total
,
617 gboolean compute_symlinks
)
619 static guint64 timestamp
= 0;
620 /* update with 25 FPS rate */
621 static const guint64 delay
= G_USEC_PER_SEC
/ 25;
623 status_msg_t
*sm
= STATUS_MSG (dsm
);
627 struct dirent
*dirent
;
628 FileProgressStatus ret
= FILE_CONT
;
630 if (!compute_symlinks
)
632 res
= mc_lstat (dirname_vpath
, &s
);
636 /* don't scan symlink to directory */
637 if (S_ISLNK (s
.st_mode
))
640 *ret_total
+= (uintmax_t) s
.st_size
;
647 dir
= mc_opendir (dirname_vpath
);
651 while (ret
== FILE_CONT
&& (dirent
= mc_readdir (dir
)) != NULL
)
653 vfs_path_t
*tmp_vpath
;
655 if (DIR_IS_DOT (dirent
->d_name
) || DIR_IS_DOTDOT (dirent
->d_name
))
658 tmp_vpath
= vfs_path_append_new (dirname_vpath
, dirent
->d_name
, (char *) NULL
);
660 res
= mc_lstat (tmp_vpath
, &s
);
663 if (S_ISDIR (s
.st_mode
))
665 do_compute_dir_size (tmp_vpath
, dsm
, dir_count
, ret_marked
, ret_total
,
672 *ret_total
+= (uintmax_t) s
.st_size
;
675 if (ret
== FILE_CONT
&& sm
->update
!= NULL
&& mc_time_elapsed (×tamp
, delay
))
677 dsm
->dirname_vpath
= tmp_vpath
;
678 dsm
->dir_count
= *dir_count
;
679 dsm
->total_size
= *ret_total
;
680 ret
= sm
->update (sm
);
684 vfs_path_free (tmp_vpath
);
691 /* --------------------------------------------------------------------------------------------- */
693 * panel_compute_totals:
695 * compute the number of files and the number of bytes
696 * used up by the whole selection, recursing directories
697 * as required. In addition, it checks to see if it will
698 * overwrite any files by doing the copy.
701 static FileProgressStatus
702 panel_compute_totals (const WPanel
* panel
, dirsize_status_msg_t
* sm
, size_t * ret_count
,
703 uintmax_t * ret_total
, gboolean compute_symlinks
)
706 size_t dir_count
= 0;
708 for (i
= 0; i
< panel
->dir
.len
; i
++)
712 if (!panel
->dir
.list
[i
].f
.marked
)
715 s
= &panel
->dir
.list
[i
].st
;
717 if (S_ISDIR (s
->st_mode
))
720 FileProgressStatus status
;
722 p
= vfs_path_append_new (panel
->cwd_vpath
, panel
->dir
.list
[i
].fname
, (char *) NULL
);
723 status
= compute_dir_size (p
, sm
, &dir_count
, ret_count
, ret_total
, compute_symlinks
);
726 if (status
!= FILE_CONT
)
732 *ret_total
+= (uintmax_t) s
->st_size
;
739 /* --------------------------------------------------------------------------------------------- */
741 /** Initialize variables for progress bars */
742 static FileProgressStatus
743 panel_operate_init_totals (const WPanel
* panel
, const vfs_path_t
* source
,
744 const struct stat
*source_stat
, file_op_context_t
* ctx
,
745 gboolean compute_totals
, filegui_dialog_type_t dialog_type
)
747 FileProgressStatus status
;
749 #ifdef ENABLE_BACKGROUND
750 if (mc_global
.we_are_background
)
754 if (verbose
&& compute_totals
)
756 dirsize_status_msg_t dsm
;
758 memset (&dsm
, 0, sizeof (dsm
));
759 dsm
.allow_skip
= TRUE
;
760 status_msg_init (STATUS_MSG (&dsm
), _("Directory scanning"), 0, dirsize_status_init_cb
,
761 dirsize_status_update_cb
, dirsize_status_deinit_cb
);
763 ctx
->progress_count
= 0;
764 ctx
->progress_bytes
= 0;
767 status
= panel_compute_totals (panel
, &dsm
, &ctx
->progress_count
, &ctx
->progress_bytes
,
769 else if (S_ISDIR (source_stat
->st_mode
))
771 size_t dir_count
= 0;
773 status
= compute_dir_size (source
, &dsm
, &dir_count
, &ctx
->progress_count
,
774 &ctx
->progress_bytes
, ctx
->follow_links
);
778 ctx
->progress_count
++;
779 ctx
->progress_bytes
+= (uintmax_t) source_stat
->st_size
;
783 status_msg_deinit (STATUS_MSG (&dsm
));
785 ctx
->progress_totals_computed
= (status
== FILE_CONT
);
787 if (status
== FILE_SKIP
)
793 ctx
->progress_count
= panel
->marked
;
794 ctx
->progress_bytes
= panel
->total
;
795 ctx
->progress_totals_computed
= FALSE
;
798 /* destroy already created UI for single file rename operation */
799 file_op_context_destroy_ui (ctx
);
801 file_op_context_create_ui (ctx
, TRUE
, dialog_type
);
806 /* --------------------------------------------------------------------------------------------- */
808 static FileProgressStatus
809 progress_update_one (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, off_t add
)
811 struct timeval tv_current
;
812 static struct timeval tv_start
= { 0, 0 };
814 tctx
->progress_count
++;
815 tctx
->progress_bytes
+= (uintmax_t) add
;
817 if (tv_start
.tv_sec
== 0)
819 gettimeofday (&tv_start
, (struct timezone
*) NULL
);
821 gettimeofday (&tv_current
, (struct timezone
*) NULL
);
822 if ((tv_current
.tv_sec
- tv_start
.tv_sec
) > FILEOP_UPDATE_INTERVAL
)
824 if (verbose
&& ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
826 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
827 file_progress_show_total (tctx
, ctx
, tctx
->progress_bytes
, TRUE
);
829 tv_start
.tv_sec
= tv_current
.tv_sec
;
832 return check_progress_buttons (ctx
);
835 /* --------------------------------------------------------------------------------------------- */
837 static FileProgressStatus
838 real_warn_same_file (enum OperationMode mode
, const char *fmt
, const char *a
, const char *b
)
842 const char *head_msg
;
844 head_msg
= mode
== Foreground
? MSG_ERROR
: _("Background process error");
846 msg
= g_strdup_printf (fmt
, a
, b
);
847 result
= query_dialog (head_msg
, msg
, D_ERROR
, 2, _("&Skip"), _("&Abort"));
851 return (result
== 1) ? FILE_ABORT
: FILE_SKIP
;
854 /* --------------------------------------------------------------------------------------------- */
856 static FileProgressStatus
857 warn_same_file (const char *fmt
, const char *a
, const char *b
)
859 #ifdef ENABLE_BACKGROUND
864 FileProgressStatus (*f
) (enum OperationMode
, const char *fmt
, const char *a
, const char *b
);
868 pntr
.f
= real_warn_same_file
;
870 if (mc_global
.we_are_background
)
871 return parent_call (pntr
.p
, NULL
, 3, strlen (fmt
), fmt
, strlen (a
), a
, strlen (b
), b
);
873 return real_warn_same_file (Foreground
, fmt
, a
, b
);
876 /* --------------------------------------------------------------------------------------------- */
879 check_same_file (const char *a
, const struct stat
*ast
, const char *b
, const struct stat
*bst
,
880 FileProgressStatus
* status
)
882 if (ast
->st_dev
!= bst
->st_dev
|| ast
->st_ino
!= bst
->st_ino
)
885 if (S_ISDIR (ast
->st_mode
))
886 *status
= warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a
, b
);
888 *status
= warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a
, b
);
893 /* --------------------------------------------------------------------------------------------- */
896 get_times (const struct stat
*sb
, mc_timesbuf_t
* times
)
898 #ifdef HAVE_UTIMENSAT
899 (*times
)[0] = sb
->st_atim
;
900 (*times
)[1] = sb
->st_mtim
;
902 times
->actime
= sb
->st_atime
;
903 times
->modtime
= sb
->st_mtime
;
907 /* --------------------------------------------------------------------------------------------- */
908 /* {{{ Query/status report routines */
910 static FileProgressStatus
911 real_do_file_error (enum OperationMode mode
, gboolean allow_retry
, const char *error
)
916 msg
= mode
== Foreground
? MSG_ERROR
: _("Background process error");
920 query_dialog (msg
, error
, D_ERROR
, 4, _("&Skip"), _("Ski&p all"), _("&Retry"),
923 result
= query_dialog (msg
, error
, D_ERROR
, 3, _("&Skip"), _("Ski&p all"), _("&Abort"));
949 /* --------------------------------------------------------------------------------------------- */
951 static FileProgressStatus
952 real_query_recursive (file_op_context_t
* ctx
, enum OperationMode mode
, const char *s
)
954 if (ctx
->recursive_result
< RECURSIVE_ALWAYS
)
959 msg
= mode
== Foreground
960 ? _("Directory \"%s\" not empty.\nDelete it recursively?")
961 : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
962 text
= g_strdup_printf (msg
, path_trunc (s
, 30));
967 ctx
->recursive_result
=
968 query_dialog (op_names
[OP_DELETE
], text
, D_ERROR
, 5, _("&Yes"), _("&No"), _("A&ll"),
969 _("Non&e"), _("&Abort"));
972 if (ctx
->recursive_result
!= RECURSIVE_ABORT
)
976 switch (ctx
->recursive_result
)
979 case RECURSIVE_ALWAYS
:
983 case RECURSIVE_NEVER
:
986 case RECURSIVE_ABORT
:
992 /* --------------------------------------------------------------------------------------------- */
994 #ifdef ENABLE_BACKGROUND
995 static FileProgressStatus
996 do_file_error (gboolean allow_retry
, const char *str
)
1002 FileProgressStatus (*f
) (enum OperationMode
, gboolean
, const char *);
1006 pntr
.f
= real_do_file_error
;
1008 if (mc_global
.we_are_background
)
1009 return parent_call (pntr
.p
, NULL
, 2, sizeof (allow_retry
), allow_retry
, strlen (str
), str
);
1011 return real_do_file_error (Foreground
, allow_retry
, str
);
1014 /* --------------------------------------------------------------------------------------------- */
1016 static FileProgressStatus
1017 query_recursive (file_op_context_t
* ctx
, const char *s
)
1023 FileProgressStatus (*f
) (file_op_context_t
*, enum OperationMode
, const char *);
1027 pntr
.f
= real_query_recursive
;
1029 if (mc_global
.we_are_background
)
1030 return parent_call (pntr
.p
, ctx
, 1, strlen (s
), s
);
1032 return real_query_recursive (ctx
, Foreground
, s
);
1035 /* --------------------------------------------------------------------------------------------- */
1037 static FileProgressStatus
1038 query_replace (file_op_context_t
* ctx
, const char *src
, struct stat
*src_stat
, const char *dst
,
1039 struct stat
*dst_stat
)
1045 FileProgressStatus (*f
) (file_op_context_t
*, enum OperationMode
, const char *,
1046 struct stat
*, const char *, struct stat
*);
1050 pntr
.f
= file_progress_real_query_replace
;
1052 if (mc_global
.we_are_background
)
1053 return parent_call (pntr
.p
, ctx
, 4, strlen (src
), src
, sizeof (struct stat
), src_stat
,
1054 strlen (dst
), dst
, sizeof (struct stat
), dst_stat
);
1056 return file_progress_real_query_replace (ctx
, Foreground
, src
, src_stat
, dst
, dst_stat
);
1060 /* --------------------------------------------------------------------------------------------- */
1062 static FileProgressStatus
1063 do_file_error (gboolean allow_retry
, const char *str
)
1065 return real_do_file_error (Foreground
, allow_retry
, str
);
1068 /* --------------------------------------------------------------------------------------------- */
1070 static FileProgressStatus
1071 query_recursive (file_op_context_t
* ctx
, const char *s
)
1073 return real_query_recursive (ctx
, Foreground
, s
);
1076 /* --------------------------------------------------------------------------------------------- */
1078 static FileProgressStatus
1079 query_replace (file_op_context_t
* ctx
, const char *src
, struct stat
*src_stat
, const char *dst
,
1080 struct stat
*dst_stat
)
1082 return file_progress_real_query_replace (ctx
, Foreground
, src
, src_stat
, dst
, dst_stat
);
1085 #endif /* !ENABLE_BACKGROUND */
1087 /* --------------------------------------------------------------------------------------------- */
1088 /** Report error with two files */
1090 static FileProgressStatus
1091 files_error (const char *format
, const char *file1
, const char *file2
)
1093 char buf
[BUF_MEDIUM
];
1094 char *nfile1
= g_strdup (path_trunc (file1
, 15));
1095 char *nfile2
= g_strdup (path_trunc (file2
, 15));
1097 g_snprintf (buf
, sizeof (buf
), format
, nfile1
, nfile2
, unix_error_string (errno
));
1102 return do_file_error (TRUE
, buf
);
1107 /* --------------------------------------------------------------------------------------------- */
1110 copy_file_file_display_progress (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
,
1111 struct timeval tv_current
, struct timeval tv_transfer_start
,
1112 off_t file_size
, off_t n_read_total
)
1116 /* 1. Update rotating dash after some time */
1119 /* 3. Compute ETA */
1120 dt
= (tv_current
.tv_sec
- tv_transfer_start
.tv_sec
);
1122 if (n_read_total
== 0)
1123 ctx
->eta_secs
= 0.0;
1126 ctx
->eta_secs
= ((dt
/ (double) n_read_total
) * file_size
) - dt
;
1127 ctx
->bps
= n_read_total
/ ((dt
< 1) ? 1 : dt
);
1130 /* 4. Compute BPS rate */
1131 ctx
->bps_time
= (tv_current
.tv_sec
- tv_transfer_start
.tv_sec
);
1132 if (ctx
->bps_time
< 1)
1134 ctx
->bps
= n_read_total
/ ctx
->bps_time
;
1136 /* 5. Compute total ETA and BPS */
1137 if (ctx
->progress_bytes
!= 0)
1139 uintmax_t remain_bytes
;
1141 remain_bytes
= ctx
->progress_bytes
- tctx
->copied_bytes
;
1144 int total_secs
= tv_current
.tv_sec
- tctx
->transfer_start
.tv_sec
;
1149 tctx
->bps
= tctx
->copied_bytes
/ total_secs
;
1150 tctx
->eta_secs
= (tctx
->bps
!= 0) ? remain_bytes
/ tctx
->bps
: 0;
1153 /* broken on lot of little files */
1155 tctx
->bps
= (tctx
->bps
* (tctx
->bps_count
- 1) + ctx
->bps
) / tctx
->bps_count
;
1156 tctx
->eta_secs
= (tctx
->bps
!= 0) ? remain_bytes
/ tctx
->bps
: 0;
1161 /* --------------------------------------------------------------------------------------------- */
1164 try_remove_file (file_op_context_t
* ctx
, const vfs_path_t
* vpath
, FileProgressStatus
* status
)
1166 while (mc_unlink (vpath
) != 0 && !ctx
->skip_all
)
1168 *status
= file_error (TRUE
, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath
));
1169 if (*status
== FILE_RETRY
)
1171 if (*status
== FILE_SKIPALL
)
1172 ctx
->skip_all
= TRUE
;
1179 /* --------------------------------------------------------------------------------------------- */
1181 /* {{{ Move routines */
1184 * Move single file or one of many files from one location to another.
1186 * @panel pointer to panel in case of single file, NULL otherwise
1187 * @tctx file operation total context object
1188 * @ctx file operation context object
1189 * @s source file name
1190 * @d destination file name
1192 * @return operation result
1194 static FileProgressStatus
1195 move_file_file (const WPanel
* panel
, file_op_total_context_t
* tctx
, file_op_context_t
* ctx
,
1196 const char *s
, const char *d
)
1198 struct stat src_stat
, dst_stat
;
1199 FileProgressStatus return_status
= FILE_CONT
;
1200 gboolean copy_done
= FALSE
;
1201 gboolean old_ask_overwrite
;
1202 vfs_path_t
*src_vpath
, *dst_vpath
;
1204 src_vpath
= vfs_path_from_str (s
);
1205 dst_vpath
= vfs_path_from_str (d
);
1207 file_progress_show_source (ctx
, src_vpath
);
1208 file_progress_show_target (ctx
, dst_vpath
);
1210 /* FIXME: do we really need to check buttons in case of single file? */
1211 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1213 return_status
= FILE_ABORT
;
1219 while (mc_lstat (src_vpath
, &src_stat
) != 0)
1221 /* Source doesn't exist */
1223 return_status
= FILE_SKIPALL
;
1226 return_status
= file_error (TRUE
, _("Cannot stat file \"%s\"\n%s"), s
);
1227 if (return_status
== FILE_SKIPALL
)
1228 ctx
->skip_all
= TRUE
;
1231 if (return_status
!= FILE_RETRY
)
1235 if (mc_lstat (dst_vpath
, &dst_stat
) == 0)
1237 if (check_same_file (s
, &src_stat
, d
, &dst_stat
, &return_status
))
1240 if (S_ISDIR (dst_stat
.st_mode
))
1242 message (D_ERROR
, MSG_ERROR
, _("Cannot overwrite directory \"%s\""), d
);
1244 return_status
= FILE_SKIP
;
1248 if (confirm_overwrite
)
1250 return_status
= query_replace (ctx
, s
, &src_stat
, d
, &dst_stat
);
1251 if (return_status
!= FILE_CONT
)
1254 /* Ok to overwrite */
1257 if (!ctx
->do_append
)
1259 if (S_ISLNK (src_stat
.st_mode
) && ctx
->stable_symlinks
)
1261 return_status
= make_symlink (ctx
, s
, d
);
1262 if (return_status
== FILE_CONT
)
1263 goto retry_src_remove
;
1267 if (mc_rename (src_vpath
, dst_vpath
) == 0)
1269 /* FIXME: do we really need to update progress in case of single file? */
1270 return_status
= progress_update_one (tctx
, ctx
, src_stat
.st_size
);
1275 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1276 one nfs to the same, but on the server it is on two different
1277 filesystems. Then nfs returns EIO instead of EXDEV.
1278 Hope it will not hurt if we always in case of error try to copy/delete. */
1280 errno
= EXDEV
; /* Hack to copy (append) the file and then delete it */
1285 return_status
= FILE_SKIPALL
;
1288 return_status
= files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s
, d
);
1289 if (return_status
== FILE_SKIPALL
)
1290 ctx
->skip_all
= TRUE
;
1291 if (return_status
== FILE_RETRY
)
1299 /* Failed rename -> copy the file instead */
1302 /* In case of single file, calculate totals. In case of many files,
1303 totals are calcuated already. */
1305 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1306 FILEGUI_DIALOG_ONE_ITEM
);
1307 if (return_status
!= FILE_CONT
)
1311 old_ask_overwrite
= tctx
->ask_overwrite
;
1312 tctx
->ask_overwrite
= FALSE
;
1313 return_status
= copy_file_file (tctx
, ctx
, s
, d
);
1314 tctx
->ask_overwrite
= old_ask_overwrite
;
1315 if (return_status
!= FILE_CONT
)
1320 /* FIXME: there is no need to update progress and check buttons
1321 at the finish of single file operation. */
1324 file_progress_show_source (ctx
, NULL
);
1325 file_progress_show (ctx
, 0, 0, "", FALSE
);
1327 return_status
= check_progress_buttons (ctx
);
1328 if (return_status
!= FILE_CONT
)
1335 if (!try_remove_file (ctx
, src_vpath
, &return_status
) && panel
== NULL
)
1339 return_status
= progress_update_one (tctx
, ctx
, src_stat
.st_size
);
1342 vfs_path_free (src_vpath
);
1343 vfs_path_free (dst_vpath
);
1345 return return_status
;
1350 /* --------------------------------------------------------------------------------------------- */
1351 /* {{{ Erase routines */
1352 /** Don't update progress status if progress_count==NULL */
1354 static FileProgressStatus
1355 erase_file (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const vfs_path_t
* vpath
)
1358 FileProgressStatus return_status
;
1360 /* check buttons if deleting info was changed */
1361 if (file_progress_show_deleting (ctx
, vfs_path_as_str (vpath
), &tctx
->progress_count
))
1363 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
1364 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1370 if (tctx
->progress_count
!= 0 && mc_lstat (vpath
, &buf
) != 0)
1372 /* ignore, most likely the mc_unlink fails, too */
1376 if (!try_remove_file (ctx
, vpath
, &return_status
) && return_status
== FILE_ABORT
)
1379 if (tctx
->progress_count
== 0)
1382 return check_progress_buttons (ctx
);
1385 /* --------------------------------------------------------------------------------------------- */
1387 static FileProgressStatus
1388 try_erase_dir (file_op_context_t
* ctx
, const char *dir
)
1390 FileProgressStatus return_status
= FILE_CONT
;
1392 while (my_rmdir (dir
) != 0 && !ctx
->skip_all
)
1394 return_status
= file_error (TRUE
, _("Cannot remove directory \"%s\"\n%s"), dir
);
1395 if (return_status
== FILE_SKIPALL
)
1396 ctx
->skip_all
= TRUE
;
1397 if (return_status
!= FILE_RETRY
)
1401 return return_status
;
1404 /* --------------------------------------------------------------------------------------------- */
1407 Recursive remove of files
1409 skip ->warn every level, gets default
1410 skipall->remove as much as possible
1412 static FileProgressStatus
1413 recursive_erase (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const vfs_path_t
* vpath
)
1415 struct dirent
*next
;
1418 FileProgressStatus return_status
= FILE_CONT
;
1420 reading
= mc_opendir (vpath
);
1421 if (reading
== NULL
)
1424 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
1426 vfs_path_t
*tmp_vpath
;
1429 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
1432 tmp_vpath
= vfs_path_append_new (vpath
, next
->d_name
, (char *) NULL
);
1433 if (mc_lstat (tmp_vpath
, &buf
) != 0)
1435 mc_closedir (reading
);
1436 vfs_path_free (tmp_vpath
);
1439 if (S_ISDIR (buf
.st_mode
))
1440 return_status
= recursive_erase (tctx
, ctx
, tmp_vpath
);
1442 return_status
= erase_file (tctx
, ctx
, tmp_vpath
);
1443 vfs_path_free (tmp_vpath
);
1445 mc_closedir (reading
);
1447 if (return_status
== FILE_ABORT
)
1450 s
= vfs_path_as_str (vpath
);
1452 file_progress_show_deleting (ctx
, s
, NULL
);
1453 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
1454 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1459 return try_erase_dir (ctx
, s
);
1462 /* --------------------------------------------------------------------------------------------- */
1463 /** Return -1 on error, 1 if there are no entries besides "." and ".."
1464 in the directory path points to, 0 else. */
1467 check_dir_is_empty (const vfs_path_t
* vpath
)
1473 dir
= mc_opendir (vpath
);
1477 for (d
= mc_readdir (dir
); d
!= NULL
; d
= mc_readdir (dir
))
1478 if (!DIR_IS_DOT (d
->d_name
) && !DIR_IS_DOTDOT (d
->d_name
))
1488 /* --------------------------------------------------------------------------------------------- */
1490 static FileProgressStatus
1491 erase_dir_iff_empty (file_op_context_t
* ctx
, const vfs_path_t
* vpath
, size_t count
)
1495 s
= vfs_path_as_str (vpath
);
1497 file_progress_show_deleting (ctx
, s
, NULL
);
1498 file_progress_show_count (ctx
, count
, ctx
->progress_count
);
1499 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1504 if (check_dir_is_empty (vpath
) != 1)
1507 /* not empty or error */
1508 return try_erase_dir (ctx
, s
);
1512 /* --------------------------------------------------------------------------------------------- */
1515 erase_dir_after_copy (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
,
1516 const vfs_path_t
* vpath
, FileProgressStatus
* status
)
1518 if (ctx
->erase_at_end
)
1520 /* Reset progress count before delete to avoid counting files twice */
1521 tctx
->progress_count
= tctx
->prev_progress_count
;
1523 while (erase_list
!= NULL
&& *status
!= FILE_ABORT
)
1525 struct link
*lp
= (struct link
*) erase_list
->data
;
1527 if (S_ISDIR (lp
->st_mode
))
1528 *status
= erase_dir_iff_empty (ctx
, lp
->src_vpath
, tctx
->progress_count
);
1530 *status
= erase_file (tctx
, ctx
, lp
->src_vpath
);
1532 erase_list
= g_slist_remove (erase_list
, lp
);
1536 /* Save progress counter before move next directory */
1537 tctx
->prev_progress_count
= tctx
->progress_count
;
1540 erase_dir_iff_empty (ctx
, vpath
, tctx
->progress_count
);
1545 /* --------------------------------------------------------------------------------------------- */
1548 * Move single directory or one of many directories from one location to another.
1550 * @panel pointer to panel in case of single directory, NULL otherwise
1551 * @tctx file operation total context object
1552 * @ctx file operation context object
1553 * @s source directory name
1554 * @d destination directory name
1556 * @return operation result
1558 static FileProgressStatus
1559 do_move_dir_dir (const WPanel
* panel
, file_op_total_context_t
* tctx
, file_op_context_t
* ctx
,
1560 const char *s
, const char *d
)
1562 struct stat src_stat
, dst_stat
;
1563 FileProgressStatus return_status
= FILE_CONT
;
1564 gboolean move_over
= FALSE
;
1566 vfs_path_t
*src_vpath
, *dst_vpath
;
1567 gboolean calc_total
= FALSE
;
1569 src_vpath
= vfs_path_from_str (s
);
1570 dst_vpath
= vfs_path_from_str (d
);
1572 file_progress_show_source (ctx
, src_vpath
);
1573 file_progress_show_target (ctx
, dst_vpath
);
1575 /* FIXME: do we really need to check buttons in case of single directory? */
1576 if (panel
!= NULL
&& check_progress_buttons (ctx
) == FILE_ABORT
)
1578 return_status
= FILE_ABORT
;
1584 mc_stat (src_vpath
, &src_stat
);
1586 dstat_ok
= (mc_stat (dst_vpath
, &dst_stat
) == 0);
1588 if (dstat_ok
&& check_same_file (s
, &src_stat
, d
, &dst_stat
, &return_status
))
1592 ; /* destination doesn't exist */
1593 else if (!ctx
->dive_into_subdirs
)
1600 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
1601 vfs_path_free (tmp
);
1604 d
= vfs_path_as_str (dst_vpath
);
1606 /* Check if the user inputted an existing dir */
1608 if (mc_stat (dst_vpath
, &dst_stat
) == 0)
1614 /* In case of single directory, calculate totals. In case of many directories,
1615 totals are calcuated already. */
1617 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1618 FILEGUI_DIALOG_MULTI_ITEM
);
1619 if (return_status
!= FILE_CONT
)
1625 return_status
= copy_dir_dir (tctx
, ctx
, s
, d
, FALSE
, TRUE
, TRUE
, NULL
);
1627 if (return_status
!= FILE_CONT
)
1631 else if (ctx
->skip_all
)
1632 return_status
= FILE_SKIPALL
;
1635 if (S_ISDIR (dst_stat
.st_mode
))
1636 return_status
= file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), d
);
1638 return_status
= file_error (TRUE
, _("Cannot overwrite file \"%s\"\n%s"), d
);
1639 if (return_status
== FILE_SKIPALL
)
1640 ctx
->skip_all
= TRUE
;
1641 if (return_status
== FILE_RETRY
)
1642 goto retry_dst_stat
;
1649 if (mc_rename (src_vpath
, dst_vpath
) == 0)
1651 return_status
= FILE_CONT
;
1659 return_status
= files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s
, d
);
1660 if (return_status
== FILE_SKIPALL
)
1661 ctx
->skip_all
= TRUE
;
1662 if (return_status
== FILE_RETRY
)
1668 /* Failed because of filesystem boundary -> copy dir instead */
1669 if (panel
!= NULL
&& !calc_total
)
1671 /* In case of single directory, calculate totals. In case of many directories,
1672 totals are calcuated already. */
1674 panel_operate_init_totals (panel
, src_vpath
, &src_stat
, ctx
, TRUE
,
1675 FILEGUI_DIALOG_MULTI_ITEM
);
1676 if (return_status
!= FILE_CONT
)
1680 return_status
= copy_dir_dir (tctx
, ctx
, s
, d
, FALSE
, FALSE
, TRUE
, NULL
);
1682 if (return_status
!= FILE_CONT
)
1686 /* FIXME: there is no need to update progress and check buttons
1687 at the finish of single directory operation. */
1690 file_progress_show_source (ctx
, NULL
);
1691 file_progress_show_target (ctx
, NULL
);
1692 file_progress_show (ctx
, 0, 0, "", FALSE
);
1694 return_status
= check_progress_buttons (ctx
);
1695 if (return_status
!= FILE_CONT
)
1701 erase_dir_after_copy (tctx
, ctx
, src_vpath
, &return_status
);
1704 erase_list
= free_linklist (erase_list
);
1706 vfs_path_free (src_vpath
);
1707 vfs_path_free (dst_vpath
);
1708 return return_status
;
1711 /* --------------------------------------------------------------------------------------------- */
1713 /* {{{ Panel operate routines */
1716 * Return currently selected entry name or the name of the first marked
1717 * entry if there is one.
1721 panel_get_file (const WPanel
* panel
)
1723 if (get_current_type () == view_tree
)
1726 const vfs_path_t
*selected_name
;
1728 tree
= (WTree
*) get_panel_widget (get_current_index ());
1729 selected_name
= tree_selected_name (tree
);
1730 return vfs_path_as_str (selected_name
);
1733 if (panel
->marked
!= 0)
1737 for (i
= 0; i
< panel
->dir
.len
; i
++)
1738 if (panel
->dir
.list
[i
].f
.marked
)
1739 return panel
->dir
.list
[i
].fname
;
1742 return panel
->dir
.list
[panel
->selected
].fname
;
1745 /* --------------------------------------------------------------------------------------------- */
1748 check_single_entry (const WPanel
* panel
, gboolean force_single
, struct stat
*src_stat
)
1754 source
= selection (panel
)->fname
;
1756 source
= panel_get_file (panel
);
1758 ok
= !DIR_IS_DOTDOT (source
);
1761 message (D_ERROR
, MSG_ERROR
, _("Cannot operate on \"..\"!"));
1764 vfs_path_t
*source_vpath
;
1766 source_vpath
= vfs_path_from_str (source
);
1768 /* Update stat to get actual info */
1769 ok
= mc_lstat (source_vpath
, src_stat
) == 0;
1772 message (D_ERROR
, MSG_ERROR
, _("Cannot stat \"%s\"\n%s"),
1773 path_trunc (source
, 30), unix_error_string (errno
));
1775 /* Directory was changed outside MC. Reload it forced */
1776 if (!panel
->is_panelized
)
1778 panel_update_flags_t flags
= UP_RELOAD
;
1780 /* don't update panelized panel */
1781 if (get_other_type () == view_listing
&& other_panel
->is_panelized
)
1782 flags
|= UP_ONLY_CURRENT
;
1784 update_panels (flags
, UP_KEEPSEL
);
1788 vfs_path_free (source_vpath
);
1791 return ok
? source
: NULL
;
1794 /* --------------------------------------------------------------------------------------------- */
1796 * Generate user prompt for panel operation.
1797 * src_stat must be not NULL for single source, and NULL for multiple sources
1801 panel_operate_generate_prompt (const WPanel
* panel
, FileOperation operation
,
1802 const struct stat
*src_stat
)
1805 char *format_string
;
1808 static gboolean i18n_flag
= FALSE
;
1813 for (i
= G_N_ELEMENTS (op_names1
); i
-- != 0;)
1814 op_names1
[i
] = Q_ (op_names1
[i
]);
1817 for (i
= G_N_ELEMENTS (prompt_parts
); i
-- != 0;)
1818 prompt_parts
[i
] = _(prompt_parts
[i
]);
1820 one_format
= _(one_format
);
1821 many_format
= _(many_format
);
1822 #endif /* ENABLE_NLS */
1826 /* Possible prompts:
1828 * "Copy file \"%s\" with source mask:"
1829 * "Copy %d files with source mask:"
1830 * "Copy directory \"%s\" with source mask:"
1831 * "Copy %d directories with source mask:"
1832 * "Copy %d files/directories with source mask:"
1834 * "Move file \"%s\" with source mask:"
1835 * "Move %d files with source mask:"
1836 * "Move directory \"%s\" with source mask:"
1837 * "Move %d directories with source mask:"
1838 * "Move %d files/directories with source mask:"
1840 * "Delete file \"%s\"?"
1841 * "Delete %d files?"
1842 * "Delete directory \"%s\"?"
1843 * "Delete %d directories?"
1844 * "Delete %d files/directories?"
1847 cp
= (src_stat
!= NULL
? one_format
: many_format
);
1849 /* 1. Substitute %o */
1850 format_string
= str_replace_all (cp
, "%o", op_names1
[(int) operation
]);
1852 /* 2. Substitute %n */
1853 cp
= operation
== OP_DELETE
? "\n" : " ";
1855 format_string
= str_replace_all (sp
, "%n", cp
);
1858 /* 3. Substitute %f */
1859 if (src_stat
!= NULL
)
1860 cp
= S_ISDIR (src_stat
->st_mode
) ? prompt_parts
[2] : prompt_parts
[0];
1861 else if (panel
->marked
== panel
->dirs_marked
)
1862 cp
= prompt_parts
[3];
1864 cp
= panel
->dirs_marked
!= 0 ? prompt_parts
[4] : prompt_parts
[1];
1867 format_string
= str_replace_all (sp
, "%f", cp
);
1870 /* 4. Substitute %m */
1871 cp
= operation
== OP_DELETE
? "?" : prompt_parts
[5];
1873 format_string
= str_replace_all (sp
, "%m", cp
);
1876 return format_string
;
1879 /* --------------------------------------------------------------------------------------------- */
1882 do_confirm_copy_move (const WPanel
* panel
, FileOperation operation
, gboolean force_single
,
1883 const char *source
, struct stat
*src_stat
, file_op_context_t
* ctx
,
1886 const char *tmp_dest_dir
;
1891 /* Forced single operations default to the original name */
1893 tmp_dest_dir
= source
;
1894 else if (get_other_type () == view_listing
)
1895 tmp_dest_dir
= vfs_path_as_str (other_panel
->cwd_vpath
);
1897 tmp_dest_dir
= vfs_path_as_str (panel
->cwd_vpath
);
1900 * Add trailing backslash only when do non-local ops.
1901 * It saves user from occasional file renames (when destination
1904 if (!force_single
&& tmp_dest_dir
!= NULL
&& tmp_dest_dir
[0] != '\0'
1905 && !IS_PATH_SEP (tmp_dest_dir
[strlen (tmp_dest_dir
) - 1]))
1907 /* add trailing separator */
1908 dest_dir
= g_strconcat (tmp_dest_dir
, PATH_SEP_STR
, (char *) NULL
);
1913 dest_dir
= g_strdup (tmp_dest_dir
);
1916 if (dest_dir
== NULL
)
1922 /* Generate confirmation prompt */
1923 format
= panel_operate_generate_prompt (panel
, operation
, src_stat
);
1925 ret
= file_mask_dialog (ctx
, operation
, source
!= NULL
, format
,
1926 source
!= NULL
? source
: (const void *) &panel
->marked
, dest_dir
,
1935 /* --------------------------------------------------------------------------------------------- */
1938 do_confirm_erase (const WPanel
* panel
, const char *source
, struct stat
*src_stat
)
1942 char fmd_buf
[BUF_MEDIUM
];
1947 /* Generate confirmation prompt */
1948 format
= panel_operate_generate_prompt (panel
, OP_DELETE
, src_stat
);
1951 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, panel
->marked
);
1954 const int fmd_xlen
= 64;
1956 i
= fmd_xlen
- str_term_width1 (format
) - 4;
1957 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, str_trunc (source
, i
));
1965 i
= query_dialog (op_names
[OP_DELETE
], fmd_buf
, D_ERROR
, 2, _("&Yes"), _("&No"));
1970 /* --------------------------------------------------------------------------------------------- */
1972 static FileProgressStatus
1973 operate_single_file (const WPanel
* panel
, FileOperation operation
, file_op_total_context_t
* tctx
,
1974 file_op_context_t
* ctx
, const char *src
, struct stat
*src_stat
,
1975 const char *dest
, filegui_dialog_type_t dialog_type
)
1977 FileProgressStatus value
;
1978 vfs_path_t
*src_vpath
;
1981 if (g_path_is_absolute (src
))
1982 src_vpath
= vfs_path_from_str (src
);
1984 src_vpath
= vfs_path_append_new (panel
->cwd_vpath
, src
, (char *) NULL
);
1986 is_file
= !S_ISDIR (src_stat
->st_mode
);
1987 /* Is link to directory? */
1992 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
1993 is_file
= !(is_link
&& ctx
->follow_links
);
1997 if (operation
== OP_DELETE
)
1999 value
= panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
, dialog_type
);
2000 if (value
== FILE_CONT
)
2003 value
= erase_file (tctx
, ctx
, src_vpath
);
2005 value
= erase_dir (tctx
, ctx
, src_vpath
);
2012 src
= vfs_path_as_str (src_vpath
);
2014 temp
= build_dest (ctx
, src
, dest
, &value
);
2022 /* we use file_mask_op_follow_links only with OP_COPY */
2023 ctx
->stat_func (src_vpath
, src_stat
);
2026 panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
,
2028 if (value
== FILE_CONT
)
2030 is_file
= !S_ISDIR (src_stat
->st_mode
);
2031 /* Is link to directory? */
2036 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
2037 is_file
= !(is_link
&& ctx
->follow_links
);
2041 value
= copy_file_file (tctx
, ctx
, src
, dest
);
2043 value
= copy_dir_dir (tctx
, ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2048 #ifdef ENABLE_BACKGROUND
2049 /* create UI to show confirmation dialog */
2050 if (!mc_global
.we_are_background
)
2051 file_op_context_create_ui (ctx
, TRUE
, FILEGUI_DIALOG_ONE_ITEM
);
2054 value
= move_file_file (panel
, tctx
, ctx
, src
, dest
);
2056 value
= do_move_dir_dir (panel
, tctx
, ctx
, src
, dest
);
2060 /* Unknown file operation */
2068 vfs_path_free (src_vpath
);
2073 /* --------------------------------------------------------------------------------------------- */
2075 static FileProgressStatus
2076 operate_one_file (const WPanel
* panel
, FileOperation operation
, file_op_total_context_t
* tctx
,
2077 file_op_context_t
* ctx
, const char *src
, struct stat
*src_stat
, const char *dest
)
2079 FileProgressStatus value
= FILE_CONT
;
2080 vfs_path_t
*src_vpath
;
2083 if (g_path_is_absolute (src
))
2084 src_vpath
= vfs_path_from_str (src
);
2086 src_vpath
= vfs_path_append_new (panel
->cwd_vpath
, src
, (char *) NULL
);
2088 is_file
= !S_ISDIR (src_stat
->st_mode
);
2090 if (operation
== OP_DELETE
)
2093 value
= erase_file (tctx
, ctx
, src_vpath
);
2095 value
= erase_dir (tctx
, ctx
, src_vpath
);
2101 src
= vfs_path_as_str (src_vpath
);
2103 temp
= build_dest (ctx
, src
, dest
, &value
);
2111 /* we use file_mask_op_follow_links only with OP_COPY */
2112 ctx
->stat_func (src_vpath
, src_stat
);
2113 is_file
= !S_ISDIR (src_stat
->st_mode
);
2116 value
= copy_file_file (tctx
, ctx
, src
, dest
);
2118 value
= copy_dir_dir (tctx
, ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2119 dest_dirs
= free_linklist (dest_dirs
);
2124 value
= move_file_file (NULL
, tctx
, ctx
, src
, dest
);
2126 value
= do_move_dir_dir (NULL
, tctx
, ctx
, src
, dest
);
2130 /* Unknown file operation */
2138 vfs_path_free (src_vpath
);
2143 /* --------------------------------------------------------------------------------------------- */
2145 #ifdef ENABLE_BACKGROUND
2147 end_bg_process (file_op_context_t
* ctx
, enum OperationMode mode
)
2154 unregister_task_with_pid (pid
);
2155 /* file_op_context_destroy(ctx); */
2161 /* --------------------------------------------------------------------------------------------- */
2162 /*** public functions ****************************************************************************/
2163 /* --------------------------------------------------------------------------------------------- */
2165 /* Is file symlink to directory or not.
2167 * @param path file or directory
2168 * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2169 * @param stale_link TRUE if file is stale link to directory
2171 * @return TRUE if file symlink to directory, ELSE otherwise.
2174 file_is_symlink_to_dir (const vfs_path_t
* vpath
, struct stat
* st
, gboolean
* stale_link
)
2177 gboolean stale
= FALSE
;
2178 gboolean res
= FALSE
;
2184 if (mc_lstat (vpath
, st
) != 0)
2188 if (S_ISLNK (st
->st_mode
))
2192 stale
= (mc_stat (vpath
, &st3
) != 0);
2195 res
= (S_ISDIR (st3
.st_mode
) != 0);
2199 if (stale_link
!= NULL
)
2200 *stale_link
= stale
;
2205 /* --------------------------------------------------------------------------------------------- */
2208 copy_file_file (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
,
2209 const char *src_path
, const char *dst_path
)
2211 uid_t src_uid
= (uid_t
) (-1);
2212 gid_t src_gid
= (gid_t
) (-1);
2214 int src_desc
, dest_desc
= -1;
2215 mode_t src_mode
= 0; /* The mode of the source file */
2216 struct stat src_stat
, dst_stat
;
2217 mc_timesbuf_t times
;
2218 gboolean dst_exists
= FALSE
, appending
= FALSE
;
2219 off_t file_size
= -1;
2220 FileProgressStatus return_status
, temp_status
;
2221 struct timeval tv_transfer_start
;
2222 dest_status_t dst_status
= DEST_NONE
;
2224 vfs_path_t
*src_vpath
= NULL
, *dst_vpath
= NULL
;
2227 /* FIXME: We should not be using global variables! */
2229 return_status
= FILE_RETRY
;
2231 dst_vpath
= vfs_path_from_str (dst_path
);
2232 src_vpath
= vfs_path_from_str (src_path
);
2234 file_progress_show_source (ctx
, src_vpath
);
2235 file_progress_show_target (ctx
, dst_vpath
);
2237 if (check_progress_buttons (ctx
) == FILE_ABORT
)
2239 return_status
= FILE_ABORT
;
2245 while (mc_stat (dst_vpath
, &dst_stat
) == 0)
2247 if (S_ISDIR (dst_stat
.st_mode
))
2250 return_status
= FILE_SKIPALL
;
2254 file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), dst_path
);
2255 if (return_status
== FILE_SKIPALL
)
2256 ctx
->skip_all
= TRUE
;
2257 if (return_status
== FILE_RETRY
)
2267 while ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2270 return_status
= FILE_SKIPALL
;
2273 return_status
= file_error (TRUE
, _("Cannot stat source file \"%s\"\n%s"), src_path
);
2274 if (return_status
== FILE_SKIPALL
)
2275 ctx
->skip_all
= TRUE
;
2278 if (return_status
!= FILE_RETRY
)
2284 /* Destination already exists */
2285 if (check_same_file (src_path
, &src_stat
, dst_path
, &dst_stat
, &return_status
))
2288 /* Should we replace destination? */
2289 if (tctx
->ask_overwrite
)
2292 return_status
= query_replace (ctx
, src_path
, &src_stat
, dst_path
, &dst_stat
);
2293 if (return_status
!= FILE_CONT
)
2298 if (!ctx
->do_append
)
2300 /* Check the hardlinks */
2301 if (!ctx
->follow_links
)
2303 switch (check_hardlinks (src_vpath
, &src_stat
, dst_vpath
, &ctx
->skip_all
))
2306 /* We have made a hardlink - no more processing is necessary */
2307 return_status
= FILE_CONT
;
2310 case HARDLINK_ABORT
:
2311 return_status
= FILE_ABORT
;
2319 if (S_ISLNK (src_stat
.st_mode
))
2321 return_status
= make_symlink (ctx
, src_path
, dst_path
);
2325 if (S_ISCHR (src_stat
.st_mode
) || S_ISBLK (src_stat
.st_mode
) || S_ISFIFO (src_stat
.st_mode
)
2326 || S_ISNAM (src_stat
.st_mode
) || S_ISSOCK (src_stat
.st_mode
))
2330 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2331 rdev
= src_stat
.st_rdev
;
2334 while (mc_mknod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
, rdev
) < 0
2338 file_error (TRUE
, _("Cannot create special file \"%s\"\n%s"), dst_path
);
2339 if (return_status
== FILE_RETRY
)
2341 if (return_status
== FILE_SKIPALL
)
2342 ctx
->skip_all
= TRUE
;
2347 while (ctx
->preserve_uidgid
2348 && mc_chown (dst_vpath
, src_stat
.st_uid
, src_stat
.st_gid
) != 0 && !ctx
->skip_all
)
2350 temp_status
= file_error (TRUE
, _("Cannot chown target file \"%s\"\n%s"), dst_path
);
2351 if (temp_status
== FILE_SKIP
)
2353 if (temp_status
== FILE_SKIPALL
)
2354 ctx
->skip_all
= TRUE
;
2355 if (temp_status
!= FILE_RETRY
)
2357 return_status
= temp_status
;
2362 while (ctx
->preserve
&& mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
) != 0
2365 temp_status
= file_error (TRUE
, _("Cannot chmod target file \"%s\"\n%s"), dst_path
);
2366 if (temp_status
== FILE_SKIP
)
2368 if (temp_status
== FILE_SKIPALL
)
2369 ctx
->skip_all
= TRUE
;
2370 if (temp_status
!= FILE_RETRY
)
2372 return_status
= temp_status
;
2377 return_status
= FILE_CONT
;
2382 gettimeofday (&tv_transfer_start
, (struct timezone
*) NULL
);
2384 while ((src_desc
= mc_open (src_vpath
, O_RDONLY
| O_LINEAR
)) < 0 && !ctx
->skip_all
)
2386 return_status
= file_error (TRUE
, _("Cannot open source file \"%s\"\n%s"), src_path
);
2387 if (return_status
== FILE_RETRY
)
2389 if (return_status
== FILE_SKIPALL
)
2390 ctx
->skip_all
= TRUE
;
2391 if (return_status
== FILE_SKIP
)
2393 ctx
->do_append
= FALSE
;
2397 if (ctx
->do_reget
!= 0)
2399 if (mc_lseek (src_desc
, ctx
->do_reget
, SEEK_SET
) != ctx
->do_reget
)
2401 message (D_ERROR
, _("Warning"), _("Reget failed, about to overwrite file"));
2403 ctx
->do_append
= FALSE
;
2407 while (mc_fstat (src_desc
, &src_stat
) != 0)
2410 return_status
= FILE_SKIPALL
;
2413 return_status
= file_error (TRUE
, _("Cannot fstat source file \"%s\"\n%s"), src_path
);
2414 if (return_status
== FILE_RETRY
)
2416 if (return_status
== FILE_SKIPALL
)
2417 ctx
->skip_all
= TRUE
;
2418 ctx
->do_append
= FALSE
;
2423 src_mode
= src_stat
.st_mode
;
2424 src_uid
= src_stat
.st_uid
;
2425 src_gid
= src_stat
.st_gid
;
2426 get_times (&src_stat
, ×
);
2427 file_size
= src_stat
.st_size
;
2429 open_flags
= O_WRONLY
;
2433 open_flags
|= O_APPEND
;
2435 open_flags
|= O_CREAT
| O_TRUNC
;
2439 open_flags
|= O_CREAT
| O_EXCL
;
2442 while ((dest_desc
= mc_open (dst_vpath
, open_flags
, src_mode
)) < 0)
2444 if (errno
!= EEXIST
)
2447 return_status
= FILE_SKIPALL
;
2451 file_error (TRUE
, _("Cannot create target file \"%s\"\n%s"), dst_path
);
2452 if (return_status
== FILE_RETRY
)
2454 if (return_status
== FILE_SKIPALL
)
2455 ctx
->skip_all
= TRUE
;
2456 ctx
->do_append
= FALSE
;
2461 dst_status
= DEST_SHORT
; /* file opened, but not fully copied */
2463 appending
= ctx
->do_append
;
2464 ctx
->do_append
= FALSE
;
2466 /* Try clone the file first. */
2467 if (vfs_clone_file (dest_desc
, src_desc
) == 0)
2469 dst_status
= DEST_FULL
;
2470 return_status
= FILE_CONT
;
2474 /* Find out the optimal buffer size. */
2475 while (mc_fstat (dest_desc
, &dst_stat
) != 0)
2478 return_status
= FILE_SKIPALL
;
2481 return_status
= file_error (TRUE
, _("Cannot fstat target file \"%s\"\n%s"), dst_path
);
2482 if (return_status
== FILE_RETRY
)
2484 if (return_status
== FILE_SKIPALL
)
2485 ctx
->skip_all
= TRUE
;
2490 /* try preallocate space; if fail, try copy anyway */
2491 while (mc_global
.vfs
.preallocate_space
&&
2492 vfs_preallocate (dest_desc
, file_size
, appending
? dst_stat
.st_size
: 0) != 0)
2496 /* cannot allocate, start the file copying anyway */
2497 return_status
= FILE_CONT
;
2502 file_error (TRUE
, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path
);
2504 if (return_status
== FILE_SKIPALL
)
2505 ctx
->skip_all
= TRUE
;
2507 if (ctx
->skip_all
|| return_status
== FILE_SKIP
)
2509 /* skip the space allocation error, start file copying */
2510 return_status
= FILE_CONT
;
2514 if (return_status
== FILE_ABORT
)
2516 mc_close (dest_desc
);
2518 mc_unlink (dst_vpath
);
2519 dst_status
= DEST_NONE
;
2523 /* return_status == FILE_RETRY -- try allocate space again */
2526 ctx
->eta_secs
= 0.0;
2529 if (tctx
->bps
== 0 || (file_size
/ (tctx
->bps
)) > FILEOP_UPDATE_INTERVAL
)
2530 file_progress_show (ctx
, 0, file_size
, "", TRUE
);
2532 file_progress_show (ctx
, 1, 1, "", TRUE
);
2533 return_status
= check_progress_buttons (ctx
);
2536 if (return_status
== FILE_CONT
)
2539 off_t n_read_total
= 0;
2540 struct timeval tv_current
, tv_last_update
, tv_last_input
;
2541 int secs
, update_secs
;
2542 const char *stalled_msg
= "";
2543 gboolean is_first_time
= TRUE
;
2545 tv_last_update
= tv_transfer_start
;
2547 bufsize
= io_blksize (dst_stat
);
2548 buf
= g_malloc (bufsize
);
2552 ssize_t n_read
= -1, n_written
;
2555 if (mc_ctl (src_desc
, VFS_CTL_IS_NOTREADY
, 0) == 0)
2556 while ((n_read
= mc_read (src_desc
, buf
, bufsize
)) < 0 && !ctx
->skip_all
)
2559 file_error (TRUE
, _("Cannot read source file \"%s\"\n%s"), src_path
);
2560 if (return_status
== FILE_RETRY
)
2562 if (return_status
== FILE_SKIPALL
)
2563 ctx
->skip_all
= TRUE
;
2570 gettimeofday (&tv_current
, NULL
);
2576 n_read_total
+= n_read
;
2578 /* Windows NT ftp servers report that files have no
2579 * permissions: -------, so if we happen to have actually
2580 * read something, we should fix the permissions.
2582 if ((src_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
)) == 0)
2583 src_mode
= S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
;
2584 gettimeofday (&tv_last_input
, NULL
);
2587 while ((n_written
= mc_write (dest_desc
, t
, (size_t) n_read
)) < n_read
)
2589 gboolean write_errno_nospace
;
2593 n_read
-= n_written
;
2598 write_errno_nospace
= (n_written
< 0 && errno
== ENOSPC
);
2601 return_status
= FILE_SKIPALL
;
2604 file_error (TRUE
, _("Cannot write target file \"%s\"\n%s"), dst_path
);
2606 if (return_status
== FILE_SKIP
)
2608 if (write_errno_nospace
)
2612 if (return_status
== FILE_SKIPALL
)
2614 ctx
->skip_all
= TRUE
;
2615 if (write_errno_nospace
)
2618 if (return_status
!= FILE_RETRY
)
2623 tctx
->copied_bytes
= tctx
->progress_bytes
+ n_read_total
+ ctx
->do_reget
;
2625 secs
= (tv_current
.tv_sec
- tv_last_update
.tv_sec
);
2626 update_secs
= (tv_current
.tv_sec
- tv_last_input
.tv_sec
);
2628 if (is_first_time
|| secs
> FILEOP_UPDATE_INTERVAL
)
2630 copy_file_file_display_progress (tctx
, ctx
,
2632 tv_transfer_start
, file_size
, n_read_total
);
2633 tv_last_update
= tv_current
;
2635 is_first_time
= FALSE
;
2637 if (update_secs
> FILEOP_STALLING_INTERVAL
)
2639 stalled_msg
= _("(stalled)");
2643 gboolean force_update
;
2646 (tv_current
.tv_sec
- tctx
->transfer_start
.tv_sec
) > FILEOP_UPDATE_INTERVAL
;
2648 if (verbose
&& ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
2650 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
2651 file_progress_show_total (tctx
, ctx
, tctx
->copied_bytes
, force_update
);
2654 file_progress_show (ctx
, n_read_total
+ ctx
->do_reget
, file_size
, stalled_msg
,
2659 return_status
= check_progress_buttons (ctx
);
2661 if (return_status
!= FILE_CONT
)
2668 dst_status
= DEST_FULL
; /* copy successful, don't remove target file */
2674 rotate_dash (FALSE
);
2675 while (src_desc
!= -1 && mc_close (src_desc
) < 0 && !ctx
->skip_all
)
2677 temp_status
= file_error (TRUE
, _("Cannot close source file \"%s\"\n%s"), src_path
);
2678 if (temp_status
== FILE_RETRY
)
2680 if (temp_status
== FILE_ABORT
)
2681 return_status
= temp_status
;
2682 if (temp_status
== FILE_SKIPALL
)
2683 ctx
->skip_all
= TRUE
;
2687 while (dest_desc
!= -1 && mc_close (dest_desc
) < 0 && !ctx
->skip_all
)
2689 temp_status
= file_error (TRUE
, _("Cannot close target file \"%s\"\n%s"), dst_path
);
2690 if (temp_status
== FILE_RETRY
)
2692 if (temp_status
== FILE_SKIPALL
)
2693 ctx
->skip_all
= TRUE
;
2694 return_status
= temp_status
;
2698 if (dst_status
== DEST_SHORT
)
2700 /* Query to remove short file */
2701 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved. Keep it?"),
2702 D_ERROR
, 2, _("&Delete"), _("&Keep")) == 0)
2703 mc_unlink (dst_vpath
);
2705 else if (dst_status
== DEST_FULL
)
2707 /* Copy has succeeded */
2708 if (!appending
&& ctx
->preserve_uidgid
)
2710 while (mc_chown (dst_vpath
, src_uid
, src_gid
) != 0 && !ctx
->skip_all
)
2712 temp_status
= file_error (TRUE
, _("Cannot chown target file \"%s\"\n%s"), dst_path
);
2713 if (temp_status
== FILE_RETRY
)
2715 if (temp_status
== FILE_SKIPALL
)
2717 ctx
->skip_all
= TRUE
;
2718 return_status
= FILE_CONT
;
2720 if (temp_status
== FILE_SKIP
)
2721 return_status
= FILE_CONT
;
2730 while (mc_chmod (dst_vpath
, (src_mode
& ctx
->umask_kill
)) != 0 && !ctx
->skip_all
)
2733 file_error (TRUE
, _("Cannot chmod target file \"%s\"\n%s"), dst_path
);
2734 if (temp_status
== FILE_RETRY
)
2736 if (temp_status
== FILE_SKIPALL
)
2738 ctx
->skip_all
= TRUE
;
2739 return_status
= FILE_CONT
;
2741 if (temp_status
== FILE_SKIP
)
2742 return_status
= FILE_CONT
;
2746 else if (!dst_exists
)
2748 src_mode
= umask (-1);
2750 src_mode
= 0100666 & ~src_mode
;
2751 mc_chmod (dst_vpath
, (src_mode
& ctx
->umask_kill
));
2753 mc_utime (dst_vpath
, ×
);
2757 if (return_status
== FILE_CONT
)
2758 return_status
= progress_update_one (tctx
, ctx
, file_size
);
2761 vfs_path_free (src_vpath
);
2762 vfs_path_free (dst_vpath
);
2763 return return_status
;
2766 /* --------------------------------------------------------------------------------------------- */
2768 * I think these copy_*_* functions should have a return type.
2769 * anyway, this function *must* have two directories as arguments.
2771 /* FIXME: This function needs to check the return values of the
2775 copy_dir_dir (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const char *s
, const char *d
,
2776 gboolean toplevel
, gboolean move_over
, gboolean do_delete
, GSList
* parent_dirs
)
2778 struct dirent
*next
;
2779 struct stat dst_stat
, src_stat
;
2781 FileProgressStatus return_status
= FILE_CONT
;
2783 vfs_path_t
*src_vpath
, *dst_vpath
;
2784 gboolean do_mkdir
= TRUE
;
2786 src_vpath
= vfs_path_from_str (s
);
2787 dst_vpath
= vfs_path_from_str (d
);
2789 /* First get the mode of the source dir */
2792 if ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2795 return_status
= FILE_SKIPALL
;
2798 return_status
= file_error (TRUE
, _("Cannot stat source directory \"%s\"\n%s"), s
);
2799 if (return_status
== FILE_RETRY
)
2800 goto retry_src_stat
;
2801 if (return_status
== FILE_SKIPALL
)
2802 ctx
->skip_all
= TRUE
;
2807 if (is_in_linklist (dest_dirs
, src_vpath
, &src_stat
) != NULL
)
2809 /* Don't copy a directory we created before (we don't want to copy
2810 infinitely if a directory is copied into itself) */
2811 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
2812 return_status
= FILE_CONT
;
2816 /* Hmm, hardlink to directory??? - Norbert */
2817 /* FIXME: In this step we should do something in case the destination already exist */
2818 /* Check the hardlinks */
2821 switch (check_hardlinks (src_vpath
, &src_stat
, dst_vpath
, &ctx
->skip_all
))
2824 /* We have made a hardlink - no more processing is necessary */
2827 case HARDLINK_ABORT
:
2828 return_status
= FILE_ABORT
;
2836 if (!S_ISDIR (src_stat
.st_mode
))
2839 return_status
= FILE_SKIPALL
;
2842 return_status
= file_error (TRUE
, _("Source \"%s\" is not a directory\n%s"), s
);
2843 if (return_status
== FILE_RETRY
)
2844 goto retry_src_stat
;
2845 if (return_status
== FILE_SKIPALL
)
2846 ctx
->skip_all
= TRUE
;
2851 if (is_in_linklist (parent_dirs
, src_vpath
, &src_stat
) != NULL
)
2853 /* we found a cyclic symbolic link */
2854 message (D_ERROR
, MSG_ERROR
, _("Cannot copy cyclic symbolic link\n\"%s\""), s
);
2855 return_status
= FILE_SKIP
;
2859 lp
= g_new0 (struct link
, 1);
2860 lp
->vfs
= vfs_path_get_by_index (src_vpath
, -1)->class;
2861 lp
->ino
= src_stat
.st_ino
;
2862 lp
->dev
= src_stat
.st_dev
;
2863 parent_dirs
= g_slist_prepend (parent_dirs
, lp
);
2866 /* Now, check if the dest dir exists, if not, create it. */
2867 if (mc_stat (dst_vpath
, &dst_stat
) != 0)
2869 /* Here the dir doesn't exist : make it ! */
2870 if (move_over
&& mc_rename (src_vpath
, dst_vpath
) == 0)
2872 return_status
= FILE_CONT
;
2879 * If the destination directory exists, we want to copy the whole
2880 * directory, but we only want this to happen once.
2882 * Escape sequences added to the * to compiler warnings.
2883 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
2884 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
2886 if (!S_ISDIR (dst_stat
.st_mode
))
2889 return_status
= FILE_SKIPALL
;
2893 file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"), d
);
2894 if (return_status
== FILE_SKIPALL
)
2895 ctx
->skip_all
= TRUE
;
2896 if (return_status
== FILE_RETRY
)
2897 goto retry_dst_stat
;
2901 /* Dive into subdir if exists */
2902 if (toplevel
&& ctx
->dive_into_subdirs
)
2907 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
2908 vfs_path_free (tmp
);
2915 d
= vfs_path_as_str (dst_vpath
);
2919 while (my_mkdir (dst_vpath
, (src_stat
.st_mode
& ctx
->umask_kill
) | S_IRWXU
) != 0)
2922 return_status
= FILE_SKIPALL
;
2926 file_error (TRUE
, _("Cannot create target directory \"%s\"\n%s"), d
);
2927 if (return_status
== FILE_SKIPALL
)
2928 ctx
->skip_all
= TRUE
;
2930 if (return_status
!= FILE_RETRY
)
2934 lp
= g_new0 (struct link
, 1);
2935 mc_stat (dst_vpath
, &dst_stat
);
2936 lp
->vfs
= vfs_path_get_by_index (dst_vpath
, -1)->class;
2937 lp
->ino
= dst_stat
.st_ino
;
2938 lp
->dev
= dst_stat
.st_dev
;
2939 dest_dirs
= g_slist_prepend (dest_dirs
, lp
);
2942 if (ctx
->preserve_uidgid
)
2944 while (mc_chown (dst_vpath
, src_stat
.st_uid
, src_stat
.st_gid
) != 0)
2947 return_status
= FILE_SKIPALL
;
2950 return_status
= file_error (TRUE
, _("Cannot chown target directory \"%s\"\n%s"), d
);
2951 if (return_status
== FILE_SKIPALL
)
2952 ctx
->skip_all
= TRUE
;
2954 if (return_status
!= FILE_RETRY
)
2959 /* open the source dir for reading */
2960 reading
= mc_opendir (src_vpath
);
2961 if (reading
== NULL
)
2964 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
2967 vfs_path_t
*tmp_vpath
;
2970 * Now, we don't want '.' and '..' to be created / copied at any time
2972 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
2975 /* get the filename and add it to the src directory */
2976 path
= mc_build_filename (s
, next
->d_name
, (char *) NULL
);
2977 tmp_vpath
= vfs_path_from_str (path
);
2979 (*ctx
->stat_func
) (tmp_vpath
, &dst_stat
);
2980 if (S_ISDIR (dst_stat
.st_mode
))
2984 mdpath
= mc_build_filename (d
, next
->d_name
, (char *) NULL
);
2986 * From here, we just intend to recursively copy subdirs, not
2987 * the double functionality of copying different when the target
2988 * dir already exists. So, we give the recursive call the flag 0
2989 * meaning no toplevel.
2992 copy_dir_dir (tctx
, ctx
, path
, mdpath
, FALSE
, FALSE
, do_delete
, parent_dirs
);
2999 dest_file
= mc_build_filename (d
, x_basename (path
), (char *) NULL
);
3000 return_status
= copy_file_file (tctx
, ctx
, path
, dest_file
);
3006 if (do_delete
&& return_status
== FILE_CONT
)
3008 if (ctx
->erase_at_end
)
3010 lp
= g_new0 (struct link
, 1);
3011 lp
->src_vpath
= tmp_vpath
;
3012 lp
->st_mode
= dst_stat
.st_mode
;
3013 erase_list
= g_slist_append (erase_list
, lp
);
3016 else if (S_ISDIR (dst_stat
.st_mode
))
3017 return_status
= erase_dir_iff_empty (ctx
, tmp_vpath
, tctx
->progress_count
);
3019 return_status
= erase_file (tctx
, ctx
, tmp_vpath
);
3021 vfs_path_free (tmp_vpath
);
3023 mc_closedir (reading
);
3027 mc_timesbuf_t times
;
3029 mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
);
3030 get_times (&src_stat
, ×
);
3031 mc_utime (dst_vpath
, ×
);
3035 src_stat
.st_mode
= umask (-1);
3036 umask (src_stat
.st_mode
);
3037 src_stat
.st_mode
= 0100777 & ~src_stat
.st_mode
;
3038 mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
);
3042 free_link (parent_dirs
->data
);
3043 g_slist_free_1 (parent_dirs
);
3045 vfs_path_free (src_vpath
);
3046 vfs_path_free (dst_vpath
);
3047 return return_status
;
3052 /* --------------------------------------------------------------------------------------------- */
3053 /* {{{ Move routines */
3056 move_dir_dir (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const char *s
, const char *d
)
3058 return do_move_dir_dir (NULL
, tctx
, ctx
, s
, d
);
3063 /* --------------------------------------------------------------------------------------------- */
3064 /* {{{ Erase routines */
3067 erase_dir (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const vfs_path_t
* vpath
)
3069 file_progress_show_deleting (ctx
, vfs_path_as_str (vpath
), NULL
);
3070 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
3071 if (check_progress_buttons (ctx
) == FILE_ABORT
)
3076 /* The old way to detect a non empty directory was:
3077 error = my_rmdir (s);
3078 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3079 For the linux user space nfs server (nfs-server-2.2beta29-2)
3080 we would have to check also for EIO. I hope the new way is
3081 fool proof. (Norbert)
3083 if (check_dir_is_empty (vpath
) == 0)
3085 FileProgressStatus error
;
3087 error
= query_recursive (ctx
, vfs_path_as_str (vpath
));
3088 if (error
== FILE_CONT
)
3089 error
= recursive_erase (tctx
, ctx
, vpath
);
3093 return try_erase_dir (ctx
, vfs_path_as_str (vpath
));
3098 /* --------------------------------------------------------------------------------------------- */
3099 /* {{{ Panel operate routines */
3102 dirsize_status_init_cb (status_msg_t
* sm
)
3104 dirsize_status_msg_t
*dsm
= (dirsize_status_msg_t
*) sm
;
3105 WGroup
*gd
= GROUP (sm
->dlg
);
3106 Widget
*wd
= WIDGET (sm
->dlg
);
3108 const char *b1_name
= N_("&Abort");
3109 const char *b2_name
= N_("&Skip");
3110 int b_width
, ui_width
;
3113 b1_name
= _(b1_name
);
3114 b2_name
= _(b2_name
);
3117 b_width
= str_term_width1 (b1_name
) + 4;
3118 if (dsm
->allow_skip
)
3119 b_width
+= str_term_width1 (b2_name
) + 4 + 1;
3121 ui_width
= MAX (COLS
/ 2, b_width
+ 6);
3122 dsm
->dirname
= label_new (2, 3, "");
3123 group_add_widget (gd
, dsm
->dirname
);
3124 dsm
->count_size
= label_new (3, 3, "");
3125 group_add_widget (gd
, dsm
->count_size
);
3126 group_add_widget (gd
, hline_new (4, -1, -1));
3128 dsm
->abort_button
= WIDGET (button_new (5, 3, FILE_ABORT
, NORMAL_BUTTON
, b1_name
, NULL
));
3129 group_add_widget (gd
, dsm
->abort_button
);
3130 if (dsm
->allow_skip
)
3132 dsm
->skip_button
= WIDGET (button_new (5, 3, FILE_SKIP
, NORMAL_BUTTON
, b2_name
, NULL
));
3133 group_add_widget (gd
, dsm
->skip_button
);
3134 widget_select (dsm
->skip_button
);
3137 widget_set_size (wd
, wd
->y
, wd
->x
, 8, ui_width
);
3138 dirsize_status_locate_buttons (dsm
);
3141 /* --------------------------------------------------------------------------------------------- */
3144 dirsize_status_update_cb (status_msg_t
* sm
)
3146 dirsize_status_msg_t
*dsm
= (dirsize_status_msg_t
*) sm
;
3147 Widget
*wd
= WIDGET (sm
->dlg
);
3149 /* update second (longer label) */
3150 label_set_textv (dsm
->count_size
, _("Directories: %zu, total size: %s"),
3151 dsm
->dir_count
, size_trunc_sep (dsm
->total_size
, panels_options
.kilobyte_si
));
3153 /* enlarge dialog if required */
3154 if (WIDGET (dsm
->count_size
)->cols
+ 6 > wd
->cols
)
3156 dlg_set_size (sm
->dlg
, wd
->lines
, WIDGET (dsm
->count_size
)->cols
+ 6);
3157 dirsize_status_locate_buttons (dsm
);
3161 /* adjust first label */
3162 label_set_text (dsm
->dirname
, str_trunc (vfs_path_as_str (dsm
->dirname_vpath
), wd
->cols
- 6));
3164 switch (status_msg_common_update (sm
))
3176 /* --------------------------------------------------------------------------------------------- */
3179 dirsize_status_deinit_cb (status_msg_t
* sm
)
3183 /* schedule to update passive panel */
3184 if (get_other_type () == view_listing
)
3185 other_panel
->dirty
= 1;
3188 /* --------------------------------------------------------------------------------------------- */
3192 * Computes the number of bytes used by the files in a directory
3196 compute_dir_size (const vfs_path_t
* dirname_vpath
, dirsize_status_msg_t
* sm
,
3197 size_t * ret_dir_count
, size_t * ret_marked_count
, uintmax_t * ret_total
,
3198 gboolean compute_symlinks
)
3200 return do_compute_dir_size (dirname_vpath
, sm
, ret_dir_count
, ret_marked_count
, ret_total
,
3204 /* --------------------------------------------------------------------------------------------- */
3208 * Performs one of the operations on the selection on the source_panel
3209 * (copy, delete, move).
3211 * Returns TRUE if did change the directory
3212 * structure, Returns FALSE if user aborted
3214 * force_single forces operation on the current entry and affects
3215 * default destination. Current filename is used as default.
3219 panel_operate (void *source_panel
, FileOperation operation
, gboolean force_single
)
3221 WPanel
*panel
= PANEL (source_panel
);
3222 const gboolean single_entry
= force_single
|| (panel
->marked
<= 1)
3223 || (get_current_type () == view_tree
);
3225 const char *source
= NULL
;
3227 vfs_path_t
*dest_vpath
= NULL
;
3228 char *save_cwd
= NULL
, *save_dest
= NULL
;
3229 struct stat src_stat
;
3230 gboolean ret_val
= TRUE
;
3232 FileProgressStatus value
;
3233 file_op_context_t
*ctx
;
3234 file_op_total_context_t
*tctx
;
3235 vfs_path_t
*tmp_vpath
;
3236 filegui_dialog_type_t dialog_type
= FILEGUI_DIALOG_ONE_ITEM
;
3238 gboolean do_bg
= FALSE
; /* do background operation? */
3240 static gboolean i18n_flag
= FALSE
;
3243 for (i
= G_N_ELEMENTS (op_names
); i
-- != 0;)
3244 op_names
[i
] = Q_ (op_names
[i
]);
3248 linklist
= free_linklist (linklist
);
3249 dest_dirs
= free_linklist (dest_dirs
);
3253 source
= check_single_entry (panel
, force_single
, &src_stat
);
3259 ctx
= file_op_context_new (operation
);
3261 /* Show confirmation dialog */
3262 if (operation
!= OP_DELETE
)
3265 do_confirm_copy_move (panel
, operation
, force_single
, source
, &src_stat
, ctx
, &do_bg
);
3273 dest_vpath
= vfs_path_from_str (dest
);
3275 else if (confirm_delete
&& !do_confirm_erase (panel
, source
, &src_stat
))
3281 tctx
= file_op_total_context_new ();
3282 gettimeofday (&tctx
->transfer_start
, (struct timezone
*) NULL
);
3284 #ifdef ENABLE_BACKGROUND
3285 /* Did the user select to do a background operation? */
3290 v
= do_background (ctx
,
3291 g_strconcat (op_names
[operation
], ": ",
3292 vfs_path_as_str (panel
->cwd_vpath
), (char *) NULL
));
3294 message (D_ERROR
, MSG_ERROR
, _("Sorry, I could not put the job in background"));
3296 /* If we are the parent */
3299 mc_setctl (panel
->cwd_vpath
, VFS_SETCTL_FORGET
, NULL
);
3301 mc_setctl (dest_vpath
, VFS_SETCTL_FORGET
, NULL
);
3302 vfs_path_free (dest_vpath
);
3304 /* file_op_context_destroy (ctx); */
3309 #endif /* ENABLE_BACKGROUND */
3311 if (operation
== OP_DELETE
)
3312 dialog_type
= FILEGUI_DIALOG_DELETE_ITEM
;
3313 else if (single_entry
&& S_ISDIR (selection (panel
)->st
.st_mode
))
3314 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
3315 else if (single_entry
|| force_single
)
3316 dialog_type
= FILEGUI_DIALOG_ONE_ITEM
;
3318 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
3321 /* Initialize things */
3322 /* We do not want to trash cache every time file is
3323 created/touched. However, this will make our cache contain
3326 && (mc_setctl (dest_vpath
, VFS_SETCTL_STALE_DATA
, GUINT_TO_POINTER (1)) != 0))
3327 save_dest
= g_strdup (dest
);
3329 if ((vfs_path_tokens_count (panel
->cwd_vpath
) != 0)
3330 && (mc_setctl (panel
->cwd_vpath
, VFS_SETCTL_STALE_DATA
, GUINT_TO_POINTER (1)) != 0))
3331 save_cwd
= g_strdup (vfs_path_as_str (panel
->cwd_vpath
));
3333 /* Now, let's do the job */
3335 /* This code is only called by the tree and panel code */
3338 /* We now have ETA in all cases */
3340 /* One file: FIXME mc_chdir will take user out of any vfs */
3341 if ((operation
!= OP_COPY
) && (get_current_type () == view_tree
))
3346 vpath
= vfs_path_from_str (PATH_SEP_STR
);
3347 chdir_retcode
= mc_chdir (vpath
);
3348 vfs_path_free (vpath
);
3349 if (chdir_retcode
< 0)
3357 operate_single_file (panel
, operation
, tctx
, ctx
, source
, &src_stat
, dest
, dialog_type
);
3359 if ((value
== FILE_CONT
) && !force_single
)
3360 unmark_files (panel
);
3366 /* Check destination for copy or move operation */
3367 while (operation
!= OP_DELETE
)
3370 struct stat dst_stat
;
3372 dst_result
= mc_stat (dest_vpath
, &dst_stat
);
3374 if ((dst_result
!= 0) || S_ISDIR (dst_stat
.st_mode
))
3378 || file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"),
3379 dest
) != FILE_RETRY
)
3383 /* TODO: the good way is required to skip directories scanning in case of rename/move
3384 * of several directories. Since reqular expression can be used for destination,
3385 * some directory movements can be a cross-filesystem and directory scanning is useful
3386 * for those directories only. */
3388 if (panel_operate_init_totals (panel
, NULL
, NULL
, ctx
, file_op_compute_totals
, dialog_type
)
3391 /* Loop for every file, perform the actual copy operation */
3392 for (i
= 0; i
< panel
->dir
.len
; i
++)
3394 const char *source2
;
3396 if (!panel
->dir
.list
[i
].f
.marked
)
3397 continue; /* Skip the unmarked ones */
3399 source2
= panel
->dir
.list
[i
].fname
;
3400 src_stat
= panel
->dir
.list
[i
].st
;
3402 value
= operate_one_file (panel
, operation
, tctx
, ctx
, source2
, &src_stat
, dest
);
3404 if (value
== FILE_ABORT
)
3407 if (value
== FILE_CONT
)
3408 do_file_mark (panel
, i
, 0);
3410 if (verbose
&& ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
3412 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
3413 file_progress_show_total (tctx
, ctx
, tctx
->progress_bytes
, FALSE
);
3416 if (operation
!= OP_DELETE
)
3417 file_progress_show (ctx
, 0, 0, "", FALSE
);
3419 if (check_progress_buttons (ctx
) == FILE_ABORT
)
3423 } /* Loop for every file */
3425 } /* Many entries */
3429 if (save_cwd
!= NULL
)
3431 tmp_vpath
= vfs_path_from_str (save_cwd
);
3432 mc_setctl (tmp_vpath
, VFS_SETCTL_STALE_DATA
, NULL
);
3433 vfs_path_free (tmp_vpath
);
3437 if (save_dest
!= NULL
)
3439 tmp_vpath
= vfs_path_from_str (save_dest
);
3440 mc_setctl (tmp_vpath
, VFS_SETCTL_STALE_DATA
, NULL
);
3441 vfs_path_free (tmp_vpath
);
3445 linklist
= free_linklist (linklist
);
3446 dest_dirs
= free_linklist (dest_dirs
);
3448 vfs_path_free (dest_vpath
);
3449 MC_PTR_FREE (ctx
->dest_mask
);
3451 #ifdef ENABLE_BACKGROUND
3452 /* Let our parent know we are saying bye bye */
3453 if (mc_global
.we_are_background
)
3455 int cur_pid
= getpid ();
3456 /* Send pid to parent with child context, it is fork and
3457 don't modify real parent ctx */
3459 parent_call ((void *) end_bg_process
, ctx
, 0);
3462 my_exit (EXIT_SUCCESS
);
3464 #endif /* ENABLE_BACKGROUND */
3466 file_op_total_context_destroy (tctx
);
3468 file_op_context_destroy (ctx
);
3475 /* --------------------------------------------------------------------------------------------- */
3476 /* {{{ Query/status report routines */
3477 /** Report error with one file */
3479 file_error (gboolean allow_retry
, const char *format
, const char *file
)
3481 char buf
[BUF_MEDIUM
];
3483 g_snprintf (buf
, sizeof (buf
), format
, path_trunc (file
, 30), unix_error_string (errno
));
3485 return do_file_error (allow_retry
, buf
);
3488 /* --------------------------------------------------------------------------------------------- */
3491 Cause emacs to enter folding mode for this file: