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/strescape.h"
63 #include "lib/strutil.h"
65 #include "lib/vfs/mc-vfs/vfs.h"
66 #include "lib/widget.h"
69 #include "layout.h" /* rotate_dash() */
70 #include "background.h" /* we_are_background */
72 /* Needed for current_panel, other_panel and WTree */
76 #include "midnight.h" /* current_panel */
82 /*** global variables ****************************************************************************/
84 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
85 const char *op_names
[3] = {
86 N_("DialogTitle|Copy"),
87 N_("DialogTitle|Move"),
88 N_("DialogTitle|Delete")
91 /*** file scope macro definitions ****************************************************************/
93 /* Hack: the vfs code should not rely on this */
94 #define WITH_FULL_PATHS 1
96 #define FILEOP_UPDATE_INTERVAL 2
97 #define FILEOP_STALLING_INTERVAL 4
99 /*** file scope type declarations ****************************************************************/
101 /* This is a hard link cache */
105 struct vfs_class
*vfs
;
113 /* Status of the destination file */
116 DEST_NONE
= 0, /* Not created */
117 DEST_SHORT
= 1, /* Created, not fully copied */
118 DEST_FULL
= 2 /* Created, fully copied */
122 * This array introduced to avoid translation problems. The former (op_names)
123 * is assumed to be nouns, suitable in dialog box titles; this one should
124 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
125 * (I don't use spaces around the words, because someday they could be
126 * dropped, when widgets get smarter)
129 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
130 static const char *op_names1
[] = {
131 N_("FileOperation|Copy"),
132 N_("FileOperation|Move"),
133 N_("FileOperation|Delete")
137 * These are formats for building a prompt. Parts encoded as follows:
138 * %o - operation from op_names1
139 * %f - file/files or files/directories, as appropriate
140 * %m - "with source mask" or question mark for delete
141 * %s - source name (truncated)
142 * %d - number of marked files
143 * %e - "to:" or question mark for delete
145 * xgettext:no-c-format */
146 static const char *one_format
= N_("%o %f \"%s\"%m");
147 /* xgettext:no-c-format */
148 static const char *many_format
= N_("%o %d %f%m");
150 static const char *prompt_parts
[] = {
155 N_("files/directories"),
156 /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
157 N_(" with source mask:"),
161 static const char *question_format
= N_("%s?");
163 /*** file scope variables ************************************************************************/
165 /* the hard link cache */
166 static struct link
*linklist
= NULL
;
168 /* the files-to-be-erased list */
169 static struct link
*erase_list
;
172 * In copy_dir_dir we use two additional single linked lists: The first -
173 * variable name `parent_dirs' - holds information about already copied
174 * directories and is used to detect cyclic symbolic links.
175 * The second (`dest_dirs' below) holds information about just created
176 * target directories and is used to detect when an directory is copied
177 * into itself (we don't want to copy infinitly).
178 * Both lists don't use the linkcount and name structure members of struct
181 static struct link
*dest_dirs
= NULL
;
183 static FileProgressStatus transform_error
= FILE_CONT
;
185 /*** file scope functions ************************************************************************/
186 /* --------------------------------------------------------------------------------------------- */
188 static FileProgressStatus
query_replace (FileOpContext
* ctx
, const char *destname
,
189 struct stat
*_s_stat
, struct stat
*_d_stat
);
190 static FileProgressStatus
query_recursive (FileOpContext
* ctx
, const char *s
);
191 static FileProgressStatus
do_file_error (const char *str
);
192 static FileProgressStatus
erase_dir_iff_empty (FileOpContext
* ctx
, const char *s
);
193 static FileProgressStatus
erase_file (FileOpTotalContext
* tctx
, FileOpContext
* ctx
,
194 const char *s
, gboolean is_toplevel_file
);
195 static FileProgressStatus
files_error (const char *format
, const char *file1
, const char *file2
);
197 /* --------------------------------------------------------------------------------------------- */
200 transform_source (FileOpContext
* ctx
, const char *source
)
205 s
= g_strdup (source
);
207 /* We remove \n from the filename since regex routines would use \n as an anchor */
208 /* this is just to be allowed to maniupulate file names with \n on it */
209 for (q
= s
; *q
!= '\0'; q
++)
213 fnsource
= (char *) x_basename (s
);
215 if (mc_search_run (ctx
->search_handle
, fnsource
, 0, strlen (fnsource
), NULL
))
216 q
= mc_search_prepare_replace_str2 (ctx
->search_handle
, ctx
->dest_mask
);
220 transform_error
= FILE_SKIP
;
227 /* --------------------------------------------------------------------------------------------- */
230 free_linklist (struct link
**lc_linklist
)
232 struct link
*lp
, *lp2
;
234 for (lp
= *lc_linklist
; lp
!= NULL
; lp
= lp2
)
242 /* --------------------------------------------------------------------------------------------- */
245 is_in_linklist (struct link
*lp
, const char *path
, struct stat
*sb
)
247 ino_t ino
= sb
->st_ino
;
248 dev_t dev
= sb
->st_dev
;
249 struct vfs_class
*vfs
= vfs_get_class (path
);
254 if (lp
->ino
== ino
&& lp
->dev
== dev
)
261 /* --------------------------------------------------------------------------------------------- */
263 * Returns 0 if the inode wasn't found in the cache and 1 if it was found
264 * and a hardlink was succesfully made
268 check_hardlinks (const char *src_name
, const char *dst_name
, struct stat
*pstat
)
271 struct vfs_class
*my_vfs
= vfs_get_class (src_name
);
272 ino_t ino
= pstat
->st_ino
;
273 dev_t dev
= pstat
->st_dev
;
274 struct stat link_stat
;
277 if ((vfs_file_class_flags (src_name
) & VFSF_NOLINKS
) != 0)
280 for (lp
= linklist
; lp
!= NULL
; lp
= lp
->next
)
281 if (lp
->vfs
== my_vfs
&& lp
->ino
== ino
&& lp
->dev
== dev
)
283 if (!mc_stat (lp
->name
, &link_stat
) && link_stat
.st_ino
== ino
284 && link_stat
.st_dev
== dev
&& vfs_get_class (lp
->name
) == my_vfs
)
286 p
= strchr (lp
->name
, 0) + 1; /* i.e. where the `name' file
288 if (vfs_get_class (dst_name
) == vfs_get_class (p
))
290 if (!mc_stat (p
, &link_stat
))
292 if (!mc_link (p
, dst_name
))
297 message (D_ERROR
, MSG_ERROR
, _("Cannot make the hardlink"));
300 lp
= (struct link
*) g_try_malloc (sizeof (struct link
) + strlen (src_name
)
301 + strlen (dst_name
) + 1);
308 strcpy (lp
->name
, src_name
);
309 lpdstname
= lp
->name
+ strlen (lp
->name
) + 1;
310 strcpy (lpdstname
, dst_name
);
317 /* --------------------------------------------------------------------------------------------- */
319 * Duplicate the contents of the symbolic link src_path in dst_path.
320 * Try to make a stable symlink if the option "stable symlink" was
321 * set in the file mask dialog.
322 * If dst_path is an existing symlink it will be deleted silently
323 * (upper levels take already care of existing files at dst_path).
326 static FileProgressStatus
327 make_symlink (FileOpContext
* ctx
, const char *src_path
, const char *dst_path
)
329 char link_target
[MC_MAXPATHLEN
];
331 FileProgressStatus return_status
;
333 gboolean dst_is_symlink
;
335 dst_is_symlink
= (mc_lstat (dst_path
, &sb
) == 0) && S_ISLNK (sb
.st_mode
);
338 len
= mc_readlink (src_path
, link_target
, MC_MAXPATHLEN
- 1);
341 return_status
= file_error (_("Cannot read source link \"%s\"\n%s"), src_path
);
342 if (return_status
== FILE_RETRY
)
343 goto retry_src_readlink
;
344 return return_status
;
346 link_target
[len
] = 0;
348 if (ctx
->stable_symlinks
)
349 if (!vfs_file_is_local (src_path
) || !vfs_file_is_local (dst_path
))
351 message (D_ERROR
, MSG_ERROR
,
352 _("Cannot make stable symlinks across"
353 "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
354 ctx
->stable_symlinks
= FALSE
;
357 if (ctx
->stable_symlinks
&& !g_path_is_absolute (link_target
))
361 const char *r
= strrchr (src_path
, PATH_SEP
);
365 p
= g_strndup (src_path
, r
- src_path
+ 1);
366 if (g_path_is_absolute (dst_path
))
367 q
= g_strdup (dst_path
);
369 q
= g_strconcat (p
, dst_path
, (char *) NULL
);
370 s
= strrchr (q
, PATH_SEP
);
374 s
= g_strconcat (p
, link_target
, (char *) NULL
);
376 g_strlcpy (link_target
, s
, sizeof (link_target
));
378 s
= diff_two_paths (q
, link_target
);
381 g_strlcpy (link_target
, s
, sizeof (link_target
));
391 if (mc_symlink (link_target
, dst_path
) == 0)
395 * if dst_exists, it is obvious that this had failed.
396 * We can delete the old symlink and try again...
400 if (!mc_unlink (dst_path
))
401 if (mc_symlink (link_target
, dst_path
) == 0)
405 return_status
= file_error (_("Cannot create target symlink \"%s\"\n%s"), dst_path
);
406 if (return_status
== FILE_RETRY
)
407 goto retry_dst_symlink
;
408 return return_status
;
411 /* --------------------------------------------------------------------------------------------- */
413 static FileProgressStatus
414 progress_update_one (FileOpTotalContext
* tctx
, FileOpContext
* ctx
, off_t add
,
415 gboolean is_toplevel_file
)
417 struct timeval tv_current
;
418 static struct timeval tv_start
= { };
420 if (is_toplevel_file
|| ctx
->progress_totals_computed
)
422 tctx
->progress_count
++;
423 tctx
->progress_bytes
+= add
;
425 if (tv_start
.tv_sec
== 0)
427 gettimeofday (&tv_start
, (struct timezone
*) NULL
);
429 gettimeofday (&tv_current
, (struct timezone
*) NULL
);
430 if ((tv_current
.tv_sec
- tv_start
.tv_sec
) > FILEOP_UPDATE_INTERVAL
)
432 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
433 file_progress_show_total (tctx
, ctx
, tctx
->progress_bytes
, TRUE
);
434 tv_start
.tv_sec
= tv_current
.tv_sec
;
437 return check_progress_buttons (ctx
);
440 /* --------------------------------------------------------------------------------------------- */
442 static FileProgressStatus
443 real_warn_same_file (enum OperationMode mode
, const char *fmt
, const char *a
, const char *b
)
447 const char *head_msg
;
449 head_msg
= mode
== Foreground
? MSG_ERROR
: _("Background process error");
451 msg
= g_strdup_printf (fmt
, a
, b
);
452 result
= query_dialog (head_msg
, msg
, D_ERROR
, 2, _("&Skip"), _("&Abort"));
456 return (result
== 1) ? FILE_ABORT
: FILE_SKIP
;
459 /* --------------------------------------------------------------------------------------------- */
461 #ifdef WITH_BACKGROUND
462 static FileProgressStatus
463 warn_same_file (const char *fmt
, const char *a
, const char *b
)
468 FileProgressStatus (*f
) (enum OperationMode
, const char *fmt
,
469 const char *a
, const char *b
);
471 pntr
.f
= real_warn_same_file
;
473 if (we_are_background
)
474 return parent_call (pntr
.p
, NULL
, 3, strlen (fmt
), fmt
, strlen (a
), a
, strlen (b
), b
);
476 return real_warn_same_file (Foreground
, fmt
, a
, b
);
479 static FileProgressStatus
480 warn_same_file (const char *fmt
, const char *a
, const char *b
)
482 return real_warn_same_file (Foreground
, fmt
, a
, b
);
486 /* --------------------------------------------------------------------------------------------- */
489 copy_file_file_display_progress (FileOpTotalContext
* tctx
, FileOpContext
* ctx
,
490 struct timeval tv_current
, struct timeval tv_transfer_start
,
491 off_t file_size
, off_t n_read_total
)
495 /* 1. Update rotating dash after some time */
499 dt
= (tv_current
.tv_sec
- tv_transfer_start
.tv_sec
);
503 ctx
->eta_secs
= ((dt
/ (double) n_read_total
) * file_size
) - dt
;
504 ctx
->bps
= n_read_total
/ ((dt
< 1) ? 1 : dt
);
509 /* 4. Compute BPS rate */
510 ctx
->bps_time
= (tv_current
.tv_sec
- tv_transfer_start
.tv_sec
);
511 if (ctx
->bps_time
< 1)
513 ctx
->bps
= n_read_total
/ ctx
->bps_time
;
515 /* 5. Compute total ETA and BPS */
516 if (ctx
->progress_bytes
!= 0)
519 tctx
->copyed_bytes
= tctx
->progress_bytes
+ n_read_total
+ ctx
->do_reget
;
520 remain_bytes
= ctx
->progress_bytes
- tctx
->copyed_bytes
;
523 int total_secs
= tv_current
.tv_sec
- tctx
->transfer_start
.tv_sec
;
527 tctx
->bps
= tctx
->copyed_bytes
/ total_secs
;
528 tctx
->eta_secs
= remain_bytes
/ tctx
->bps
;
531 /* broken on lot of little files */
533 tctx
->bps
= (tctx
->bps
* (tctx
->bps_count
- 1) + ctx
->bps
) / tctx
->bps_count
;
534 tctx
->eta_secs
= remain_bytes
/ tctx
->bps
;
539 /* --------------------------------------------------------------------------------------------- */
542 /* {{{ Move routines */
543 static FileProgressStatus
544 move_file_file (FileOpTotalContext
* tctx
, FileOpContext
* ctx
, const char *s
, const char *d
)
546 struct stat src_stats
, dst_stats
;
547 FileProgressStatus return_status
= FILE_CONT
;
548 gboolean copy_done
= FALSE
;
549 gboolean old_ask_overwrite
;
551 file_progress_show_source (ctx
, s
);
552 file_progress_show_target (ctx
, d
);
553 if (check_progress_buttons (ctx
) == FILE_ABORT
)
558 while (mc_lstat (s
, &src_stats
) != 0)
560 /* Source doesn't exist */
561 return_status
= file_error (_("Cannot stat file \"%s\"\n%s"), s
);
562 if (return_status
!= FILE_RETRY
)
563 return return_status
;
566 if (mc_lstat (d
, &dst_stats
) == 0)
568 if (src_stats
.st_dev
== dst_stats
.st_dev
&& src_stats
.st_ino
== dst_stats
.st_ino
)
569 return warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), s
, d
);
571 if (S_ISDIR (dst_stats
.st_mode
))
573 message (D_ERROR
, MSG_ERROR
, _("Cannot overwrite directory \"%s\""), d
);
578 if (confirm_overwrite
)
580 return_status
= query_replace (ctx
, d
, &src_stats
, &dst_stats
);
581 if (return_status
!= FILE_CONT
)
582 return return_status
;
584 /* Ok to overwrite */
589 if (S_ISLNK (src_stats
.st_mode
) && ctx
->stable_symlinks
)
591 return_status
= make_symlink (ctx
, s
, d
);
592 if (return_status
== FILE_CONT
)
593 goto retry_src_remove
;
595 return return_status
;
598 if (mc_rename (s
, d
) == 0)
599 return progress_update_one (tctx
, ctx
, src_stats
.st_size
, TRUE
);
602 /* Comparison to EXDEV seems not to work in nfs if you're moving from
603 one nfs to the same, but on the server it is on two different
604 filesystems. Then nfs returns EIO instead of EXDEV.
605 Hope it will not hurt if we always in case of error try to copy/delete. */
607 errno
= EXDEV
; /* Hack to copy (append) the file and then delete it */
611 return_status
= files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s
, d
);
612 if (return_status
== FILE_RETRY
)
614 return return_status
;
618 /* Failed because filesystem boundary -> copy the file instead */
619 old_ask_overwrite
= tctx
->ask_overwrite
;
620 tctx
->ask_overwrite
= FALSE
;
621 return_status
= copy_file_file (tctx
, ctx
, s
, d
);
622 tctx
->ask_overwrite
= old_ask_overwrite
;
623 if (return_status
!= FILE_CONT
)
624 return return_status
;
628 file_progress_show_source (ctx
, NULL
);
629 file_progress_show (ctx
, 0, 0, "", FALSE
);
631 return_status
= check_progress_buttons (ctx
);
632 if (return_status
!= FILE_CONT
)
633 return return_status
;
640 return_status
= file_error (_("Cannot remove file \"%s\"\n%s"), s
);
641 if (return_status
== FILE_RETRY
)
642 goto retry_src_remove
;
643 return return_status
;
647 return_status
= progress_update_one (tctx
, ctx
, src_stats
.st_size
, TRUE
);
649 return return_status
;
654 /* --------------------------------------------------------------------------------------------- */
655 /* {{{ Erase routines */
656 /** Don't update progress status if progress_count==NULL */
658 static FileProgressStatus
659 erase_file (FileOpTotalContext
* tctx
, FileOpContext
* ctx
, const char *s
,
660 gboolean is_toplevel_file
)
665 file_progress_show_deleting (ctx
, s
);
666 if (check_progress_buttons (ctx
) == FILE_ABORT
)
670 if (tctx
->progress_count
&& mc_lstat (s
, &buf
))
672 /* ignore, most likely the mc_unlink fails, too */
676 while (mc_unlink (s
))
678 return_status
= file_error (_("Cannot delete file \"%s\"\n%s"), s
);
679 if (return_status
!= FILE_RETRY
)
680 return return_status
;
683 if (tctx
->progress_count
)
684 return progress_update_one (tctx
, ctx
, buf
.st_size
, is_toplevel_file
);
689 /* --------------------------------------------------------------------------------------------- */
691 static FileProgressStatus
692 recursive_erase (FileOpTotalContext
* tctx
, FileOpContext
* ctx
, const char *s
)
698 FileProgressStatus return_status
= FILE_CONT
;
700 if (!strcmp (s
, ".."))
703 reading
= mc_opendir (s
);
708 while ((next
= mc_readdir (reading
)) && return_status
== FILE_CONT
)
710 if (!strcmp (next
->d_name
, "."))
712 if (!strcmp (next
->d_name
, ".."))
714 path
= concat_dir_and_file (s
, next
->d_name
);
715 if (mc_lstat (path
, &buf
))
718 mc_closedir (reading
);
721 if (S_ISDIR (buf
.st_mode
))
723 (recursive_erase (tctx
, ctx
, path
) != FILE_CONT
) ? FILE_RETRY
: FILE_CONT
;
725 return_status
= erase_file (tctx
, ctx
, path
, 0);
728 mc_closedir (reading
);
729 if (return_status
!= FILE_CONT
)
730 return return_status
;
731 file_progress_show_deleting (ctx
, s
);
732 if (check_progress_buttons (ctx
) == FILE_ABORT
)
738 return_status
= file_error (_("Cannot remove directory \"%s\"\n%s"), s
);
739 if (return_status
!= FILE_RETRY
)
740 return return_status
;
746 /* --------------------------------------------------------------------------------------------- */
747 /** Return -1 on error, 1 if there are no entries besides "." and ".."
748 in the directory path points to, 0 else. */
751 check_dir_is_empty (const char *path
)
757 dir
= mc_opendir (path
);
761 for (i
= 1, d
= mc_readdir (dir
); d
; d
= mc_readdir (dir
))
763 if (d
->d_name
[0] == '.' && (d
->d_name
[1] == '\0' ||
764 (d
->d_name
[1] == '.' && d
->d_name
[2] == '\0')))
765 continue; /* "." or ".." */
774 /* --------------------------------------------------------------------------------------------- */
776 static FileProgressStatus
777 erase_dir_iff_empty (FileOpContext
* ctx
, const char *s
)
779 FileProgressStatus error
;
781 if (strcmp (s
, "..") == 0)
784 if (strcmp (s
, ".") == 0)
787 file_progress_show_deleting (ctx
, s
);
788 if (check_progress_buttons (ctx
) == FILE_ABORT
)
792 if (1 != check_dir_is_empty (s
)) /* not empty or error */
797 error
= file_error (_("Cannot remove directory \"%s\"\n%s"), s
);
798 if (error
!= FILE_RETRY
)
807 /* --------------------------------------------------------------------------------------------- */
808 /* {{{ Panel operate routines */
811 * Return currently selected entry name or the name of the first marked
812 * entry if there is one.
816 panel_get_file (WPanel
* panel
, struct stat
*stat_buf
)
820 if (get_current_type () == view_tree
)
822 WTree
*tree
= (WTree
*) get_panel_widget (get_current_index ());
823 char *tree_name
= tree_selected_name (tree
);
825 mc_stat (tree_name
, stat_buf
);
831 for (i
= 0; i
< panel
->count
; i
++)
832 if (panel
->dir
.list
[i
].f
.marked
)
834 *stat_buf
= panel
->dir
.list
[i
].st
;
835 return panel
->dir
.list
[i
].fname
;
840 *stat_buf
= panel
->dir
.list
[panel
->selected
].st
;
841 return panel
->dir
.list
[panel
->selected
].fname
;
843 g_assert_not_reached ();
847 /* --------------------------------------------------------------------------------------------- */
849 * panel_compute_totals:
851 * compute the number of files and the number of bytes
852 * used up by the whole selection, recursing directories
853 * as required. In addition, it checks to see if it will
854 * overwrite any files by doing the copy.
857 static FileProgressStatus
858 panel_compute_totals (const WPanel
* panel
, const void *ui
,
859 compute_dir_size_callback cback
,
860 off_t
* ret_marked
, double *ret_total
, gboolean compute_symlinks
)
867 for (i
= 0; i
< panel
->count
; i
++)
871 if (!panel
->dir
.list
[i
].f
.marked
)
874 s
= &panel
->dir
.list
[i
].st
;
876 if (S_ISDIR (s
->st_mode
))
879 off_t subdir_count
= 0;
880 double subdir_bytes
= 0;
881 FileProgressStatus status
;
883 dir_name
= concat_dir_and_file (panel
->cwd
, panel
->dir
.list
[i
].fname
);
885 status
= compute_dir_size (dir_name
, ui
, cback
,
886 &subdir_count
, &subdir_bytes
, compute_symlinks
);
889 if (status
!= FILE_CONT
)
892 *ret_marked
+= subdir_count
;
893 *ret_total
+= subdir_bytes
;
898 *ret_total
+= s
->st_size
;
905 /* --------------------------------------------------------------------------------------------- */
906 /** Initialize variables for progress bars */
907 static FileProgressStatus
908 panel_operate_init_totals (FileOperation operation
,
909 const WPanel
* panel
, const char *source
, FileOpContext
* ctx
)
911 FileProgressStatus status
;
913 if (operation
!= OP_MOVE
&& verbose
&& file_op_compute_totals
)
915 ComputeDirSizeUI
*ui
;
917 ui
= compute_dir_size_create_ui ();
920 status
= compute_dir_size (source
, ui
, compute_dir_size_update_ui
,
921 &ctx
->progress_count
, &ctx
->progress_bytes
,
924 status
= panel_compute_totals (panel
, ui
, compute_dir_size_update_ui
,
925 &ctx
->progress_count
, &ctx
->progress_bytes
,
928 compute_dir_size_destroy_ui (ui
);
930 ctx
->progress_totals_computed
= (status
== FILE_CONT
);
935 ctx
->progress_count
= panel
->marked
;
936 ctx
->progress_bytes
= panel
->total
;
937 ctx
->progress_totals_computed
= FALSE
;
943 /* --------------------------------------------------------------------------------------------- */
945 * Generate user prompt for panel operation.
946 * single_source is the name if the source entry or NULL for multiple
948 * src_stat is only used when single_source is not NULL.
952 panel_operate_generate_prompt (const WPanel
* panel
, FileOperation operation
,
953 gboolean single_source
, const struct stat
*src_stat
)
956 char format_string
[BUF_MEDIUM
];
957 char *dp
= format_string
;
958 gboolean build_question
= FALSE
;
960 static gboolean i18n_flag
= FALSE
;
965 for (i
= sizeof (op_names1
) / sizeof (op_names1
[0]); i
--;)
966 op_names1
[i
] = Q_ (op_names1
[i
]);
969 for (i
= sizeof (prompt_parts
) / sizeof (prompt_parts
[0]); i
--;)
970 prompt_parts
[i
] = _(prompt_parts
[i
]);
972 one_format
= _(one_format
);
973 many_format
= _(many_format
);
974 question_format
= _(question_format
);
975 #endif /* ENABLE_NLS */
979 sp
= single_source
? one_format
: many_format
;
990 cp
= op_names1
[operation
];
993 if (operation
== OP_DELETE
)
996 build_question
= TRUE
;
999 cp
= prompt_parts
[5];
1002 if (operation
== OP_DELETE
)
1005 build_question
= TRUE
;
1008 cp
= prompt_parts
[6];
1012 cp
= S_ISDIR (src_stat
->st_mode
) ? prompt_parts
[2] : prompt_parts
[0];
1014 cp
= (panel
->marked
== panel
->dirs_marked
)
1016 : (panel
->dirs_marked
? prompt_parts
[4] : prompt_parts
[1]);
1037 char tmp
[BUF_MEDIUM
];
1039 memmove (tmp
, format_string
, sizeof (tmp
));
1040 g_snprintf (format_string
, sizeof (format_string
), question_format
, tmp
);
1043 return g_strdup (format_string
);
1046 /* --------------------------------------------------------------------------------------------- */
1048 #ifdef WITH_BACKGROUND
1050 end_bg_process (FileOpContext
* ctx
, enum OperationMode mode
)
1057 unregister_task_with_pid (pid
);
1058 /* file_op_context_destroy(ctx); */
1064 /* --------------------------------------------------------------------------------------------- */
1065 /* {{{ Query/status report routines */
1067 static FileProgressStatus
1068 real_do_file_error (enum OperationMode mode
, const char *error
)
1073 msg
= mode
== Foreground
? MSG_ERROR
: _("Background process error");
1074 result
= query_dialog (msg
, error
, D_ERROR
, 3, _("&Skip"), _("&Retry"), _("&Abort"));
1092 /* --------------------------------------------------------------------------------------------- */
1093 /** Report error with two files */
1095 static FileProgressStatus
1096 files_error (const char *format
, const char *file1
, const char *file2
)
1098 char buf
[BUF_MEDIUM
];
1099 char *nfile1
= g_strdup (path_trunc (file1
, 15));
1100 char *nfile2
= g_strdup (path_trunc (file2
, 15));
1102 g_snprintf (buf
, sizeof (buf
), format
, nfile1
, nfile2
, unix_error_string (errno
));
1107 return do_file_error (buf
);
1110 /* --------------------------------------------------------------------------------------------- */
1112 static FileProgressStatus
1113 real_query_recursive (FileOpContext
* ctx
, enum OperationMode mode
, const char *s
)
1117 if (ctx
->recursive_result
< RECURSIVE_ALWAYS
)
1119 const char *msg
= mode
== Foreground
1120 ? _("\nDirectory not empty.\nDelete it recursively?")
1121 : _("\nBackground process: Directory not empty.\nDelete it recursively?");
1122 text
= g_strconcat (_("Delete:"), " ", path_trunc (s
, 30), (char *) NULL
);
1127 ctx
->recursive_result
=
1128 (FileCopyMode
) query_dialog (text
, msg
, D_ERROR
, 5,
1129 _("&Yes"), _("&No"), _("A&ll"), _("Non&e"), _("&Abort"));
1131 if (ctx
->recursive_result
!= RECURSIVE_ABORT
)
1136 switch (ctx
->recursive_result
)
1139 case RECURSIVE_ALWAYS
:
1143 case RECURSIVE_NEVER
:
1146 case RECURSIVE_ABORT
:
1152 /* --------------------------------------------------------------------------------------------- */
1154 #ifdef WITH_BACKGROUND
1155 static FileProgressStatus
1156 do_file_error (const char *str
)
1161 FileProgressStatus (*f
) (enum OperationMode
, const char *);
1163 pntr
.f
= real_do_file_error
;
1165 if (we_are_background
)
1166 return parent_call (pntr
.p
, NULL
, 1, strlen (str
), str
);
1168 return real_do_file_error (Foreground
, str
);
1171 /* --------------------------------------------------------------------------------------------- */
1173 static FileProgressStatus
1174 query_recursive (FileOpContext
* ctx
, const char *s
)
1179 FileProgressStatus (*f
) (FileOpContext
*, enum OperationMode
, const char *);
1181 pntr
.f
= real_query_recursive
;
1183 if (we_are_background
)
1184 return parent_call (pntr
.p
, ctx
, 1, strlen (s
), s
);
1186 return real_query_recursive (ctx
, Foreground
, s
);
1189 /* --------------------------------------------------------------------------------------------- */
1191 static FileProgressStatus
1192 query_replace (FileOpContext
* ctx
, const char *destname
, struct stat
*_s_stat
,
1193 struct stat
*_d_stat
)
1198 FileProgressStatus (*f
) (FileOpContext
*, enum OperationMode
, const char *,
1199 struct stat
*, struct stat
*);
1201 pntr
.f
= file_progress_real_query_replace
;
1203 if (we_are_background
)
1204 return parent_call (pntr
.p
, ctx
, 3, strlen (destname
), destname
,
1205 sizeof (struct stat
), _s_stat
, sizeof (struct stat
), _d_stat
);
1207 return file_progress_real_query_replace (ctx
, Foreground
, destname
, _s_stat
, _d_stat
);
1211 /* --------------------------------------------------------------------------------------------- */
1213 static FileProgressStatus
1214 do_file_error (const char *str
)
1216 return real_do_file_error (Foreground
, str
);
1219 /* --------------------------------------------------------------------------------------------- */
1221 static FileProgressStatus
1222 query_recursive (FileOpContext
* ctx
, const char *s
)
1224 return real_query_recursive (ctx
, Foreground
, s
);
1227 /* --------------------------------------------------------------------------------------------- */
1229 static FileProgressStatus
1230 query_replace (FileOpContext
* ctx
, const char *destname
, struct stat
*_s_stat
,
1231 struct stat
*_d_stat
)
1233 return file_progress_real_query_replace (ctx
, Foreground
, destname
, _s_stat
, _d_stat
);
1236 #endif /* !WITH_BACKGROUND */
1239 /* --------------------------------------------------------------------------------------------- */
1240 /*** public functions ****************************************************************************/
1241 /* --------------------------------------------------------------------------------------------- */
1244 copy_file_file (FileOpTotalContext
* tctx
, FileOpContext
* ctx
,
1245 const char *src_path
, const char *dst_path
)
1247 uid_t src_uid
= (uid_t
) - 1;
1248 gid_t src_gid
= (gid_t
) - 1;
1250 int src_desc
, dest_desc
= -1;
1251 int n_read
, n_written
;
1252 mode_t src_mode
= 0; /* The mode of the source file */
1253 struct stat sb
, sb2
;
1255 gboolean dst_exists
= FALSE
, appending
= FALSE
;
1256 off_t n_read_total
= 0, file_size
= -1;
1257 FileProgressStatus return_status
, temp_status
;
1258 struct timeval tv_transfer_start
;
1259 dest_status_t dst_status
= DEST_NONE
;
1261 gboolean is_first_time
= TRUE
;
1263 /* FIXME: We should not be using global variables! */
1265 return_status
= FILE_RETRY
;
1267 file_progress_show_source (ctx
, src_path
);
1268 file_progress_show_target (ctx
, dst_path
);
1269 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1274 while (mc_stat (dst_path
, &sb2
) == 0)
1276 if (S_ISDIR (sb2
.st_mode
))
1278 return_status
= file_error (_("Cannot overwrite directory\"%s\"\n%s"), dst_path
);
1279 if (return_status
== FILE_RETRY
)
1281 return return_status
;
1287 while ((*ctx
->stat_func
) (src_path
, &sb
))
1289 return_status
= file_error (_("Cannot stat source file \"%s\"\n%s"), src_path
);
1290 if (return_status
!= FILE_RETRY
)
1291 return return_status
;
1296 /* Destination already exists */
1297 if (sb
.st_dev
== sb2
.st_dev
&& sb
.st_ino
== sb2
.st_ino
)
1298 return warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), src_path
, dst_path
);
1299 /* Should we replace destination? */
1300 if (tctx
->ask_overwrite
)
1303 return_status
= query_replace (ctx
, dst_path
, &sb
, &sb2
);
1304 if (return_status
!= FILE_CONT
)
1305 return return_status
;
1309 if (!ctx
->do_append
)
1311 /* Check the hardlinks */
1312 if (!ctx
->follow_links
&& sb
.st_nlink
> 1 && check_hardlinks (src_path
, dst_path
, &sb
) == 1)
1314 /* We have made a hardlink - no more processing is necessary */
1318 if (S_ISLNK (sb
.st_mode
))
1319 return make_symlink (ctx
, src_path
, dst_path
);
1321 if (S_ISCHR (sb
.st_mode
) || S_ISBLK (sb
.st_mode
) ||
1322 S_ISFIFO (sb
.st_mode
) || S_ISNAM (sb
.st_mode
) || S_ISSOCK (sb
.st_mode
))
1324 while (mc_mknod (dst_path
, sb
.st_mode
& ctx
->umask_kill
, sb
.st_rdev
) < 0)
1326 return_status
= file_error (_("Cannot create special file \"%s\"\n%s"), dst_path
);
1327 if (return_status
== FILE_RETRY
)
1329 return return_status
;
1333 while (ctx
->preserve_uidgid
&& mc_chown (dst_path
, sb
.st_uid
, sb
.st_gid
))
1335 temp_status
= file_error (_("Cannot chown target file \"%s\"\n%s"), dst_path
);
1336 if (temp_status
== FILE_RETRY
)
1340 while (ctx
->preserve
&& mc_chmod (dst_path
, sb
.st_mode
& ctx
->umask_kill
))
1342 temp_status
= file_error (_("Cannot chmod target file \"%s\"\n%s"), dst_path
);
1343 if (temp_status
== FILE_RETRY
)
1351 gettimeofday (&tv_transfer_start
, (struct timezone
*) NULL
);
1353 while ((src_desc
= mc_open (src_path
, O_RDONLY
| O_LINEAR
)) < 0)
1355 return_status
= file_error (_("Cannot open source file \"%s\"\n%s"), src_path
);
1356 if (return_status
== FILE_RETRY
)
1359 return return_status
;
1362 if (ctx
->do_reget
!= 0)
1364 if (mc_lseek (src_desc
, ctx
->do_reget
, SEEK_SET
) != ctx
->do_reget
)
1366 message (D_ERROR
, _("Warning"), _("Reget failed, about to overwrite file"));
1368 ctx
->do_append
= FALSE
;
1372 while (mc_fstat (src_desc
, &sb
))
1374 return_status
= file_error (_("Cannot fstat source file \"%s\"\n%s"), src_path
);
1375 if (return_status
== FILE_RETRY
)
1377 ctx
->do_append
= FALSE
;
1380 src_mode
= sb
.st_mode
;
1381 src_uid
= sb
.st_uid
;
1382 src_gid
= sb
.st_gid
;
1383 utb
.actime
= sb
.st_atime
;
1384 utb
.modtime
= sb
.st_mtime
;
1385 file_size
= sb
.st_size
;
1387 open_flags
= O_WRONLY
;
1390 if (ctx
->do_append
!= 0)
1391 open_flags
|= O_APPEND
;
1393 open_flags
|= O_CREAT
| O_TRUNC
;
1397 open_flags
|= O_CREAT
| O_EXCL
;
1400 while ((dest_desc
= mc_open (dst_path
, open_flags
, src_mode
)) < 0)
1402 if (errno
== EEXIST
)
1405 return_status
= file_error (_("Cannot create target file \"%s\"\n%s"), dst_path
);
1406 if (return_status
== FILE_RETRY
)
1408 ctx
->do_append
= FALSE
;
1411 dst_status
= DEST_SHORT
; /* file opened, but not fully copied */
1413 appending
= ctx
->do_append
;
1414 ctx
->do_append
= FALSE
;
1416 /* Find out the optimal buffer size. */
1417 while (mc_fstat (dest_desc
, &sb
))
1419 return_status
= file_error (_("Cannot fstat target file \"%s\"\n%s"), dst_path
);
1420 if (return_status
== FILE_RETRY
)
1425 ctx
->eta_secs
= 0.0;
1428 if (tctx
->bps
== 0 || (file_size
/ (tctx
->bps
)) > FILEOP_UPDATE_INTERVAL
)
1429 file_progress_show (ctx
, 0, file_size
, "", TRUE
);
1431 file_progress_show (ctx
, 1, 1, "", TRUE
);
1432 return_status
= check_progress_buttons (ctx
);
1435 if (return_status
!= FILE_CONT
)
1439 struct timeval tv_current
, tv_last_update
, tv_last_input
;
1440 int secs
, update_secs
;
1441 const char *stalled_msg
= "";
1443 tv_last_update
= tv_transfer_start
;
1450 if (mc_ctl (src_desc
, VFS_CTL_IS_NOTREADY
, 0))
1453 while ((n_read
= mc_read (src_desc
, buf
, sizeof (buf
))) < 0)
1455 return_status
= file_error (_("Cannot read source file\"%s\"\n%s"), src_path
);
1456 if (return_status
== FILE_RETRY
)
1463 gettimeofday (&tv_current
, NULL
);
1468 n_read_total
+= n_read
;
1470 /* Windows NT ftp servers report that files have no
1471 * permissions: -------, so if we happen to have actually
1472 * read something, we should fix the permissions.
1474 if ((src_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
)) == 0)
1475 src_mode
= S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
;
1476 gettimeofday (&tv_last_input
, NULL
);
1479 while ((n_written
= mc_write (dest_desc
, t
, n_read
)) < n_read
)
1483 n_read
-= n_written
;
1487 return_status
= file_error (_("Cannot write target file \"%s\"\n%s"), dst_path
);
1488 if (return_status
!= FILE_RETRY
)
1492 secs
= (tv_current
.tv_sec
- tv_last_update
.tv_sec
);
1493 update_secs
= (tv_current
.tv_sec
- tv_last_input
.tv_sec
);
1495 if (is_first_time
|| secs
> FILEOP_UPDATE_INTERVAL
)
1497 copy_file_file_display_progress (tctx
, ctx
,
1499 tv_transfer_start
, file_size
, n_read_total
);
1500 tv_last_update
= tv_current
;
1502 is_first_time
= FALSE
;
1504 if (update_secs
> FILEOP_STALLING_INTERVAL
)
1506 stalled_msg
= _("(stalled)");
1510 gboolean force_update
=
1511 (tv_current
.tv_sec
- tctx
->transfer_start
.tv_sec
) > FILEOP_UPDATE_INTERVAL
;
1512 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
1513 file_progress_show_total (tctx
, ctx
,
1514 tctx
->progress_bytes
+ n_read_total
+ ctx
->do_reget
,
1518 file_progress_show (ctx
, n_read_total
+ ctx
->do_reget
, file_size
, stalled_msg
,
1523 return_status
= check_progress_buttons (ctx
);
1525 if (return_status
!= FILE_CONT
)
1533 dst_status
= DEST_FULL
; /* copy successful, don't remove target file */
1536 while (src_desc
!= -1 && mc_close (src_desc
) < 0)
1538 temp_status
= file_error (_("Cannot close source file \"%s\"\n%s"), src_path
);
1539 if (temp_status
== FILE_RETRY
)
1541 if (temp_status
== FILE_ABORT
)
1542 return_status
= temp_status
;
1546 while (dest_desc
!= -1 && mc_close (dest_desc
) < 0)
1548 temp_status
= file_error (_("Cannot close target file \"%s\"\n%s"), dst_path
);
1549 if (temp_status
== FILE_RETRY
)
1551 return_status
= temp_status
;
1555 if (dst_status
== DEST_SHORT
)
1557 /* Remove short file */
1559 result
= query_dialog (Q_ ("DialogTitle|Copy"),
1560 _("Incomplete file was retrieved. Keep it?"),
1561 D_ERROR
, 2, _("&Delete"), _("&Keep"));
1563 mc_unlink (dst_path
);
1565 else if (dst_status
== DEST_FULL
)
1567 /* Copy has succeeded */
1568 if (!appending
&& ctx
->preserve_uidgid
)
1570 while (mc_chown (dst_path
, src_uid
, src_gid
))
1572 temp_status
= file_error (_("Cannot chown target file \"%s\"\n%s"), dst_path
);
1573 if (temp_status
== FILE_RETRY
)
1575 return_status
= temp_status
;
1584 while (mc_chmod (dst_path
, (src_mode
& ctx
->umask_kill
)))
1586 temp_status
= file_error (_("Cannot chmod target file \"%s\"\n%s"), dst_path
);
1587 if (temp_status
!= FILE_RETRY
)
1589 return_status
= temp_status
;
1596 src_mode
= umask (-1);
1598 src_mode
= 0100666 & ~src_mode
;
1599 mc_chmod (dst_path
, (src_mode
& ctx
->umask_kill
));
1601 mc_utime (dst_path
, &utb
);
1605 if (return_status
== FILE_CONT
)
1606 return_status
= progress_update_one (tctx
, ctx
, file_size
, tctx
->is_toplevel_file
);
1608 return return_status
;
1611 /* --------------------------------------------------------------------------------------------- */
1613 * I think these copy_*_* functions should have a return type.
1614 * anyway, this function *must* have two directories as arguments.
1616 /* FIXME: This function needs to check the return values of the
1620 copy_dir_dir (FileOpTotalContext
* tctx
, FileOpContext
* ctx
, const char *s
, const char *_d
,
1621 gboolean toplevel
, gboolean move_over
, gboolean do_delete
, struct link
* parent_dirs
)
1623 struct dirent
*next
;
1624 struct stat buf
, cbuf
;
1626 char *dest_dir
= NULL
;
1627 FileProgressStatus return_status
= FILE_CONT
;
1634 /* First get the mode of the source dir */
1636 if ((*ctx
->stat_func
) (s
, &cbuf
))
1638 return_status
= file_error (_("Cannot stat source directory \"%s\"\n%s"), s
);
1639 if (return_status
== FILE_RETRY
)
1640 goto retry_src_stat
;
1644 if (is_in_linklist (dest_dirs
, s
, &cbuf
))
1646 /* Don't copy a directory we created before (we don't want to copy
1647 infinitely if a directory is copied into itself) */
1648 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
1649 return_status
= FILE_CONT
;
1653 /* Hmm, hardlink to directory??? - Norbert */
1654 /* FIXME: In this step we should do something
1655 in case the destination already exist */
1656 /* Check the hardlinks */
1657 if (ctx
->preserve
&& cbuf
.st_nlink
> 1 && check_hardlinks (s
, d
, &cbuf
) == 1)
1659 /* We have made a hardlink - no more processing is necessary */
1663 if (!S_ISDIR (cbuf
.st_mode
))
1665 return_status
= file_error (_("Source \"%s\" is not a directory\n%s"), s
);
1666 if (return_status
== FILE_RETRY
)
1667 goto retry_src_stat
;
1671 if (is_in_linklist (parent_dirs
, s
, &cbuf
))
1673 /* we found a cyclic symbolic link */
1674 message (D_ERROR
, MSG_ERROR
, _("Cannot copy cyclic symbolic link\n\"%s\""), s
);
1675 return_status
= FILE_SKIP
;
1679 lp
= g_new (struct link
, 1);
1680 lp
->vfs
= vfs_get_class (s
);
1681 lp
->ino
= cbuf
.st_ino
;
1682 lp
->dev
= cbuf
.st_dev
;
1683 lp
->next
= parent_dirs
;
1687 /* Now, check if the dest dir exists, if not, create it. */
1688 if (mc_stat (d
, &buf
))
1690 /* Here the dir doesn't exist : make it ! */
1693 if (mc_rename (s
, d
) == 0)
1695 return_status
= FILE_CONT
;
1705 * If the destination directory exists, we want to copy the whole
1706 * directory, but we only want this to happen once.
1708 * Escape sequences added to the * to compiler warnings.
1709 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
1710 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
1712 if (!S_ISDIR (buf
.st_mode
))
1714 return_status
= file_error (_("Destination \"%s\" must be a directory\n%s"), d
);
1715 if (return_status
== FILE_RETRY
)
1716 goto retry_dst_stat
;
1719 /* Dive into subdir if exists */
1720 if (toplevel
&& ctx
->dive_into_subdirs
)
1722 dest_dir
= concat_dir_and_file (d
, x_basename (s
));
1731 while (my_mkdir (dest_dir
, (cbuf
.st_mode
& ctx
->umask_kill
) | S_IRWXU
))
1733 return_status
= file_error (_("Cannot create target directory \"%s\"\n%s"), dest_dir
);
1734 if (return_status
!= FILE_RETRY
)
1738 lp
= g_new (struct link
, 1);
1739 mc_stat (dest_dir
, &buf
);
1740 lp
->vfs
= vfs_get_class (dest_dir
);
1741 lp
->ino
= buf
.st_ino
;
1742 lp
->dev
= buf
.st_dev
;
1743 lp
->next
= dest_dirs
;
1746 if (ctx
->preserve_uidgid
)
1748 while (mc_chown (dest_dir
, cbuf
.st_uid
, cbuf
.st_gid
))
1750 return_status
= file_error (_("Cannot chown target directory \"%s\"\n%s"), dest_dir
);
1751 if (return_status
!= FILE_RETRY
)
1757 /* open the source dir for reading */
1758 reading
= mc_opendir (s
);
1759 if (reading
== NULL
)
1762 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
)
1766 * Now, we don't want '.' and '..' to be created / copied at any time
1768 if (!strcmp (next
->d_name
, "."))
1770 if (!strcmp (next
->d_name
, ".."))
1773 /* get the filename and add it to the src directory */
1774 path
= concat_dir_and_file (s
, next
->d_name
);
1776 (*ctx
->stat_func
) (path
, &buf
);
1777 if (S_ISDIR (buf
.st_mode
))
1781 mdpath
= concat_dir_and_file (dest_dir
, next
->d_name
);
1783 * From here, we just intend to recursively copy subdirs, not
1784 * the double functionality of copying different when the target
1785 * dir already exists. So, we give the recursive call the flag 0
1786 * meaning no toplevel.
1789 copy_dir_dir (tctx
, ctx
, path
, mdpath
, FALSE
, FALSE
, do_delete
, parent_dirs
);
1796 dest_file
= concat_dir_and_file (dest_dir
, x_basename (path
));
1797 return_status
= copy_file_file (tctx
, ctx
, path
, dest_file
);
1800 if (do_delete
&& return_status
== FILE_CONT
)
1802 if (ctx
->erase_at_end
)
1804 static struct link
*tail
;
1805 size_t len
= strlen (path
);
1806 lp
= g_malloc (sizeof (struct link
) + len
);
1807 strncpy (lp
->name
, path
, len
+ 1);
1808 lp
->st_mode
= buf
.st_mode
;
1810 if (erase_list
!= NULL
)
1816 erase_list
= tail
= lp
;
1820 if (S_ISDIR (buf
.st_mode
))
1822 return_status
= erase_dir_iff_empty (ctx
, path
);
1825 return_status
= erase_file (tctx
, ctx
, path
, FALSE
);
1830 mc_closedir (reading
);
1834 mc_chmod (dest_dir
, cbuf
.st_mode
& ctx
->umask_kill
);
1835 utb
.actime
= cbuf
.st_atime
;
1836 utb
.modtime
= cbuf
.st_mtime
;
1837 mc_utime (dest_dir
, &utb
);
1841 cbuf
.st_mode
= umask (-1);
1842 umask (cbuf
.st_mode
);
1843 cbuf
.st_mode
= 0100777 & ~cbuf
.st_mode
;
1844 mc_chmod (dest_dir
, cbuf
.st_mode
& ctx
->umask_kill
);
1849 g_free (parent_dirs
);
1852 return return_status
;
1857 /* --------------------------------------------------------------------------------------------- */
1858 /* {{{ Move routines */
1861 move_dir_dir (FileOpTotalContext
* tctx
, FileOpContext
* ctx
, const char *s
, const char *d
)
1863 struct stat sbuf
, dbuf
, destbuf
;
1866 FileProgressStatus return_status
;
1867 gboolean move_over
= FALSE
;
1870 file_progress_show_source (ctx
, s
);
1871 file_progress_show_target (ctx
, d
);
1872 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1878 dstat_ok
= (mc_stat (d
, &dbuf
) == 0);
1880 if (dstat_ok
&& sbuf
.st_dev
== dbuf
.st_dev
&& sbuf
.st_ino
== dbuf
.st_ino
)
1881 return warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), s
, d
);
1884 destdir
= g_strdup (d
); /* destination doesn't exist */
1885 else if (!ctx
->dive_into_subdirs
)
1887 destdir
= g_strdup (d
);
1891 destdir
= concat_dir_and_file (d
, x_basename (s
));
1893 /* Check if the user inputted an existing dir */
1895 if (!mc_stat (destdir
, &destbuf
))
1899 return_status
= copy_dir_dir (tctx
, ctx
, s
, destdir
, FALSE
, TRUE
, TRUE
, NULL
);
1901 if (return_status
!= FILE_CONT
)
1907 if (S_ISDIR (destbuf
.st_mode
))
1908 return_status
= file_error (_("Cannot overwrite directory \"%s\"\n%s"), destdir
);
1910 return_status
= file_error (_("Cannot overwrite file \"%s\"\n%s"), destdir
);
1911 if (return_status
== FILE_RETRY
)
1912 goto retry_dst_stat
;
1915 return return_status
;
1919 if (mc_rename (s
, destdir
) == 0)
1921 return_status
= FILE_CONT
;
1927 return_status
= files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s
, d
);
1928 if (return_status
== FILE_RETRY
)
1932 /* Failed because of filesystem boundary -> copy dir instead */
1933 return_status
= copy_dir_dir (tctx
, ctx
, s
, destdir
, FALSE
, FALSE
, TRUE
, NULL
);
1935 if (return_status
!= FILE_CONT
)
1938 file_progress_show_source (ctx
, NULL
);
1939 file_progress_show (ctx
, 0, 0, "", FALSE
);
1941 return_status
= check_progress_buttons (ctx
);
1942 if (return_status
!= FILE_CONT
)
1946 if (ctx
->erase_at_end
)
1948 for (; erase_list
&& return_status
!= FILE_ABORT
;)
1950 if (S_ISDIR (erase_list
->st_mode
))
1952 return_status
= erase_dir_iff_empty (ctx
, erase_list
->name
);
1955 return_status
= erase_file (tctx
, ctx
, erase_list
->name
, FALSE
);
1957 erase_list
= erase_list
->next
;
1961 erase_dir_iff_empty (ctx
, s
);
1968 erase_list
= erase_list
->next
;
1971 return return_status
;
1976 /* --------------------------------------------------------------------------------------------- */
1977 /* {{{ Erase routines */
1980 erase_dir (FileOpTotalContext
* tctx
, FileOpContext
* ctx
, const char *s
)
1982 FileProgressStatus error
;
1984 if (strcmp (s
, "..") == 0)
1987 if (strcmp (s
, ".") == 0)
1990 file_progress_show_deleting (ctx
, s
);
1991 if (check_progress_buttons (ctx
) == FILE_ABORT
)
1995 /* The old way to detect a non empty directory was:
1996 error = my_rmdir (s);
1997 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1998 For the linux user space nfs server (nfs-server-2.2beta29-2)
1999 we would have to check also for EIO. I hope the new way is
2000 fool proof. (Norbert)
2002 error
= check_dir_is_empty (s
);
2005 error
= query_recursive (ctx
, s
);
2006 if (error
== FILE_CONT
)
2007 return recursive_erase (tctx
, ctx
, s
);
2012 while (my_rmdir (s
) == -1)
2014 error
= file_error (_("Cannot remove directory \"%s\"\n%s"), s
);
2015 if (error
!= FILE_RETRY
)
2024 /* --------------------------------------------------------------------------------------------- */
2025 /* {{{ Panel operate routines */
2028 compute_dir_size_create_ui (void)
2030 ComputeDirSizeUI
*ui
;
2032 const char *b_name
= N_("&Abort");
2038 ui
= g_new (ComputeDirSizeUI
, 1);
2040 ui
->dlg
= create_dlg (TRUE
, 0, 0, 8, COLS
/ 2, dialog_colors
, NULL
,
2041 NULL
, _("Directory scanning"), DLG_CENTER
);
2042 ui
->dirname
= label_new (3, 3, "");
2043 add_widget (ui
->dlg
, ui
->dirname
);
2045 add_widget (ui
->dlg
,
2046 button_new (5, (ui
->dlg
->cols
- strlen (b_name
)) / 2,
2047 FILE_ABORT
, NORMAL_BUTTON
, b_name
, NULL
));
2049 /* We will manage the dialog without any help,
2050 that's why we have to call init_dlg */
2056 /* --------------------------------------------------------------------------------------------- */
2059 compute_dir_size_destroy_ui (ComputeDirSizeUI
* ui
)
2063 /* schedule to update passive panel */
2064 other_panel
->dirty
= 1;
2066 /* close and destroy dialog */
2067 dlg_run_done (ui
->dlg
);
2068 destroy_dlg (ui
->dlg
);
2073 /* --------------------------------------------------------------------------------------------- */
2076 compute_dir_size_update_ui (const void *ui
, const char *dirname
)
2078 const ComputeDirSizeUI
*this = (const ComputeDirSizeUI
*) ui
;
2085 label_set_text (this->dirname
, str_trunc (dirname
, this->dlg
->cols
- 6));
2087 event
.x
= -1; /* Don't show the GPM cursor */
2088 c
= tty_get_event (&event
, FALSE
, FALSE
);
2092 /* Reinitialize to avoid old values after events other than
2093 selecting a button */
2094 this->dlg
->ret_value
= FILE_CONT
;
2096 dlg_process_event (this->dlg
, c
, &event
);
2098 switch (this->dlg
->ret_value
)
2108 /* --------------------------------------------------------------------------------------------- */
2112 * Computes the number of bytes used by the files in a directory
2116 compute_dir_size (const char *dirname
, const void *ui
,
2117 compute_dir_size_callback cback
,
2118 off_t
* ret_marked
, double *ret_total
, gboolean compute_symlinks
)
2123 struct dirent
*dirent
;
2124 FileProgressStatus ret
= FILE_CONT
;
2126 if (!compute_symlinks
)
2128 res
= mc_lstat (dirname
, &s
);
2132 /* don't scan symlink to directory */
2133 if (S_ISLNK (s
.st_mode
))
2136 *ret_total
+= s
.st_size
;
2141 dir
= mc_opendir (dirname
);
2146 while ((dirent
= mc_readdir (dir
)) != NULL
)
2150 ret
= (cback
!= NULL
) ? cback (ui
, dirname
) : FILE_CONT
;
2152 if (ret
!= FILE_CONT
)
2155 if (strcmp (dirent
->d_name
, ".") == 0)
2157 if (strcmp (dirent
->d_name
, "..") == 0)
2160 fullname
= concat_dir_and_file (dirname
, dirent
->d_name
);
2161 res
= mc_lstat (fullname
, &s
);
2169 if (S_ISDIR (s
.st_mode
))
2171 off_t subdir_count
= 0;
2172 double subdir_bytes
= 0;
2175 compute_dir_size (fullname
, ui
, cback
, &subdir_count
, &subdir_bytes
,
2178 if (ret
!= FILE_CONT
)
2184 *ret_marked
+= subdir_count
;
2185 *ret_total
+= subdir_bytes
;
2190 *ret_total
+= s
.st_size
;
2201 /* --------------------------------------------------------------------------------------------- */
2205 * Performs one of the operations on the selection on the source_panel
2206 * (copy, delete, move).
2208 * Returns TRUE if did change the directory
2209 * structure, Returns FALSE if user aborted
2211 * force_single forces operation on the current entry and affects
2212 * default destination. Current filename is used as default.
2216 panel_operate (void *source_panel
, FileOperation operation
, gboolean force_single
)
2218 WPanel
*panel
= (WPanel
*) source_panel
;
2219 const gboolean single_entry
= force_single
|| (panel
->marked
<= 1)
2220 || (get_current_type () == view_tree
);
2222 char *source
= NULL
;
2223 #ifdef WITH_FULL_PATHS
2224 char *source_with_path
= NULL
;
2226 #define source_with_path source
2227 #endif /* !WITH_FULL_PATHS */
2230 char *save_cwd
= NULL
, *save_dest
= NULL
;
2231 struct stat src_stat
;
2232 gboolean ret_val
= TRUE
;
2234 FileProgressStatus value
;
2236 FileOpTotalContext
*tctx
;
2238 gboolean do_bg
= FALSE
; /* do background operation? */
2240 static gboolean i18n_flag
= FALSE
;
2243 for (i
= sizeof (op_names1
) / sizeof (op_names1
[0]); i
--;)
2244 op_names
[i
] = Q_ (op_names
[i
]);
2248 free_linklist (&linklist
);
2249 free_linklist (&dest_dirs
);
2251 /* Update panel contents to avoid actions on deleted files */
2252 if (!panel
->is_panelized
)
2254 update_panels (UP_RELOAD
, UP_KEEPSEL
);
2262 source
= selection (panel
)->fname
;
2263 src_stat
= selection (panel
)->st
;
2266 source
= panel_get_file (panel
, &src_stat
);
2268 if (!strcmp (source
, ".."))
2270 message (D_ERROR
, MSG_ERROR
, _("Cannot operate on \"..\"!"));
2275 ctx
= file_op_context_new (operation
);
2276 tctx
= file_op_total_context_new ();
2277 gettimeofday (&(tctx
->transfer_start
), (struct timezone
*) NULL
);
2279 /* Show confirmation dialog */
2280 if (operation
!= OP_DELETE
)
2286 /* Forced single operations default to the original name */
2289 else if (get_other_type () == view_listing
)
2290 dest_dir
= other_panel
->cwd
;
2292 dest_dir
= panel
->cwd
;
2294 * Add trailing backslash only when do non-local ops.
2295 * It saves user from occasional file renames (when destination
2298 if (!force_single
&& dest_dir
[0] != '\0' && dest_dir
[strlen (dest_dir
) - 1] != PATH_SEP
)
2300 /* add trailing separator */
2301 dest_dir_
= g_strconcat (dest_dir
, PATH_SEP_STR
, (char *) NULL
);
2306 dest_dir_
= g_strdup (dest_dir
);
2308 if (dest_dir_
== NULL
)
2310 file_op_total_context_destroy (tctx
);
2311 file_op_context_destroy (ctx
);
2315 /* Generate confirmation prompt */
2316 format
= panel_operate_generate_prompt (panel
, operation
, source
!= NULL
, &src_stat
);
2318 dest
= file_mask_dialog (ctx
, operation
, source
!= NULL
, format
,
2319 source
!= NULL
? (void *) source
2320 : (void *) &panel
->marked
, dest_dir_
, &do_bg
);
2325 if (dest
== NULL
|| dest
[0] == '\0')
2327 file_op_total_context_destroy (tctx
);
2328 file_op_context_destroy (ctx
);
2333 else if (confirm_delete
)
2336 char fmd_buf
[BUF_MEDIUM
];
2338 /* Generate confirmation prompt */
2339 format
= panel_operate_generate_prompt (panel
, OP_DELETE
, source
!= NULL
, &src_stat
);
2342 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, panel
->marked
);
2345 const int fmd_xlen
= 64;
2346 i
= fmd_xlen
- str_term_width1 (format
) - 4;
2347 g_snprintf (fmd_buf
, sizeof (fmd_buf
), format
, str_trunc (source
, i
));
2355 i
= query_dialog (op_names
[operation
], fmd_buf
, D_ERROR
, 2, _("&Yes"), _("&No"));
2359 file_op_total_context_destroy (tctx
);
2360 file_op_context_destroy (ctx
);
2366 filegui_dialog_type_t dialog_type
;
2368 if (operation
== OP_DELETE
)
2369 dialog_type
= FILEGUI_DIALOG_DELETE_ITEM
;
2372 dialog_type
= !((operation
!= OP_COPY
) || (single_entry
) || (force_single
))
2373 ? FILEGUI_DIALOG_MULTI_ITEM
: FILEGUI_DIALOG_ONE_ITEM
;
2375 if ((single_entry
) && (operation
== OP_COPY
) && S_ISDIR (selection (panel
)->st
.st_mode
))
2376 dialog_type
= FILEGUI_DIALOG_MULTI_ITEM
;
2379 /* Background also need ctx->ui, but not full */
2381 file_op_context_create_ui_without_init (ctx
, 1, dialog_type
);
2383 file_op_context_create_ui (ctx
, 1, dialog_type
);
2386 #ifdef WITH_BACKGROUND
2387 /* Did the user select to do a background operation? */
2392 v
= do_background (ctx
, g_strconcat (op_names
[operation
], ": ", panel
->cwd
, (char *) NULL
));
2394 message (D_ERROR
, MSG_ERROR
, _("Sorry, I could not put the job in background"));
2396 /* If we are the parent */
2399 mc_setctl (panel
->cwd
, VFS_SETCTL_FORGET
, NULL
);
2400 mc_setctl (dest
, VFS_SETCTL_FORGET
, NULL
);
2401 /* file_op_context_destroy (ctx); */
2405 #endif /* WITH_BACKGROUND */
2407 /* Initialize things */
2408 /* We do not want to trash cache every time file is
2409 created/touched. However, this will make our cache contain
2411 if ((dest
!= NULL
) && (mc_setctl (dest
, VFS_SETCTL_STALE_DATA
, (void *) 1)))
2412 save_dest
= g_strdup (dest
);
2414 if ((panel
->cwd
[0] != '\0') && (mc_setctl (panel
->cwd
, VFS_SETCTL_STALE_DATA
, (void *) 1)))
2415 save_cwd
= g_strdup (panel
->cwd
);
2417 /* Now, let's do the job */
2419 /* This code is only called by the tree and panel code */
2422 /* We now have ETA in all cases */
2424 /* One file: FIXME mc_chdir will take user out of any vfs */
2425 if ((operation
!= OP_COPY
) && (get_current_type () == view_tree
) &&
2426 (mc_chdir (PATH_SEP_STR
) < 0))
2432 /* The source and src_stat variables have been initialized before */
2433 #ifdef WITH_FULL_PATHS
2434 source_with_path
= concat_dir_and_file (panel
->cwd
, source
);
2435 #endif /* WITH_FULL_PATHS */
2437 if (panel_operate_init_totals (operation
, panel
, source_with_path
, ctx
) == FILE_CONT
)
2439 if (operation
== OP_DELETE
)
2441 if (S_ISDIR (src_stat
.st_mode
))
2442 value
= erase_dir (tctx
, ctx
, source_with_path
);
2444 value
= erase_file (tctx
, ctx
, source_with_path
, 1);
2448 temp
= transform_source (ctx
, source_with_path
);
2450 value
= transform_error
;
2453 char *repl_dest
, *temp2
;
2455 repl_dest
= mc_search_prepare_replace_str2 (ctx
->search_handle
, dest
);
2456 temp2
= concat_dir_and_file (repl_dest
, temp
);
2465 /* we use file_mask_op_follow_links only with OP_COPY */
2466 ctx
->stat_func (source_with_path
, &src_stat
);
2468 if (S_ISDIR (src_stat
.st_mode
))
2469 value
= copy_dir_dir (tctx
, ctx
, source_with_path
, dest
,
2470 TRUE
, FALSE
, FALSE
, NULL
);
2472 value
= copy_file_file (tctx
, ctx
, source_with_path
, dest
);
2476 if (S_ISDIR (src_stat
.st_mode
))
2477 value
= move_dir_dir (tctx
, ctx
, source_with_path
, dest
);
2479 value
= move_file_file (tctx
, ctx
, source_with_path
, dest
);
2483 /* Unknown file operation */
2487 } /* Copy or move operation */
2489 if ((value
== FILE_CONT
) && !force_single
)
2490 unmark_files (panel
);
2497 /* Check destination for copy or move operation */
2498 while (operation
!= OP_DELETE
)
2501 struct stat dst_stat
;
2503 dst_result
= mc_stat (dest
, &dst_stat
);
2505 if ((dst_result
!= 0) || S_ISDIR (dst_stat
.st_mode
))
2508 if (file_error (_("Destination \"%s\" must be a directory\n%s"), dest
) != FILE_RETRY
)
2512 if (panel_operate_init_totals (operation
, panel
, NULL
, ctx
) == FILE_CONT
)
2514 /* Loop for every file, perform the actual copy operation */
2515 for (i
= 0; i
< panel
->count
; i
++)
2517 if (!panel
->dir
.list
[i
].f
.marked
)
2518 continue; /* Skip the unmarked ones */
2520 source
= panel
->dir
.list
[i
].fname
;
2521 src_stat
= panel
->dir
.list
[i
].st
;
2523 #ifdef WITH_FULL_PATHS
2524 g_free (source_with_path
);
2525 source_with_path
= concat_dir_and_file (panel
->cwd
, source
);
2526 #endif /* WITH_FULL_PATHS */
2528 if (operation
== OP_DELETE
)
2530 if (S_ISDIR (src_stat
.st_mode
))
2531 value
= erase_dir (tctx
, ctx
, source_with_path
);
2533 value
= erase_file (tctx
, ctx
, source_with_path
, 1);
2537 temp
= transform_source (ctx
, source_with_path
);
2540 value
= transform_error
;
2543 char *temp2
, *temp3
, *repl_dest
;
2545 repl_dest
= mc_search_prepare_replace_str2 (ctx
->search_handle
, dest
);
2546 temp2
= concat_dir_and_file (repl_dest
, temp
);
2549 temp3
= source_with_path
;
2550 source_with_path
= strutils_shell_unescape (source_with_path
);
2553 temp2
= strutils_shell_unescape (temp2
);
2559 /* we use file_mask_op_follow_links only with OP_COPY */
2560 ctx
->stat_func (source_with_path
, &src_stat
);
2561 if (S_ISDIR (src_stat
.st_mode
))
2562 value
= copy_dir_dir (tctx
, ctx
, source_with_path
, temp2
,
2563 TRUE
, FALSE
, FALSE
, NULL
);
2565 value
= copy_file_file (tctx
, ctx
, source_with_path
, temp2
);
2566 free_linklist (&dest_dirs
);
2570 if (S_ISDIR (src_stat
.st_mode
))
2571 value
= move_dir_dir (tctx
, ctx
, source_with_path
, temp2
);
2573 value
= move_file_file (tctx
, ctx
, source_with_path
, temp2
);
2577 /* Unknown file operation */
2583 } /* Copy or move operation */
2585 if (value
== FILE_ABORT
)
2588 if (value
== FILE_CONT
)
2589 do_file_mark (panel
, i
, 0);
2591 file_progress_show_count (ctx
, tctx
->progress_count
, ctx
->progress_count
);
2595 file_progress_show_total (tctx
, ctx
, tctx
->progress_bytes
, FALSE
);
2597 if (operation
!= OP_DELETE
)
2598 file_progress_show (ctx
, 0, 0, "", FALSE
);
2601 if (check_progress_buttons (ctx
) == FILE_ABORT
)
2605 } /* Loop for every file */
2607 } /* Many entries */
2611 if (save_cwd
!= NULL
)
2613 mc_setctl (save_cwd
, VFS_SETCTL_STALE_DATA
, NULL
);
2617 if (save_dest
!= NULL
)
2619 mc_setctl (save_dest
, VFS_SETCTL_STALE_DATA
, NULL
);
2623 free_linklist (&linklist
);
2624 free_linklist (&dest_dirs
);
2625 #ifdef WITH_FULL_PATHS
2626 g_free (source_with_path
);
2627 #endif /* WITH_FULL_PATHS */
2629 g_free (ctx
->dest_mask
);
2630 ctx
->dest_mask
= NULL
;
2632 #ifdef WITH_BACKGROUND
2633 /* Let our parent know we are saying bye bye */
2634 if (we_are_background
)
2636 int cur_pid
= getpid ();
2637 /* Send pid to parent with child context, it is fork and
2638 don't modify real parent ctx */
2640 parent_call ((void *) end_bg_process
, ctx
, 0);
2645 #endif /* WITH_BACKGROUND */
2647 file_op_context_destroy (ctx
);
2648 file_op_total_context_destroy (tctx
);
2655 /* --------------------------------------------------------------------------------------------- */
2656 /* {{{ Query/status report routines */
2657 /** Report error with one file */
2659 file_error (const char *format
, const char *file
)
2661 char buf
[BUF_MEDIUM
];
2663 g_snprintf (buf
, sizeof (buf
), format
, path_trunc (file
, 30), unix_error_string (errno
));
2665 return do_file_error (buf
);
2668 /* --------------------------------------------------------------------------------------------- */
2671 Cause emacs to enter folding mode for this file: