4 Copyright (C) 1994-2018
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
;
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
, &sb
) == 0) && S_ISLNK (sb
.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
, 4, _("&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 *destname
, struct stat
*_s_stat
,
1039 struct stat
*_d_stat
)
1045 FileProgressStatus (*f
) (file_op_context_t
*, enum OperationMode
, const char *,
1046 struct stat
*, struct stat
*);
1050 pntr
.f
= file_progress_real_query_replace
;
1052 if (mc_global
.we_are_background
)
1053 return parent_call (pntr
.p
, ctx
, 3, strlen (destname
), destname
,
1054 sizeof (struct stat
), _s_stat
, sizeof (struct stat
), _d_stat
);
1056 return file_progress_real_query_replace (ctx
, Foreground
, destname
, _s_stat
, _d_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 *destname
, struct stat
*_s_stat
,
1080 struct stat
*_d_stat
)
1082 return file_progress_real_query_replace (ctx
, Foreground
, destname
, _s_stat
, _d_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_stats
, dst_stats
;
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_stats
) != 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_stats
) == 0)
1237 if (check_same_file (s
, &src_stats
, d
, &dst_stats
, &return_status
))
1240 if (S_ISDIR (dst_stats
.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
, d
, &src_stats
, &dst_stats
);
1251 if (return_status
!= FILE_CONT
)
1254 /* Ok to overwrite */
1257 if (!ctx
->do_append
)
1259 if (S_ISLNK (src_stats
.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_stats
.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_stats
, 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_stats
.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 file_progress_show_deleting (ctx
, vfs_path_as_str (vpath
), &tctx
->progress_count
);
1361 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
1362 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1367 if (tctx
->progress_count
!= 0 && mc_lstat (vpath
, &buf
) != 0)
1369 /* ignore, most likely the mc_unlink fails, too */
1373 if (!try_remove_file (ctx
, vpath
, &return_status
) && return_status
== FILE_ABORT
)
1376 if (tctx
->progress_count
== 0)
1379 return check_progress_buttons (ctx
);
1382 /* --------------------------------------------------------------------------------------------- */
1384 static FileProgressStatus
1385 try_erase_dir (file_op_context_t
* ctx
, const char *dir
)
1387 FileProgressStatus return_status
= FILE_CONT
;
1389 while (my_rmdir (dir
) != 0 && !ctx
->skip_all
)
1391 return_status
= file_error (TRUE
, _("Cannot remove directory \"%s\"\n%s"), dir
);
1392 if (return_status
== FILE_SKIPALL
)
1393 ctx
->skip_all
= TRUE
;
1394 if (return_status
!= FILE_RETRY
)
1398 return return_status
;
1401 /* --------------------------------------------------------------------------------------------- */
1404 Recursive remove of files
1406 skip ->warn every level, gets default
1407 skipall->remove as much as possible
1409 static FileProgressStatus
1410 recursive_erase (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const vfs_path_t
* vpath
)
1412 struct dirent
*next
;
1415 FileProgressStatus return_status
= FILE_CONT
;
1417 reading
= mc_opendir (vpath
);
1418 if (reading
== NULL
)
1421 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
1423 vfs_path_t
*tmp_vpath
;
1426 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
1429 tmp_vpath
= vfs_path_append_new (vpath
, next
->d_name
, (char *) NULL
);
1430 if (mc_lstat (tmp_vpath
, &buf
) != 0)
1432 mc_closedir (reading
);
1433 vfs_path_free (tmp_vpath
);
1436 if (S_ISDIR (buf
.st_mode
))
1437 return_status
= recursive_erase (tctx
, ctx
, tmp_vpath
);
1439 return_status
= erase_file (tctx
, ctx
, tmp_vpath
);
1440 vfs_path_free (tmp_vpath
);
1442 mc_closedir (reading
);
1444 if (return_status
== FILE_ABORT
)
1447 s
= vfs_path_as_str (vpath
);
1449 file_progress_show_deleting (ctx
, s
, NULL
);
1450 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
1451 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1456 return try_erase_dir (ctx
, s
);
1459 /* --------------------------------------------------------------------------------------------- */
1460 /** Return -1 on error, 1 if there are no entries besides "." and ".."
1461 in the directory path points to, 0 else. */
1464 check_dir_is_empty (const vfs_path_t
* vpath
)
1470 dir
= mc_opendir (vpath
);
1474 for (d
= mc_readdir (dir
); d
!= NULL
; d
= mc_readdir (dir
))
1475 if (!DIR_IS_DOT (d
->d_name
) && !DIR_IS_DOTDOT (d
->d_name
))
1485 /* --------------------------------------------------------------------------------------------- */
1487 static FileProgressStatus
1488 erase_dir_iff_empty (file_op_context_t
* ctx
, const vfs_path_t
* vpath
, size_t count
)
1492 s
= vfs_path_as_str (vpath
);
1494 file_progress_show_deleting (ctx
, s
, NULL
);
1495 file_progress_show_count (ctx
, count
, ctx
->progress_count
);
1496 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1501 if (check_dir_is_empty (vpath
) != 1)
1504 /* not empty or error */
1505 return try_erase_dir (ctx
, s
);
1509 /* --------------------------------------------------------------------------------------------- */
1512 erase_dir_after_copy (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
,
1513 const vfs_path_t
* vpath
, FileProgressStatus
* status
)
1515 if (ctx
->erase_at_end
)
1517 /* Reset progress count before delete to avoid counting files twice */
1518 tctx
->progress_count
= tctx
->prev_progress_count
;
1520 while (erase_list
!= NULL
&& *status
!= FILE_ABORT
)
1522 struct link
*lp
= (struct link
*) erase_list
->data
;
1524 if (S_ISDIR (lp
->st_mode
))
1525 *status
= erase_dir_iff_empty (ctx
, lp
->src_vpath
, tctx
->progress_count
);
1527 *status
= erase_file (tctx
, ctx
, lp
->src_vpath
);
1529 erase_list
= g_slist_remove (erase_list
, lp
);
1533 /* Save progress counter before move next directory */
1534 tctx
->prev_progress_count
= tctx
->progress_count
;
1537 erase_dir_iff_empty (ctx
, vpath
, tctx
->progress_count
);
1542 /* --------------------------------------------------------------------------------------------- */
1545 * Move single directory or one of many directories from one location to another.
1547 * @panel pointer to panel in case of single directory, NULL otherwise
1548 * @tctx file operation total context object
1549 * @ctx file operation context object
1550 * @s source directory name
1551 * @d destination directory name
1553 * @return operation result
1555 static FileProgressStatus
1556 do_move_dir_dir (const WPanel
* panel
, file_op_total_context_t
* tctx
, file_op_context_t
* ctx
,
1557 const char *s
, const char *d
)
1559 struct stat sbuf
, dbuf
;
1560 FileProgressStatus return_status
= FILE_CONT
;
1561 gboolean move_over
= FALSE
;
1563 vfs_path_t
*src_vpath
, *dst_vpath
;
1565 src_vpath
= vfs_path_from_str (s
);
1566 dst_vpath
= vfs_path_from_str (d
);
1568 file_progress_show_source (ctx
, src_vpath
);
1569 file_progress_show_target (ctx
, dst_vpath
);
1571 /* FIXME: do we really need to check buttons in case of single directory? */
1572 if (panel
!= NULL
&& check_progress_buttons (ctx
) == FILE_ABORT
)
1574 return_status
= FILE_ABORT
;
1580 mc_stat (src_vpath
, &sbuf
);
1582 dstat_ok
= (mc_stat (dst_vpath
, &dbuf
) == 0);
1584 if (dstat_ok
&& check_same_file (s
, &sbuf
, d
, &dbuf
, &return_status
))
1588 ; /* destination doesn't exist */
1589 else if (!ctx
->dive_into_subdirs
)
1596 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
1597 vfs_path_free (tmp
);
1600 d
= vfs_path_as_str (dst_vpath
);
1602 /* Check if the user inputted an existing dir */
1604 if (mc_stat (dst_vpath
, &dbuf
) == 0)
1608 return_status
= copy_dir_dir (tctx
, ctx
, s
, d
, FALSE
, TRUE
, TRUE
, NULL
);
1610 if (return_status
!= FILE_CONT
)
1614 else if (ctx
->skip_all
)
1615 return_status
= FILE_SKIPALL
;
1618 if (S_ISDIR (dbuf
.st_mode
))
1619 return_status
= file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), d
);
1621 return_status
= file_error (TRUE
, _("Cannot overwrite file \"%s\"\n%s"), d
);
1622 if (return_status
== FILE_SKIPALL
)
1623 ctx
->skip_all
= TRUE
;
1624 if (return_status
== FILE_RETRY
)
1625 goto retry_dst_stat
;
1632 if (mc_rename (src_vpath
, dst_vpath
) == 0)
1634 return_status
= FILE_CONT
;
1642 return_status
= files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s
, d
);
1643 if (return_status
== FILE_SKIPALL
)
1644 ctx
->skip_all
= TRUE
;
1645 if (return_status
== FILE_RETRY
)
1651 /* Failed because of filesystem boundary -> copy dir instead */
1654 /* In case of single directory, calculate totals. In case of many directories,
1655 totals are calcuated already. */
1657 panel_operate_init_totals (panel
, src_vpath
, &sbuf
, ctx
, FALSE
,
1658 FILEGUI_DIALOG_ONE_ITEM
);
1659 if (return_status
!= FILE_CONT
)
1663 return_status
= copy_dir_dir (tctx
, ctx
, s
, d
, FALSE
, FALSE
, TRUE
, NULL
);
1665 if (return_status
!= FILE_CONT
)
1669 /* FIXME: there is no need to update progress and check buttons
1670 at the finish of single directory operation. */
1673 file_progress_show_source (ctx
, NULL
);
1674 file_progress_show_target (ctx
, NULL
);
1675 file_progress_show (ctx
, 0, 0, "", FALSE
);
1677 return_status
= check_progress_buttons (ctx
);
1678 if (return_status
!= FILE_CONT
)
1684 erase_dir_after_copy (tctx
, ctx
, src_vpath
, &return_status
);
1687 erase_list
= free_linklist (erase_list
);
1689 vfs_path_free (src_vpath
);
1690 vfs_path_free (dst_vpath
);
1691 return return_status
;
1694 /* --------------------------------------------------------------------------------------------- */
1696 /* {{{ Panel operate routines */
1699 * Return currently selected entry name or the name of the first marked
1700 * entry if there is one.
1704 panel_get_file (const WPanel
* panel
)
1706 if (get_current_type () == view_tree
)
1709 const vfs_path_t
*selected_name
;
1711 tree
= (WTree
*) get_panel_widget (get_current_index ());
1712 selected_name
= tree_selected_name (tree
);
1713 return vfs_path_as_str (selected_name
);
1716 if (panel
->marked
!= 0)
1720 for (i
= 0; i
< panel
->dir
.len
; i
++)
1721 if (panel
->dir
.list
[i
].f
.marked
)
1722 return panel
->dir
.list
[i
].fname
;
1725 return panel
->dir
.list
[panel
->selected
].fname
;
1728 /* --------------------------------------------------------------------------------------------- */
1731 check_single_entry (const WPanel
* panel
, gboolean force_single
, struct stat
*src_stat
)
1737 source
= selection (panel
)->fname
;
1739 source
= panel_get_file (panel
);
1741 ok
= !DIR_IS_DOTDOT (source
);
1744 message (D_ERROR
, MSG_ERROR
, _("Cannot operate on \"..\"!"));
1747 vfs_path_t
*source_vpath
;
1749 source_vpath
= vfs_path_from_str (source
);
1751 /* Update stat to get actual info */
1752 ok
= mc_lstat (source_vpath
, src_stat
) == 0;
1755 message (D_ERROR
, MSG_ERROR
, _("Cannot stat \"%s\"\n%s"),
1756 path_trunc (source
, 30), unix_error_string (errno
));
1758 /* Directory was changed outside MC. Reload it forced */
1759 if (!panel
->is_panelized
)
1761 panel_update_flags_t flags
= UP_RELOAD
;
1763 /* don't update panelized panel */
1764 if (get_other_type () == view_listing
&& other_panel
->is_panelized
)
1765 flags
|= UP_ONLY_CURRENT
;
1767 update_panels (flags
, UP_KEEPSEL
);
1771 vfs_path_free (source_vpath
);
1774 return ok
? source
: NULL
;
1777 /* --------------------------------------------------------------------------------------------- */
1779 * Generate user prompt for panel operation.
1780 * src_stat must be not NULL for single source, and NULL for multiple sources
1784 panel_operate_generate_prompt (const WPanel
* panel
, FileOperation operation
,
1785 const struct stat
*src_stat
)
1788 char *format_string
;
1791 static gboolean i18n_flag
= FALSE
;
1796 for (i
= G_N_ELEMENTS (op_names1
); i
-- != 0;)
1797 op_names1
[i
] = Q_ (op_names1
[i
]);
1800 for (i
= G_N_ELEMENTS (prompt_parts
); i
-- != 0;)
1801 prompt_parts
[i
] = _(prompt_parts
[i
]);
1803 one_format
= _(one_format
);
1804 many_format
= _(many_format
);
1805 #endif /* ENABLE_NLS */
1809 /* Possible prompts:
1811 * "Copy file \"%s\" with source mask:"
1812 * "Copy %d files with source mask:"
1813 * "Copy directory \"%s\" with source mask:"
1814 * "Copy %d directories with source mask:"
1815 * "Copy %d files/directories with source mask:"
1817 * "Move file \"%s\" with source mask:"
1818 * "Move %d files with source mask:"
1819 * "Move directory \"%s\" with source mask:"
1820 * "Move %d directories with source mask:"
1821 * "Move %d files/directories with source mask:"
1823 * "Delete file \"%s\"?"
1824 * "Delete %d files?"
1825 * "Delete directory \"%s\"?"
1826 * "Delete %d directories?"
1827 * "Delete %d files/directories?"
1830 cp
= (src_stat
!= NULL
? one_format
: many_format
);
1832 /* 1. Substitute %o */
1833 format_string
= str_replace_all (cp
, "%o", op_names1
[(int) operation
]);
1835 /* 2. Substitute %n */
1836 cp
= operation
== OP_DELETE
? "\n" : " ";
1838 format_string
= str_replace_all (sp
, "%n", cp
);
1841 /* 3. Substitute %f */
1842 if (src_stat
!= NULL
)
1843 cp
= S_ISDIR (src_stat
->st_mode
) ? prompt_parts
[2] : prompt_parts
[0];
1844 else if (panel
->marked
== panel
->dirs_marked
)
1845 cp
= prompt_parts
[3];
1847 cp
= panel
->dirs_marked
!= 0 ? prompt_parts
[4] : prompt_parts
[1];
1850 format_string
= str_replace_all (sp
, "%f", cp
);
1853 /* 4. Substitute %m */
1854 cp
= operation
== OP_DELETE
? "?" : prompt_parts
[5];
1856 format_string
= str_replace_all (sp
, "%m", cp
);
1859 return format_string
;
1862 /* --------------------------------------------------------------------------------------------- */
1865 do_confirm_copy_move (const WPanel
* panel
, FileOperation operation
, gboolean force_single
,
1866 const char *source
, struct stat
*src_stat
, file_op_context_t
* ctx
,
1869 const char *tmp_dest_dir
;
1874 /* Forced single operations default to the original name */
1876 tmp_dest_dir
= source
;
1877 else if (get_other_type () == view_listing
)
1878 tmp_dest_dir
= vfs_path_as_str (other_panel
->cwd_vpath
);
1880 tmp_dest_dir
= vfs_path_as_str (panel
->cwd_vpath
);
1883 * Add trailing backslash only when do non-local ops.
1884 * It saves user from occasional file renames (when destination
1887 if (!force_single
&& tmp_dest_dir
!= NULL
&& tmp_dest_dir
[0] != '\0'
1888 && !IS_PATH_SEP (tmp_dest_dir
[strlen (tmp_dest_dir
) - 1]))
1890 /* add trailing separator */
1891 dest_dir
= g_strconcat (tmp_dest_dir
, PATH_SEP_STR
, (char *) NULL
);
1896 dest_dir
= g_strdup (tmp_dest_dir
);
1899 if (dest_dir
== NULL
)
1905 /* Generate confirmation prompt */
1906 format
= panel_operate_generate_prompt (panel
, operation
, src_stat
);
1908 ret
= file_mask_dialog (ctx
, operation
, source
!= NULL
, format
,
1909 source
!= NULL
? source
: (const void *) &panel
->marked
, dest_dir
,
1918 /* --------------------------------------------------------------------------------------------- */
1921 do_confirm_erase (const WPanel
* panel
, const char *source
, struct stat
*src_stat
)
1925 char fmd_buf
[BUF_MEDIUM
];
1930 /* Generate confirmation prompt */
1931 format
= panel_operate_generate_prompt (panel
, OP_DELETE
, src_stat
);
1934 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, panel
->marked
);
1937 const int fmd_xlen
= 64;
1939 i
= fmd_xlen
- str_term_width1 (format
) - 4;
1940 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, str_trunc (source
, i
));
1948 i
= query_dialog (op_names
[OP_DELETE
], fmd_buf
, D_ERROR
, 2, _("&Yes"), _("&No"));
1953 /* --------------------------------------------------------------------------------------------- */
1955 static FileProgressStatus
1956 operate_single_file (const WPanel
* panel
, FileOperation operation
, file_op_total_context_t
* tctx
,
1957 file_op_context_t
* ctx
, const char *src
, struct stat
*src_stat
,
1958 const char *dest
, filegui_dialog_type_t dialog_type
)
1960 FileProgressStatus value
;
1961 vfs_path_t
*src_vpath
;
1964 if (g_path_is_absolute (src
))
1965 src_vpath
= vfs_path_from_str (src
);
1967 src_vpath
= vfs_path_append_new (panel
->cwd_vpath
, src
, (char *) NULL
);
1969 is_file
= !S_ISDIR (src_stat
->st_mode
);
1970 /* Is link to directory? */
1975 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
1976 is_file
= !(is_link
&& ctx
->follow_links
);
1980 if (operation
== OP_DELETE
)
1982 value
= panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
, dialog_type
);
1983 if (value
== FILE_CONT
)
1986 value
= erase_file (tctx
, ctx
, src_vpath
);
1988 value
= erase_dir (tctx
, ctx
, src_vpath
);
1995 src
= vfs_path_as_str (src_vpath
);
1997 temp
= build_dest (ctx
, src
, dest
, &value
);
2005 /* we use file_mask_op_follow_links only with OP_COPY */
2006 ctx
->stat_func (src_vpath
, src_stat
);
2009 panel_operate_init_totals (panel
, src_vpath
, src_stat
, ctx
, !is_file
,
2011 if (value
== FILE_CONT
)
2013 is_file
= !S_ISDIR (src_stat
->st_mode
);
2014 /* Is link to directory? */
2019 is_link
= file_is_symlink_to_dir (src_vpath
, src_stat
, NULL
);
2020 is_file
= !(is_link
&& ctx
->follow_links
);
2024 value
= copy_file_file (tctx
, ctx
, src
, dest
);
2026 value
= copy_dir_dir (tctx
, ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2031 #ifdef ENABLE_BACKGROUND
2032 /* create UI to show confirmation dialog */
2033 if (!mc_global
.we_are_background
)
2034 file_op_context_create_ui (ctx
, TRUE
, FILEGUI_DIALOG_ONE_ITEM
);
2037 value
= move_file_file (panel
, tctx
, ctx
, src
, dest
);
2039 value
= do_move_dir_dir (panel
, tctx
, ctx
, src
, dest
);
2043 /* Unknown file operation */
2051 vfs_path_free (src_vpath
);
2056 /* --------------------------------------------------------------------------------------------- */
2058 static FileProgressStatus
2059 operate_one_file (const WPanel
* panel
, FileOperation operation
, file_op_total_context_t
* tctx
,
2060 file_op_context_t
* ctx
, const char *src
, struct stat
*src_stat
, const char *dest
)
2062 FileProgressStatus value
= FILE_CONT
;
2063 vfs_path_t
*src_vpath
;
2066 if (g_path_is_absolute (src
))
2067 src_vpath
= vfs_path_from_str (src
);
2069 src_vpath
= vfs_path_append_new (panel
->cwd_vpath
, src
, (char *) NULL
);
2071 is_file
= !S_ISDIR (src_stat
->st_mode
);
2073 if (operation
== OP_DELETE
)
2076 value
= erase_file (tctx
, ctx
, src_vpath
);
2078 value
= erase_dir (tctx
, ctx
, src_vpath
);
2084 src
= vfs_path_as_str (src_vpath
);
2086 temp
= build_dest (ctx
, src
, dest
, &value
);
2094 /* we use file_mask_op_follow_links only with OP_COPY */
2095 ctx
->stat_func (src_vpath
, src_stat
);
2096 is_file
= !S_ISDIR (src_stat
->st_mode
);
2099 value
= copy_file_file (tctx
, ctx
, src
, dest
);
2101 value
= copy_dir_dir (tctx
, ctx
, src
, dest
, TRUE
, FALSE
, FALSE
, NULL
);
2102 dest_dirs
= free_linklist (dest_dirs
);
2107 value
= move_file_file (NULL
, tctx
, ctx
, src
, dest
);
2109 value
= do_move_dir_dir (NULL
, tctx
, ctx
, src
, dest
);
2113 /* Unknown file operation */
2121 vfs_path_free (src_vpath
);
2126 /* --------------------------------------------------------------------------------------------- */
2128 #ifdef ENABLE_BACKGROUND
2130 end_bg_process (file_op_context_t
* ctx
, enum OperationMode mode
)
2137 unregister_task_with_pid (pid
);
2138 /* file_op_context_destroy(ctx); */
2144 /* --------------------------------------------------------------------------------------------- */
2145 /*** public functions ****************************************************************************/
2146 /* --------------------------------------------------------------------------------------------- */
2148 /* Is file symlink to directory or not.
2150 * @param path file or directory
2151 * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2152 * @param stale_link TRUE if file is stale link to directory
2154 * @return TRUE if file symlink to directory, ELSE otherwise.
2157 file_is_symlink_to_dir (const vfs_path_t
* vpath
, struct stat
* st
, gboolean
* stale_link
)
2160 gboolean stale
= FALSE
;
2161 gboolean res
= FALSE
;
2167 if (mc_lstat (vpath
, st
) != 0)
2171 if (S_ISLNK (st
->st_mode
))
2175 stale
= (mc_stat (vpath
, &st3
) != 0);
2178 res
= (S_ISDIR (st3
.st_mode
) != 0);
2182 if (stale_link
!= NULL
)
2183 *stale_link
= stale
;
2188 /* --------------------------------------------------------------------------------------------- */
2191 copy_file_file (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
,
2192 const char *src_path
, const char *dst_path
)
2194 uid_t src_uid
= (uid_t
) (-1);
2195 gid_t src_gid
= (gid_t
) (-1);
2197 int src_desc
, dest_desc
= -1;
2198 mode_t src_mode
= 0; /* The mode of the source file */
2199 struct stat src_stat
, dst_stat
;
2200 mc_timesbuf_t times
;
2201 gboolean dst_exists
= FALSE
, appending
= FALSE
;
2202 off_t file_size
= -1;
2203 FileProgressStatus return_status
, temp_status
;
2204 struct timeval tv_transfer_start
;
2205 dest_status_t dst_status
= DEST_NONE
;
2207 vfs_path_t
*src_vpath
= NULL
, *dst_vpath
= NULL
;
2210 /* FIXME: We should not be using global variables! */
2212 return_status
= FILE_RETRY
;
2214 dst_vpath
= vfs_path_from_str (dst_path
);
2215 src_vpath
= vfs_path_from_str (src_path
);
2217 file_progress_show_source (ctx
, src_vpath
);
2218 file_progress_show_target (ctx
, dst_vpath
);
2220 if (check_progress_buttons (ctx
) == FILE_ABORT
)
2222 return_status
= FILE_ABORT
;
2228 while (mc_stat (dst_vpath
, &dst_stat
) == 0)
2230 if (S_ISDIR (dst_stat
.st_mode
))
2233 return_status
= FILE_SKIPALL
;
2237 file_error (TRUE
, _("Cannot overwrite directory \"%s\"\n%s"), dst_path
);
2238 if (return_status
== FILE_SKIPALL
)
2239 ctx
->skip_all
= TRUE
;
2240 if (return_status
== FILE_RETRY
)
2250 while ((*ctx
->stat_func
) (src_vpath
, &src_stat
) != 0)
2253 return_status
= FILE_SKIPALL
;
2256 return_status
= file_error (TRUE
, _("Cannot stat source file \"%s\"\n%s"), src_path
);
2257 if (return_status
== FILE_SKIPALL
)
2258 ctx
->skip_all
= TRUE
;
2261 if (return_status
!= FILE_RETRY
)
2267 /* Destination already exists */
2268 if (check_same_file (src_path
, &src_stat
, dst_path
, &dst_stat
, &return_status
))
2271 /* Should we replace destination? */
2272 if (tctx
->ask_overwrite
)
2275 return_status
= query_replace (ctx
, dst_path
, &src_stat
, &dst_stat
);
2276 if (return_status
!= FILE_CONT
)
2281 if (!ctx
->do_append
)
2283 /* Check the hardlinks */
2284 if (!ctx
->follow_links
)
2286 switch (check_hardlinks (src_vpath
, &src_stat
, dst_vpath
, &ctx
->skip_all
))
2289 /* We have made a hardlink - no more processing is necessary */
2290 return_status
= FILE_CONT
;
2293 case HARDLINK_ABORT
:
2294 return_status
= FILE_ABORT
;
2302 if (S_ISLNK (src_stat
.st_mode
))
2304 return_status
= make_symlink (ctx
, src_path
, dst_path
);
2308 if (S_ISCHR (src_stat
.st_mode
) || S_ISBLK (src_stat
.st_mode
) || S_ISFIFO (src_stat
.st_mode
)
2309 || S_ISNAM (src_stat
.st_mode
) || S_ISSOCK (src_stat
.st_mode
))
2313 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2314 rdev
= src_stat
.st_rdev
;
2317 while (mc_mknod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
, rdev
) < 0
2321 file_error (TRUE
, _("Cannot create special file \"%s\"\n%s"), dst_path
);
2322 if (return_status
== FILE_RETRY
)
2324 if (return_status
== FILE_SKIPALL
)
2325 ctx
->skip_all
= TRUE
;
2330 while (ctx
->preserve_uidgid
2331 && mc_chown (dst_vpath
, src_stat
.st_uid
, src_stat
.st_gid
) != 0 && !ctx
->skip_all
)
2333 temp_status
= file_error (TRUE
, _("Cannot chown target file \"%s\"\n%s"), dst_path
);
2334 if (temp_status
== FILE_SKIP
)
2336 if (temp_status
== FILE_SKIPALL
)
2337 ctx
->skip_all
= TRUE
;
2338 if (temp_status
!= FILE_RETRY
)
2340 return_status
= temp_status
;
2345 while (ctx
->preserve
&& mc_chmod (dst_vpath
, src_stat
.st_mode
& ctx
->umask_kill
) != 0
2348 temp_status
= file_error (TRUE
, _("Cannot chmod target file \"%s\"\n%s"), dst_path
);
2349 if (temp_status
== FILE_SKIP
)
2351 if (temp_status
== FILE_SKIPALL
)
2352 ctx
->skip_all
= TRUE
;
2353 if (temp_status
!= FILE_RETRY
)
2355 return_status
= temp_status
;
2360 return_status
= FILE_CONT
;
2365 gettimeofday (&tv_transfer_start
, (struct timezone
*) NULL
);
2367 while ((src_desc
= mc_open (src_vpath
, O_RDONLY
| O_LINEAR
)) < 0 && !ctx
->skip_all
)
2369 return_status
= file_error (TRUE
, _("Cannot open source file \"%s\"\n%s"), src_path
);
2370 if (return_status
== FILE_RETRY
)
2372 if (return_status
== FILE_SKIPALL
)
2373 ctx
->skip_all
= TRUE
;
2374 if (return_status
== FILE_SKIP
)
2376 ctx
->do_append
= FALSE
;
2380 if (ctx
->do_reget
!= 0)
2382 if (mc_lseek (src_desc
, ctx
->do_reget
, SEEK_SET
) != ctx
->do_reget
)
2384 message (D_ERROR
, _("Warning"), _("Reget failed, about to overwrite file"));
2386 ctx
->do_append
= FALSE
;
2390 while (mc_fstat (src_desc
, &src_stat
) != 0)
2393 return_status
= FILE_SKIPALL
;
2396 return_status
= file_error (TRUE
, _("Cannot fstat source file \"%s\"\n%s"), src_path
);
2397 if (return_status
== FILE_RETRY
)
2399 if (return_status
== FILE_SKIPALL
)
2400 ctx
->skip_all
= TRUE
;
2401 ctx
->do_append
= FALSE
;
2406 src_mode
= src_stat
.st_mode
;
2407 src_uid
= src_stat
.st_uid
;
2408 src_gid
= src_stat
.st_gid
;
2409 get_times (&src_stat
, ×
);
2410 file_size
= src_stat
.st_size
;
2412 open_flags
= O_WRONLY
;
2416 open_flags
|= O_APPEND
;
2418 open_flags
|= O_CREAT
| O_TRUNC
;
2422 open_flags
|= O_CREAT
| O_EXCL
;
2425 while ((dest_desc
= mc_open (dst_vpath
, open_flags
, src_mode
)) < 0)
2427 if (errno
!= EEXIST
)
2430 return_status
= FILE_SKIPALL
;
2434 file_error (TRUE
, _("Cannot create target file \"%s\"\n%s"), dst_path
);
2435 if (return_status
== FILE_RETRY
)
2437 if (return_status
== FILE_SKIPALL
)
2438 ctx
->skip_all
= TRUE
;
2439 ctx
->do_append
= FALSE
;
2444 dst_status
= DEST_SHORT
; /* file opened, but not fully copied */
2446 appending
= ctx
->do_append
;
2447 ctx
->do_append
= FALSE
;
2449 /* Try clone the file first. */
2450 if (vfs_clone_file (dest_desc
, src_desc
) == 0)
2452 dst_status
= DEST_FULL
;
2453 return_status
= FILE_CONT
;
2457 /* Find out the optimal buffer size. */
2458 while (mc_fstat (dest_desc
, &dst_stat
) != 0)
2461 return_status
= FILE_SKIPALL
;
2464 return_status
= file_error (TRUE
, _("Cannot fstat target file \"%s\"\n%s"), dst_path
);
2465 if (return_status
== FILE_RETRY
)
2467 if (return_status
== FILE_SKIPALL
)
2468 ctx
->skip_all
= TRUE
;
2473 /* try preallocate space; if fail, try copy anyway */
2474 while (mc_global
.vfs
.preallocate_space
&&
2475 vfs_preallocate (dest_desc
, file_size
, appending
? dst_stat
.st_size
: 0) != 0)
2479 /* cannot allocate, start the file copying anyway */
2480 return_status
= FILE_CONT
;
2485 file_error (TRUE
, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path
);
2487 if (return_status
== FILE_SKIPALL
)
2488 ctx
->skip_all
= TRUE
;
2490 if (ctx
->skip_all
|| return_status
== FILE_SKIP
)
2492 /* skip the space allocation error, start file copying */
2493 return_status
= FILE_CONT
;
2497 if (return_status
== FILE_ABORT
)
2499 mc_close (dest_desc
);
2501 mc_unlink (dst_vpath
);
2502 dst_status
= DEST_NONE
;
2506 /* return_status == FILE_RETRY -- try allocate space again */
2509 ctx
->eta_secs
= 0.0;
2512 if (tctx
->bps
== 0 || (file_size
/ (tctx
->bps
)) > FILEOP_UPDATE_INTERVAL
)
2513 file_progress_show (ctx
, 0, file_size
, "", TRUE
);
2515 file_progress_show (ctx
, 1, 1, "", TRUE
);
2516 return_status
= check_progress_buttons (ctx
);
2519 if (return_status
== FILE_CONT
)
2522 off_t n_read_total
= 0;
2523 struct timeval tv_current
, tv_last_update
, tv_last_input
;
2524 int secs
, update_secs
;
2525 const char *stalled_msg
= "";
2526 gboolean is_first_time
= TRUE
;
2528 tv_last_update
= tv_transfer_start
;
2530 bufsize
= io_blksize (dst_stat
);
2531 buf
= g_malloc (bufsize
);
2535 ssize_t n_read
= -1, n_written
;
2538 if (mc_ctl (src_desc
, VFS_CTL_IS_NOTREADY
, 0) == 0)
2539 while ((n_read
= mc_read (src_desc
, buf
, bufsize
)) < 0 && !ctx
->skip_all
)
2542 file_error (TRUE
, _("Cannot read source file \"%s\"\n%s"), src_path
);
2543 if (return_status
== FILE_RETRY
)
2545 if (return_status
== FILE_SKIPALL
)
2546 ctx
->skip_all
= TRUE
;
2553 gettimeofday (&tv_current
, NULL
);
2559 n_read_total
+= n_read
;
2561 /* Windows NT ftp servers report that files have no
2562 * permissions: -------, so if we happen to have actually
2563 * read something, we should fix the permissions.
2565 if ((src_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
)) == 0)
2566 src_mode
= S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
;
2567 gettimeofday (&tv_last_input
, NULL
);
2570 while ((n_written
= mc_write (dest_desc
, t
, (size_t) n_read
)) < n_read
)
2572 gboolean write_errno_nospace
;
2576 n_read
-= n_written
;
2581 write_errno_nospace
= (n_written
< 0 && errno
== ENOSPC
);
2584 return_status
= FILE_SKIPALL
;
2587 file_error (TRUE
, _("Cannot write target file \"%s\"\n%s"), dst_path
);
2589 if (return_status
== FILE_SKIP
)
2591 if (write_errno_nospace
)
2595 if (return_status
== FILE_SKIPALL
)
2597 ctx
->skip_all
= TRUE
;
2598 if (write_errno_nospace
)
2601 if (return_status
!= FILE_RETRY
)
2606 tctx
->copied_bytes
= tctx
->progress_bytes
+ n_read_total
+ ctx
->do_reget
;
2608 secs
= (tv_current
.tv_sec
- tv_last_update
.tv_sec
);
2609 update_secs
= (tv_current
.tv_sec
- tv_last_input
.tv_sec
);
2611 if (is_first_time
|| secs
> FILEOP_UPDATE_INTERVAL
)
2613 copy_file_file_display_progress (tctx
, ctx
,
2615 tv_transfer_start
, file_size
, n_read_total
);
2616 tv_last_update
= tv_current
;
2618 is_first_time
= FALSE
;
2620 if (update_secs
> FILEOP_STALLING_INTERVAL
)
2622 stalled_msg
= _("(stalled)");
2626 gboolean force_update
;
2629 (tv_current
.tv_sec
- tctx
->transfer_start
.tv_sec
) > FILEOP_UPDATE_INTERVAL
;
2631 if (verbose
&& ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
2633 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
2634 file_progress_show_total (tctx
, ctx
, tctx
->copied_bytes
, force_update
);
2637 file_progress_show (ctx
, n_read_total
+ ctx
->do_reget
, file_size
, stalled_msg
,
2642 return_status
= check_progress_buttons (ctx
);
2644 if (return_status
!= FILE_CONT
)
2651 dst_status
= DEST_FULL
; /* copy successful, don't remove target file */
2657 rotate_dash (FALSE
);
2658 while (src_desc
!= -1 && mc_close (src_desc
) < 0 && !ctx
->skip_all
)
2660 temp_status
= file_error (TRUE
, _("Cannot close source file \"%s\"\n%s"), src_path
);
2661 if (temp_status
== FILE_RETRY
)
2663 if (temp_status
== FILE_ABORT
)
2664 return_status
= temp_status
;
2665 if (temp_status
== FILE_SKIPALL
)
2666 ctx
->skip_all
= TRUE
;
2670 while (dest_desc
!= -1 && mc_close (dest_desc
) < 0 && !ctx
->skip_all
)
2672 temp_status
= file_error (TRUE
, _("Cannot close target file \"%s\"\n%s"), dst_path
);
2673 if (temp_status
== FILE_RETRY
)
2675 if (temp_status
== FILE_SKIPALL
)
2676 ctx
->skip_all
= TRUE
;
2677 return_status
= temp_status
;
2681 if (dst_status
== DEST_SHORT
)
2683 /* Query to remove short file */
2684 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved. Keep it?"),
2685 D_ERROR
, 2, _("&Delete"), _("&Keep")) == 0)
2686 mc_unlink (dst_vpath
);
2688 else if (dst_status
== DEST_FULL
)
2690 /* Copy has succeeded */
2691 if (!appending
&& ctx
->preserve_uidgid
)
2693 while (mc_chown (dst_vpath
, src_uid
, src_gid
) != 0 && !ctx
->skip_all
)
2695 temp_status
= file_error (TRUE
, _("Cannot chown target file \"%s\"\n%s"), dst_path
);
2696 if (temp_status
== FILE_RETRY
)
2698 if (temp_status
== FILE_SKIPALL
)
2700 ctx
->skip_all
= TRUE
;
2701 return_status
= FILE_CONT
;
2703 if (temp_status
== FILE_SKIP
)
2704 return_status
= FILE_CONT
;
2713 while (mc_chmod (dst_vpath
, (src_mode
& ctx
->umask_kill
)) != 0 && !ctx
->skip_all
)
2716 file_error (TRUE
, _("Cannot chmod target file \"%s\"\n%s"), dst_path
);
2717 if (temp_status
== FILE_RETRY
)
2719 if (temp_status
== FILE_SKIPALL
)
2721 ctx
->skip_all
= TRUE
;
2722 return_status
= FILE_CONT
;
2724 if (temp_status
== FILE_SKIP
)
2725 return_status
= FILE_CONT
;
2729 else if (!dst_exists
)
2731 src_mode
= umask (-1);
2733 src_mode
= 0100666 & ~src_mode
;
2734 mc_chmod (dst_vpath
, (src_mode
& ctx
->umask_kill
));
2736 mc_utime (dst_vpath
, ×
);
2740 if (return_status
== FILE_CONT
)
2741 return_status
= progress_update_one (tctx
, ctx
, file_size
);
2744 vfs_path_free (src_vpath
);
2745 vfs_path_free (dst_vpath
);
2746 return return_status
;
2749 /* --------------------------------------------------------------------------------------------- */
2751 * I think these copy_*_* functions should have a return type.
2752 * anyway, this function *must* have two directories as arguments.
2754 /* FIXME: This function needs to check the return values of the
2758 copy_dir_dir (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const char *s
, const char *d
,
2759 gboolean toplevel
, gboolean move_over
, gboolean do_delete
, GSList
* parent_dirs
)
2761 struct dirent
*next
;
2762 struct stat buf
, cbuf
;
2764 FileProgressStatus return_status
= FILE_CONT
;
2766 vfs_path_t
*src_vpath
, *dst_vpath
;
2767 gboolean do_mkdir
= TRUE
;
2769 src_vpath
= vfs_path_from_str (s
);
2770 dst_vpath
= vfs_path_from_str (d
);
2772 /* First get the mode of the source dir */
2775 if ((*ctx
->stat_func
) (src_vpath
, &cbuf
) != 0)
2778 return_status
= FILE_SKIPALL
;
2781 return_status
= file_error (TRUE
, _("Cannot stat source directory \"%s\"\n%s"), s
);
2782 if (return_status
== FILE_RETRY
)
2783 goto retry_src_stat
;
2784 if (return_status
== FILE_SKIPALL
)
2785 ctx
->skip_all
= TRUE
;
2790 if (is_in_linklist (dest_dirs
, src_vpath
, &cbuf
) != NULL
)
2792 /* Don't copy a directory we created before (we don't want to copy
2793 infinitely if a directory is copied into itself) */
2794 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
2795 return_status
= FILE_CONT
;
2799 /* Hmm, hardlink to directory??? - Norbert */
2800 /* FIXME: In this step we should do something in case the destination already exist */
2801 /* Check the hardlinks */
2804 switch (check_hardlinks (src_vpath
, &cbuf
, dst_vpath
, &ctx
->skip_all
))
2807 /* We have made a hardlink - no more processing is necessary */
2810 case HARDLINK_ABORT
:
2811 return_status
= FILE_ABORT
;
2819 if (!S_ISDIR (cbuf
.st_mode
))
2822 return_status
= FILE_SKIPALL
;
2825 return_status
= file_error (TRUE
, _("Source \"%s\" is not a directory\n%s"), s
);
2826 if (return_status
== FILE_RETRY
)
2827 goto retry_src_stat
;
2828 if (return_status
== FILE_SKIPALL
)
2829 ctx
->skip_all
= TRUE
;
2834 if (is_in_linklist (parent_dirs
, src_vpath
, &cbuf
) != NULL
)
2836 /* we found a cyclic symbolic link */
2837 message (D_ERROR
, MSG_ERROR
, _("Cannot copy cyclic symbolic link\n\"%s\""), s
);
2838 return_status
= FILE_SKIP
;
2842 lp
= g_new0 (struct link
, 1);
2843 lp
->vfs
= vfs_path_get_by_index (src_vpath
, -1)->class;
2844 lp
->ino
= cbuf
.st_ino
;
2845 lp
->dev
= cbuf
.st_dev
;
2846 parent_dirs
= g_slist_prepend (parent_dirs
, lp
);
2849 /* Now, check if the dest dir exists, if not, create it. */
2850 if (mc_stat (dst_vpath
, &buf
) != 0)
2852 /* Here the dir doesn't exist : make it ! */
2853 if (move_over
&& mc_rename (src_vpath
, dst_vpath
) == 0)
2855 return_status
= FILE_CONT
;
2862 * If the destination directory exists, we want to copy the whole
2863 * directory, but we only want this to happen once.
2865 * Escape sequences added to the * to compiler warnings.
2866 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
2867 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
2869 if (!S_ISDIR (buf
.st_mode
))
2872 return_status
= FILE_SKIPALL
;
2876 file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"), d
);
2877 if (return_status
== FILE_SKIPALL
)
2878 ctx
->skip_all
= TRUE
;
2879 if (return_status
== FILE_RETRY
)
2880 goto retry_dst_stat
;
2884 /* Dive into subdir if exists */
2885 if (toplevel
&& ctx
->dive_into_subdirs
)
2890 dst_vpath
= vfs_path_append_new (dst_vpath
, x_basename (s
), (char *) NULL
);
2891 vfs_path_free (tmp
);
2898 d
= vfs_path_as_str (dst_vpath
);
2902 while (my_mkdir (dst_vpath
, (cbuf
.st_mode
& ctx
->umask_kill
) | S_IRWXU
) != 0)
2905 return_status
= FILE_SKIPALL
;
2909 file_error (TRUE
, _("Cannot create target directory \"%s\"\n%s"), d
);
2910 if (return_status
== FILE_SKIPALL
)
2911 ctx
->skip_all
= TRUE
;
2913 if (return_status
!= FILE_RETRY
)
2917 lp
= g_new0 (struct link
, 1);
2918 mc_stat (dst_vpath
, &buf
);
2919 lp
->vfs
= vfs_path_get_by_index (dst_vpath
, -1)->class;
2920 lp
->ino
= buf
.st_ino
;
2921 lp
->dev
= buf
.st_dev
;
2922 dest_dirs
= g_slist_prepend (dest_dirs
, lp
);
2925 if (ctx
->preserve_uidgid
)
2927 while (mc_chown (dst_vpath
, cbuf
.st_uid
, cbuf
.st_gid
) != 0)
2930 return_status
= FILE_SKIPALL
;
2933 return_status
= file_error (TRUE
, _("Cannot chown target directory \"%s\"\n%s"), d
);
2934 if (return_status
== FILE_SKIPALL
)
2935 ctx
->skip_all
= TRUE
;
2937 if (return_status
!= FILE_RETRY
)
2942 /* open the source dir for reading */
2943 reading
= mc_opendir (src_vpath
);
2944 if (reading
== NULL
)
2947 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
2950 vfs_path_t
*tmp_vpath
;
2953 * Now, we don't want '.' and '..' to be created / copied at any time
2955 if (DIR_IS_DOT (next
->d_name
) || DIR_IS_DOTDOT (next
->d_name
))
2958 /* get the filename and add it to the src directory */
2959 path
= mc_build_filename (s
, next
->d_name
, (char *) NULL
);
2960 tmp_vpath
= vfs_path_from_str (path
);
2962 (*ctx
->stat_func
) (tmp_vpath
, &buf
);
2963 if (S_ISDIR (buf
.st_mode
))
2967 mdpath
= mc_build_filename (d
, next
->d_name
, (char *) NULL
);
2969 * From here, we just intend to recursively copy subdirs, not
2970 * the double functionality of copying different when the target
2971 * dir already exists. So, we give the recursive call the flag 0
2972 * meaning no toplevel.
2975 copy_dir_dir (tctx
, ctx
, path
, mdpath
, FALSE
, FALSE
, do_delete
, parent_dirs
);
2982 dest_file
= mc_build_filename (d
, x_basename (path
), (char *) NULL
);
2983 return_status
= copy_file_file (tctx
, ctx
, path
, dest_file
);
2989 if (do_delete
&& return_status
== FILE_CONT
)
2991 if (ctx
->erase_at_end
)
2993 lp
= g_new0 (struct link
, 1);
2994 lp
->src_vpath
= tmp_vpath
;
2995 lp
->st_mode
= buf
.st_mode
;
2996 erase_list
= g_slist_append (erase_list
, lp
);
2999 else if (S_ISDIR (buf
.st_mode
))
3000 return_status
= erase_dir_iff_empty (ctx
, tmp_vpath
, tctx
->progress_count
);
3002 return_status
= erase_file (tctx
, ctx
, tmp_vpath
);
3004 vfs_path_free (tmp_vpath
);
3006 mc_closedir (reading
);
3010 mc_timesbuf_t times
;
3012 mc_chmod (dst_vpath
, cbuf
.st_mode
& ctx
->umask_kill
);
3013 get_times (&cbuf
, ×
);
3014 mc_utime (dst_vpath
, ×
);
3018 cbuf
.st_mode
= umask (-1);
3019 umask (cbuf
.st_mode
);
3020 cbuf
.st_mode
= 0100777 & ~cbuf
.st_mode
;
3021 mc_chmod (dst_vpath
, cbuf
.st_mode
& ctx
->umask_kill
);
3025 free_link (parent_dirs
->data
);
3026 g_slist_free_1 (parent_dirs
);
3028 vfs_path_free (src_vpath
);
3029 vfs_path_free (dst_vpath
);
3030 return return_status
;
3035 /* --------------------------------------------------------------------------------------------- */
3036 /* {{{ Move routines */
3039 move_dir_dir (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const char *s
, const char *d
)
3041 return do_move_dir_dir (NULL
, tctx
, ctx
, s
, d
);
3046 /* --------------------------------------------------------------------------------------------- */
3047 /* {{{ Erase routines */
3050 erase_dir (file_op_total_context_t
* tctx
, file_op_context_t
* ctx
, const vfs_path_t
* s_vpath
)
3052 file_progress_show_deleting (ctx
, vfs_path_as_str (s_vpath
), NULL
);
3053 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
3054 if (check_progress_buttons (ctx
) == FILE_ABORT
)
3059 /* The old way to detect a non empty directory was:
3060 error = my_rmdir (s);
3061 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3062 For the linux user space nfs server (nfs-server-2.2beta29-2)
3063 we would have to check also for EIO. I hope the new way is
3064 fool proof. (Norbert)
3066 if (check_dir_is_empty (s_vpath
) == 0)
3068 FileProgressStatus error
;
3070 error
= query_recursive (ctx
, vfs_path_as_str (s_vpath
));
3071 if (error
== FILE_CONT
)
3072 error
= recursive_erase (tctx
, ctx
, s_vpath
);
3076 return try_erase_dir (ctx
, vfs_path_as_str (s_vpath
));
3081 /* --------------------------------------------------------------------------------------------- */
3082 /* {{{ Panel operate routines */
3085 dirsize_status_init_cb (status_msg_t
* sm
)
3087 dirsize_status_msg_t
*dsm
= (dirsize_status_msg_t
*) sm
;
3088 Widget
*wd
= WIDGET (sm
->dlg
);
3090 const char *b1_name
= N_("&Abort");
3091 const char *b2_name
= N_("&Skip");
3092 int b_width
, ui_width
;
3095 b1_name
= _(b1_name
);
3096 b2_name
= _(b2_name
);
3099 b_width
= str_term_width1 (b1_name
) + 4;
3100 if (dsm
->allow_skip
)
3101 b_width
+= str_term_width1 (b2_name
) + 4 + 1;
3103 ui_width
= MAX (COLS
/ 2, b_width
+ 6);
3104 dsm
->dirname
= label_new (2, 3, "");
3105 add_widget (sm
->dlg
, dsm
->dirname
);
3106 dsm
->count_size
= label_new (3, 3, "");
3107 add_widget (sm
->dlg
, dsm
->count_size
);
3108 add_widget (sm
->dlg
, hline_new (4, -1, -1));
3110 dsm
->abort_button
= WIDGET (button_new (5, 3, FILE_ABORT
, NORMAL_BUTTON
, b1_name
, NULL
));
3111 add_widget (sm
->dlg
, dsm
->abort_button
);
3112 if (dsm
->allow_skip
)
3114 dsm
->skip_button
= WIDGET (button_new (5, 3, FILE_SKIP
, NORMAL_BUTTON
, b2_name
, NULL
));
3115 add_widget (sm
->dlg
, dsm
->skip_button
);
3116 widget_select (dsm
->skip_button
);
3119 widget_set_size (wd
, wd
->y
, wd
->x
, 8, ui_width
);
3120 dirsize_status_locate_buttons (dsm
);
3123 /* --------------------------------------------------------------------------------------------- */
3126 dirsize_status_update_cb (status_msg_t
* sm
)
3128 dirsize_status_msg_t
*dsm
= (dirsize_status_msg_t
*) sm
;
3129 Widget
*wd
= WIDGET (sm
->dlg
);
3131 /* update second (longer label) */
3132 label_set_textv (dsm
->count_size
, _("Directories: %zu, total size: %s"),
3133 dsm
->dir_count
, size_trunc_sep (dsm
->total_size
, panels_options
.kilobyte_si
));
3135 /* enlarge dialog if required */
3136 if (WIDGET (dsm
->count_size
)->cols
+ 6 > wd
->cols
)
3138 dlg_set_size (sm
->dlg
, wd
->lines
, WIDGET (dsm
->count_size
)->cols
+ 6);
3139 dirsize_status_locate_buttons (dsm
);
3140 dlg_redraw (sm
->dlg
);
3143 /* adjust first label */
3144 label_set_text (dsm
->dirname
, str_trunc (vfs_path_as_str (dsm
->dirname_vpath
), wd
->cols
- 6));
3146 switch (status_msg_common_update (sm
))
3158 /* --------------------------------------------------------------------------------------------- */
3161 dirsize_status_deinit_cb (status_msg_t
* sm
)
3165 /* schedule to update passive panel */
3166 if (get_other_type () == view_listing
)
3167 other_panel
->dirty
= 1;
3170 /* --------------------------------------------------------------------------------------------- */
3174 * Computes the number of bytes used by the files in a directory
3178 compute_dir_size (const vfs_path_t
* dirname_vpath
, dirsize_status_msg_t
* sm
,
3179 size_t * ret_dir_count
, size_t * ret_marked_count
, uintmax_t * ret_total
,
3180 gboolean compute_symlinks
)
3182 return do_compute_dir_size (dirname_vpath
, sm
, ret_dir_count
, ret_marked_count
, ret_total
,
3186 /* --------------------------------------------------------------------------------------------- */
3190 * Performs one of the operations on the selection on the source_panel
3191 * (copy, delete, move).
3193 * Returns TRUE if did change the directory
3194 * structure, Returns FALSE if user aborted
3196 * force_single forces operation on the current entry and affects
3197 * default destination. Current filename is used as default.
3201 panel_operate (void *source_panel
, FileOperation operation
, gboolean force_single
)
3203 WPanel
*panel
= PANEL (source_panel
);
3204 const gboolean single_entry
= force_single
|| (panel
->marked
<= 1)
3205 || (get_current_type () == view_tree
);
3207 const char *source
= NULL
;
3209 vfs_path_t
*dest_vpath
= NULL
;
3210 char *save_cwd
= NULL
, *save_dest
= NULL
;
3211 struct stat src_stat
;
3212 gboolean ret_val
= TRUE
;
3214 FileProgressStatus value
;
3215 file_op_context_t
*ctx
;
3216 file_op_total_context_t
*tctx
;
3217 vfs_path_t
*tmp_vpath
;
3218 filegui_dialog_type_t dialog_type
= FILEGUI_DIALOG_ONE_ITEM
;
3220 gboolean do_bg
= FALSE
; /* do background operation? */
3222 static gboolean i18n_flag
= FALSE
;
3225 for (i
= G_N_ELEMENTS (op_names
); i
-- != 0;)
3226 op_names
[i
] = Q_ (op_names
[i
]);
3230 linklist
= free_linklist (linklist
);
3231 dest_dirs
= free_linklist (dest_dirs
);
3235 source
= check_single_entry (panel
, force_single
, &src_stat
);
3241 ctx
= file_op_context_new (operation
);
3243 /* Show confirmation dialog */
3244 if (operation
!= OP_DELETE
)
3247 do_confirm_copy_move (panel
, operation
, force_single
, source
, &src_stat
, ctx
, &do_bg
);
3255 dest_vpath
= vfs_path_from_str (dest
);
3257 else if (confirm_delete
&& !do_confirm_erase (panel
, source
, &src_stat
))
3263 tctx
= file_op_total_context_new ();
3264 gettimeofday (&tctx
->transfer_start
, (struct timezone
*) NULL
);
3266 #ifdef ENABLE_BACKGROUND
3267 /* Did the user select to do a background operation? */
3272 v
= do_background (ctx
,
3273 g_strconcat (op_names
[operation
], ": ",
3274 vfs_path_as_str (panel
->cwd_vpath
), (char *) NULL
));
3276 message (D_ERROR
, MSG_ERROR
, _("Sorry, I could not put the job in background"));
3278 /* If we are the parent */
3281 mc_setctl (panel
->cwd_vpath
, VFS_SETCTL_FORGET
, NULL
);
3283 mc_setctl (dest_vpath
, VFS_SETCTL_FORGET
, NULL
);
3284 vfs_path_free (dest_vpath
);
3286 /* file_op_context_destroy (ctx); */
3291 #endif /* ENABLE_BACKGROUND */
3293 if (operation
== OP_DELETE
)
3294 dialog_type
= FILEGUI_DIALOG_DELETE_ITEM
;
3295 else if (single_entry
&& S_ISDIR (selection (panel
)->st
.st_mode
))
3296 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
3297 else if (single_entry
|| force_single
)
3298 dialog_type
= FILEGUI_DIALOG_ONE_ITEM
;
3300 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
3303 /* Initialize things */
3304 /* We do not want to trash cache every time file is
3305 created/touched. However, this will make our cache contain
3308 && (mc_setctl (dest_vpath
, VFS_SETCTL_STALE_DATA
, GUINT_TO_POINTER (1)) != 0))
3309 save_dest
= g_strdup (dest
);
3311 if ((vfs_path_tokens_count (panel
->cwd_vpath
) != 0)
3312 && (mc_setctl (panel
->cwd_vpath
, VFS_SETCTL_STALE_DATA
, GUINT_TO_POINTER (1)) != 0))
3313 save_cwd
= g_strdup (vfs_path_as_str (panel
->cwd_vpath
));
3315 /* Now, let's do the job */
3317 /* This code is only called by the tree and panel code */
3320 /* We now have ETA in all cases */
3322 /* One file: FIXME mc_chdir will take user out of any vfs */
3323 if ((operation
!= OP_COPY
) && (get_current_type () == view_tree
))
3328 vpath
= vfs_path_from_str (PATH_SEP_STR
);
3329 chdir_retcode
= mc_chdir (vpath
);
3330 vfs_path_free (vpath
);
3331 if (chdir_retcode
< 0)
3339 operate_single_file (panel
, operation
, tctx
, ctx
, source
, &src_stat
, dest
, dialog_type
);
3341 if ((value
== FILE_CONT
) && !force_single
)
3342 unmark_files (panel
);
3348 /* Check destination for copy or move operation */
3349 while (operation
!= OP_DELETE
)
3352 struct stat dst_stat
;
3354 dst_result
= mc_stat (dest_vpath
, &dst_stat
);
3356 if ((dst_result
!= 0) || S_ISDIR (dst_stat
.st_mode
))
3360 || file_error (TRUE
, _("Destination \"%s\" must be a directory\n%s"),
3361 dest
) != FILE_RETRY
)
3365 /* TODO: the good way is required to skip directories scanning in case of rename/move
3366 * of several directories. Since reqular expression can be used for destination,
3367 * some directory movements can be a cross-filesystem and directory scanning is useful
3368 * for those directories only. */
3370 if (panel_operate_init_totals (panel
, NULL
, NULL
, ctx
, file_op_compute_totals
, dialog_type
)
3373 /* Loop for every file, perform the actual copy operation */
3374 for (i
= 0; i
< panel
->dir
.len
; i
++)
3376 const char *source2
;
3378 if (!panel
->dir
.list
[i
].f
.marked
)
3379 continue; /* Skip the unmarked ones */
3381 source2
= panel
->dir
.list
[i
].fname
;
3382 src_stat
= panel
->dir
.list
[i
].st
;
3384 value
= operate_one_file (panel
, operation
, tctx
, ctx
, source2
, &src_stat
, dest
);
3386 if (value
== FILE_ABORT
)
3389 if (value
== FILE_CONT
)
3390 do_file_mark (panel
, i
, 0);
3392 if (verbose
&& ctx
->dialog_type
== FILEGUI_DIALOG_MULTI_ITEM
)
3394 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
3395 file_progress_show_total (tctx
, ctx
, tctx
->progress_bytes
, FALSE
);
3398 if (operation
!= OP_DELETE
)
3399 file_progress_show (ctx
, 0, 0, "", FALSE
);
3401 if (check_progress_buttons (ctx
) == FILE_ABORT
)
3405 } /* Loop for every file */
3407 } /* Many entries */
3411 if (save_cwd
!= NULL
)
3413 tmp_vpath
= vfs_path_from_str (save_cwd
);
3414 mc_setctl (tmp_vpath
, VFS_SETCTL_STALE_DATA
, NULL
);
3415 vfs_path_free (tmp_vpath
);
3419 if (save_dest
!= NULL
)
3421 tmp_vpath
= vfs_path_from_str (save_dest
);
3422 mc_setctl (tmp_vpath
, VFS_SETCTL_STALE_DATA
, NULL
);
3423 vfs_path_free (tmp_vpath
);
3427 linklist
= free_linklist (linklist
);
3428 dest_dirs
= free_linklist (dest_dirs
);
3430 vfs_path_free (dest_vpath
);
3431 MC_PTR_FREE (ctx
->dest_mask
);
3433 #ifdef ENABLE_BACKGROUND
3434 /* Let our parent know we are saying bye bye */
3435 if (mc_global
.we_are_background
)
3437 int cur_pid
= getpid ();
3438 /* Send pid to parent with child context, it is fork and
3439 don't modify real parent ctx */
3441 parent_call ((void *) end_bg_process
, ctx
, 0);
3444 my_exit (EXIT_SUCCESS
);
3446 #endif /* ENABLE_BACKGROUND */
3448 file_op_total_context_destroy (tctx
);
3450 file_op_context_destroy (ctx
);
3457 /* --------------------------------------------------------------------------------------------- */
3458 /* {{{ Query/status report routines */
3459 /** Report error with one file */
3461 file_error (gboolean allow_retry
, const char *format
, const char *file
)
3463 char buf
[BUF_MEDIUM
];
3465 g_snprintf (buf
, sizeof (buf
), format
, path_trunc (file
, 30), unix_error_string (errno
));
3467 return do_file_error (allow_retry
, buf
);
3470 /* --------------------------------------------------------------------------------------------- */
3473 Cause emacs to enter folding mode for this file: