2 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
3 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
5 Written by: 1994, 1995 Janne Kukonlehto
6 1994, 1995 Fred Leeflang
7 1994, 1995, 1996 Miguel de Icaza
8 1995, 1996 Jakub Jelinek
12 The copy code was based in GNU's cp, and was written by:
13 Torbjorn Granlund, David MacKenzie, and Jim Meyering.
15 The move code was based in GNU's mv, and was written by:
16 Mike Parker and David MacKenzie.
18 Janne Kukonlehto added much error recovery to them for being used
19 in an interactive program.
21 This program is free software; you can redistribute it and/or modify
22 it under the terms of the GNU General Public License as published by
23 the Free Software Foundation; either version 2 of the License, or
24 (at your option) any later version.
26 This program is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 GNU General Public License for more details.
31 You should have received a copy of the GNU General Public License
32 along with this program; if not, write to the Free Software
33 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
36 * Please note that all dialogs used here must be safe for background
41 * \brief Source: file management
44 /* {{{ Include files */
53 #include <sys/types.h>
58 #include "lib/global.h"
59 #include "lib/tty/tty.h"
60 #include "lib/tty/key.h"
61 #include "lib/search.h"
62 #include "lib/vfs/mc-vfs/vfs-impl.h"
63 #include "lib/vfs/mc-vfs/vfs.h"
64 #include "lib/strescape.h"
65 #include "lib/strutil.h"
74 #include "background.h" /* we_are_background */
76 /* Needed for current_panel, other_panel and WTree */
85 /* Hack: the vfs code should not rely on this */
86 #define WITH_FULL_PATHS 1
91 * Whether the Midnight Commander tries to provide more
92 * information about copy/move sizes and bytes transfered
93 * at the expense of some speed
95 int file_op_compute_totals
= 1;
97 /* This is a hard link cache */
100 struct vfs_class
*vfs
;
108 /* the hard link cache */
109 static struct link
*linklist
= NULL
;
111 /* the files-to-be-erased list */
112 static struct link
*erase_list
;
115 * In copy_dir_dir we use two additional single linked lists: The first -
116 * variable name `parent_dirs' - holds information about already copied
117 * directories and is used to detect cyclic symbolic links.
118 * The second (`dest_dirs' below) holds information about just created
119 * target directories and is used to detect when an directory is copied
120 * into itself (we don't want to copy infinitly).
121 * Both lists don't use the linkcount and name structure members of struct
124 static struct link
*dest_dirs
= NULL
;
126 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
127 const char *op_names
[3] = {
128 N_("DialogTitle|Copy"),
129 N_("DialogTitle|Move"),
130 N_("DialogTitle|Delete")
135 static FileProgressStatus
query_replace (FileOpContext
* ctx
, const char *destname
,
136 struct stat
*_s_stat
, struct stat
*_d_stat
);
137 static FileProgressStatus
query_recursive (FileOpContext
* ctx
, const char *s
);
138 static FileProgressStatus
do_file_error (const char *str
);
139 static FileProgressStatus
erase_dir_iff_empty (FileOpContext
*ctx
, const char *s
);
140 static FileProgressStatus
erase_file (FileOpTotalContext
*tctx
, FileOpContext
*ctx
,
141 const char *s
, gboolean is_toplevel_file
);
142 static FileProgressStatus
files_error (const char *format
, const char *file1
,
145 static FileProgressStatus transform_error
= FILE_CONT
;
148 transform_source (FileOpContext
*ctx
, const char *source
)
153 s
= g_strdup (source
);
155 /* We remove \n from the filename since regex routines would use \n as an anchor */
156 /* this is just to be allowed to maniupulate file names with \n on it */
157 for (q
= s
; *q
!= '\0'; q
++)
161 fnsource
= (char *) x_basename (s
);
163 if (mc_search_run (ctx
->search_handle
, fnsource
, 0, strlen (fnsource
), NULL
))
164 q
= mc_search_prepare_replace_str2 (ctx
->search_handle
, ctx
->dest_mask
);
167 transform_error
= FILE_SKIP
;
175 free_linklist (struct link
**lc_linklist
)
177 struct link
*lp
, *lp2
;
179 for (lp
= *lc_linklist
; lp
!= NULL
; lp
= lp2
) {
187 is_in_linklist (struct link
*lp
, const char *path
, struct stat
*sb
)
189 ino_t ino
= sb
->st_ino
;
190 dev_t dev
= sb
->st_dev
;
192 struct vfs_class
*vfs
= vfs_get_class (path
);
193 #endif /* ENABLE_VFS */
200 #endif /* ENABLE_VFS */
201 if (lp
->ino
== ino
&& lp
->dev
== dev
)
209 * Returns 0 if the inode wasn't found in the cache and 1 if it was found
210 * and a hardlink was succesfully made
213 check_hardlinks (const char *src_name
, const char *dst_name
, struct stat
*pstat
)
216 struct vfs_class
*my_vfs
= vfs_get_class (src_name
);
217 ino_t ino
= pstat
->st_ino
;
218 dev_t dev
= pstat
->st_dev
;
219 struct stat link_stat
;
222 if (vfs_file_class_flags (src_name
) & VFSF_NOLINKS
)
225 for (lp
= linklist
; lp
!= NULL
; lp
= lp
->next
)
226 if (lp
->vfs
== my_vfs
&& lp
->ino
== ino
&& lp
->dev
== dev
) {
227 if (!mc_stat (lp
->name
, &link_stat
) && link_stat
.st_ino
== ino
228 && link_stat
.st_dev
== dev
229 && vfs_get_class (lp
->name
) == my_vfs
) {
230 p
= strchr (lp
->name
, 0) + 1; /* i.e. where the `name' file
232 if (vfs_get_class (dst_name
) == vfs_get_class (p
)) {
233 if (!mc_stat (p
, &link_stat
)) {
234 if (!mc_link (p
, dst_name
))
239 message (D_ERROR
, MSG_ERROR
, _(" Cannot make the hardlink "));
242 lp
= (struct link
*) g_try_malloc (sizeof (struct link
) + strlen (src_name
)
243 + strlen (dst_name
) + 1);
249 strcpy (lp
->name
, src_name
);
250 lpdstname
= lp
->name
+ strlen(lp
->name
) + 1;
251 strcpy (lpdstname
, dst_name
);
259 * Duplicate the contents of the symbolic link src_path in dst_path.
260 * Try to make a stable symlink if the option "stable symlink" was
261 * set in the file mask dialog.
262 * If dst_path is an existing symlink it will be deleted silently
263 * (upper levels take already care of existing files at dst_path).
265 static FileProgressStatus
266 make_symlink (FileOpContext
*ctx
, const char *src_path
, const char *dst_path
)
268 char link_target
[MC_MAXPATHLEN
];
270 FileProgressStatus return_status
;
272 gboolean dst_is_symlink
;
274 dst_is_symlink
= (mc_lstat (dst_path
, &sb
) == 0) && S_ISLNK (sb
.st_mode
);
277 len
= mc_readlink (src_path
, link_target
, MC_MAXPATHLEN
- 1);
280 file_error (_(" Cannot read source link \"%s\" \n %s "),
282 if (return_status
== FILE_RETRY
)
283 goto retry_src_readlink
;
284 return return_status
;
286 link_target
[len
] = 0;
288 if (ctx
->stable_symlinks
)
289 if (!vfs_file_is_local (src_path
) || !vfs_file_is_local (dst_path
)) {
290 message (D_ERROR
, MSG_ERROR
,
291 _(" Cannot make stable symlinks across "
292 "non-local filesystems: \n\n"
293 " Option Stable Symlinks will be disabled "));
294 ctx
->stable_symlinks
= FALSE
;
297 if (ctx
->stable_symlinks
&& !g_path_is_absolute (link_target
)) {
300 const char *r
= strrchr (src_path
, PATH_SEP
);
303 p
= g_strndup (src_path
, r
- src_path
+ 1);
304 if (g_path_is_absolute (dst_path
))
305 q
= g_strdup (dst_path
);
307 q
= g_strconcat (p
, dst_path
, (char *) NULL
);
308 s
= strrchr (q
, PATH_SEP
);
311 s
= g_strconcat (p
, link_target
, (char *) NULL
);
313 g_strlcpy (link_target
, s
, sizeof (link_target
));
315 s
= diff_two_paths (q
, link_target
);
317 g_strlcpy (link_target
, s
, sizeof (link_target
));
326 if (mc_symlink (link_target
, dst_path
) == 0)
330 * if dst_exists, it is obvious that this had failed.
331 * We can delete the old symlink and try again...
333 if (dst_is_symlink
) {
334 if (!mc_unlink (dst_path
))
335 if (mc_symlink (link_target
, dst_path
) == 0)
340 file_error (_(" Cannot create target symlink \"%s\" \n %s "),
342 if (return_status
== FILE_RETRY
)
343 goto retry_dst_symlink
;
344 return return_status
;
347 static FileProgressStatus
348 progress_update_one (FileOpTotalContext
*tctx
, FileOpContext
*ctx
, off_t add
, gboolean is_toplevel_file
)
351 if (is_toplevel_file
|| ctx
->progress_totals_computed
) {
352 tctx
->progress_count
++;
353 tctx
->progress_bytes
+= add
;
356 /* Apply some heuristic here to not call the update stuff very often */
357 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
358 file_progress_show_bytes (ctx
, tctx
->progress_bytes
, ctx
->progress_bytes
);
360 return check_progress_buttons (ctx
);
363 /* Status of the destination file */
365 DEST_NONE
= 0, /* Not created */
366 DEST_SHORT
= 1, /* Created, not fully copied */
367 DEST_FULL
= 2 /* Created, fully copied */
370 static FileProgressStatus
371 real_warn_same_file (enum OperationMode mode
, const char *fmt
,
372 const char *a
, const char *b
)
376 const char *head_msg
;
378 head_msg
= mode
== Foreground
? MSG_ERROR
:
379 _(" Background process error ");
381 msg
= g_strdup_printf (fmt
, a
, b
);
382 result
= query_dialog (head_msg
, msg
, D_ERROR
, 2, _("&Skip"), _("&Abort"));
385 if ( result
) { /* 1 == Abort */
392 #ifdef WITH_BACKGROUND
393 static FileProgressStatus
394 warn_same_file (const char *fmt
, const char *a
, const char *b
)
398 FileProgressStatus (*f
) (enum OperationMode
, const char *fmt
,
399 const char *a
, const char *b
);
401 pntr
.f
= real_warn_same_file
;
403 if (we_are_background
)
404 return parent_call (pntr
.p
, NULL
, 3, strlen (fmt
),
405 fmt
, strlen(a
), a
, strlen(b
), b
);
407 return real_warn_same_file (Foreground
, fmt
, a
, b
);
410 static FileProgressStatus
411 warn_same_file (const char *fmt
, const char *a
, const char *b
)
413 return real_warn_same_file (Foreground
, fmt
, a
, b
);
417 #define FILEOP_UPDATE_INTERVAL 2
418 #define FILEOP_STALLING_INTERVAL 4
420 copy_file_file_display_progress (FileOpTotalContext
*tctx
, FileOpContext
*ctx
,
421 struct timeval tv_current
, struct timeval tv_transfer_start
,
422 off_t file_size
, off_t n_read_total
)
426 /* 1. Update rotating dash after some time */
430 dt
= (tv_current
.tv_sec
- tv_transfer_start
.tv_sec
);
433 ctx
->eta_secs
= ((dt
/ (double) n_read_total
) * file_size
) - dt
;
434 ctx
->bps
= n_read_total
/ ((dt
< 1) ? 1 : dt
);
438 /* 4. Compute BPS rate */
439 ctx
->bps_time
= (tv_current
.tv_sec
- tv_transfer_start
.tv_sec
);
440 if (ctx
->bps_time
< 1)
442 ctx
->bps
= n_read_total
/ ctx
->bps_time
;
444 /* 5. Compute total ETA and BPS*/
445 if (ctx
->progress_bytes
!= 0) {
447 tctx
->copyed_bytes
= tctx
->progress_bytes
+ n_read_total
+ ctx
->do_reget
;
448 remain_bytes
= ctx
->progress_bytes
- tctx
->copyed_bytes
;
451 int total_secs
= tv_current
.tv_sec
- tctx
->transfer_start
.tv_sec
;
455 tctx
->bps
= tctx
->copyed_bytes
/ total_secs
;
456 tctx
->eta_secs
= remain_bytes
/ tctx
->bps
;
459 /* broken on lot of little files */
461 tctx
->bps
= ( tctx
->bps
* (tctx
->bps_count
- 1) + ctx
->bps
) / tctx
->bps_count
;
462 tctx
->eta_secs
= remain_bytes
/ tctx
->bps
;
468 copy_file_file (FileOpTotalContext
*tctx
, FileOpContext
*ctx
,
469 const char *src_path
, const char *dst_path
)
471 uid_t src_uid
= (uid_t
) -1;
472 gid_t src_gid
= (gid_t
) -1;
474 int src_desc
, dest_desc
= -1;
475 int n_read
, n_written
;
476 mode_t src_mode
= 0; /* The mode of the source file */
479 gboolean dst_exists
= FALSE
, appending
= FALSE
;
480 off_t n_read_total
= 0, file_size
= -1;
481 FileProgressStatus return_status
, temp_status
;
482 struct timeval tv_transfer_start
;
483 dest_status_t dst_status
= DEST_NONE
;
485 gboolean is_first_time
=TRUE
;
487 /* FIXME: We should not be using global variables! */
489 return_status
= FILE_RETRY
;
491 file_progress_show_source (ctx
, src_path
);
492 file_progress_show_target (ctx
, dst_path
);
493 if (check_progress_buttons (ctx
) == FILE_ABORT
)
498 while (mc_stat (dst_path
, &sb2
) == 0) {
499 if (S_ISDIR (sb2
.st_mode
)) {
501 file_error (_(" Cannot overwrite directory \"%s\" \n %s "),
503 if (return_status
== FILE_RETRY
)
505 return return_status
;
511 while ((*ctx
->stat_func
) (src_path
, &sb
)) {
513 file_error (_(" Cannot stat source file \"%s\" \n %s "),
515 if (return_status
!= FILE_RETRY
)
516 return return_status
;
520 /* Destination already exists */
521 if (sb
.st_dev
== sb2
.st_dev
&& sb
.st_ino
== sb2
.st_ino
)
522 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "),
524 /* Should we replace destination? */
525 if (tctx
->ask_overwrite
) {
527 return_status
= query_replace (ctx
, dst_path
, &sb
, &sb2
);
528 if (return_status
!= FILE_CONT
)
529 return return_status
;
533 if (!ctx
->do_append
) {
534 /* Check the hardlinks */
535 if (!ctx
->follow_links
&& sb
.st_nlink
> 1 &&
536 check_hardlinks (src_path
, dst_path
, &sb
) == 1) {
537 /* We have made a hardlink - no more processing is necessary */
541 if (S_ISLNK (sb
.st_mode
))
542 return make_symlink (ctx
, src_path
, dst_path
);
544 if (S_ISCHR (sb
.st_mode
) || S_ISBLK (sb
.st_mode
) ||
545 S_ISFIFO (sb
.st_mode
) || S_ISNAM (sb
.st_mode
) ||
546 S_ISSOCK (sb
.st_mode
)) {
547 while (mc_mknod (dst_path
, sb
.st_mode
& ctx
->umask_kill
,
549 return_status
= file_error (
550 _(" Cannot create special file \"%s\" \n %s "), dst_path
);
551 if (return_status
== FILE_RETRY
)
553 return return_status
;
557 while (ctx
->preserve_uidgid
558 && mc_chown (dst_path
, sb
.st_uid
, sb
.st_gid
)) {
559 temp_status
= file_error (
560 _(" Cannot chown target file \"%s\" \n %s "), dst_path
);
561 if (temp_status
== FILE_RETRY
)
565 while (ctx
->preserve
&&
566 mc_chmod (dst_path
, sb
.st_mode
& ctx
->umask_kill
)) {
567 temp_status
= file_error (
568 _(" Cannot chmod target file \"%s\" \n %s "), dst_path
);
569 if (temp_status
== FILE_RETRY
)
577 gettimeofday (&tv_transfer_start
, (struct timezone
*) NULL
);
579 while ((src_desc
= mc_open (src_path
, O_RDONLY
| O_LINEAR
)) < 0) {
580 return_status
= file_error (
581 _(" Cannot open source file \"%s\" \n %s "), src_path
);
582 if (return_status
== FILE_RETRY
)
585 return return_status
;
588 if (ctx
->do_reget
!= 0) {
589 if (mc_lseek (src_desc
, ctx
->do_reget
, SEEK_SET
) != ctx
->do_reget
) {
590 message (D_ERROR
, _("Warning"),
591 _(" Reget failed, about to overwrite file "));
593 ctx
->do_append
= FALSE
;
597 while (mc_fstat (src_desc
, &sb
)) {
598 return_status
= file_error (
599 _(" Cannot fstat source file \"%s\" \n %s "), src_path
);
600 if (return_status
== FILE_RETRY
)
602 ctx
->do_append
= FALSE
;
605 src_mode
= sb
.st_mode
;
608 utb
.actime
= sb
.st_atime
;
609 utb
.modtime
= sb
.st_mtime
;
610 file_size
= sb
.st_size
;
612 open_flags
= O_WRONLY
;
614 if (ctx
->do_append
!= 0)
615 open_flags
|= O_APPEND
;
617 open_flags
|= O_CREAT
| O_TRUNC
;
619 open_flags
|= O_CREAT
| O_EXCL
;
622 while ((dest_desc
= mc_open (dst_path
, open_flags
, src_mode
)) < 0) {
623 if (errno
== EEXIST
) {
626 return_status
= file_error (
627 _(" Cannot create target file \"%s\" \n %s "), dst_path
);
628 if (return_status
== FILE_RETRY
)
630 ctx
->do_append
= FALSE
;
633 dst_status
= DEST_SHORT
; /* file opened, but not fully copied */
635 appending
= ctx
->do_append
;
636 ctx
->do_append
= FALSE
;
638 /* Find out the optimal buffer size. */
639 while (mc_fstat (dest_desc
, &sb
)) {
640 return_status
= file_error (
641 _(" Cannot fstat target file \"%s\" \n %s "), dst_path
);
642 if (return_status
== FILE_RETRY
)
650 if (tctx
->bps
== 0 || (file_size
/(tctx
->bps
)) > FILEOP_UPDATE_INTERVAL
) {
651 file_progress_show (ctx
, 0, file_size
);
653 file_progress_show (ctx
, 1, 1);
655 return_status
= check_progress_buttons (ctx
);
658 if (return_status
!= FILE_CONT
)
662 struct timeval tv_current
, tv_last_update
, tv_last_input
;
663 int secs
, update_secs
;
664 const char *stalled_msg
="";
666 tv_last_update
= tv_transfer_start
;
672 if (mc_ctl (src_desc
, VFS_CTL_IS_NOTREADY
, 0))
675 while ((n_read
= mc_read (src_desc
, buf
, sizeof (buf
))) < 0) {
676 return_status
= file_error (
677 _(" Cannot read source file \"%s\" \n %s "), src_path
);
678 if (return_status
== FILE_RETRY
)
685 gettimeofday (&tv_current
, NULL
);
689 n_read_total
+= n_read
;
691 /* Windows NT ftp servers report that files have no
692 * permissions: -------, so if we happen to have actually
693 * read something, we should fix the permissions.
695 if ((src_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
)) == 0)
696 src_mode
= S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
;
697 gettimeofday (&tv_last_input
, NULL
);
701 mc_write (dest_desc
, t
, n_read
)) < n_read
) {
708 file_error (_(" Cannot write target file \"%s\" \n %s "),
710 if (return_status
!= FILE_RETRY
)
714 secs
= (tv_current
.tv_sec
- tv_last_update
.tv_sec
);
715 update_secs
= (tv_current
.tv_sec
- tv_last_input
.tv_sec
);
717 if (is_first_time
|| secs
> FILEOP_UPDATE_INTERVAL
)
719 copy_file_file_display_progress(tctx
, ctx
,
724 tv_last_update
= tv_current
;
726 is_first_time
= FALSE
;
728 if (update_secs
> FILEOP_STALLING_INTERVAL
) {
729 stalled_msg
= _("(stalled)");
733 file_progress_set_stalled_label (ctx
, stalled_msg
);
734 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
735 file_progress_show_bytes (ctx
, tctx
->progress_bytes
+ n_read_total
+ ctx
->do_reget
,
736 ctx
->progress_bytes
);
738 if ((ctx
->progress_bytes
!= 0) && (tv_current
.tv_sec
- tctx
->transfer_start
.tv_sec
) > FILEOP_UPDATE_INTERVAL
) {
739 file_progress_show_total (tctx
, ctx
);
742 file_progress_show (ctx
, n_read_total
+ ctx
->do_reget
, file_size
);
745 return_status
= check_progress_buttons (ctx
);
747 if (return_status
!= FILE_CONT
) {
754 dst_status
= DEST_FULL
; /* copy successful, don't remove target file */
757 while (src_desc
!= -1 && mc_close (src_desc
) < 0) {
758 temp_status
= file_error (
759 _(" Cannot close source file \"%s\" \n %s "), src_path
);
760 if (temp_status
== FILE_RETRY
)
762 if (temp_status
== FILE_ABORT
)
763 return_status
= temp_status
;
767 while (dest_desc
!= -1 && mc_close (dest_desc
) < 0) {
768 temp_status
= file_error (
769 _(" Cannot close target file \"%s\" \n %s "), dst_path
);
770 if (temp_status
== FILE_RETRY
)
772 return_status
= temp_status
;
776 if (dst_status
== DEST_SHORT
) {
777 /* Remove short file */
779 result
= query_dialog (Q_("DialogTitle|Copy"),
780 _("Incomplete file was retrieved. Keep it?"),
781 D_ERROR
, 2, _("&Delete"), _("&Keep"));
783 mc_unlink (dst_path
);
784 } else if (dst_status
== DEST_FULL
) {
785 /* Copy has succeeded */
786 if (!appending
&& ctx
->preserve_uidgid
) {
787 while (mc_chown (dst_path
, src_uid
, src_gid
)) {
788 temp_status
= file_error (
789 _(" Cannot chown target file \"%s\" \n %s "), dst_path
);
790 if (temp_status
== FILE_RETRY
)
792 return_status
= temp_status
;
799 while (mc_chmod (dst_path
, (src_mode
& ctx
->umask_kill
))) {
800 temp_status
= file_error (
801 _(" Cannot chmod target file \"%s\" \n %s "), dst_path
);
802 if (temp_status
!= FILE_RETRY
) {
803 return_status
= temp_status
;
808 src_mode
= umask(-1);
810 src_mode
= 0100666 & ~src_mode
;
811 mc_chmod (dst_path
, (src_mode
& ctx
->umask_kill
));
813 mc_utime (dst_path
, &utb
);
817 if (return_status
== FILE_CONT
)
818 return_status
= progress_update_one (tctx
, ctx
, file_size
, tctx
->is_toplevel_file
);
820 return return_status
;
822 #undef FILEOP_UPDATE_INTERVAL
824 * I think these copy_*_* functions should have a return type.
825 * anyway, this function *must* have two directories as arguments.
827 /* FIXME: This function needs to check the return values of the
830 copy_dir_dir (FileOpTotalContext
*tctx
, FileOpContext
*ctx
, const char *s
, const char *_d
,
831 gboolean toplevel
, gboolean move_over
, gboolean do_delete
,
832 struct link
*parent_dirs
)
835 struct stat buf
, cbuf
;
837 char *dest_dir
= NULL
;
838 FileProgressStatus return_status
= FILE_CONT
;
843 d
= strutils_shell_unescape (_d
);
845 /* First get the mode of the source dir */
847 if ((*ctx
->stat_func
) (s
, &cbuf
)) {
849 file_error (_(" Cannot stat source directory \"%s\" \n %s "), s
);
850 if (return_status
== FILE_RETRY
)
855 if (is_in_linklist (dest_dirs
, s
, &cbuf
)) {
856 /* Don't copy a directory we created before (we don't want to copy
857 infinitely if a directory is copied into itself) */
858 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
859 return_status
= FILE_CONT
;
863 /* Hmm, hardlink to directory??? - Norbert */
864 /* FIXME: In this step we should do something
865 in case the destination already exist */
866 /* Check the hardlinks */
867 if (ctx
->preserve
&& cbuf
.st_nlink
> 1
868 && check_hardlinks (s
, d
, &cbuf
) == 1) {
869 /* We have made a hardlink - no more processing is necessary */
873 if (!S_ISDIR (cbuf
.st_mode
)) {
875 file_error (_(" Source \"%s\" is not a directory \n %s "), s
);
876 if (return_status
== FILE_RETRY
)
881 if (is_in_linklist (parent_dirs
, s
, &cbuf
)) {
882 /* we found a cyclic symbolic link */
883 message (D_ERROR
, MSG_ERROR
,
884 _(" Cannot copy cyclic symbolic link \n `%s' "), s
);
885 return_status
= FILE_SKIP
;
889 lp
= g_new (struct link
, 1);
890 lp
->vfs
= vfs_get_class (s
);
891 lp
->ino
= cbuf
.st_ino
;
892 lp
->dev
= cbuf
.st_dev
;
893 lp
->next
= parent_dirs
;
897 /* Now, check if the dest dir exists, if not, create it. */
898 if (mc_stat (d
, &buf
)) {
899 /* Here the dir doesn't exist : make it ! */
901 if (mc_rename (s
, d
) == 0) {
902 return_status
= FILE_CONT
;
910 * If the destination directory exists, we want to copy the whole
911 * directory, but we only want this to happen once.
913 * Escape sequences added to the * to compiler warnings.
914 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
915 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
917 if (!S_ISDIR (buf
.st_mode
)) {
918 return_status
= file_error(
919 _(" Destination \"%s\" must be a directory \n %s "), d
);
920 if (return_status
== FILE_RETRY
)
924 /* Dive into subdir if exists */
925 if (toplevel
&& ctx
->dive_into_subdirs
) {
926 dest_dir
= concat_dir_and_file (d
, x_basename (s
));
933 while (my_mkdir (dest_dir
, (cbuf
.st_mode
& ctx
->umask_kill
) | S_IRWXU
)) {
934 return_status
= file_error (
935 _(" Cannot create target directory \"%s\" \n %s "), dest_dir
);
936 if (return_status
!= FILE_RETRY
)
940 lp
= g_new (struct link
, 1);
941 mc_stat (dest_dir
, &buf
);
942 lp
->vfs
= vfs_get_class (dest_dir
);
943 lp
->ino
= buf
.st_ino
;
944 lp
->dev
= buf
.st_dev
;
945 lp
->next
= dest_dirs
;
948 if (ctx
->preserve_uidgid
) {
949 while (mc_chown (dest_dir
, cbuf
.st_uid
, cbuf
.st_gid
)) {
950 return_status
= file_error (
951 _(" Cannot chown target directory \"%s\" \n %s "), dest_dir
);
952 if (return_status
!= FILE_RETRY
)
958 /* open the source dir for reading */
959 reading
= mc_opendir (s
);
963 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
) {
966 * Now, we don't want '.' and '..' to be created / copied at any time
968 if (!strcmp (next
->d_name
, "."))
970 if (!strcmp (next
->d_name
, ".."))
973 /* get the filename and add it to the src directory */
974 path
= concat_dir_and_file (s
, next
->d_name
);
976 (*ctx
->stat_func
) (path
, &buf
);
977 if (S_ISDIR (buf
.st_mode
)) {
980 mdpath
= concat_dir_and_file (dest_dir
, next
->d_name
);
982 * From here, we just intend to recursively copy subdirs, not
983 * the double functionality of copying different when the target
984 * dir already exists. So, we give the recursive call the flag 0
985 * meaning no toplevel.
987 return_status
= copy_dir_dir (tctx
, ctx
, path
, mdpath
, FALSE
, FALSE
, do_delete
, parent_dirs
);
992 dest_file
= concat_dir_and_file (dest_dir
, x_basename (path
));
993 return_status
= copy_file_file (tctx
, ctx
, path
, dest_file
);
996 if (do_delete
&& return_status
== FILE_CONT
) {
997 if (ctx
->erase_at_end
) {
998 static struct link
*tail
;
999 size_t len
= strlen (path
);
1000 lp
= g_malloc (sizeof (struct link
) + len
);
1001 strncpy (lp
->name
, path
, len
+ 1);
1002 lp
->st_mode
= buf
.st_mode
;
1004 if (erase_list
!= NULL
) {
1008 erase_list
= tail
= lp
;
1010 if (S_ISDIR (buf
.st_mode
)) {
1011 return_status
= erase_dir_iff_empty (ctx
, path
);
1013 return_status
= erase_file (tctx
, ctx
, path
, FALSE
);
1018 mc_closedir (reading
);
1020 if (ctx
->preserve
) {
1021 mc_chmod (dest_dir
, cbuf
.st_mode
& ctx
->umask_kill
);
1022 utb
.actime
= cbuf
.st_atime
;
1023 utb
.modtime
= cbuf
.st_mtime
;
1024 mc_utime (dest_dir
, &utb
);
1026 cbuf
.st_mode
= umask(-1);
1027 umask(cbuf
.st_mode
);
1028 cbuf
.st_mode
= 0100777 & ~cbuf
.st_mode
;
1029 mc_chmod (dest_dir
, cbuf
.st_mode
& ctx
->umask_kill
);
1034 g_free (parent_dirs
);
1037 return return_status
;
1042 /* {{{ Move routines */
1044 static FileProgressStatus
1045 move_file_file (FileOpTotalContext
*tctx
, FileOpContext
*ctx
, const char *s
, const char *d
)
1047 struct stat src_stats
, dst_stats
;
1048 FileProgressStatus return_status
= FILE_CONT
;
1049 gboolean copy_done
= FALSE
;
1051 file_progress_show_source (ctx
, s
);
1052 file_progress_show_target (ctx
, d
);
1053 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1058 while (mc_lstat (s
, &src_stats
) != 0) {
1059 /* Source doesn't exist */
1061 file_error (_(" Cannot stat file \"%s\" \n %s "), s
);
1062 if (return_status
!= FILE_RETRY
)
1063 return return_status
;
1066 if (mc_lstat (d
, &dst_stats
) == 0) {
1067 if (src_stats
.st_dev
== dst_stats
.st_dev
1068 && src_stats
.st_ino
== dst_stats
.st_ino
)
1069 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "), s
, d
);
1071 if (S_ISDIR (dst_stats
.st_mode
)) {
1072 message (D_ERROR
, MSG_ERROR
,
1073 _(" Cannot overwrite directory `%s' "), d
);
1078 if (confirm_overwrite
) {
1079 return_status
= query_replace (ctx
, d
, &src_stats
, &dst_stats
);
1080 if (return_status
!= FILE_CONT
)
1081 return return_status
;
1083 /* Ok to overwrite */
1086 if (!ctx
->do_append
) {
1087 if (S_ISLNK (src_stats
.st_mode
) && ctx
->stable_symlinks
) {
1088 if ((return_status
= make_symlink (ctx
, s
, d
)) == FILE_CONT
) {
1089 goto retry_src_remove
;
1091 return return_status
;
1094 if (mc_rename (s
, d
) == 0) {
1095 return progress_update_one (tctx
, ctx
, src_stats
.st_size
, TRUE
);
1099 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1100 one nfs to the same, but on the server it is on two different
1101 filesystems. Then nfs returns EIO instead of EXDEV.
1102 Hope it will not hurt if we always in case of error try to copy/delete. */
1104 errno
= EXDEV
; /* Hack to copy (append) the file and then delete it */
1106 if (errno
!= EXDEV
) {
1108 files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s
,
1110 if (return_status
== FILE_RETRY
)
1112 return return_status
;
1116 /* Failed because filesystem boundary -> copy the file instead */
1117 return_status
= copy_file_file (tctx
, ctx
, s
, d
);
1118 if (return_status
!= FILE_CONT
)
1119 return return_status
;
1123 file_progress_show_source (ctx
, NULL
);
1124 file_progress_show (ctx
, 0, 0);
1126 return_status
= check_progress_buttons (ctx
);
1127 if (return_status
!= FILE_CONT
)
1128 return return_status
;
1133 if (mc_unlink (s
)) {
1135 file_error (_(" Cannot remove file \"%s\" \n %s "), s
);
1136 if (return_status
== FILE_RETRY
)
1137 goto retry_src_remove
;
1138 return return_status
;
1142 return_status
= progress_update_one (tctx
, ctx
, src_stats
.st_size
, TRUE
);
1145 return return_status
;
1149 move_dir_dir (FileOpTotalContext
*tctx
, FileOpContext
*ctx
, const char *s
, const char *d
)
1151 struct stat sbuf
, dbuf
, destbuf
;
1154 FileProgressStatus return_status
;
1155 gboolean move_over
= FALSE
;
1158 file_progress_show_source (ctx
, s
);
1159 file_progress_show_target (ctx
, d
);
1160 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1166 dstat_ok
= (mc_stat (d
, &dbuf
) == 0);
1168 if (dstat_ok
&& sbuf
.st_dev
== dbuf
.st_dev
&& sbuf
.st_ino
== dbuf
.st_ino
)
1169 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same directory "), s
, d
);
1172 destdir
= g_strdup (d
); /* destination doesn't exist */
1173 else if (!ctx
->dive_into_subdirs
) {
1174 destdir
= g_strdup (d
);
1177 destdir
= concat_dir_and_file (d
, x_basename (s
));
1179 /* Check if the user inputted an existing dir */
1181 if (!mc_stat (destdir
, &destbuf
)) {
1183 return_status
= copy_dir_dir (tctx
, ctx
, s
, destdir
, FALSE
, TRUE
, TRUE
, NULL
);
1185 if (return_status
!= FILE_CONT
)
1189 if (S_ISDIR (destbuf
.st_mode
))
1192 (" Cannot overwrite directory \"%s\" %s "),
1196 file_error (_(" Cannot overwrite file \"%s\" %s "),
1198 if (return_status
== FILE_RETRY
)
1199 goto retry_dst_stat
;
1202 return return_status
;
1206 if (mc_rename (s
, destdir
) == 0) {
1207 return_status
= FILE_CONT
;
1211 if (errno
!= EXDEV
) {
1214 (" Cannot move directory \"%s\" to \"%s\" \n %s "),
1216 if (return_status
== FILE_RETRY
)
1220 /* Failed because of filesystem boundary -> copy dir instead */
1222 copy_dir_dir (tctx
, ctx
, s
, destdir
, FALSE
, FALSE
, TRUE
, NULL
);
1224 if (return_status
!= FILE_CONT
)
1227 file_progress_show_source (ctx
, NULL
);
1228 file_progress_show (ctx
, 0, 0);
1230 return_status
= check_progress_buttons (ctx
);
1231 if (return_status
!= FILE_CONT
)
1235 if (ctx
->erase_at_end
) {
1236 for (; erase_list
&& return_status
!= FILE_ABORT
;) {
1237 if (S_ISDIR (erase_list
->st_mode
)) {
1239 erase_dir_iff_empty (ctx
, erase_list
->name
);
1242 erase_file (tctx
, ctx
, erase_list
->name
, FALSE
);
1244 erase_list
= erase_list
->next
;
1248 erase_dir_iff_empty (ctx
, s
);
1252 while (erase_list
) {
1254 erase_list
= erase_list
->next
;
1257 return return_status
;
1262 /* {{{ Erase routines */
1263 /* Don't update progress status if progress_count==NULL */
1264 static FileProgressStatus
1265 erase_file (FileOpTotalContext
*tctx
, FileOpContext
*ctx
, const char *s
, gboolean is_toplevel_file
)
1270 file_progress_show_deleting (ctx
, s
);
1271 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1275 if (tctx
->progress_count
&& mc_lstat (s
, &buf
)) {
1276 /* ignore, most likely the mc_unlink fails, too */
1280 while (mc_unlink (s
)) {
1282 file_error (_(" Cannot delete file \"%s\" \n %s "), s
);
1283 if (return_status
!= FILE_RETRY
)
1284 return return_status
;
1287 if (tctx
->progress_count
)
1288 return progress_update_one (tctx
, ctx
, buf
.st_size
, is_toplevel_file
);
1293 static FileProgressStatus
1294 recursive_erase (FileOpTotalContext
*tctx
, FileOpContext
*ctx
, const char *s
)
1296 struct dirent
*next
;
1300 FileProgressStatus return_status
= FILE_CONT
;
1302 if (!strcmp (s
, ".."))
1305 reading
= mc_opendir (s
);
1310 while ((next
= mc_readdir (reading
)) && return_status
== FILE_CONT
) {
1311 if (!strcmp (next
->d_name
, "."))
1313 if (!strcmp (next
->d_name
, ".."))
1315 path
= concat_dir_and_file (s
, next
->d_name
);
1316 if (mc_lstat (path
, &buf
)) {
1318 mc_closedir (reading
);
1321 if (S_ISDIR (buf
.st_mode
))
1323 (recursive_erase (tctx
, ctx
, path
) != FILE_CONT
) ? FILE_RETRY
: FILE_CONT
;
1326 erase_file (tctx
, ctx
, path
, 0);
1329 mc_closedir (reading
);
1330 if (return_status
!= FILE_CONT
)
1331 return return_status
;
1332 file_progress_show_deleting (ctx
, s
);
1333 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1337 while (my_rmdir (s
)) {
1339 file_error (_(" Cannot remove directory \"%s\" \n %s "), s
);
1340 if (return_status
!= FILE_RETRY
)
1341 return return_status
;
1347 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1348 in the directory path points to, 0 else. */
1350 check_dir_is_empty (const char *path
)
1356 dir
= mc_opendir (path
);
1360 for (i
= 1, d
= mc_readdir (dir
); d
; d
= mc_readdir (dir
)) {
1361 if (d
->d_name
[0] == '.' && (d
->d_name
[1] == '\0' ||
1362 (d
->d_name
[1] == '.'
1363 && d
->d_name
[2] == '\0')))
1364 continue; /* "." or ".." */
1374 erase_dir (FileOpTotalContext
*tctx
, FileOpContext
*ctx
, const char *s
)
1376 FileProgressStatus error
;
1378 if (strcmp (s
, "..") == 0)
1381 if (strcmp (s
, ".") == 0)
1384 file_progress_show_deleting (ctx
, s
);
1385 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1389 /* The old way to detect a non empty directory was:
1390 error = my_rmdir (s);
1391 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1392 For the linux user space nfs server (nfs-server-2.2beta29-2)
1393 we would have to check also for EIO. I hope the new way is
1394 fool proof. (Norbert)
1396 error
= check_dir_is_empty (s
);
1397 if (error
== 0) { /* not empty */
1398 error
= query_recursive (ctx
, s
);
1399 if (error
== FILE_CONT
)
1400 return recursive_erase (tctx
, ctx
, s
);
1405 while (my_rmdir (s
) == -1) {
1407 file_error (_(" Cannot remove directory \"%s\" \n %s "), s
);
1408 if (error
!= FILE_RETRY
)
1415 static FileProgressStatus
1416 erase_dir_iff_empty (FileOpContext
*ctx
, const char *s
)
1418 FileProgressStatus error
;
1420 if (strcmp (s
, "..") == 0)
1423 if (strcmp (s
, ".") == 0)
1426 file_progress_show_deleting (ctx
, s
);
1427 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1431 if (1 != check_dir_is_empty (s
)) /* not empty or error */
1434 while (my_rmdir (s
)) {
1436 file_error (_(" Cannot remove directory \"%s\" \n %s "), s
);
1437 if (error
!= FILE_RETRY
)
1446 /* {{{ Panel operate routines */
1449 * Return currently selected entry name or the name of the first marked
1450 * entry if there is one.
1453 panel_get_file (WPanel
*panel
, struct stat
*stat_buf
)
1457 if (get_current_type () == view_tree
) {
1458 WTree
*tree
= (WTree
*) get_panel_widget (get_current_index ());
1459 char *tree_name
= tree_selected_name (tree
);
1461 mc_stat (tree_name
, stat_buf
);
1465 if (panel
->marked
) {
1466 for (i
= 0; i
< panel
->count
; i
++)
1467 if (panel
->dir
.list
[i
].f
.marked
) {
1468 *stat_buf
= panel
->dir
.list
[i
].st
;
1469 return panel
->dir
.list
[i
].fname
;
1472 *stat_buf
= panel
->dir
.list
[panel
->selected
].st
;
1473 return panel
->dir
.list
[panel
->selected
].fname
;
1475 g_assert_not_reached ();
1481 compute_dir_size_create_ui (void)
1483 ComputeDirSizeUI
*ui
;
1485 const char *b_name
= N_("&Abort");
1491 ui
= g_new (ComputeDirSizeUI
, 1);
1493 ui
->dlg
= create_dlg (0, 0, 8, COLS
/2, dialog_colors
, NULL
,
1494 NULL
, _("Directory scanning"), DLG_CENTER
);
1495 ui
->dirname
= label_new (3, 3, "");
1496 add_widget (ui
->dlg
, ui
->dirname
);
1498 add_widget (ui
->dlg
,
1499 button_new (5, (ui
->dlg
->cols
- strlen (b_name
))/2,
1500 FILE_ABORT
, NORMAL_BUTTON
, b_name
, NULL
));
1502 /* We will manage the dialog without any help,
1503 that's why we have to call init_dlg */
1510 compute_dir_size_destroy_ui (ComputeDirSizeUI
*ui
)
1513 /* schedule to update passive panel */
1514 other_panel
->dirty
= 1;
1516 /* close and destroy dialog */
1517 dlg_run_done (ui
->dlg
);
1518 destroy_dlg (ui
->dlg
);
1524 compute_dir_size_update_ui (const void *ui
, const char *dirname
)
1526 const ComputeDirSizeUI
*this = (const ComputeDirSizeUI
*) ui
;
1533 label_set_text (this->dirname
, name_trunc (dirname
, this->dlg
->cols
- 6));
1535 event
.x
= -1; /* Don't show the GPM cursor */
1536 c
= tty_get_event (&event
, FALSE
, FALSE
);
1540 /* Reinitialize to avoid old values after events other than
1541 selecting a button */
1542 this->dlg
->ret_value
= FILE_CONT
;
1544 dlg_process_event (this->dlg
, c
, &event
);
1546 switch (this->dlg
->ret_value
) {
1558 * Computes the number of bytes used by the files in a directory
1561 compute_dir_size (const char *dirname
, const void *ui
,
1562 compute_dir_size_callback cback
,
1563 off_t
*ret_marked
, double *ret_total
)
1566 struct dirent
*dirent
;
1567 FileProgressStatus ret
= FILE_CONT
;
1569 dir
= mc_opendir (dirname
);
1574 while ((dirent
= mc_readdir (dir
)) != NULL
) {
1579 ret
= (cback
!= NULL
) ? cback (ui
, dirname
) : FILE_CONT
;
1581 if (ret
!= FILE_CONT
)
1584 if (strcmp (dirent
->d_name
, ".") == 0)
1586 if (strcmp (dirent
->d_name
, "..") == 0)
1589 fullname
= concat_dir_and_file (dirname
, dirent
->d_name
);
1590 res
= mc_lstat (fullname
, &s
);
1597 if (S_ISDIR (s
.st_mode
)) {
1598 off_t subdir_count
= 0;
1599 double subdir_bytes
= 0;
1601 ret
= compute_dir_size (fullname
, ui
, cback
, &subdir_count
, &subdir_bytes
);
1603 if (ret
!= FILE_CONT
) {
1608 *ret_marked
+= subdir_count
;
1609 *ret_total
+= subdir_bytes
;
1612 *ret_total
+= s
.st_size
;
1624 * panel_compute_totals:
1626 * compute the number of files and the number of bytes
1627 * used up by the whole selection, recursing directories
1628 * as required. In addition, it checks to see if it will
1629 * overwrite any files by doing the copy.
1631 static FileProgressStatus
1632 panel_compute_totals (const WPanel
*panel
, const void *ui
,
1633 compute_dir_size_callback cback
,
1634 off_t
*ret_marked
, double *ret_total
)
1641 for (i
= 0; i
< panel
->count
; i
++) {
1644 if (!panel
->dir
.list
[i
].f
.marked
)
1647 s
= &panel
->dir
.list
[i
].st
;
1649 if (S_ISDIR (s
->st_mode
)) {
1651 off_t subdir_count
= 0;
1652 double subdir_bytes
= 0;
1653 FileProgressStatus status
;
1656 concat_dir_and_file (panel
->cwd
, panel
->dir
.list
[i
].fname
);
1658 status
= compute_dir_size (dir_name
, ui
, cback
,
1659 &subdir_count
, &subdir_bytes
);
1662 if (status
!= FILE_CONT
)
1665 *ret_marked
+= subdir_count
;
1666 *ret_total
+= subdir_bytes
;
1669 *ret_total
+= s
->st_size
;
1676 /* Initialize variables for progress bars */
1677 static FileProgressStatus
1678 panel_operate_init_totals (FileOperation operation
,
1679 const WPanel
*panel
, const char *source
,
1682 FileProgressStatus status
;
1684 if (operation
!= OP_MOVE
&& verbose
&& file_op_compute_totals
) {
1685 ComputeDirSizeUI
*ui
;
1687 ui
= compute_dir_size_create_ui ();
1690 status
= compute_dir_size (source
, ui
, compute_dir_size_update_ui
,
1691 &ctx
->progress_count
, &ctx
->progress_bytes
);
1693 status
= panel_compute_totals (panel
, ui
, compute_dir_size_update_ui
,
1694 &ctx
->progress_count
, &ctx
->progress_bytes
);
1696 compute_dir_size_destroy_ui (ui
);
1698 ctx
->progress_totals_computed
= (status
== FILE_CONT
);
1701 ctx
->progress_count
= panel
->marked
;
1702 ctx
->progress_bytes
= panel
->total
;
1703 ctx
->progress_totals_computed
= FALSE
;
1710 * This array introduced to avoid translation problems. The former (op_names)
1711 * is assumed to be nouns, suitable in dialog box titles; this one should
1712 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
1713 * (I don't use spaces around the words, because someday they could be
1714 * dropped, when widgets get smarter)
1717 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
1718 static const char *op_names1
[] = {
1719 N_("FileOperation|Copy"),
1720 N_("FileOperation|Move"),
1721 N_("FileOperation|Delete")
1725 * These are formats for building a prompt. Parts encoded as follows:
1726 * %o - operation from op_names1
1727 * %f - file/files or files/directories, as appropriate
1728 * %m - "with source mask" or question mark for delete
1729 * %s - source name (truncated)
1730 * %d - number of marked files
1731 * %e - "to:" or question mark for delete
1733 * xgettext:no-c-format */
1734 static const char *one_format
= N_("%o %f \"%s\"%m");
1735 /* xgettext:no-c-format */
1736 static const char *many_format
= N_("%o %d %f%m");
1738 static const char *prompt_parts
[] = {
1743 N_("files/directories"),
1744 N_(" with source mask:"),
1748 static const char *question_format
= N_("%s?");
1751 * Generate user prompt for panel operation.
1752 * single_source is the name if the source entry or NULL for multiple
1754 * src_stat is only used when single_source is not NULL.
1757 panel_operate_generate_prompt (const WPanel
*panel
, FileOperation operation
,
1758 gboolean single_source
,
1759 const struct stat
*src_stat
)
1761 const char *sp
, *cp
;
1762 char format_string
[BUF_MEDIUM
];
1763 char *dp
= format_string
;
1764 gboolean build_question
= FALSE
;
1767 static gboolean i18n_flag
= FALSE
;
1771 for (i
= sizeof (op_names1
) / sizeof (op_names1
[0]); i
--;)
1772 op_names1
[i
] = Q_(op_names1
[i
]);
1774 for (i
= sizeof (prompt_parts
) / sizeof (prompt_parts
[0]); i
--;)
1775 prompt_parts
[i
] = _(prompt_parts
[i
]);
1777 one_format
= _(one_format
);
1778 many_format
= _(many_format
);
1779 question_format
= _(question_format
);
1782 #endif /* ENABLE_NLS */
1784 sp
= single_source
? one_format
: many_format
;
1786 while (*sp
!= '\0') {
1792 cp
= op_names1
[operation
];
1795 if (operation
== OP_DELETE
) {
1797 build_question
= TRUE
;
1799 cp
= prompt_parts
[5];
1802 if (operation
== OP_DELETE
) {
1804 build_question
= TRUE
;
1806 cp
= prompt_parts
[6];
1809 if (single_source
) {
1810 cp
= S_ISDIR (src_stat
->
1811 st_mode
) ? prompt_parts
[2] : prompt_parts
[0];
1813 cp
= (panel
->marked
== panel
->dirs_marked
)
1815 : (panel
->dirs_marked
? prompt_parts
[4] : prompt_parts
[1]);
1833 if (build_question
) {
1834 char tmp
[BUF_MEDIUM
];
1836 memmove (tmp
, format_string
, sizeof (tmp
));
1837 g_snprintf (format_string
, sizeof (format_string
),
1838 question_format
, tmp
);
1841 return g_strdup (format_string
);
1844 #ifdef WITH_BACKGROUND
1846 end_bg_process (FileOpContext
*ctx
, enum OperationMode mode
) {
1852 unregister_task_with_pid(pid
);
1853 // file_op_context_destroy(ctx);
1861 * Performs one of the operations on the selection on the source_panel
1862 * (copy, delete, move).
1864 * Returns TRUE if did change the directory
1865 * structure, Returns FALSE if user aborted
1867 * force_single forces operation on the current entry and affects
1868 * default destination. Current filename is used as default.
1871 panel_operate (void *source_panel
, FileOperation operation
, gboolean force_single
)
1873 WPanel
*panel
= (WPanel
*) source_panel
;
1874 const gboolean single_entry
= force_single
|| (panel
->marked
<= 1)
1875 || (get_current_type () == view_tree
);
1877 char *source
= NULL
;
1878 #ifdef WITH_FULL_PATHS
1879 char *source_with_path
= NULL
;
1881 # define source_with_path source
1882 #endif /* !WITH_FULL_PATHS */
1885 char *save_cwd
= NULL
, *save_dest
= NULL
;
1886 struct stat src_stat
;
1888 FileProgressStatus value
;
1890 FileOpTotalContext
*tctx
;
1892 gboolean do_bg
= FALSE
; /* do background operation? */
1895 static gboolean i18n_flag
= FALSE
;
1897 for (i
= sizeof (op_names1
) / sizeof (op_names1
[0]); i
--;)
1898 op_names
[i
] = Q_(op_names
[i
]);
1901 #endif /* ENABLE_NLS */
1903 free_linklist (&linklist
);
1904 free_linklist (&dest_dirs
);
1906 /* Update panel contents to avoid actions on deleted files */
1907 if (!panel
->is_panelized
) {
1908 update_panels (UP_RELOAD
, UP_KEEPSEL
);
1914 source
= selection (panel
)->fname
;
1915 src_stat
= selection (panel
)->st
;
1917 source
= panel_get_file (panel
, &src_stat
);
1920 if (!strcmp (source
, "..")) {
1921 message (D_ERROR
, MSG_ERROR
, _(" Cannot operate on \"..\"! "));
1926 ctx
= file_op_context_new (operation
);
1927 tctx
= file_op_total_context_new ();
1928 gettimeofday (&(tctx
->transfer_start
), (struct timezone
*) NULL
);
1930 /* Show confirmation dialog */
1931 if (operation
!= OP_DELETE
) {
1936 /* Forced single operations default to the original name */
1939 else if (get_other_type () == view_listing
)
1940 dest_dir
= other_panel
->cwd
;
1942 dest_dir
= panel
->cwd
;
1944 * Add trailing backslash only when do non-local ops.
1945 * It saves user from occasional file renames (when destination
1949 && dest_dir
[0] != '\0'
1950 && dest_dir
[strlen (dest_dir
) - 1] != PATH_SEP
) {
1951 /* add trailing separator */
1952 dest_dir_
= g_strconcat (dest_dir
, PATH_SEP_STR
, (char *) NULL
);
1955 dest_dir_
= g_strdup (dest_dir
);
1957 if (dest_dir_
== NULL
) {
1958 file_op_total_context_destroy (tctx
);
1959 file_op_context_destroy (ctx
);
1963 /* Generate confirmation prompt */
1964 format
= panel_operate_generate_prompt (panel
, operation
,
1965 source
!= NULL
, &src_stat
);
1967 dest
= file_mask_dialog (ctx
, operation
, source
!= NULL
, format
,
1968 source
!= NULL
? (void *) source
1969 : (void *) &panel
->marked
,
1975 if (dest
== NULL
|| dest
[0] == '\0') {
1976 file_op_total_context_destroy (tctx
);
1977 file_op_context_destroy (ctx
);
1981 } else if (confirm_delete
) {
1983 char fmd_buf
[BUF_MEDIUM
];
1985 /* Generate confirmation prompt */
1986 format
= panel_operate_generate_prompt (panel
, OP_DELETE
,
1987 source
!= NULL
, &src_stat
);
1990 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, panel
->marked
);
1992 const int fmd_xlen
= 64;
1993 i
= fmd_xlen
- str_term_width1 (format
) - 4;
1994 g_snprintf (fmd_buf
, sizeof (fmd_buf
),
1995 format
, str_trunc (source
, i
));
2003 i
= query_dialog (op_names
[operation
], fmd_buf
, D_ERROR
, 2,
2004 _("&Yes"), _("&No"));
2007 file_op_total_context_destroy (tctx
);
2008 file_op_context_destroy (ctx
);
2014 gboolean show_total
= !((operation
!= OP_COPY
) || (single_entry
) || (force_single
));
2016 if ((single_entry
) && (operation
== OP_COPY
) && S_ISDIR (selection (panel
)->st
.st_mode
))
2018 /* Background also need ctx->ui, but not full */
2020 file_op_context_create_ui_without_init (ctx
, 1, show_total
);
2022 file_op_context_create_ui (ctx
, 1, show_total
);
2025 #ifdef WITH_BACKGROUND
2026 /* Did the user select to do a background operation? */
2030 v
= do_background (ctx
,
2031 g_strconcat (op_names
[operation
], ": ",
2032 panel
->cwd
, (char *) NULL
));
2034 message (D_ERROR
, MSG_ERROR
,
2035 _(" Sorry, I could not put the job in background "));
2038 /* If we are the parent */
2040 mc_setctl (panel
->cwd
, VFS_SETCTL_FORGET
, NULL
);
2041 mc_setctl (dest
, VFS_SETCTL_FORGET
, NULL
);
2042 /* file_op_context_destroy (ctx); */
2046 #endif /* WITH_BACKGROUND */
2048 /* Initialize things */
2049 /* We do not want to trash cache every time file is
2050 created/touched. However, this will make our cache contain
2053 && (mc_setctl (dest
, VFS_SETCTL_STALE_DATA
, (void *) 1)))
2054 save_dest
= g_strdup (dest
);
2056 if ((panel
->cwd
[0] != '\0')
2057 && (mc_setctl (panel
->cwd
, VFS_SETCTL_STALE_DATA
, (void *) 1)))
2058 save_cwd
= g_strdup (panel
->cwd
);
2060 /* Now, let's do the job */
2062 /* This code is only called by the tree and panel code */
2064 /* We now have ETA in all cases */
2066 /* One file: FIXME mc_chdir will take user out of any vfs */
2067 if (operation
!= OP_COPY
&& get_current_type () == view_tree
)
2068 mc_chdir (PATH_SEP_STR
);
2070 /* The source and src_stat variables have been initialized before */
2071 #ifdef WITH_FULL_PATHS
2072 source_with_path
= concat_dir_and_file (panel
->cwd
, source
);
2073 #endif /* WITH_FULL_PATHS */
2075 if (panel_operate_init_totals (operation
, panel
,
2076 source_with_path
, ctx
) == FILE_CONT
) {
2077 if (operation
== OP_DELETE
) {
2078 if (S_ISDIR (src_stat
.st_mode
))
2079 value
= erase_dir (tctx
, ctx
, source_with_path
);
2081 value
= erase_file (tctx
, ctx
, source_with_path
, 1);
2083 temp
= transform_source (ctx
, source_with_path
);
2085 value
= transform_error
;
2087 char *repl_dest
, *temp2
;
2089 repl_dest
= mc_search_prepare_replace_str2 (ctx
->search_handle
, dest
);
2090 temp2
= concat_dir_and_file (repl_dest
, temp
);
2096 switch (operation
) {
2098 /* we use file_mask_op_follow_links only with OP_COPY */
2099 ctx
->stat_func (source_with_path
, &src_stat
);
2101 if (S_ISDIR (src_stat
.st_mode
))
2102 value
= copy_dir_dir (tctx
, ctx
, source_with_path
, dest
,
2103 TRUE
, FALSE
, FALSE
, NULL
);
2105 value
= copy_file_file (tctx
, ctx
, source_with_path
, dest
);
2109 if (S_ISDIR (src_stat
.st_mode
))
2110 value
= move_dir_dir (tctx
, ctx
, source_with_path
, dest
);
2112 value
= move_file_file (tctx
, ctx
, source_with_path
, dest
);
2116 /* Unknown file operation */
2120 } /* Copy or move operation */
2122 if ((value
== FILE_CONT
) && !force_single
)
2123 unmark_files (panel
);
2128 /* Check destination for copy or move operation */
2129 while (operation
!= OP_DELETE
) {
2131 struct stat dst_stat
;
2133 dst_result
= mc_stat (dest
, &dst_stat
);
2135 if ((dst_result
!= 0) || S_ISDIR (dst_stat
.st_mode
))
2138 if (file_error (_(" Destination \"%s\" must be a directory \n %s "),
2139 dest
) != FILE_RETRY
)
2143 if (panel_operate_init_totals (operation
, panel
, NULL
, ctx
) == FILE_CONT
) {
2144 /* Loop for every file, perform the actual copy operation */
2145 for (i
= 0; i
< panel
->count
; i
++) {
2146 if (!panel
->dir
.list
[i
].f
.marked
)
2147 continue; /* Skip the unmarked ones */
2149 source
= panel
->dir
.list
[i
].fname
;
2150 src_stat
= panel
->dir
.list
[i
].st
;
2152 #ifdef WITH_FULL_PATHS
2153 g_free (source_with_path
);
2154 source_with_path
= concat_dir_and_file (panel
->cwd
, source
);
2155 #endif /* WITH_FULL_PATHS */
2157 if (operation
== OP_DELETE
) {
2158 if (S_ISDIR (src_stat
.st_mode
))
2159 value
= erase_dir (tctx
, ctx
, source_with_path
);
2161 value
= erase_file (tctx
, ctx
, source_with_path
, 1);
2163 temp
= transform_source (ctx
, source_with_path
);
2166 value
= transform_error
;
2168 char *temp2
, *temp3
, *repl_dest
;
2170 repl_dest
= mc_search_prepare_replace_str2 (ctx
->search_handle
, dest
);
2171 temp2
= concat_dir_and_file (repl_dest
, temp
);
2174 temp3
= source_with_path
;
2175 source_with_path
= strutils_shell_unescape (source_with_path
);
2178 temp2
= strutils_shell_unescape (temp2
);
2181 switch (operation
) {
2183 /* we use file_mask_op_follow_links only with OP_COPY */
2184 ctx
->stat_func (source_with_path
, &src_stat
);
2185 if (S_ISDIR (src_stat
.st_mode
))
2186 value
= copy_dir_dir (tctx
, ctx
, source_with_path
, temp2
,
2187 TRUE
, FALSE
, FALSE
, NULL
);
2189 value
= copy_file_file (tctx
, ctx
, source_with_path
, temp2
);
2190 free_linklist (&dest_dirs
);
2194 if (S_ISDIR (src_stat
.st_mode
))
2195 value
= move_dir_dir (tctx
, ctx
, source_with_path
, temp2
);
2197 value
= move_file_file (tctx
, ctx
, source_with_path
, temp2
);
2201 /* Unknown file operation */
2207 } /* Copy or move operation */
2209 if (value
== FILE_ABORT
)
2212 if (value
== FILE_CONT
)
2213 do_file_mark (panel
, i
, 0);
2215 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
2219 file_progress_show_bytes (ctx
, tctx
->progress_bytes
, ctx
->progress_bytes
);
2221 if (operation
!= OP_DELETE
)
2222 file_progress_show (ctx
, 0, 0);
2225 if (check_progress_buttons (ctx
) == FILE_ABORT
)
2229 } /* Loop for every file */
2231 } /* Many entries */
2235 if (save_cwd
!= NULL
) {
2236 mc_setctl (save_cwd
, VFS_SETCTL_STALE_DATA
, NULL
);
2240 if (save_dest
!= NULL
) {
2241 mc_setctl (save_dest
, VFS_SETCTL_STALE_DATA
, NULL
);
2245 free_linklist (&linklist
);
2246 free_linklist (&dest_dirs
);
2247 #ifdef WITH_FULL_PATHS
2248 g_free (source_with_path
);
2249 #endif /* WITH_FULL_PATHS */
2251 g_free (ctx
->dest_mask
);
2252 ctx
->dest_mask
= NULL
;
2254 #ifdef WITH_BACKGROUND
2255 /* Let our parent know we are saying bye bye */
2256 if (we_are_background
) {
2257 int cur_pid
= getpid();
2258 /* Send pid to parent with child context, it is fork and
2259 don't modify real parent ctx */
2261 parent_call ((void *) end_bg_process
, ctx
, 0);
2266 #endif /* WITH_BACKGROUND */
2268 file_op_context_destroy (ctx
);
2274 /* {{{ Query/status report routines */
2276 static FileProgressStatus
2277 real_do_file_error (enum OperationMode mode
, const char *error
)
2282 msg
= mode
== Foreground
? MSG_ERROR
: _(" Background process error ");
2284 query_dialog (msg
, error
, D_ERROR
, 3, _("&Skip"), _("&Retry"),
2302 /* Report error with one file */
2304 file_error (const char *format
, const char *file
)
2306 char buf
[BUF_MEDIUM
];
2308 g_snprintf (buf
, sizeof (buf
), format
,
2309 path_trunc (file
, 30), unix_error_string (errno
));
2311 return do_file_error (buf
);
2314 /* Report error with two files */
2315 static FileProgressStatus
2316 files_error (const char *format
, const char *file1
, const char *file2
)
2318 char buf
[BUF_MEDIUM
];
2319 char *nfile1
= g_strdup (path_trunc (file1
, 15));
2320 char *nfile2
= g_strdup (path_trunc (file2
, 15));
2322 g_snprintf (buf
, sizeof (buf
), format
, nfile1
, nfile2
,
2323 unix_error_string (errno
));
2328 return do_file_error (buf
);
2331 static FileProgressStatus
2332 real_query_recursive (FileOpContext
*ctx
, enum OperationMode mode
, const char *s
)
2336 if (ctx
->recursive_result
< RECURSIVE_ALWAYS
) {
2337 const char *msg
= mode
== Foreground
2338 ? _("\n Directory not empty. \n"
2339 " Delete it recursively? ")
2340 : _("\n Background process: Directory not empty \n"
2341 " Delete it recursively? ");
2342 text
= g_strconcat (_(" Delete: "), path_trunc (s
, 30), " ", (char *) NULL
);
2347 ctx
->recursive_result
=
2348 (FileCopyMode
) query_dialog (text
, msg
, D_ERROR
, 5,
2349 _("&Yes"), _("&No"),
2350 _("A&ll"), _("Non&e"),
2353 if (ctx
->recursive_result
!= RECURSIVE_ABORT
)
2358 switch (ctx
->recursive_result
) {
2360 case RECURSIVE_ALWAYS
:
2364 case RECURSIVE_NEVER
:
2367 case RECURSIVE_ABORT
:
2373 #ifdef WITH_BACKGROUND
2374 static FileProgressStatus
2375 do_file_error (const char *str
)
2379 FileProgressStatus (*f
) (enum OperationMode
, const char *);
2381 pntr
.f
= real_do_file_error
;
2383 if (we_are_background
)
2384 return parent_call (pntr
.p
, NULL
, 1, strlen (str
),
2387 return real_do_file_error (Foreground
, str
);
2390 static FileProgressStatus
2391 query_recursive (FileOpContext
*ctx
, const char *s
)
2395 FileProgressStatus (*f
) (FileOpContext
*, enum OperationMode
, const char *);
2397 pntr
.f
= real_query_recursive
;
2399 if (we_are_background
)
2400 return parent_call (pntr
.p
, ctx
, 1, strlen (s
), s
);
2402 return real_query_recursive (ctx
, Foreground
, s
);
2405 static FileProgressStatus
2406 query_replace (FileOpContext
*ctx
, const char *destname
, struct stat
*_s_stat
,
2407 struct stat
*_d_stat
)
2411 FileProgressStatus (*f
) (FileOpContext
*, enum OperationMode
, const char *,
2412 struct stat
*, struct stat
*);
2414 pntr
.f
= file_progress_real_query_replace
;
2416 if (we_are_background
)
2417 return parent_call (pntr
.p
,
2420 strlen (destname
), destname
,
2421 sizeof (struct stat
), _s_stat
,
2422 sizeof (struct stat
), _d_stat
);
2424 return file_progress_real_query_replace (ctx
, Foreground
, destname
,
2429 static FileProgressStatus
2430 do_file_error (const char *str
)
2432 return real_do_file_error (Foreground
, str
);
2435 static FileProgressStatus
2436 query_recursive (FileOpContext
*ctx
, const char *s
)
2438 return real_query_recursive (ctx
, Foreground
, s
);
2441 static FileProgressStatus
2442 query_replace (FileOpContext
*ctx
, const char *destname
, struct stat
*_s_stat
,
2443 struct stat
*_d_stat
)
2445 return file_progress_real_query_replace (ctx
, Foreground
, destname
,
2449 #endif /* !WITH_BACKGROUND */
2452 Cause emacs to enter folding mode for this file: