3 /* File managing. Important notes on this file:
5 About the use of dialogs in this file:
6 If you want to add a new dialog box (or call a routine that pops
7 up a dialog box), you have to provide a wrapper for background
8 operations (ie, background operations have to up-call to the parent
11 For example, instead of using the message() routine, in this
12 file, you should use one of the stubs that call message with the
13 proper number of arguments (ie, message_1s, message_2s and so on).
15 Actually, that is a rule that should be followed by any routines
16 that may be called from this module.
21 Copyright (C) 1994, 1995, 1996 The Free Software Foundation
23 Written by: 1994, 1995 Janne Kukonlehto
24 1994, 1995 Fred Leeflang
25 1994, 1995, 1996 Miguel de Icaza
26 1995, 1996 Jakub Jelinek
30 The copy code was based in GNU's cp, and was written by:
31 Torbjorn Granlund, David MacKenzie, and Jim Meyering.
33 The move code was based in GNU's mv, and was written by:
34 Mike Parker and David MacKenzie.
36 Janne Kukonlehto added much error recovery to them for being used
37 in an interactive program.
39 This program is free software; you can redistribute it and/or modify
40 it under the terms of the GNU General Public License as published by
41 the Free Software Foundation; either version 2 of the License, or
42 (at your option) any later version.
44 This program is distributed in the hope that it will be useful,
45 but WITHOUT ANY WARRANTY; without even the implied warranty of
46 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
47 GNU General Public License for more details.
49 You should have received a copy of the GNU General Public License
50 along with this program; if not, write to the Free Software
51 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
55 /* {{{ Include files */
58 /* Hack: the vfs code should not rely on this */
59 #define WITH_FULL_PATHS 1
61 #include <sys/types.h>
77 #include <sys/param.h>
80 # include <sys/timeb.h> /* alex: for struct timeb, used in time.h */
81 #endif /* SCO_FLAVOR */
83 #include <sys/time.h___>
93 /* Needed by query_replace */
99 #include "main.h" /* WANT_WIDGETS-> we get the the_hint def */
104 #include "background.h"
106 /* Needed for current_panel, other_panel and WTree */
111 #include "../vfs/vfs.h"
117 #if USE_VFS && USE_NETCODE
125 /* rcsid [] = "$Id: file.c,v 1.2 2003/06/27 21:10:35 gvg Exp $" */
128 /* Recursive operation on subdirectories */
129 int dive_into_subdirs
= 0;
131 /* When moving directories cross filesystem boundaries delete the successfull
132 copied files when all files below the directory and its subdirectories
134 If erase_at_end is zero files will be deleted immediately after their
135 successful copy (Note: this behaviour is not tested and at the moment
136 it can't be changed at runtime) */
137 int erase_at_end
= 1;
139 /* Preserve the original files' owner, group, permissions, and
140 timestamps (owner, group only as root). */
143 /* The value of the "preserve Attributes" checkbox in the copy file dialog.
144 We can't use the value of "preserve" because it can change in order to
145 preserve file attributs when moving files across filesystem boundaries
146 (we want to keep the value of the checkbox between copy operations). */
149 /* If running as root, preserve the original uid/gid
150 (we don't want to try chwon for non root)
151 preserve_uidgid = preserve && uid == 0 */
152 int preserve_uidgid
= 0;
154 /* The bits to preserve in created files' modes on file copy */
155 int umask_kill
= 0777777;
157 /* If on, it gets a little scrict with dangerous operations */
158 int know_not_what_am_i_doing
= 0;
160 int stable_symlinks
= 0;
162 /* The next two are not static, since they are used on background.c */
163 /* Controls appending to files, shared with filequery.c */
166 /* With ETA on we have extra screen space */
169 /* result from the recursive query */
170 int recursive_result
;
172 /* The estimated time of arrival in seconds */
175 /* Used to save the hint line */
176 static int last_hint_line
;
178 /* mapping operations into names */
179 char *operation_names
[] = { "Copy", "Move", "Delete" };
181 /* This is a hard link cache */
192 /* the hard link cache */
193 struct link
*linklist
= NULL
;
195 /* the files-to-be-erased list */
196 struct link
*erase_list
;
198 /* In copy_dir_dir we use two additional single linked lists: The first -
199 variable name `parent_dirs' - holds information about already copied
200 directories and is used to detect cyclic symbolic links.
201 The second (`dest_dirs' below) holds information about just created
202 target directories and is used to detect when an directory is copied
203 into itself (we don't want to copy infinitly).
204 Both lists don't use the linkcount and name structure members of struct
206 struct link
*dest_dirs
= 0;
208 struct re_pattern_buffer rx
;
209 struct re_registers regs
;
210 static char *dest_mask
= NULL
;
212 /* To symlinks the difference between `follow Links' checked and not
213 checked is the stat call used (mc_stat resp. mc_lstat) */
214 int (*xstat
)(char *, struct stat
*) = mc_lstat
;
216 static int op_follow_links
= 0;
218 /* File operate window sizes */
222 #define WX_ETA_EXTRA 12
224 #define FCOPY_GAUGE_X 14
225 #define FCOPY_LABEL_X 5
227 /* Used for button result values */
229 REPLACE_YES
= B_USER
,
248 /* Pointer to the operate dialog */
249 static Dlg_head
*op_dlg
;
252 unsigned long bps
= 0, bps_time
= 0;
254 static char *op_names
[] = { N_(" Copy "), N_(" Move "), N_(" Delete ") };
255 static int selected_button
;
256 static int last_percentage
[3];
258 /* Replace dialog: color set, descriptor and filename */
259 static int replace_colors
[4];
260 static Dlg_head
*replace_dlg
;
261 static char *replace_filename
;
262 static int replace_result
;
264 static struct stat
*s_stat
, *d_stat
;
266 static int recursive_erase (char *s
);
267 static int erase_file (char *s
);
269 /* Describe the components in the panel operations window */
270 static WLabel
*FileLabel
[2];
271 static WLabel
*FileString
[2];
272 static WLabel
*ProgressLabel
[3];
273 static WGauge
*ProgressGauge
[3];
274 static WLabel
*eta_label
;
275 static WLabel
*bps_label
;
276 static WLabel
*stalled_label
;
280 /* {{{ File progress display routines */
289 c
= get_event (&event
, 0, 0);
292 dlg_process_event (op_dlg
, c
, &event
);
293 switch (op_dlg
->ret_value
) {
321 xv_dispatch_something ();
325 #endif /* HAVE_XVIEW */
337 if (op_dlg
->ret_value
== B_CANCEL
)
340 return op_dlg
->ret_value
;
342 #endif /* HAVE_GNOME */
347 op_win_callback (struct Dlg_head
*h
, int id
, int msg
)
352 attrset (COLOR_NORMAL
);
354 draw_box (h
, 1, 2, h
->lines
-2, h
->cols
-4);
362 create_op_win (int op
, int with_eta
)
365 int minus
= verbose
? 0 : 3;
366 int eta_offset
= with_eta
? (WX_ETA_EXTRA
) / 2 : 0;
376 recursive_result
= 0;
377 showing_eta
= with_eta
;
378 showing_bps
= with_eta
;
379 eta_extra
= with_eta
? WX_ETA_EXTRA
: 0;
380 x_size
= (WX
+ 4) + eta_extra
;
382 op_dlg
= create_dlg (0, 0, WY
-minus
+4, x_size
, dialog_colors
,
383 op_win_callback
, "", "opwin", DLG_CENTER
);
386 last_hint_line
= the_hint
->widget
.y
;
387 if ((op_dlg
->y
+ op_dlg
->lines
) > last_hint_line
)
388 the_hint
->widget
.y
= op_dlg
->y
+ op_dlg
->lines
+1;
391 x_set_dialog_title (op_dlg
, "");
393 tk_new_frame (op_dlg
, "b.");
394 add_widgetl (op_dlg
, button_new (BY
-minus
, WX
- 19 + eta_offset
, FILE_ABORT
,
395 NORMAL_BUTTON
, _("&Abort"), 0, 0, "abort"),
397 add_widgetl (op_dlg
, button_new (BY
-minus
, 14 + eta_offset
, FILE_SKIP
,
398 NORMAL_BUTTON
, _("&Skip"), 0, 0, "skip"),
401 tk_new_frame (op_dlg
, "2.");
402 add_widgetl (op_dlg
, ProgressGauge
[2] = gauge_new (7, FCOPY_GAUGE_X
, 0, 100, 0, "g-1"),
404 add_widgetl (op_dlg
, ProgressLabel
[2] = label_new (7, FCOPY_LABEL_X
, fifteen
, "l-1"),
406 add_widgetl (op_dlg
, bps_label
= label_new (7, WX
, "", "bps-label"), XV_WLAY_NEXTROW
);
408 tk_new_frame (op_dlg
, "1.");
409 add_widgetl (op_dlg
, ProgressGauge
[1] = gauge_new (8, FCOPY_GAUGE_X
, 0, 100, 0, "g-2"),
411 add_widgetl (op_dlg
, ProgressLabel
[1] = label_new (8, FCOPY_LABEL_X
, fifteen
, "l-2"),
413 add_widgetl (op_dlg
, stalled_label
= label_new (8, WX
, "", "stalled"), XV_WLAY_NEXTROW
);
415 tk_new_frame (op_dlg
, "0.");
416 add_widgetl (op_dlg
, ProgressGauge
[0] = gauge_new (6, FCOPY_GAUGE_X
, 0, 100, 0, "g-3"),
418 add_widgetl (op_dlg
, ProgressLabel
[0] = label_new (6, FCOPY_LABEL_X
, fifteen
, "l-3"),
420 add_widgetl (op_dlg
, eta_label
= label_new (6, WX
, "", "eta_label"), XV_WLAY_NEXTROW
);
422 tk_new_frame (op_dlg
, "f1.");
423 add_widgetl (op_dlg
, FileString
[1] = label_new (4, FCOPY_GAUGE_X
, sixty
, "fs-l-1"),
425 add_widgetl (op_dlg
, FileLabel
[1] = label_new (4, FCOPY_LABEL_X
, fifteen
, "fs-l-2"),
427 tk_new_frame (op_dlg
, "f0.");
428 add_widgetl (op_dlg
, FileString
[0] = label_new (3, FCOPY_GAUGE_X
, sixty
, "fs-x-1"),
430 add_widgetl (op_dlg
, FileLabel
[0] = label_new (3, FCOPY_LABEL_X
, fifteen
, "fs-x-2"),
433 /* We will manage the dialog without any help, that's why
434 we have to call init_dlg */
437 selected_button
= FILE_SKIP
;
438 for (i
= 0; i
< 3; i
++)
439 last_percentage
[i
] = -99;
443 destroy_op_win (void)
446 xtoolkit_kill_dialog (op_dlg
);
448 dlg_run_done (op_dlg
);
449 destroy_dlg (op_dlg
);
451 the_hint
->widget
.y
= last_hint_line
;
459 label_set_text (ProgressLabel
[n
], "");
460 gauge_show (ProgressGauge
[n
], 0);
462 return check_buttons ();
466 #define truncFileString(s) name_trunc (s, eta_extra + 47)
468 #define truncFileString(s) s
472 show_source (char *s
)
476 #ifdef WITH_FULL_PATHS
477 int i
= strlen (cpanel
->cwd
);
479 /* We remove the full path we have added before */
480 if (!strncmp (s
, cpanel
->cwd
, i
)){
481 if (s
[i
] == PATH_SEP
)
484 #endif /* WITH_FULL_PATHS */
486 label_set_text (FileLabel
[0], _("Source"));
487 label_set_text (FileString
[0], truncFileString (s
));
488 return check_buttons ();
490 label_set_text (FileLabel
[0], "");
491 label_set_text (FileString
[0], "");
492 return check_buttons ();
497 show_target (char *s
)
500 label_set_text (FileLabel
[1], _("Target"));
501 label_set_text (FileString
[1], truncFileString (s
));
502 return check_buttons ();
504 label_set_text (FileLabel
[1], "");
505 label_set_text (FileString
[1], "");
506 return check_buttons ();
511 show_deleting (char *s
)
513 label_set_text (FileLabel
[0], _("Deleting"));
514 label_set_text (FileString
[0], truncFileString (s
));
515 return check_buttons ();
519 show_bar (int n
, long done
, long total
)
521 gauge_set_value (ProgressGauge
[n
], (int) total
, (int) done
);
522 gauge_show (ProgressGauge
[n
], 1);
523 return check_buttons ();
529 int eta_hours
, eta_mins
, eta_s
;
530 char eta_buffer
[30];
535 eta_hours
= eta_secs
/ (60 * 60);
536 eta_mins
= (eta_secs
- (eta_hours
* 60 * 60)) / 60;
537 eta_s
= eta_secs
- ((eta_hours
* 60 * 60) + eta_mins
* 60 );
538 sprintf (eta_buffer
, "ETA %d:%02d.%02d", eta_hours
, eta_mins
, eta_s
);
539 label_set_text (eta_label
, eta_buffer
);
545 char bps_buffer
[30];
551 if (bps
> 1024*1024){
552 sprintf (bps_buffer
, "%.2f MBS", bps
/ (1024*1024.0));
554 sprintf (bps_buffer
, "%.2f KBS", bps
/ 1024.0);
556 sprintf (bps_buffer
, "%ld BPS", bps
);
557 label_set_text (bps_label
, bps_buffer
);
561 show_file_progress (long done
, long total
)
564 return check_buttons ();
566 label_set_text (ProgressLabel
[0], _("File"));
569 return show_bar (0, done
, total
);
571 return show_no_bar (0);
575 show_count_progress (long done
, long total
)
578 return check_buttons ();
580 label_set_text (ProgressLabel
[1], _("Count"));
581 return show_bar (1, done
, total
);
583 return show_no_bar (1);
587 show_bytes_progress (long done
, long total
)
590 return check_buttons ();
592 label_set_text (ProgressLabel
[2], _("Bytes"));
593 return show_bar (2, done
, total
);
595 return show_no_bar (2);
601 /* {{{ Copy routines */
603 enum CaseConvs
{ NO_CONV
=0, UP_CHAR
=1, LOW_CHAR
=2, UP_SECT
=4, LOW_SECT
=8 };
606 convert_case (int c
, enum CaseConvs
*conversion
)
608 if (*conversion
& UP_CHAR
){
609 *conversion
&= ~UP_CHAR
;
611 } else if (*conversion
& LOW_CHAR
){
612 *conversion
&= ~LOW_CHAR
;
614 } else if (*conversion
& UP_SECT
){
616 } else if (*conversion
& LOW_SECT
){
622 static int transform_error
= 0;
624 do_transform_source (char *source
)
627 char *fnsource
= x_basename (source
);
629 enum CaseConvs case_conv
= NO_CONV
;
630 static char fntarget
[MC_MAXPATHLEN
];
632 len
= strlen (fnsource
);
633 j
= re_match (&rx
, fnsource
, len
, 0, ®s
);
635 transform_error
= FILE_SKIP
;
638 for (next_reg
= 1, j
= 0, k
= 0; j
< strlen (dest_mask
); j
++) {
639 switch (dest_mask
[j
]) {
642 if (! isdigit (dest_mask
[j
])){
643 /* Backslash followed by non-digit */
644 switch (dest_mask
[j
]){
646 case_conv
|= UP_SECT
;
647 case_conv
&= ~LOW_SECT
;
650 case_conv
|= UP_CHAR
;
653 case_conv
|= LOW_SECT
;
654 case_conv
&= ~UP_SECT
;
657 case_conv
|= LOW_CHAR
;
663 /* Backslash as quote mark */
664 fntarget
[k
++] = convert_case (dest_mask
[j
], &case_conv
);
668 /* Backslash followed by digit */
669 next_reg
= dest_mask
[j
] - '0';
674 if (next_reg
< 0 || next_reg
>= RE_NREGS
675 || regs
.start
[next_reg
] < 0) {
676 message_1s (1, MSG_ERROR
, _(" Invalid target mask "));
677 transform_error
= FILE_ABORT
;
680 for (l
= regs
.start
[next_reg
]; l
< regs
.end
[next_reg
]; l
++)
681 fntarget
[k
++] = convert_case (fnsource
[l
], &case_conv
);
686 fntarget
[k
++] = convert_case (dest_mask
[j
], &case_conv
);
695 transform_source (char *source
)
697 char *s
= strdup (source
);
700 /* We remove \n from the filename since regex routines would use \n as an anchor */
701 /* this is just to be allowed to maniupulate file names with \n on it */
702 for (q
= s
; *q
; q
++){
706 q
= do_transform_source (s
);
712 free_linklist (struct link
**linklist
)
714 struct link
*lp
, *lp2
;
716 for (lp
= *linklist
; lp
!= NULL
; lp
= lp2
){
725 is_in_linklist (struct link
*lp
, char *path
, struct stat
*sb
)
727 ino_t ino
= sb
->st_ino
;
728 dev_t dev
= sb
->st_dev
;
729 vfs
*vfs
= vfs_type (path
);
732 if (lp
->vfs
== vfs
&& lp
->ino
== ino
&& lp
->dev
== dev
)
740 is_in_linklist (struct link
*lp
, char *path
, struct stat
*sb
)
742 ino_t ino
= sb
->st_ino
;
743 dev_t dev
= sb
->st_dev
;
746 if (lp
->ino
== ino
&& lp
->dev
== dev
)
754 /* Returns 0 if the inode wasn't found in the cache and 1 if it was found
755 and a hardlink was succesfully made */
757 check_hardlinks (char *src_name
, char *dst_name
, struct stat
*pstat
)
760 vfs
*my_vfs
= vfs_type (src_name
);
761 ino_t ino
= pstat
->st_ino
;
762 dev_t dev
= pstat
->st_dev
;
763 struct stat link_stat
;
766 if (vfs_file_is_ftp (src_name
))
768 for (lp
= linklist
; lp
!= NULL
; lp
= lp
-> next
)
769 if (lp
->vfs
== my_vfs
&& lp
->ino
== ino
&& lp
->dev
== dev
){
770 if (!mc_stat (lp
->name
, &link_stat
) && link_stat
.st_ino
== ino
&&
771 link_stat
.st_dev
== dev
&& vfs_type (lp
->name
) == my_vfs
){
772 p
= strchr (lp
->name
, 0) + 1; /* i.e. where the `name' file
774 if (vfs_type (dst_name
) == vfs_type (p
)){
775 if (!mc_stat (p
, &link_stat
)){
776 if (!mc_link (p
, dst_name
))
781 /* FIXME: Announce we couldn't make the hardlink */
784 lp
= (struct link
*) xmalloc (sizeof (struct link
) + strlen (src_name
)
785 + strlen (dst_name
) + 1, "Hardlink cache");
790 strcpy (lp
->name
, src_name
);
791 p
= strchr (lp
->name
, 0) + 1;
792 strcpy (p
, dst_name
);
799 /* Duplicate the contents of the symbolic link src_path in dst_path.
800 Try to make a stable symlink if the option "stable symlink" was
801 set in the file mask dialog.
802 If dst_path is an existing symlink it will be deleted silently
803 (upper levels take already care of existing files at dst_path).
806 make_symlink (char *src_path
, char *dst_path
)
808 char link_target
[MC_MAXPATHLEN
];
814 if (mc_lstat (dst_path
, &sb
) == 0 && S_ISLNK (sb
.st_mode
))
820 len
= mc_readlink (src_path
, link_target
, MC_MAXPATHLEN
);
822 return_status
= file_error
823 (_(" Cannot read source link \"%s\" \n %s "), src_path
);
824 if (return_status
== FILE_RETRY
)
825 goto retry_src_readlink
;
826 return return_status
;
828 link_target
[len
] = 0;
830 if (stable_symlinks
&& (!vfs_file_is_local (src_path
) ||
831 !vfs_file_is_local (dst_path
))) {
832 message_1s (1, MSG_ERROR
, _(" Cannot make stable symlinks across "
833 "non-local filesystems: \n\n"
834 " Option Stable Symlinks will be disabled "));
838 if (stable_symlinks
&& *link_target
!= PATH_SEP
) {
841 p
= strdup (src_path
);
842 r
= strrchr (p
, PATH_SEP
);
845 if (*dst_path
== PATH_SEP
)
846 q
= strdup (dst_path
);
848 q
= copy_strings (p
, dst_path
, 0);
849 r
= strrchr (q
, PATH_SEP
);
852 s
= copy_strings (p
, link_target
, NULL
);
853 strcpy (link_target
, s
);
855 s
= diff_two_paths (q
, link_target
);
857 strcpy (link_target
, s
);
866 if (mc_symlink (link_target
, dst_path
) == 0)
870 * if dst_exists, it is obvious that this had failed.
871 * We can delete the old symlink and try again...
873 if (dst_is_symlink
) {
874 if (!mc_unlink (dst_path
))
875 if (mc_symlink (link_target
, dst_path
) == 0)
879 return_status
= file_error
880 (_(" Cannot create target symlink \"%s\" \n %s "), dst_path
);
881 if (return_status
== FILE_RETRY
)
882 goto retry_dst_symlink
;
883 return return_status
;
888 copy_file_file (char *src_path
, char *dst_path
, int ask_overwrite
)
895 int buf_size
= 8*1024;
900 int src_mode
; /* The mode of the source file */
904 long n_read_total
= 0;
906 int return_status
, temp_status
;
907 int do_remote_copy
= 0;
909 /* bitmask used to remember which resourses we should release on return
910 A single goto label is much easier to handle than a bunch of gotos ;-). */
911 unsigned resources
= 0;
913 return_status
= FILE_RETRY
;
915 if (show_source (src_path
) == FILE_ABORT
916 || show_target (dst_path
) == FILE_ABORT
)
921 if (mc_stat (dst_path
, &sb2
) == 0){
922 if (S_ISDIR (sb2
.st_mode
)){
923 return_status
= file_error (_(" Cannot overwrite directory \"%s\" \n %s "),
925 if (return_status
== FILE_RETRY
)
927 return return_status
;
933 if ((*xstat
)(src_path
, &sb
)){
934 return_status
= file_error (_(" Cannot stat source file \"%s\" \n %s "),
936 if (return_status
== FILE_RETRY
)
937 goto retry_src_xstat
;
938 return return_status
;
942 /* .ado: For OS/2 or NT: no st_ino exists, it is better to just try to
943 * overwrite the target file
946 /* Destination already exists */
947 if (sb
.st_dev
== sb2
.st_dev
&& sb
.st_ino
== sb2
.st_ino
){
948 message_3s (1, MSG_ERROR
, _(" `%s' and `%s' are the same file. "),
955 /* Should we replace destination? */
957 if (vfs_file_is_ftp (src_path
))
962 return_status
= query_replace (dst_path
, &sb
, &sb2
);
963 if (return_status
!= FILE_CONT
)
964 return return_status
;
969 /* .ado: OS2 and NT don't have hardlinks */
971 /* Check the hardlinks */
972 if (!op_follow_links
&& sb
.st_nlink
> 1 &&
973 check_hardlinks (src_path
, dst_path
, &sb
) == 1) {
974 /* We have made a hardlink - no more processing is necessary */
975 return return_status
;
978 if (S_ISLNK (sb
.st_mode
))
979 return make_symlink (src_path
, dst_path
);
983 if (S_ISCHR (sb
.st_mode
) || S_ISBLK (sb
.st_mode
) || S_ISFIFO (sb
.st_mode
)
984 || S_ISSOCK (sb
.st_mode
)){
986 if (mc_mknod (dst_path
, sb
.st_mode
& umask_kill
, sb
.st_rdev
) < 0){
987 return_status
= file_error
988 (_(" Cannot create special file \"%s\" \n %s "), dst_path
);
989 if (return_status
== FILE_RETRY
)
991 return return_status
;
997 if (preserve_uidgid
&& mc_chown (dst_path
, sb
.st_uid
, sb
.st_gid
)){
998 temp_status
= file_error
999 (_(" Cannot chown target file \"%s\" \n %s "), dst_path
);
1000 if (temp_status
== FILE_RETRY
)
1001 goto retry_mknod_uidgid
;
1007 if (preserve
&& mc_chmod (dst_path
, sb
.st_mode
& umask_kill
) < 0){
1008 temp_status
= file_error (_(" Cannnot chmod target file \"%s\" \n %s "), dst_path
);
1009 if (temp_status
== FILE_RETRY
)
1010 goto retry_mknod_chmod
;
1018 if (!do_append
&& !vfs_file_is_local (src_path
) && vfs_file_is_local (dst_path
)){
1019 mc_setctl (src_path
, MCCTL_SETREMOTECOPY
, dst_path
);
1022 if ((source_desc
= mc_open (src_path
, O_RDONLY
)) < 0){
1023 return_status
= file_error
1024 (_(" Cannot open source file \"%s\" \n %s "), src_path
);
1025 if (return_status
== FILE_RETRY
)
1026 goto retry_src_open
;
1028 return return_status
;
1032 do_remote_copy
= mc_ctl (source_desc
, MCCTL_ISREMOTECOPY
, 0);
1034 if (!do_remote_copy
) {
1036 if (mc_fstat (source_desc
, &sb
)){
1037 return_status
= file_error
1038 (_(" Cannot fstat source file \"%s\" \n %s "), src_path
);
1039 if (return_status
== FILE_RETRY
)
1040 goto retry_src_fstat
;
1045 /* Im not sure if we can delete this. sb is already filled by
1046 (*xstat)() - Norbert. */
1049 if (mc_stat (src_path
, &sb
)){
1050 return_status
= file_error
1051 (_(" Cannot stat source file \"%s\" \n %s "), src_path
);
1052 if (return_status
== FILE_RETRY
)
1053 goto retry_src_rstat
;
1059 src_mode
= sb
.st_mode
;
1061 src_uid
= sb
.st_uid
;
1062 src_gid
= sb
.st_gid
;
1064 utb
.actime
= sb
.st_atime
;
1065 utb
.modtime
= sb
.st_mtime
;
1066 file_size
= sb
.st_size
;
1068 /* Create the new regular file with small permissions initially,
1069 do not create a security hole. */
1071 if (!do_remote_copy
) {
1074 (dest_desc
= mc_open (dst_path
, O_WRONLY
| O_APPEND
)) < 0) ||
1076 (dest_desc
= mc_open (dst_path
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600)) < 0)) {
1077 return_status
= file_error
1078 (_(" Cannot create target file \"%s\" \n %s "), dst_path
);
1079 if (return_status
== FILE_RETRY
)
1080 goto retry_dst_open
;
1084 resources
|= 2; /* dst_path exists/dst_path opened */
1085 resources
|= 4; /* remove short file */
1087 appending
= do_append
;
1090 if (!do_remote_copy
) {
1092 /* Find out the optimal buffer size. */
1093 if (mc_fstat (dest_desc
, &sb
)){
1094 return_status
= file_error
1095 (_(" Cannot fstat target file \"%s\" \n %s "), dst_path
);
1096 if (return_status
== FILE_RETRY
)
1097 goto retry_dst_fstat
;
1102 buf
= (char *) xmalloc (buf_size
, "copy_file_file");
1105 return_status
= show_file_progress (0, file_size
);
1107 if (return_status
!= FILE_CONT
)
1110 if (!do_remote_copy
){
1113 n_read
= mc_read (source_desc
, buf
, buf_size
);
1115 return_status
= file_error
1116 (_(" Cannot read source file \"%s\" \n %s "), src_path
);
1117 if (return_status
== FILE_RETRY
)
1118 goto retry_src_read
;
1124 n_read_total
+= n_read
;
1127 n_written
= mc_write (dest_desc
, buf
, n_read
);
1128 if (n_written
< n_read
){
1129 return_status
= file_error
1130 (_(" Cannot write target file \"%s\" \n %s "), dst_path
);
1131 if (return_status
== FILE_RETRY
)
1132 goto retry_dst_write
;
1135 return_status
= show_file_progress (n_read_total
, file_size
);
1137 if (return_status
!= FILE_CONT
)
1141 struct timeval tv_current
;
1142 struct timeval tv_transfer_start
;
1143 struct timeval tv_last_update
;
1144 struct timeval tv_last_input
;
1145 int i
, size
, secs
, update_secs
;
1149 gettimeofday (&tv_transfer_start
, (struct timezone
*) NULL
);
1150 tv_last_update
= tv_transfer_start
;
1154 switch (size
= mc_ctl (source_desc
, MCCTL_REMOTECOPYCHUNK
, 8192)) {
1155 case MCERR_TARGETOPEN
:
1156 message_1s (1, MSG_ERROR
, _(" Can't open target file "));
1161 message_1s (1, MSG_ERROR
, _(" Can't write to local target file "));
1163 case MCERR_DATA_ON_STDIN
:
1171 /* the first time we reach this line the target file has been created
1172 or truncated and we actually have a short target file.
1173 Do we really want to delete the target file when the ftp transfer
1174 fails? If we don't delete it we would be able to use reget later.
1176 resources
|= 4; /* remove short file */
1178 if (i
&& size
!= MCERR_DATA_ON_STDIN
){
1179 n_read_total
+= size
;
1181 /* Windows NT ftp servers report that files have no
1182 * permissions: -------, so if we happen to have actually
1183 * read something, we should fix the permissions.
1186 ((S_IRUSR
|S_IWUSR
|S_IXUSR
) /* user */
1187 |(S_IXOTH
|S_IWOTH
|S_IROTH
) /* other */
1188 |(S_IXGRP
|S_IWGRP
|S_IRGRP
)))) /* group */
1189 src_mode
= S_IRUSR
|S_IWUSR
|S_IROTH
|S_IRGRP
;
1191 gettimeofday (&tv_last_input
, NULL
);
1193 /* Timed operations: */
1194 gettimeofday (&tv_current
, NULL
);
1196 /* 1. Update rotating dash after some time (hardcoded to 2 seconds) */
1197 secs
= (tv_current
.tv_sec
- tv_last_update
.tv_sec
);
1200 tv_last_update
= tv_current
;
1203 /* 2. Check for a stalled condition */
1204 update_secs
= (tv_current
.tv_sec
- tv_last_input
.tv_sec
);
1206 if (update_secs
> 4){
1207 stalled_msg
= _("(stalled)");
1210 /* 3. Compute ETA */
1211 if (secs
> 2 || eta_secs
== 0.0){
1212 dt
= (tv_current
.tv_sec
- tv_transfer_start
.tv_sec
);
1215 eta_secs
= ((dt
/ (double) n_read_total
) * file_size
) - dt
;
1216 bps
= n_read_total
/ ((dt
< 1) ? 1 : dt
);
1221 /* 4. Compute BPS rate */
1223 bps_time
= (tv_current
.tv_sec
- tv_transfer_start
.tv_sec
);
1226 bps
= n_read_total
/ bps_time
;
1229 label_set_text (stalled_label
, stalled_msg
);
1231 return_status
= show_file_progress (n_read_total
, file_size
);
1233 if (return_status
!= FILE_CONT
)
1237 resources
&= ~4; /* copy successful, don't remove target file */
1244 if ((resources
& 1) && mc_close (source_desc
) < 0){
1245 temp_status
= file_error
1246 (_(" Cannot close source file \"%s\" \n %s "), src_path
);
1247 if (temp_status
== FILE_RETRY
)
1248 goto retry_src_close
;
1249 if (temp_status
== FILE_ABORT
)
1250 return_status
= temp_status
;
1254 if ((resources
& 2) && mc_close (dest_desc
) < 0){
1255 temp_status
= file_error
1256 (_(" Cannot close target file \"%s\" \n %s "), dst_path
);
1257 if (temp_status
== FILE_RETRY
)
1258 goto retry_dst_close
;
1259 return_status
= temp_status
;
1262 if (resources
& 4) {
1263 /* Remove short file */
1264 mc_unlink (dst_path
);
1265 if (do_remote_copy
) {
1266 mc_ctl (source_desc
, MCCTL_FINISHREMOTE
, -1);
1268 } else if (resources
& (2|8)) {
1269 /* no short file and destination file exists */
1271 if (!appending
&& preserve_uidgid
) {
1273 if (mc_chown (dst_path
, src_uid
, src_gid
)){
1274 temp_status
= file_error
1275 (_(" Cannot chown target file \"%s\" \n %s "), dst_path
);
1276 if (temp_status
== FILE_RETRY
)
1277 goto retry_dst_chown
;
1278 return_status
= temp_status
;
1283 /* .ado: according to the XPG4 standard, the file must be closed before
1284 * chmod can be invoked
1287 if (!appending
&& mc_chmod (dst_path
, src_mode
& umask_kill
)){
1288 temp_status
= file_error
1289 (_(" Cannot chmod target file \"%s\" \n %s "), dst_path
);
1290 if (temp_status
== FILE_RETRY
)
1291 goto retry_dst_chmod
;
1292 return_status
= temp_status
;
1295 if (!appending
&& preserve
)
1296 mc_utime (dst_path
, &utb
);
1298 return return_status
;
1302 * I think these copy_*_* functions should have a return type.
1303 * anyway, this function *must* have two directories as arguments.
1305 /* FIXME: This function needs to check the return values of the
1308 copy_dir_dir (char *s
, char *d
, int toplevel
, int move_over
, int delete,
1309 struct link
*parent_dirs
)
1314 struct dirent
*next
;
1316 struct stat buf
, cbuf
;
1318 char *path
, *mdpath
, *dest_file
, *dest_dir
;
1319 int return_status
= FILE_CONT
;
1323 /* First get the mode of the source dir */
1325 if ((*xstat
) (s
, &cbuf
)){
1326 return_status
= file_error (_(" Cannot stat source directory \"%s\" \n %s "), s
);
1327 if (return_status
== FILE_RETRY
)
1328 goto retry_src_stat
;
1329 return return_status
;
1332 if (is_in_linklist (dest_dirs
, s
, &cbuf
)) {
1333 /* Don't copy a directory we created before (we don't want to copy
1334 infinitely if a directory is copied into itself) */
1335 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
1339 /* Hmm, hardlink to directory??? - Norbert */
1340 /* FIXME: In this step we should do something
1341 in case the destination already exist */
1342 /* Check the hardlinks */
1343 if (preserve
&& cbuf
.st_nlink
> 1 && check_hardlinks (s
, d
, &cbuf
) == 1) {
1344 /* We have made a hardlink - no more processing is necessary */
1345 return return_status
;
1348 if (!S_ISDIR (cbuf
.st_mode
)){
1349 return_status
= file_error (_(" Source directory \"%s\" is not a directory \n %s "), s
);
1350 if (return_status
== FILE_RETRY
)
1351 goto retry_src_stat
;
1352 return return_status
;
1356 if (is_in_linklist (parent_dirs
, s
, &cbuf
)) {
1357 /* we found a cyclic symbolic link */
1358 message_2s (1, MSG_ERROR
, _(" Cannot copy cyclic symbolic link \n `%s' "), s
);
1363 lp
= xmalloc (sizeof (struct link
), "parent_dirs");
1364 lp
->vfs
= vfs_type (s
);
1365 lp
->ino
= cbuf
.st_ino
;
1366 lp
->dev
= cbuf
.st_dev
;
1367 lp
->next
= parent_dirs
;
1370 /* Now, check if the dest dir exists, if not, create it. */
1371 if (mc_stat (d
, &buf
)){
1372 /* Here the dir doesn't exist : make it !*/
1375 if (mc_rename (s
, d
) == 0) {
1380 dest_dir
= copy_strings (d
, 0);
1383 * If the destination directory exists, we want to copy the whole
1384 * directory, but we only want this to happen once.
1386 * Escape sequences added to the * to avoid compiler warnings.
1387 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
1388 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
1391 /* Again, I'm getting curious. Is not d already what we wanted, incl.
1392 * masked source basename? Is not this just a relict of the past versions?
1393 * I'm afraid this will lead into a two level deep dive :(
1395 * I think this is indeed the problem. I can not remember any case where
1396 * we actually would like that behaviour -miguel
1398 * It's a documented feature (option `Dive into subdir if exists' in the
1399 * copy/move dialog). -Norbert
1401 if (toplevel
&& dive_into_subdirs
){
1402 dest_dir
= concat_dir_and_file (d
, x_basename (s
));
1406 dest_dir
= copy_strings (d
, 0);
1411 if (my_mkdir (dest_dir
, (cbuf
.st_mode
& umask_kill
) | S_IRWXU
)){
1412 return_status
= file_error (_(" Cannot create target directory \"%s\" \n %s "), dest_dir
);
1413 if (return_status
== FILE_RETRY
)
1414 goto retry_dst_mkdir
;
1418 lp
= xmalloc (sizeof (struct link
), "dest_dirs");
1419 mc_stat (dest_dir
, &buf
);
1420 lp
->vfs
= vfs_type (dest_dir
);
1421 lp
->ino
= buf
.st_ino
;
1422 lp
->dev
= buf
.st_dev
;
1423 lp
->next
= dest_dirs
;
1427 if (preserve_uidgid
) {
1429 if (mc_chown (dest_dir
, cbuf
.st_uid
, cbuf
.st_gid
)){
1430 return_status
= file_error
1431 (_(" Cannot chown target directory \"%s\" \n %s "), dest_dir
);
1432 if (return_status
== FILE_RETRY
)
1433 goto retry_dst_chown
;
1440 /* open the source dir for reading */
1441 if ((reading
= mc_opendir (s
)) == 0){
1445 while ((next
= mc_readdir (reading
)) && return_status
!= FILE_ABORT
){
1447 * Now, we don't want '.' and '..' to be created / copied at any time
1449 if (!strcmp (next
->d_name
, "."))
1451 if (!strcmp (next
->d_name
, ".."))
1454 /* get the filename and add it to the src directory */
1455 path
= concat_dir_and_file (s
, next
->d_name
);
1457 (*xstat
)(path
, &buf
);
1458 if (S_ISDIR (buf
.st_mode
)){
1459 mdpath
= concat_dir_and_file (dest_dir
, next
->d_name
);
1461 * From here, we just intend to recursively copy subdirs, not
1462 * the double functionality of copying different when the target
1463 * dir already exists. So, we give the recursive call the flag 0
1464 * meaning no toplevel.
1466 return_status
= copy_dir_dir (path
, mdpath
, 0, 0, delete, parent_dirs
);
1469 dest_file
= concat_dir_and_file (dest_dir
, x_basename (path
));
1470 return_status
= copy_file_file (path
, dest_file
, 1);
1473 if (delete && return_status
== FILE_CONT
) {
1475 static struct link
*tail
;
1476 lp
= xmalloc (sizeof (struct link
) + strlen (path
), "erase_list");
1477 strcpy (lp
->name
, path
);
1478 lp
->st_mode
= buf
.st_mode
;
1484 erase_list
= tail
= lp
;
1486 if (S_ISDIR (buf
.st_mode
)) {
1487 return_status
= erase_dir_iff_empty (path
);
1489 return_status
= erase_file (path
);
1494 /* The OS/2 mc_readdir returns a block of memory DIR
1495 * next should be freed: .ado
1502 mc_closedir (reading
);
1504 /* .ado: Directories can not have permission set in OS/2 */
1507 mc_chmod (dest_dir
, cbuf
.st_mode
& umask_kill
);
1508 utb
.actime
= cbuf
.st_atime
;
1509 utb
.modtime
= cbuf
.st_mtime
;
1510 mc_utime(dest_dir
, &utb
);
1517 return return_status
;
1522 /* {{{ Move routines */
1525 move_file_file (char *s
, char *d
)
1527 struct stat src_stats
, dst_stats
;
1528 int return_status
= FILE_CONT
;
1530 if (show_source (s
) == FILE_ABORT
1531 || show_target (d
) == FILE_ABORT
)
1537 if (mc_lstat (s
, &src_stats
) != 0){
1538 /* Source doesn't exist */
1539 return_status
= file_error (_(" Cannot stat file \"%s\" \n %s "), s
);
1540 if (return_status
== FILE_RETRY
)
1541 goto retry_src_lstat
;
1542 return return_status
;
1545 if (mc_lstat (d
, &dst_stats
) == 0){
1546 /* Destination already exists */
1547 /* .ado: for OS/2 and NT, no st_ino exists */
1549 if (src_stats
.st_dev
== dst_stats
.st_dev
1550 && src_stats
.st_ino
== dst_stats
.st_ino
){
1551 int msize
= COLS
- 36;
1552 char st
[MC_MAXPATHLEN
];
1553 char dt
[MC_MAXPATHLEN
];
1559 strcpy (st
, name_trunc (s
, msize
));
1560 strcpy (dt
, name_trunc (d
, msize
));
1561 message_3s (1, MSG_ERROR
, _(" `%s' and `%s' are the same file "),
1567 if (S_ISDIR (dst_stats
.st_mode
)){
1568 message_2s (1, MSG_ERROR
, _(" Cannot overwrite directory `%s' "), d
);
1573 if (confirm_overwrite
){
1574 if (vfs_file_is_ftp (s
))
1579 return_status
= query_replace (d
, &src_stats
, &dst_stats
);
1580 if (return_status
!= FILE_CONT
)
1581 return return_status
;
1583 /* Ok to overwrite */
1589 if (S_ISLNK (src_stats
.st_mode
) && stable_symlinks
) {
1590 if ((return_status
= make_symlink (s
, d
)) == FILE_CONT
)
1591 goto retry_src_remove
;
1593 return return_status
;
1596 if (mc_rename (s
, d
) == 0)
1600 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1601 one nfs to the same, but on the server it is on two different
1602 filesystems. Then nfs returns EIO instead of EXDEV.
1603 Hope it will not hurt if we always in case of error try to copy/delete. */
1605 errno
= EXDEV
; /* Hack to copy (append) the file and then delete it */
1607 if (errno
!= EXDEV
){
1608 return_status
= files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s
, d
);
1609 if (return_status
== FILE_RETRY
)
1611 return return_status
;
1615 /* Failed because filesystem boundary -> copy the file instead */
1616 if ((return_status
= copy_file_file (s
, d
, 0)) != FILE_CONT
)
1617 return return_status
;
1618 if ((return_status
= show_source (NULL
)) != FILE_CONT
1619 || (return_status
= show_file_progress (0, 0)) != FILE_CONT
)
1620 return return_status
;
1626 return_status
= file_error (_(" Cannot remove file \"%s\" \n %s "), s
);
1627 if (return_status
== FILE_RETRY
)
1628 goto retry_src_remove
;
1629 return return_status
;
1636 move_dir_dir (char *s
, char *d
)
1638 struct stat sbuf
, dbuf
, destbuf
;
1644 if (show_source (s
) == FILE_ABORT
1645 || show_target (d
) == FILE_ABORT
)
1651 if (mc_stat (d
, &dbuf
))
1652 destdir
= copy_strings (d
, 0); /* destination doesn't exist */
1653 else if (!dive_into_subdirs
) {
1654 destdir
= copy_strings (d
, 0);
1657 destdir
= concat_dir_and_file (d
, x_basename (s
));
1659 /* Check if the user inputted an existing dir */
1661 if (!mc_stat (destdir
, &destbuf
)){
1663 if ((return_status
= copy_dir_dir (s
, destdir
, 0, 1, 1, 0)) != FILE_CONT
)
1667 if (S_ISDIR (destbuf
.st_mode
))
1668 return_status
= file_error (_(" Cannot overwrite directory \"%s\" %s "), destdir
);
1670 return_status
= file_error (_(" Cannot overwrite file \"%s\" %s "), destdir
);
1671 if (return_status
== FILE_RETRY
)
1672 goto retry_dst_stat
;
1675 return return_status
;
1679 if (mc_rename (s
, destdir
) == 0){
1680 return_status
= FILE_CONT
;
1683 /* .ado: Drive, Do we need this anymore? */
1686 /* EXDEV: cross device; does not work everywhere */
1687 if (toupper(s
[0]) != toupper(destdir
[0]))
1692 if (errno
!= EXDEV
){
1693 return_status
= files_error (_(" Cannot move directory \"%s\" to \"%s\" \n %s "), s
, d
);
1694 if (return_status
== FILE_RETRY
)
1700 /* Failed because of filesystem boundary -> copy dir instead */
1701 if ((return_status
= copy_dir_dir (s
, destdir
, 0, 0, 1, 0)) != FILE_CONT
)
1704 if ((return_status
= show_source (NULL
)) != FILE_CONT
1705 || (return_status
= show_file_progress (0, 0)) != FILE_CONT
)
1710 for ( ; erase_list
&& return_status
!= FILE_ABORT
; ) {
1711 if (S_ISDIR (erase_list
->st_mode
)) {
1712 return_status
= erase_dir_iff_empty (erase_list
->name
);
1714 return_status
= erase_file (erase_list
->name
);
1716 erase_list
= erase_list
->next
;
1720 erase_dir_iff_empty (s
);
1724 for ( ; erase_list
; ) {
1726 erase_list
= erase_list
->next
;
1729 return return_status
;
1734 /* {{{ Erase routines */
1737 erase_file (char *s
)
1741 if (show_deleting (s
) == FILE_ABORT
)
1748 return_status
= file_error (_(" Cannot delete file \"%s\" \n %s "), s
);
1749 if (return_status
== FILE_RETRY
)
1751 return return_status
;
1757 recursive_erase (char *s
)
1759 struct dirent
*next
;
1763 int return_status
= FILE_CONT
;
1765 if (!strcmp (s
, ".."))
1768 reading
= mc_opendir (s
);
1773 while ((next
= mc_readdir (reading
)) && return_status
== FILE_CONT
){
1774 if (!strcmp (next
->d_name
, "."))
1776 if (!strcmp (next
->d_name
, ".."))
1778 path
= concat_dir_and_file (s
, next
->d_name
);
1779 if (mc_lstat (path
, &buf
)){
1783 if (S_ISDIR (buf
.st_mode
))
1784 return_status
= (recursive_erase (path
) != FILE_CONT
);
1786 return_status
= erase_file (path
);
1788 /* .ado: OS/2 returns a block of memory DIR to next and must be freed */
1794 mc_closedir (reading
);
1795 if (return_status
!= FILE_CONT
)
1796 return return_status
;
1797 if (show_deleting (s
) == FILE_ABORT
)
1802 return_status
= file_error (_(" Cannot remove directory \"%s\" \n %s "), s
);
1803 if (return_status
== FILE_RETRY
)
1805 return return_status
;
1810 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1811 in the directory path points to, 0 else. */
1813 check_dir_is_empty(char *path
)
1819 dir
= mc_opendir (path
);
1823 for (i
= 1, d
= mc_readdir (dir
); d
; d
= mc_readdir (dir
)) {
1824 if (d
->d_name
[0] == '.' && (d
->d_name
[1] == '\0' ||
1825 (d
->d_name
[1] == '.' && d
->d_name
[2] == '\0')))
1826 continue; /* "." or ".." */
1840 if (strcmp (s
, "..") == 0)
1843 if (strcmp (s
, ".") == 0)
1846 if (show_deleting (s
) == FILE_ABORT
)
1850 /* The old way to detect a non empty directory was:
1851 error = my_rmdir (s);
1852 if (error && (errno == ENOTEMPTY || errno == EEXIST))) {
1853 For the linux user space nfs server (nfs-server-2.2beta29-2)
1854 we would have to check also for EIO. I hope the new way is
1855 fool proof. (Norbert)
1857 error
= check_dir_is_empty (s
);
1858 if (error
== 0) { /* not empty */
1859 error
= query_recursive (s
);
1860 if (error
== FILE_CONT
)
1861 return recursive_erase (s
);
1867 error
= my_rmdir (s
);
1869 error
= file_error (_(" Cannot remove directory \"%s\" \n %s "), s
);
1870 if (error
== FILE_RETRY
)
1878 erase_dir_iff_empty (char *s
)
1882 if (strcmp (s
, "..") == 0)
1885 if (strcmp (s
, ".") == 0)
1888 if (show_deleting (s
) == FILE_ABORT
)
1892 if (1 != check_dir_is_empty (s
)) /* not empty or error */
1896 error
= my_rmdir (s
);
1898 error
= file_error (_(" Cannot remove directory \"%s\" \n %s "), s
);
1899 if (error
== FILE_RETRY
)
1908 /* {{{ Panel operate routines */
1910 /* Returns currently selected file or the first marked file if there is one */
1912 get_file (WPanel
*panel
, struct stat
*stat_buf
)
1916 /* No problem with Gnome, as get_current_type never returns view_tree there */
1917 if (get_current_type () == view_tree
){
1918 WTree
*tree
= (WTree
*)get_panel_widget (get_current_index ());
1920 mc_stat (tree
->selected_ptr
->name
, stat_buf
);
1921 return tree
->selected_ptr
->name
;
1925 for (i
= 0; i
< panel
->count
; i
++)
1926 if (panel
->dir
.list
[i
].f
.marked
){
1927 *stat_buf
= panel
->dir
.list
[i
].buf
;
1928 return panel
->dir
.list
[i
].fname
;
1931 *stat_buf
= panel
->dir
.list
[panel
->selected
].buf
;
1932 return panel
->dir
.list
[panel
->selected
].fname
;
1934 fprintf (stderr
, _(" Internal error: get_file \n"));
1940 is_wildcarded (char *p
)
1945 else if (*p
== '\\' && p
[1] >= '1' && p
[1] <= '9')
1951 /* Sets all global variables used by copy_file_file/move_file_file to a
1953 (file_mask_dialog sets these global variables interactively)
1956 file_mask_defaults (void)
1958 stable_symlinks
= 0;
1959 op_follow_links
= 0;
1960 dive_into_subdirs
= 0;
1964 umask_kill
= 0777777;
1965 preserve_uidgid
= (geteuid () == 0) ? 1 : 0;
1970 static int fmd_xlen
= FMD_XLEN
, fmd_i18n_flag
= 0;
1971 static QuickWidget fmd_widgets
[] = {
1976 /* follow symlinks and preserve Attributes must be the first */
1977 { quick_checkbox
, 3, 64, 8, FMDY
, N_("preserve &Attributes"), 9, 0,
1978 &op_preserve
, 0, XV_WLAY_BELOWCLOSE
, "preserve" },
1979 { quick_checkbox
, 3, 64, 7, FMDY
, N_("follow &Links"), 7, 0,
1980 &op_follow_links
, 0, XV_WLAY_BELOWCLOSE
, "follow" },
1985 { quick_input
, 3, 64, 6, FMDY
, "", 58, 0,
1986 0, 0, XV_WLAY_BELOWCLOSE
, "input2" },
1988 { quick_label
, 3, 64, 5, FMDY
, N_("to:"), 0, 0, 0, 0, XV_WLAY_BELOWOF
,"to"},
1989 { quick_checkbox
, 37, 64, 4, FMDY
, N_("&Using shell patterns"), 0, 0,
1990 0/* &source_easy_patterns */, 0, XV_WLAY_BELOWCLOSE
, "using-shell" },
1991 { quick_input
, 3, 64, 3, FMDY
, "", 58,
1992 0, 0, 0, XV_WLAY_BELOWCLOSE
, "input-def" },
1997 { quick_input
, 3, 64, 6, FMDY
, "", 58, 0,
1998 0, 0, XV_WLAY_BELOWCLOSE
, "input2" },
2001 { quick_label
, 3, 64, 2, FMDY
, "", 0, 0, 0, 0, XV_WLAY_DONTCARE
, "ql" },
2003 { quick_button
, 42, 64, 9, FMDY
, N_("&Cancel"), 0, B_CANCEL
, 0, 0, XV_WLAY_DONTCARE
,
2006 #ifdef WITH_BACKGROUND
2012 { quick_button
, 25, 64, 9, FMDY
, N_("&Background"), 0, B_USER
, 0, 0, XV_WLAY_DONTCARE
, "back" },
2013 #else /* WITH_BACKGROUND */
2020 { quick_button
, 14, 64, 9, FMDY
, N_("&Ok"), 0, B_ENTER
, 0, 0, XV_WLAY_NEXTROW
, "ok" },
2021 { quick_checkbox
, 42, 64, 8, FMDY
, N_("&Stable Symlinks"), 0, 0,
2022 &stable_symlinks
, 0, XV_WLAY_BELOWCLOSE
, "stab-sym" },
2023 { quick_checkbox
, 31, 64, 7, FMDY
, N_("&Dive into subdir if exists"), 0, 0,
2024 &dive_into_subdirs
, 0, XV_WLAY_BELOWOF
, "dive" },
2035 for (i
= sizeof (op_names
) / sizeof (op_names
[0]); i
--;)
2036 op_names
[i
] = _(op_names
[i
]);
2038 i
= sizeof (fmd_widgets
) / sizeof (fmd_widgets
[0]) - 1;
2040 if (fmd_widgets
[i
].text
[0] != '\0')
2041 fmd_widgets
[i
].text
= _(fmd_widgets
[i
].text
);
2043 len
= strlen (fmd_widgets
[FMCB11
].text
)
2044 + strlen (fmd_widgets
[FMCB21
].text
) + 15;
2045 fmd_xlen
= max (fmd_xlen
, len
);
2047 len
= strlen (fmd_widgets
[FMCB12
].text
)
2048 + strlen (fmd_widgets
[FMCB22
].text
) + 15;
2049 fmd_xlen
= max (fmd_xlen
, len
);
2051 len
= strlen (fmd_widgets
[FMBRGT
].text
)
2052 + strlen (fmd_widgets
[FMBLFT
].text
) + 11;
2055 len
+= strlen (fmd_widgets
[FMBMID
].text
) + 6;
2058 fmd_xlen
= max (fmd_xlen
, len
+ 4);
2060 len
= (fmd_xlen
- (len
+ 6)) / 2;
2061 i
= fmd_widgets
[FMBLFT
].relative_x
= len
+ 3;
2062 i
+= strlen (fmd_widgets
[FMBLFT
].text
) + 8;
2065 fmd_widgets
[FMBMID
].relative_x
= i
;
2066 i
+= strlen (fmd_widgets
[FMBMID
].text
) + 6;
2069 fmd_widgets
[FMBRGT
].relative_x
= i
;
2071 #define chkbox_xpos(i) \
2072 fmd_widgets [i].relative_x = fmd_xlen - strlen (fmd_widgets [i].text) - 6
2075 chkbox_xpos(FMCB21
);
2076 chkbox_xpos(FMCB22
);
2078 if (fmd_xlen
!= FMD_XLEN
)
2080 i
= sizeof (fmd_widgets
) / sizeof (fmd_widgets
[0]) - 1;
2082 fmd_widgets
[i
].x_divisions
= fmd_xlen
;
2084 fmd_widgets
[FMDI1
].hotkey_pos
=
2085 fmd_widgets
[FMDI2
].hotkey_pos
= fmd_xlen
- 6;
2088 #endif /* ENABLE_NLS */
2094 file_mask_dialog (int operation
, char *text
, char *def_text
, int only_one
, int *do_background
)
2096 int source_easy_patterns
= easy_patterns
;
2097 char *source_mask
, *orig_mask
, *dest_dir
;
2102 QuickDialog Quick_input
;
2107 stable_symlinks
= 0;
2108 fmd_widgets
[FMDC
].result
= &source_easy_patterns
;
2109 fmd_widgets
[FMDI1
].text
= easy_patterns
? "*" : "^\\(.*\\)$";
2110 Quick_input
.xlen
= fmd_xlen
;
2111 Quick_input
.xpos
= -1;
2112 Quick_input
.title
= op_names
[operation
];
2113 Quick_input
.help
= "[Mask Copy/Rename]";
2114 Quick_input
.ylen
= FMDY
;
2115 Quick_input
.i18n
= 1;
2117 if (operation
== OP_COPY
) {
2118 Quick_input
.class = "quick_file_mask_copy";
2119 Quick_input
.widgets
= fmd_widgets
;
2120 } else { /* operation == OP_MOVE */
2121 Quick_input
.class = "quick_file_mask_move";
2122 Quick_input
.widgets
= fmd_widgets
+ 2;
2124 fmd_widgets
[FMDI0
].text
= text
;
2125 fmd_widgets
[FMDI2
].text
= def_text
;
2126 fmd_widgets
[FMDI2
].str_result
= &dest_dir
;
2127 fmd_widgets
[FMDI1
].str_result
= &source_mask
;
2132 if ((val
= quick_dialog_skip (&Quick_input
, SKIP
)) == B_CANCEL
)
2135 if (op_follow_links
&& operation
!= OP_MOVE
)
2140 if (op_preserve
|| operation
== OP_MOVE
) {
2142 umask_kill
= 0777777;
2143 preserve_uidgid
= (geteuid () == 0) ? 1 : 0;
2147 preserve
= preserve_uidgid
= 0;
2150 umask_kill
= i
^ 0777777;
2153 orig_mask
= source_mask
;
2154 if (!dest_dir
|| !*dest_dir
) {
2159 if (source_easy_patterns
) {
2160 source_easy_patterns
= easy_patterns
;
2162 source_mask
= convert_pattern (source_mask
, match_file
, 1);
2163 easy_patterns
= source_easy_patterns
;
2164 error
= re_compile_pattern (source_mask
, strlen (source_mask
), &rx
);
2167 error
= re_compile_pattern (source_mask
, strlen (source_mask
), &rx
);
2170 message_3s (1, MSG_ERROR
, _("Invalid source pattern `%s' \n %s "),
2178 dest_mask
= strrchr (dest_dir
, PATH_SEP
);
2179 if (dest_mask
== NULL
)
2180 dest_mask
= dest_dir
;
2183 orig_mask
= dest_mask
;
2184 if (!*dest_mask
|| (!dive_into_subdirs
&& !is_wildcarded (dest_mask
) &&
2185 (!only_one
|| (!mc_stat (dest_dir
, &buf
) && S_ISDIR (buf
.st_mode
)))) ||
2186 (dive_into_subdirs
&& ((!only_one
&& !is_wildcarded (dest_mask
)) ||
2187 (only_one
&& !mc_stat (dest_dir
, &buf
) && S_ISDIR (buf
.st_mode
)))))
2188 dest_mask
= strdup ("*");
2190 dest_mask
= strdup (dest_mask
);
2195 dest_dir
= strdup ("./");
2203 * This array introduced to avoid translation problems. The former (op_names)
2204 * is assumed to be nouns, suitable in dialog box titles; this one should
2205 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
2206 * Notice first symbol - it is to fool gettext and force these strings to
2207 * be different for it. First symbol is skipped while building a prompt.
2208 * (I don't use spaces around the words, because someday they could be
2209 * dropped, when widgets get smarter)
2211 static char *op_names1
[] = { N_("1Copy"), N_("1Move"), N_("1Delete") };
2214 * These are formats for building a prompt. Parts encoded as follows:
2215 * %o - operation from op_names1
2216 * %f - file/files or files/directories, as appropriate
2217 * %m - "with source mask" or question mark for delete
2218 * %s - source name (truncated)
2219 * %d - number of marked files
2221 static char* one_format
= N_("%o %f \"%s\"%m");
2222 static char* many_format
= N_("%o %d %f%m");
2224 static char* prompt_parts
[] =
2226 N_("file"), N_("files"), N_("directory"), N_("directories"),
2227 N_("files/directories"), N_(" with source mask:")
2231 generate_prompt(char* cmd_buf
, WPanel
* panel
, int operation
, int only_one
,
2232 struct stat
* src_stat
)
2234 register char *sp
, *cp
;
2236 char format_string
[200];
2237 char *dp
= format_string
;
2238 char* source
= NULL
;
2241 static int i18n_flag
= 0;
2245 fmd_init_i18n(); /* to get proper fmd_xlen */
2247 for (i
= sizeof (op_names1
) / sizeof (op_names1
[0]); i
--;)
2248 op_names1
[i
] = _(op_names1
[i
]);
2250 for (i
= sizeof (prompt_parts
) / sizeof (prompt_parts
[0]); i
--;)
2251 prompt_parts
[i
] = _(prompt_parts
[i
]);
2253 one_format
= _(one_format
);
2254 many_format
= _(many_format
);
2257 #endif /* ENABLE_NLS */
2259 sp
= only_one
? one_format
: many_format
;
2262 source
= get_file (panel
, src_stat
);
2273 cp
= op_names1
[operation
] + 1;
2276 cp
= operation
== OP_DELETE
? "?" : prompt_parts
[5];
2281 cp
= S_ISDIR (src_stat
->st_mode
) ?
2282 prompt_parts
[2] : prompt_parts
[0];
2286 cp
= (panel
->marked
== panel
->dirs_marked
)
2288 : (panel
->dirs_marked
? prompt_parts
[4]
2289 : prompt_parts
[1]);
2310 i
= fmd_xlen
- strlen(format_string
) - 4;
2311 sprintf (cmd_buf
, format_string
, name_trunc (source
, i
));
2315 sprintf (cmd_buf
, format_string
, panel
->marked
);
2316 i
= strlen (cmd_buf
) + 6 - fmd_xlen
;
2320 fmd_init_i18n(); /* to recalculate positions of child widgets */
2328 /* Returns 1 if did change the directory structure,
2329 Returns 0 if user aborted */
2331 panel_operate (void *source_panel
, int operation
, char *thedefault
)
2333 WPanel
*panel
= source_panel
;
2334 #ifdef WITH_FULL_PATHS
2335 char *source_with_path
= NULL
;
2337 # define source_with_path source
2339 char *source
= NULL
;
2342 int only_one
= (get_current_type () == view_tree
) || (panel
->marked
<= 1);
2343 struct stat src_stat
, dst_stat
;
2346 long count
= 0, bytes
= 0;
2348 int do_bg
; /* do background operation? */
2352 free_linklist (&linklist
);
2353 free_linklist (&dest_dirs
);
2354 if (get_current_type () == view_listing
)
2355 if (!panel
->marked
&& !strcmp (selection (panel
)->fname
, "..")){
2356 message (1, MSG_ERROR
, _(" Can't operate on \"..\"! "));
2360 if (operation
< OP_COPY
|| operation
> OP_DELETE
)
2363 /* Generate confirmation prompt */
2364 source
= generate_prompt(cmd_buf
, panel
, operation
, only_one
, &src_stat
);
2366 /* Show confirmation dialog */
2367 if (operation
== OP_DELETE
&& confirm_delete
){
2368 if (know_not_what_am_i_doing
)
2370 i
= query_dialog (_(op_names
[operation
]), cmd_buf
,
2371 D_ERROR
, 2, _("&Yes"), _("&No"));
2374 } else if (operation
!= OP_DELETE
) {
2377 if (thedefault
!= NULL
)
2378 dest_dir
= thedefault
;
2379 else if (get_other_type () == view_listing
)
2380 dest_dir
= opanel
->cwd
;
2382 dest_dir
= panel
->cwd
;
2384 rx
.buffer
= (char *) xmalloc (MC_MAXPATHLEN
, "mask copying");
2385 rx
.allocated
= MC_MAXPATHLEN
;
2387 dest
= file_mask_dialog (operation
, cmd_buf
, dest_dir
, only_one
, &do_bg
);
2399 #ifdef WITH_BACKGROUND
2400 /* Did the user select to do a background operation? */
2404 v
= do_background (copy_strings (operation_names
[operation
], ": ", panel
->cwd
, 0));
2406 message (1, MSG_ERROR
, _(" Sorry, I could not put the job in background "));
2409 /* If we are the parent */
2411 vfs_force_expire (panel
->cwd
);
2412 vfs_force_expire (dest
);
2417 /* Initialize things */
2418 /* We turn on ETA display if the source is an ftp file system */
2419 create_op_win (operation
, vfs_file_is_ftp (panel
->cwd
));
2420 ftpfs_hint_reread (0);
2422 /* Now, let's do the job */
2423 /* This code is only called by the tree and panel code */
2425 /* One file: FIXME mc_chdir will take user out of any vfs */
2426 if (operation
!= OP_COPY
&& get_current_type () == view_tree
)
2427 mc_chdir (PATH_SEP_STR
);
2429 /* The source and src_stat variables have been initialized before */
2430 #ifdef WITH_FULL_PATHS
2431 source_with_path
= concat_dir_and_file (panel
->cwd
, source
);
2434 if (operation
== OP_DELETE
){
2435 /* Delete operation */
2436 if (S_ISDIR (src_stat
.st_mode
))
2437 value
= erase_dir (source_with_path
);
2439 value
= erase_file (source_with_path
);
2441 /* Copy or move operation */
2442 temp
= transform_source (source_with_path
);
2444 value
= transform_error
;
2446 temp
= get_full_name (dest
, temp
);
2453 /* we use op_follow_links only with OP_COPY,
2455 (*xstat
) (source_with_path
, &src_stat
);
2456 if (S_ISDIR (src_stat
.st_mode
))
2457 value
= copy_dir_dir (source_with_path
, dest
, 1, 0, 0, 0);
2459 value
= copy_file_file (source_with_path
, dest
, 1);
2462 if (S_ISDIR (src_stat
.st_mode
))
2463 value
= move_dir_dir (source_with_path
, dest
);
2465 value
= move_file_file (source_with_path
, dest
);
2469 message_1s (1, _(" Internal failure "), _(" Unknown file operation "));
2472 } /* Copy or move operation */
2474 if (value
== FILE_CONT
)
2475 unmark_files (panel
);
2480 if (operation
!= OP_DELETE
){
2481 /* Check destination for copy or move operation */
2482 retry_many_dst_stat
:
2483 dst_result
= mc_stat (dest
, &dst_stat
);
2484 if (dst_result
== 0 && !S_ISDIR (dst_stat
.st_mode
)){
2485 if (file_error (_(" Destination \"%s\" must be a directory \n %s "), dest
) == FILE_RETRY
)
2486 goto retry_many_dst_stat
;
2491 /* Initialize variables for progress bars */
2492 marked
= panel
->marked
;
2493 total
= panel
->total
;
2495 /* Loop for every file */
2496 for (i
= 0; i
< panel
->count
; i
++){
2497 if (!panel
->dir
.list
[i
].f
.marked
)
2498 continue; /* Skip the unmarked ones */
2499 source
= panel
->dir
.list
[i
].fname
;
2500 src_stat
= panel
->dir
.list
[i
].buf
; /* Inefficient, should we use pointers? */
2501 #ifdef WITH_FULL_PATHS
2502 if (source_with_path
)
2503 free (source_with_path
);
2504 source_with_path
= concat_dir_and_file (panel
->cwd
, source
);
2506 if (operation
== OP_DELETE
){
2507 /* Delete operation */
2508 if (S_ISDIR (src_stat
.st_mode
))
2509 value
= erase_dir (source_with_path
);
2511 value
= erase_file (source_with_path
);
2513 /* Copy or move operation */
2516 temp
= transform_source (source_with_path
);
2518 value
= transform_error
;
2520 temp
= get_full_name (dest
, temp
);
2523 /* we use op_follow_links only with OP_COPY,
2525 (*xstat
) (source_with_path
, &src_stat
);
2526 if (S_ISDIR (src_stat
.st_mode
))
2527 value
= copy_dir_dir (source_with_path
, temp
, 1, 0, 0, 0);
2529 value
= copy_file_file (source_with_path
, temp
, 1);
2530 free_linklist (&dest_dirs
);
2533 if (S_ISDIR (src_stat
.st_mode
))
2534 value
= move_dir_dir (source_with_path
, temp
);
2536 value
= move_file_file (source_with_path
, temp
);
2539 message_1s (1, _(" Internal failure "),
2540 _(" Unknown file operation "));
2544 } /* Copy or move operation */
2546 if (value
== FILE_ABORT
)
2548 if (value
== FILE_CONT
){
2549 do_file_mark (panel
, i
, 0);
2552 if (show_count_progress (count
, marked
) == FILE_ABORT
)
2554 bytes
+= src_stat
.st_size
;
2556 show_bytes_progress (bytes
, total
) == FILE_ABORT
)
2558 if (operation
!= OP_DELETE
&& verbose
2559 && show_file_progress (0, 0) == FILE_ABORT
)
2562 } /* Loop for every file */
2568 ftpfs_hint_reread (1);
2569 free_linklist (&linklist
);
2570 free_linklist (&dest_dirs
);
2572 if (source_with_path
)
2573 free (source_with_path
);
2588 #ifdef WITH_BACKGROUND
2589 /* Let our parent know we are saying bye bye */
2590 if (we_are_background
){
2592 tell_parent (MSG_CHILD_EXITING
);
2601 /* {{{ Query/status report routines */
2604 real_do_file_error (enum OperationMode mode
, char *error
)
2609 msg
= mode
== Foreground
? MSG_ERROR
: _(" Background process error ");
2610 result
= query_dialog (msg
, error
, D_ERROR
, 3, _("&Skip"), _("&Retry"), _("&Abort"));
2625 /* Report error with one file */
2627 file_error (char *format
, char *file
)
2629 sprintf (cmd_buf
, format
,
2630 name_trunc (file
, 30), unix_error_string (errno
));
2631 return do_file_error (cmd_buf
);
2634 /* Report error with two files */
2636 files_error (char *format
, char *file1
, char *file2
)
2641 strcpy (nfile1
, name_trunc (file1
, 15));
2642 strcpy (nfile2
, name_trunc (file2
, 15));
2644 sprintf (cmd_buf
, format
, nfile1
, nfile2
, unix_error_string (errno
));
2645 return do_file_error (cmd_buf
);
2648 static char *format
= N_("Target file \"%s\" already exists!");
2650 replace_callback (struct Dlg_head
*h
, int Id
, int Msg
)
2656 dialog_repaint (h
, ERROR_COLOR
, ERROR_COLOR
);
2670 * FIXME: probably it is better to replace this with quick dialog machinery,
2671 * but actually I'm not familiar with it and have not much time :(
2678 int value
; /* 0 for labels */
2684 {N_("Target file \"%s\" already exists!"),
2685 3, 4, 0, "target-e", XV_WLAY_CENTERROW
},
2686 {N_("&Abort"), BY
+ 3, 25, REPLACE_ABORT
, "abort", XV_WLAY_CENTERROW
},
2687 {N_("if &Size differs"),
2688 BY
+ 1, 28, REPLACE_SIZE
, "if-size", XV_WLAY_RIGHTOF
},
2689 {N_("non&E"), BY
, 47, REPLACE_NEVER
, "none", XV_WLAY_RIGHTOF
},
2690 {N_("&Update"), BY
, 36, REPLACE_UPDATE
, "update", XV_WLAY_RIGHTOF
},
2691 {N_("al&L"), BY
, 28, REPLACE_ALWAYS
, "all", XV_WLAY_RIGHTOF
},
2692 {N_("Overwrite all targets?"),
2693 BY
, 4, 0, "over-label", XV_WLAY_CENTERROW
},
2694 {N_("&Reget"), BY
- 1, 28, REPLACE_REGET
, "reget", XV_WLAY_RIGHTOF
},
2695 {N_("ap&Pend"), BY
- 2, 45, REPLACE_APPEND
, "append", XV_WLAY_RIGHTOF
},
2696 {N_("&No"), BY
- 2, 37, REPLACE_NO
, "no", XV_WLAY_RIGHTOF
},
2697 {N_("&Yes"), BY
- 2, 28, REPLACE_YES
, "yes", XV_WLAY_RIGHTOF
},
2698 {N_("Overwrite this target?"),
2699 BY
- 2, 4, 0, "overlab", XV_WLAY_CENTERROW
},
2700 {N_("Target date: %s, size %d"),
2701 6, 4, 0, "target-date",XV_WLAY_CENTERROW
},
2702 {N_("Source date: %s, size %d"),
2703 5, 4, 0, "source-date",XV_WLAY_CENTERROW
}
2706 #define ADD_RD_BUTTON(i)\
2707 add_widgetl (replace_dlg,\
2708 button_new (rd_widgets [i].ypos, rd_widgets [i].xpos, rd_widgets [i].value,\
2709 NORMAL_BUTTON, rd_widgets [i].text, 0, 0, rd_widgets [i].tkname), \
2710 rd_widgets [i].layout)
2712 #define ADD_RD_LABEL(i,p1,p2)\
2713 sprintf (buffer, rd_widgets [i].text, p1, p2);\
2714 add_widgetl (replace_dlg,\
2715 label_new (rd_widgets [i].ypos, rd_widgets [i].xpos, buffer, rd_widgets [i].tkname),\
2716 rd_widgets [i].layout)
2719 init_replace (enum OperationMode mode
)
2722 static int rd_xlen
= 60, rd_trunc
= X_TRUNC
;
2725 static int i18n_flag
;
2729 register int i
= sizeof (rd_widgets
) / sizeof (rd_widgets
[0]);
2731 rd_widgets
[i
].text
= _(rd_widgets
[i
].text
);
2734 *longest of "Overwrite..." labels
2735 * (assume "Target date..." are short enough)
2737 l1
= max (strlen (rd_widgets
[6].text
), strlen (rd_widgets
[11].text
));
2739 /* longest of button rows */
2740 i
= sizeof (rd_widgets
) / sizeof (rd_widgets
[0]);
2741 for (row
= l
= l2
= 0; i
--;)
2743 if (rd_widgets
[i
].value
!= 0)
2745 if (row
!= rd_widgets
[i
].ypos
)
2747 row
= rd_widgets
[i
].ypos
;
2751 l
+= strlen (rd_widgets
[i
].text
) + 4;
2754 l2
= max (l2
, l
); /* last row */
2755 rd_xlen
= max (rd_xlen
, l1
+ l2
+ 8);
2756 rd_trunc
= rd_xlen
- 6;
2758 /* Now place buttons */
2759 l1
+= 5; /* start of first button in the row */
2760 i
= sizeof (rd_widgets
) / sizeof (rd_widgets
[0]);
2762 for (l
= l1
, row
= 0; --i
> 1;)
2764 if (rd_widgets
[i
].value
!= 0)
2766 if (row
!= rd_widgets
[i
].ypos
)
2768 row
= rd_widgets
[i
].ypos
;
2771 rd_widgets
[i
].xpos
= l
;
2772 l
+= strlen (rd_widgets
[i
].text
) + 4;
2775 /* Abort button is centered */
2776 rd_widgets
[1].xpos
= (rd_xlen
- strlen (rd_widgets
[1].text
) - 3) / 2;
2779 #endif /* ENABLE_NLS */
2781 replace_colors
[0] = ERROR_COLOR
;
2782 replace_colors
[1] = COLOR_NORMAL
;
2783 replace_colors
[2] = ERROR_COLOR
;
2784 replace_colors
[3] = COLOR_NORMAL
;
2786 replace_dlg
= create_dlg (0, 0, 16, rd_xlen
, replace_colors
, replace_callback
,
2787 "[ Replace ]", "replace", DLG_CENTER
);
2789 x_set_dialog_title (replace_dlg
,
2790 mode
== Foreground
? _(" File exists ") : _(" Background process: File exists "));
2793 ADD_RD_LABEL(0, name_trunc (replace_filename
, rd_trunc
- strlen (rd_widgets
[0].text
)), 0 );
2796 tk_new_frame (replace_dlg
, "a.");
2802 ADD_RD_LABEL(6,0,0);
2804 /* "this target..." widgets */
2805 tk_new_frame (replace_dlg
, "p.");
2806 if (!S_ISDIR (d_stat
->st_mode
)){
2807 if ((do_reget
== -1 && d_stat
->st_size
&& s_stat
->st_size
> d_stat
->st_size
))
2814 ADD_RD_LABEL(11,0,0);
2816 tk_new_frame (replace_dlg
, "i.");
2817 ADD_RD_LABEL(12, file_date (d_stat
->st_mtime
), (int) d_stat
->st_size
);
2818 ADD_RD_LABEL(13, file_date (s_stat
->st_mtime
), (int) s_stat
->st_size
);
2823 real_query_replace (enum OperationMode mode
, char *destname
, struct stat
*_s_stat
,
2824 struct stat
*_d_stat
)
2826 if (replace_result
< REPLACE_ALWAYS
){
2827 replace_filename
= destname
;
2830 init_replace (mode
);
2831 run_dlg (replace_dlg
);
2832 replace_result
= replace_dlg
->ret_value
;
2833 if (replace_result
== B_CANCEL
)
2834 replace_result
= REPLACE_ABORT
;
2835 destroy_dlg (replace_dlg
);
2838 switch (replace_result
){
2839 case REPLACE_UPDATE
:
2841 if (_s_stat
->st_mtime
> _d_stat
->st_mtime
)
2848 if (_s_stat
->st_size
== _d_stat
->st_size
)
2854 do_reget
= _d_stat
->st_size
;
2856 case REPLACE_APPEND
:
2860 case REPLACE_ALWAYS
:
2874 real_query_recursive (enum OperationMode mode
, char *s
)
2876 char *confirm
, *text
;
2878 if (recursive_result
< RECURSIVE_ALWAYS
){
2880 mode
== Foreground
? _("\n Directory not empty. \n Delete it recursively? ")
2881 : _("\n Background process: Directory not empty \n Delete it recursively? ");
2882 text
= copy_strings (" Delete: ", name_trunc (s
, 30), " ", 0);
2884 if (know_not_what_am_i_doing
)
2886 recursive_result
= query_dialog (text
, msg
, D_ERROR
, 5,
2887 _("&Yes"), _("&No"), _("a&ll"), _("non&E"), _("&Abort"));
2890 if (recursive_result
!= RECURSIVE_ABORT
)
2893 if (know_not_what_am_i_doing
&& (recursive_result
== RECURSIVE_YES
2894 || recursive_result
== RECURSIVE_ALWAYS
)){
2895 text
= copy_strings (_(" Type 'yes' if you REALLY want to delete "),
2896 recursive_result
== RECURSIVE_YES
2897 ? name_trunc (s
, 19) : _("all the directories "), " ", 0);
2898 confirm
= input_dialog (mode
== Foreground
? _(" Recursive Delete ")
2899 : _(" Background process: Recursive Delete "),
2902 if (!confirm
|| strcmp (confirm
, "yes"))
2903 recursive_result
= RECURSIVE_NEVER
;
2908 switch (recursive_result
){
2910 case RECURSIVE_ALWAYS
:
2913 case RECURSIVE_NEVER
:
2915 case RECURSIVE_ABORT
:
2921 #ifdef WITH_BACKGROUND
2923 do_file_error (char *str
)
2925 return call_1s (real_do_file_error
, str
);
2929 query_recursive (char *s
)
2931 return call_1s (real_query_recursive
, s
);
2935 query_replace (char *destname
, struct stat
*_s_stat
, struct stat
*_d_stat
)
2937 if (we_are_background
)
2938 return parent_call ((void *)real_query_replace
, 3, strlen(destname
), destname
,
2939 sizeof (struct stat
), _s_stat
, sizeof(struct stat
), _d_stat
);
2941 return real_query_replace (Foreground
, destname
, _s_stat
, _d_stat
);
2945 do_file_error (char *str
)
2947 return real_do_file_error (Foreground
, str
);
2951 query_recursive (char *s
)
2953 return real_query_recursive (Foreground
, s
);
2957 query_replace (char *destname
, struct stat
*_s_stat
, struct stat
*_d_stat
)
2959 return real_query_replace (Foreground
, destname
, _s_stat
, _d_stat
);
2965 Cause emacs to enter folding mode for this file: