compute_dir_size() function now respect 'follow symlinks' option
[midnight-commander.git] / src / file.c
blobac65d321c7c3db9ec78721adb5d5b6b7a897107d
1 /* File management.
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
9 1997 Norbert Warmuth
10 1998 Pavel Machek
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
37 * operations.
40 /** \file file.c
41 * \brief Source: file management
44 /* {{{ Include files */
46 #include <config.h>
48 #include <ctype.h>
49 #include <errno.h>
50 #include <stdlib.h>
51 #include <stdio.h>
52 #include <string.h>
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include <unistd.h>
56 #include <fcntl.h>
58 #include "lib/global.h"
59 #include "lib/tty/tty.h"
60 #include "lib/tty/key.h"
61 #include "lib/search.h"
62 #include "lib/vfs/mc-vfs/vfs-impl.h"
63 #include "lib/vfs/mc-vfs/vfs.h"
64 #include "lib/strescape.h"
65 #include "lib/strutil.h"
67 #include "setup.h"
68 #include "dialog.h"
69 #include "widget.h"
70 #include "main.h"
71 #include "layout.h"
72 #include "widget.h"
73 #include "wtools.h"
74 #include "background.h" /* we_are_background */
76 /* Needed for current_panel, other_panel and WTree */
77 #include "dir.h"
78 #include "panel.h"
79 #include "file.h"
80 #include "filegui.h"
81 #include "tree.h"
83 /* }}} */
85 /* Hack: the vfs code should not rely on this */
86 #define WITH_FULL_PATHS 1
88 #define FILEOP_UPDATE_INTERVAL 2
89 #define FILEOP_STALLING_INTERVAL 4
91 int verbose = 1;
94 * Whether the Midnight Commander tries to provide more
95 * information about copy/move sizes and bytes transfered
96 * at the expense of some speed
98 int file_op_compute_totals = 1;
100 /* This is a hard link cache */
101 struct link {
102 struct link *next;
103 struct vfs_class *vfs;
104 dev_t dev;
105 ino_t ino;
106 short linkcount;
107 mode_t st_mode;
108 char name[1];
111 /* the hard link cache */
112 static struct link *linklist = NULL;
114 /* the files-to-be-erased list */
115 static struct link *erase_list;
118 * In copy_dir_dir we use two additional single linked lists: The first -
119 * variable name `parent_dirs' - holds information about already copied
120 * directories and is used to detect cyclic symbolic links.
121 * The second (`dest_dirs' below) holds information about just created
122 * target directories and is used to detect when an directory is copied
123 * into itself (we don't want to copy infinitly).
124 * Both lists don't use the linkcount and name structure members of struct
125 * link.
127 static struct link *dest_dirs = NULL;
129 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
130 const char *op_names[3] = {
131 N_("DialogTitle|Copy"),
132 N_("DialogTitle|Move"),
133 N_("DialogTitle|Delete")
136 /* }}} */
138 static FileProgressStatus query_replace (FileOpContext * ctx, const char *destname,
139 struct stat *_s_stat, struct stat *_d_stat);
140 static FileProgressStatus query_recursive (FileOpContext * ctx, const char *s);
141 static FileProgressStatus do_file_error (const char *str);
142 static FileProgressStatus erase_dir_iff_empty (FileOpContext *ctx, const char *s);
143 static FileProgressStatus erase_file (FileOpTotalContext *tctx, FileOpContext *ctx,
144 const char *s, gboolean is_toplevel_file);
145 static FileProgressStatus files_error (const char *format, const char *file1,
146 const char *file2);
148 static FileProgressStatus transform_error = FILE_CONT;
150 static char *
151 transform_source (FileOpContext *ctx, const char *source)
153 char *s, *q;
154 char *fnsource;
156 s = g_strdup (source);
158 /* We remove \n from the filename since regex routines would use \n as an anchor */
159 /* this is just to be allowed to maniupulate file names with \n on it */
160 for (q = s; *q != '\0'; q++)
161 if (*q == '\n')
162 *q = ' ';
164 fnsource = (char *) x_basename (s);
166 if (mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
167 q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
168 else {
169 q = NULL;
170 transform_error = FILE_SKIP;
173 g_free (s);
174 return q;
177 static void
178 free_linklist (struct link **lc_linklist)
180 struct link *lp, *lp2;
182 for (lp = *lc_linklist; lp != NULL; lp = lp2) {
183 lp2 = lp->next;
184 g_free (lp);
186 *lc_linklist = NULL;
189 static int
190 is_in_linklist (struct link *lp, const char *path, struct stat *sb)
192 ino_t ino = sb->st_ino;
193 dev_t dev = sb->st_dev;
194 #ifdef ENABLE_VFS
195 struct vfs_class *vfs = vfs_get_class (path);
196 #endif /* ENABLE_VFS */
198 (void) path;
200 while (lp) {
201 #ifdef ENABLE_VFS
202 if (lp->vfs == vfs)
203 #endif /* ENABLE_VFS */
204 if (lp->ino == ino && lp->dev == dev)
205 return 1;
206 lp = lp->next;
208 return 0;
212 * Returns 0 if the inode wasn't found in the cache and 1 if it was found
213 * and a hardlink was succesfully made
215 static int
216 check_hardlinks (const char *src_name, const char *dst_name, struct stat *pstat)
218 struct link *lp;
219 struct vfs_class *my_vfs = vfs_get_class (src_name);
220 ino_t ino = pstat->st_ino;
221 dev_t dev = pstat->st_dev;
222 struct stat link_stat;
223 const char *p;
225 if (vfs_file_class_flags (src_name) & VFSF_NOLINKS)
226 return 0;
228 for (lp = linklist; lp != NULL; lp = lp->next)
229 if (lp->vfs == my_vfs && lp->ino == ino && lp->dev == dev) {
230 if (!mc_stat (lp->name, &link_stat) && link_stat.st_ino == ino
231 && link_stat.st_dev == dev
232 && vfs_get_class (lp->name) == my_vfs) {
233 p = strchr (lp->name, 0) + 1; /* i.e. where the `name' file
234 was copied to */
235 if (vfs_get_class (dst_name) == vfs_get_class (p)) {
236 if (!mc_stat (p, &link_stat)) {
237 if (!mc_link (p, dst_name))
238 return 1;
242 message (D_ERROR, MSG_ERROR, _(" Cannot make the hardlink "));
243 return 0;
245 lp = (struct link *) g_try_malloc (sizeof (struct link) + strlen (src_name)
246 + strlen (dst_name) + 1);
247 if (lp) {
248 char *lpdstname;
249 lp->vfs = my_vfs;
250 lp->ino = ino;
251 lp->dev = dev;
252 strcpy (lp->name, src_name);
253 lpdstname = lp->name + strlen(lp->name) + 1;
254 strcpy (lpdstname, dst_name);
255 lp->next = linklist;
256 linklist = lp;
258 return 0;
262 * Duplicate the contents of the symbolic link src_path in dst_path.
263 * Try to make a stable symlink if the option "stable symlink" was
264 * set in the file mask dialog.
265 * If dst_path is an existing symlink it will be deleted silently
266 * (upper levels take already care of existing files at dst_path).
268 static FileProgressStatus
269 make_symlink (FileOpContext *ctx, const char *src_path, const char *dst_path)
271 char link_target[MC_MAXPATHLEN];
272 int len;
273 FileProgressStatus return_status;
274 struct stat sb;
275 gboolean dst_is_symlink;
277 dst_is_symlink = (mc_lstat (dst_path, &sb) == 0) && S_ISLNK (sb.st_mode);
279 retry_src_readlink:
280 len = mc_readlink (src_path, link_target, MC_MAXPATHLEN - 1);
281 if (len < 0) {
282 return_status =
283 file_error (_(" Cannot read source link \"%s\" \n %s "),
284 src_path);
285 if (return_status == FILE_RETRY)
286 goto retry_src_readlink;
287 return return_status;
289 link_target[len] = 0;
291 if (ctx->stable_symlinks)
292 if (!vfs_file_is_local (src_path) || !vfs_file_is_local (dst_path)) {
293 message (D_ERROR, MSG_ERROR,
294 _(" Cannot make stable symlinks across "
295 "non-local filesystems: \n\n"
296 " Option Stable Symlinks will be disabled "));
297 ctx->stable_symlinks = FALSE;
300 if (ctx->stable_symlinks && !g_path_is_absolute (link_target)) {
301 char *p, *q, *s;
303 const char *r = strrchr (src_path, PATH_SEP);
305 if (r) {
306 p = g_strndup (src_path, r - src_path + 1);
307 if (g_path_is_absolute (dst_path))
308 q = g_strdup (dst_path);
309 else
310 q = g_strconcat (p, dst_path, (char *) NULL);
311 s = strrchr (q, PATH_SEP);
312 if (s) {
313 s[1] = 0;
314 s = g_strconcat (p, link_target, (char *) NULL);
315 g_free (p);
316 g_strlcpy (link_target, s, sizeof (link_target));
317 g_free (s);
318 s = diff_two_paths (q, link_target);
319 if (s) {
320 g_strlcpy (link_target, s, sizeof (link_target));
321 g_free (s);
323 } else
324 g_free (p);
325 g_free (q);
328 retry_dst_symlink:
329 if (mc_symlink (link_target, dst_path) == 0)
330 /* Success */
331 return FILE_CONT;
333 * if dst_exists, it is obvious that this had failed.
334 * We can delete the old symlink and try again...
336 if (dst_is_symlink) {
337 if (!mc_unlink (dst_path))
338 if (mc_symlink (link_target, dst_path) == 0)
339 /* Success */
340 return FILE_CONT;
342 return_status =
343 file_error (_(" Cannot create target symlink \"%s\" \n %s "),
344 dst_path);
345 if (return_status == FILE_RETRY)
346 goto retry_dst_symlink;
347 return return_status;
350 static FileProgressStatus
351 progress_update_one (FileOpTotalContext *tctx, FileOpContext *ctx, off_t add, gboolean is_toplevel_file)
353 struct timeval tv_current;
354 static struct timeval tv_start = {};
356 if (is_toplevel_file || ctx->progress_totals_computed) {
357 tctx->progress_count++;
358 tctx->progress_bytes += add;
360 if (tv_start.tv_sec == 0) {
361 gettimeofday (&tv_start, (struct timezone *) NULL);
363 gettimeofday (&tv_current, (struct timezone *) NULL);
364 if ((tv_current.tv_sec - tv_start.tv_sec) > FILEOP_UPDATE_INTERVAL)
366 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
367 file_progress_show_total (tctx, ctx, tctx->progress_bytes, TRUE);
368 tv_start.tv_sec = tv_current.tv_sec;
371 return check_progress_buttons (ctx);
374 /* Status of the destination file */
375 typedef enum {
376 DEST_NONE = 0, /* Not created */
377 DEST_SHORT = 1, /* Created, not fully copied */
378 DEST_FULL = 2 /* Created, fully copied */
379 } dest_status_t;
381 static FileProgressStatus
382 real_warn_same_file (enum OperationMode mode, const char *fmt,
383 const char *a, const char *b)
385 char *msg;
386 int result = 0;
387 const char *head_msg;
389 head_msg = mode == Foreground ? MSG_ERROR :
390 _(" Background process error ");
392 msg = g_strdup_printf (fmt, a, b);
393 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
394 g_free(msg);
395 do_refresh ();
396 if ( result ) { /* 1 == Abort */
397 return FILE_ABORT;
398 } else {
399 return FILE_SKIP;
403 #ifdef WITH_BACKGROUND
404 static FileProgressStatus
405 warn_same_file (const char *fmt, const char *a, const char *b)
407 union {
408 void *p;
409 FileProgressStatus (*f) (enum OperationMode, const char *fmt,
410 const char *a, const char *b);
411 } pntr;
412 pntr.f = real_warn_same_file;
414 if (we_are_background)
415 return parent_call (pntr.p, NULL, 3, strlen (fmt),
416 fmt, strlen(a), a, strlen(b), b);
417 else
418 return real_warn_same_file (Foreground, fmt, a, b);
420 #else
421 static FileProgressStatus
422 warn_same_file (const char *fmt, const char *a, const char *b)
424 return real_warn_same_file (Foreground, fmt, a, b);
426 #endif
428 static void
429 copy_file_file_display_progress (FileOpTotalContext *tctx, FileOpContext *ctx,
430 struct timeval tv_current, struct timeval tv_transfer_start,
431 off_t file_size, off_t n_read_total)
433 long dt;
435 /* 1. Update rotating dash after some time */
436 rotate_dash ();
438 /* 3. Compute ETA */
439 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
441 if (n_read_total) {
442 ctx->eta_secs = ((dt / (double) n_read_total) * file_size) - dt;
443 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
444 } else
445 ctx->eta_secs = 0.0;
447 /* 4. Compute BPS rate */
448 ctx->bps_time = (tv_current.tv_sec - tv_transfer_start.tv_sec);
449 if (ctx->bps_time < 1)
450 ctx->bps_time = 1;
451 ctx->bps = n_read_total / ctx->bps_time;
453 /* 5. Compute total ETA and BPS*/
454 if (ctx->progress_bytes != 0) {
455 double remain_bytes;
456 tctx->copyed_bytes = tctx->progress_bytes + n_read_total + ctx->do_reget;
457 remain_bytes = ctx->progress_bytes - tctx->copyed_bytes;
458 #if 1
460 int total_secs = tv_current.tv_sec - tctx->transfer_start.tv_sec;
462 if (total_secs < 1)
463 total_secs = 1;
464 tctx->bps = tctx->copyed_bytes / total_secs;
465 tctx->eta_secs = remain_bytes / tctx->bps;
467 #else
468 /* broken on lot of little files */
469 tctx->bps_count++;
470 tctx->bps = ( tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count;
471 tctx->eta_secs = remain_bytes / tctx->bps;
472 #endif
476 FileProgressStatus
477 copy_file_file (FileOpTotalContext *tctx, FileOpContext *ctx,
478 const char *src_path, const char *dst_path)
480 uid_t src_uid = (uid_t) -1;
481 gid_t src_gid = (gid_t) -1;
483 int src_desc, dest_desc = -1;
484 int n_read, n_written;
485 mode_t src_mode = 0; /* The mode of the source file */
486 struct stat sb, sb2;
487 struct utimbuf utb;
488 gboolean dst_exists = FALSE, appending = FALSE;
489 off_t n_read_total = 0, file_size = -1;
490 FileProgressStatus return_status, temp_status;
491 struct timeval tv_transfer_start;
492 dest_status_t dst_status = DEST_NONE;
493 int open_flags;
494 gboolean is_first_time=TRUE;
496 /* FIXME: We should not be using global variables! */
497 ctx->do_reget = 0;
498 return_status = FILE_RETRY;
500 file_progress_show_source (ctx, src_path);
501 file_progress_show_target (ctx, dst_path);
502 if (check_progress_buttons (ctx) == FILE_ABORT)
503 return FILE_ABORT;
505 mc_refresh ();
507 while (mc_stat (dst_path, &sb2) == 0) {
508 if (S_ISDIR (sb2.st_mode)) {
509 return_status =
510 file_error (_(" Cannot overwrite directory \"%s\" \n %s "),
511 dst_path);
512 if (return_status == FILE_RETRY)
513 continue;
514 return return_status;
516 dst_exists = TRUE;
517 break;
520 while ((*ctx->stat_func) (src_path, &sb)) {
521 return_status =
522 file_error (_(" Cannot stat source file \"%s\" \n %s "),
523 src_path);
524 if (return_status != FILE_RETRY)
525 return return_status;
528 if (dst_exists) {
529 /* Destination already exists */
530 if (sb.st_dev == sb2.st_dev && sb.st_ino == sb2.st_ino)
531 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "),
532 src_path, dst_path);
533 /* Should we replace destination? */
534 if (tctx->ask_overwrite) {
535 ctx->do_reget = 0;
536 return_status = query_replace (ctx, dst_path, &sb, &sb2);
537 if (return_status != FILE_CONT)
538 return return_status;
542 if (!ctx->do_append) {
543 /* Check the hardlinks */
544 if (!ctx->follow_links && sb.st_nlink > 1 &&
545 check_hardlinks (src_path, dst_path, &sb) == 1) {
546 /* We have made a hardlink - no more processing is necessary */
547 return FILE_CONT;
550 if (S_ISLNK (sb.st_mode))
551 return make_symlink (ctx, src_path, dst_path);
553 if (S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode) ||
554 S_ISFIFO (sb.st_mode) || S_ISNAM (sb.st_mode) ||
555 S_ISSOCK (sb.st_mode)) {
556 while (mc_mknod (dst_path, sb.st_mode & ctx->umask_kill,
557 sb.st_rdev) < 0) {
558 return_status = file_error (
559 _(" Cannot create special file \"%s\" \n %s "), dst_path);
560 if (return_status == FILE_RETRY)
561 continue;
562 return return_status;
564 /* Success */
566 while (ctx->preserve_uidgid
567 && mc_chown (dst_path, sb.st_uid, sb.st_gid)) {
568 temp_status = file_error (
569 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
570 if (temp_status == FILE_RETRY)
571 continue;
572 return temp_status;
574 while (ctx->preserve &&
575 mc_chmod (dst_path, sb.st_mode & ctx->umask_kill)) {
576 temp_status = file_error (
577 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
578 if (temp_status == FILE_RETRY)
579 continue;
580 return temp_status;
582 return FILE_CONT;
586 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
588 while ((src_desc = mc_open (src_path, O_RDONLY | O_LINEAR)) < 0) {
589 return_status = file_error (
590 _(" Cannot open source file \"%s\" \n %s "), src_path);
591 if (return_status == FILE_RETRY)
592 continue;
593 ctx->do_append = 0;
594 return return_status;
597 if (ctx->do_reget != 0) {
598 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget) {
599 message (D_ERROR, _("Warning"),
600 _(" Reget failed, about to overwrite file "));
601 ctx->do_reget = 0;
602 ctx->do_append = FALSE;
606 while (mc_fstat (src_desc, &sb)) {
607 return_status = file_error (
608 _(" Cannot fstat source file \"%s\" \n %s "), src_path);
609 if (return_status == FILE_RETRY)
610 continue;
611 ctx->do_append = FALSE;
612 goto ret;
614 src_mode = sb.st_mode;
615 src_uid = sb.st_uid;
616 src_gid = sb.st_gid;
617 utb.actime = sb.st_atime;
618 utb.modtime = sb.st_mtime;
619 file_size = sb.st_size;
621 open_flags = O_WRONLY;
622 if (dst_exists) {
623 if (ctx->do_append != 0)
624 open_flags |= O_APPEND;
625 else
626 open_flags |= O_CREAT | O_TRUNC;
627 } else {
628 open_flags |= O_CREAT | O_EXCL;
631 while ((dest_desc = mc_open (dst_path, open_flags, src_mode)) < 0) {
632 if (errno == EEXIST) {
633 goto ret;
635 return_status = file_error (
636 _(" Cannot create target file \"%s\" \n %s "), dst_path);
637 if (return_status == FILE_RETRY)
638 continue;
639 ctx->do_append = FALSE;
640 goto ret;
642 dst_status = DEST_SHORT; /* file opened, but not fully copied */
644 appending = ctx->do_append;
645 ctx->do_append = FALSE;
647 /* Find out the optimal buffer size. */
648 while (mc_fstat (dest_desc, &sb)) {
649 return_status = file_error (
650 _(" Cannot fstat target file \"%s\" \n %s "), dst_path);
651 if (return_status == FILE_RETRY)
652 continue;
653 goto ret;
656 ctx->eta_secs = 0.0;
657 ctx->bps = 0;
659 if (tctx->bps == 0 || (file_size/(tctx->bps)) > FILEOP_UPDATE_INTERVAL) {
660 file_progress_show (ctx, 0, file_size, "", TRUE);
661 } else {
662 file_progress_show (ctx, 1, 1, "", TRUE);
664 return_status = check_progress_buttons (ctx);
665 mc_refresh ();
667 if (return_status != FILE_CONT)
668 goto ret;
671 struct timeval tv_current, tv_last_update, tv_last_input;
672 int secs, update_secs;
673 const char *stalled_msg="";
675 tv_last_update = tv_transfer_start;
677 for (;;) {
678 char buf[BUF_8K];
680 /* src_read */
681 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0))
682 n_read = -1;
683 else
684 while ((n_read = mc_read (src_desc, buf, sizeof (buf))) < 0) {
685 return_status = file_error (
686 _(" Cannot read source file \"%s\" \n %s "), src_path);
687 if (return_status == FILE_RETRY)
688 continue;
689 goto ret;
691 if (n_read == 0)
692 break;
694 gettimeofday (&tv_current, NULL);
696 if (n_read > 0) {
697 char *t = buf;
698 n_read_total += n_read;
700 /* Windows NT ftp servers report that files have no
701 * permissions: -------, so if we happen to have actually
702 * read something, we should fix the permissions.
704 if ((src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == 0)
705 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
706 gettimeofday (&tv_last_input, NULL);
708 /* dst_write */
709 while ((n_written =
710 mc_write (dest_desc, t, n_read)) < n_read) {
711 if (n_written > 0) {
712 n_read -= n_written;
713 t += n_written;
714 continue;
716 return_status =
717 file_error (_(" Cannot write target file \"%s\" \n %s "),
718 dst_path);
719 if (return_status != FILE_RETRY)
720 goto ret;
723 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
724 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
726 if (is_first_time || secs > FILEOP_UPDATE_INTERVAL )
728 copy_file_file_display_progress(tctx, ctx,
729 tv_current,
730 tv_transfer_start,
731 file_size,
732 n_read_total);
733 tv_last_update = tv_current;
735 is_first_time = FALSE;
737 if (update_secs > FILEOP_STALLING_INTERVAL) {
738 stalled_msg = _("(stalled)");
741 gboolean force_update =
742 (tv_current.tv_sec - tctx->transfer_start.tv_sec) > FILEOP_UPDATE_INTERVAL;
743 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
744 file_progress_show_total (tctx, ctx, tctx->progress_bytes + n_read_total + ctx->do_reget,
745 force_update);
748 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size, stalled_msg,
749 force_update);
751 mc_refresh ();
753 return_status = check_progress_buttons (ctx);
755 if (return_status != FILE_CONT) {
756 mc_refresh ();
757 goto ret;
762 dst_status = DEST_FULL; /* copy successful, don't remove target file */
764 ret:
765 while (src_desc != -1 && mc_close (src_desc) < 0) {
766 temp_status = file_error (
767 _(" Cannot close source file \"%s\" \n %s "), src_path);
768 if (temp_status == FILE_RETRY)
769 continue;
770 if (temp_status == FILE_ABORT)
771 return_status = temp_status;
772 break;
775 while (dest_desc != -1 && mc_close (dest_desc) < 0) {
776 temp_status = file_error (
777 _(" Cannot close target file \"%s\" \n %s "), dst_path);
778 if (temp_status == FILE_RETRY)
779 continue;
780 return_status = temp_status;
781 break;
784 if (dst_status == DEST_SHORT) {
785 /* Remove short file */
786 int result;
787 result = query_dialog (Q_("DialogTitle|Copy"),
788 _("Incomplete file was retrieved. Keep it?"),
789 D_ERROR, 2, _("&Delete"), _("&Keep"));
790 if (result == 0)
791 mc_unlink (dst_path);
792 } else if (dst_status == DEST_FULL) {
793 /* Copy has succeeded */
794 if (!appending && ctx->preserve_uidgid) {
795 while (mc_chown (dst_path, src_uid, src_gid)) {
796 temp_status = file_error (
797 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
798 if (temp_status == FILE_RETRY)
799 continue;
800 return_status = temp_status;
801 break;
805 if (!appending) {
806 if (ctx->preserve){
807 while (mc_chmod (dst_path, (src_mode & ctx->umask_kill))) {
808 temp_status = file_error (
809 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
810 if (temp_status != FILE_RETRY) {
811 return_status = temp_status;
812 break;
815 } else {
816 src_mode = umask(-1);
817 umask(src_mode);
818 src_mode = 0100666 & ~src_mode;
819 mc_chmod (dst_path, (src_mode & ctx->umask_kill));
821 mc_utime (dst_path, &utb);
825 if (return_status == FILE_CONT)
826 return_status = progress_update_one (tctx, ctx, file_size, tctx->is_toplevel_file);
828 return return_status;
831 * I think these copy_*_* functions should have a return type.
832 * anyway, this function *must* have two directories as arguments.
834 /* FIXME: This function needs to check the return values of the
835 function calls */
836 FileProgressStatus
837 copy_dir_dir (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, const char *_d,
838 gboolean toplevel, gboolean move_over, gboolean do_delete,
839 struct link *parent_dirs)
841 struct dirent *next;
842 struct stat buf, cbuf;
843 DIR *reading;
844 char *dest_dir = NULL;
845 FileProgressStatus return_status = FILE_CONT;
846 struct utimbuf utb;
847 struct link *lp;
848 char *d;
850 d = strutils_shell_unescape (_d);
852 /* First get the mode of the source dir */
853 retry_src_stat:
854 if ((*ctx->stat_func) (s, &cbuf)) {
855 return_status =
856 file_error (_(" Cannot stat source directory \"%s\" \n %s "), s);
857 if (return_status == FILE_RETRY)
858 goto retry_src_stat;
859 goto ret_fast;
862 if (is_in_linklist (dest_dirs, s, &cbuf)) {
863 /* Don't copy a directory we created before (we don't want to copy
864 infinitely if a directory is copied into itself) */
865 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
866 return_status = FILE_CONT;
867 goto ret_fast;
870 /* Hmm, hardlink to directory??? - Norbert */
871 /* FIXME: In this step we should do something
872 in case the destination already exist */
873 /* Check the hardlinks */
874 if (ctx->preserve && cbuf.st_nlink > 1
875 && check_hardlinks (s, d, &cbuf) == 1) {
876 /* We have made a hardlink - no more processing is necessary */
877 goto ret_fast;
880 if (!S_ISDIR (cbuf.st_mode)) {
881 return_status =
882 file_error (_(" Source \"%s\" is not a directory \n %s "), s);
883 if (return_status == FILE_RETRY)
884 goto retry_src_stat;
885 goto ret_fast;
888 if (is_in_linklist (parent_dirs, s, &cbuf)) {
889 /* we found a cyclic symbolic link */
890 message (D_ERROR, MSG_ERROR,
891 _(" Cannot copy cyclic symbolic link \n `%s' "), s);
892 return_status = FILE_SKIP;
893 goto ret_fast;
896 lp = g_new (struct link, 1);
897 lp->vfs = vfs_get_class (s);
898 lp->ino = cbuf.st_ino;
899 lp->dev = cbuf.st_dev;
900 lp->next = parent_dirs;
901 parent_dirs = lp;
903 retry_dst_stat:
904 /* Now, check if the dest dir exists, if not, create it. */
905 if (mc_stat (d, &buf)) {
906 /* Here the dir doesn't exist : make it ! */
907 if (move_over) {
908 if (mc_rename (s, d) == 0) {
909 return_status = FILE_CONT;
910 goto ret;
913 dest_dir = d;
914 d = NULL;
915 } else {
917 * If the destination directory exists, we want to copy the whole
918 * directory, but we only want this to happen once.
920 * Escape sequences added to the * to compiler warnings.
921 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
922 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
924 if (!S_ISDIR (buf.st_mode)) {
925 return_status = file_error(
926 _(" Destination \"%s\" must be a directory \n %s "), d);
927 if (return_status == FILE_RETRY)
928 goto retry_dst_stat;
929 goto ret;
931 /* Dive into subdir if exists */
932 if (toplevel && ctx->dive_into_subdirs) {
933 dest_dir = concat_dir_and_file (d, x_basename (s));
934 } else {
935 dest_dir = d;
936 d = NULL;
937 goto dont_mkdir;
940 while (my_mkdir (dest_dir, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU)) {
941 return_status = file_error (
942 _(" Cannot create target directory \"%s\" \n %s "), dest_dir);
943 if (return_status != FILE_RETRY)
944 goto ret;
947 lp = g_new (struct link, 1);
948 mc_stat (dest_dir, &buf);
949 lp->vfs = vfs_get_class (dest_dir);
950 lp->ino = buf.st_ino;
951 lp->dev = buf.st_dev;
952 lp->next = dest_dirs;
953 dest_dirs = lp;
955 if (ctx->preserve_uidgid) {
956 while (mc_chown (dest_dir, cbuf.st_uid, cbuf.st_gid)) {
957 return_status = file_error (
958 _(" Cannot chown target directory \"%s\" \n %s "), dest_dir);
959 if (return_status != FILE_RETRY)
960 goto ret;
964 dont_mkdir:
965 /* open the source dir for reading */
966 reading = mc_opendir (s);
967 if (reading == NULL)
968 goto ret;
970 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) {
971 char *path;
973 * Now, we don't want '.' and '..' to be created / copied at any time
975 if (!strcmp (next->d_name, "."))
976 continue;
977 if (!strcmp (next->d_name, ".."))
978 continue;
980 /* get the filename and add it to the src directory */
981 path = concat_dir_and_file (s, next->d_name);
983 (*ctx->stat_func) (path, &buf);
984 if (S_ISDIR (buf.st_mode)) {
985 char *mdpath;
987 mdpath = concat_dir_and_file (dest_dir, next->d_name);
989 * From here, we just intend to recursively copy subdirs, not
990 * the double functionality of copying different when the target
991 * dir already exists. So, we give the recursive call the flag 0
992 * meaning no toplevel.
994 return_status = copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
995 g_free (mdpath);
996 } else {
997 char *dest_file;
999 dest_file = concat_dir_and_file (dest_dir, x_basename (path));
1000 return_status = copy_file_file (tctx, ctx, path, dest_file);
1001 g_free (dest_file);
1003 if (do_delete && return_status == FILE_CONT) {
1004 if (ctx->erase_at_end) {
1005 static struct link *tail;
1006 size_t len = strlen (path);
1007 lp = g_malloc (sizeof (struct link) + len);
1008 strncpy (lp->name, path, len + 1);
1009 lp->st_mode = buf.st_mode;
1010 lp->next = NULL;
1011 if (erase_list != NULL) {
1012 tail->next = lp;
1013 tail = lp;
1014 } else
1015 erase_list = tail = lp;
1016 } else {
1017 if (S_ISDIR (buf.st_mode)) {
1018 return_status = erase_dir_iff_empty (ctx, path);
1019 } else
1020 return_status = erase_file (tctx, ctx, path, FALSE);
1023 g_free (path);
1025 mc_closedir (reading);
1027 if (ctx->preserve) {
1028 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
1029 utb.actime = cbuf.st_atime;
1030 utb.modtime = cbuf.st_mtime;
1031 mc_utime (dest_dir, &utb);
1032 } else {
1033 cbuf.st_mode = umask(-1);
1034 umask(cbuf.st_mode);
1035 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
1036 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
1039 ret:
1040 g_free (dest_dir);
1041 g_free (parent_dirs);
1042 ret_fast:
1043 g_free (d);
1044 return return_status;
1047 /* }}} */
1049 /* {{{ Move routines */
1051 static FileProgressStatus
1052 move_file_file (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, const char *d)
1054 struct stat src_stats, dst_stats;
1055 FileProgressStatus return_status = FILE_CONT;
1056 gboolean copy_done = FALSE;
1057 gboolean old_ask_overwrite;
1059 file_progress_show_source (ctx, s);
1060 file_progress_show_target (ctx, d);
1061 if (check_progress_buttons (ctx) == FILE_ABORT)
1062 return FILE_ABORT;
1064 mc_refresh ();
1066 while (mc_lstat (s, &src_stats) != 0) {
1067 /* Source doesn't exist */
1068 return_status =
1069 file_error (_(" Cannot stat file \"%s\" \n %s "), s);
1070 if (return_status != FILE_RETRY)
1071 return return_status;
1074 if (mc_lstat (d, &dst_stats) == 0) {
1075 if (src_stats.st_dev == dst_stats.st_dev
1076 && src_stats.st_ino == dst_stats.st_ino)
1077 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "), s, d);
1079 if (S_ISDIR (dst_stats.st_mode)) {
1080 message (D_ERROR, MSG_ERROR,
1081 _(" Cannot overwrite directory `%s' "), d);
1082 do_refresh ();
1083 return FILE_SKIP;
1086 if (confirm_overwrite) {
1087 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
1088 if (return_status != FILE_CONT)
1089 return return_status;
1091 /* Ok to overwrite */
1094 if (!ctx->do_append) {
1095 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks) {
1096 if ((return_status = make_symlink (ctx, s, d)) == FILE_CONT) {
1097 goto retry_src_remove;
1098 } else
1099 return return_status;
1102 if (mc_rename (s, d) == 0) {
1103 return progress_update_one (tctx, ctx, src_stats.st_size, TRUE);
1106 #if 0
1107 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1108 one nfs to the same, but on the server it is on two different
1109 filesystems. Then nfs returns EIO instead of EXDEV.
1110 Hope it will not hurt if we always in case of error try to copy/delete. */
1111 else
1112 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1114 if (errno != EXDEV) {
1115 return_status =
1116 files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s,
1118 if (return_status == FILE_RETRY)
1119 goto retry_rename;
1120 return return_status;
1122 #endif
1124 /* Failed because filesystem boundary -> copy the file instead */
1125 old_ask_overwrite = tctx->ask_overwrite;
1126 tctx->ask_overwrite = FALSE;
1127 return_status = copy_file_file (tctx, ctx, s, d);
1128 tctx->ask_overwrite = old_ask_overwrite;
1129 if (return_status != FILE_CONT)
1130 return return_status;
1132 copy_done = TRUE;
1134 file_progress_show_source (ctx, NULL);
1135 file_progress_show (ctx, 0, 0, "", FALSE);
1137 return_status = check_progress_buttons (ctx);
1138 if (return_status != FILE_CONT)
1139 return return_status;
1141 mc_refresh ();
1143 retry_src_remove:
1144 if (mc_unlink (s)) {
1145 return_status =
1146 file_error (_(" Cannot remove file \"%s\" \n %s "), s);
1147 if (return_status == FILE_RETRY)
1148 goto retry_src_remove;
1149 return return_status;
1152 if (!copy_done) {
1153 return_status = progress_update_one (tctx, ctx, src_stats.st_size, TRUE);
1156 return return_status;
1159 FileProgressStatus
1160 move_dir_dir (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, const char *d)
1162 struct stat sbuf, dbuf, destbuf;
1163 struct link *lp;
1164 char *destdir;
1165 FileProgressStatus return_status;
1166 gboolean move_over = FALSE;
1167 gboolean dstat_ok;
1169 file_progress_show_source (ctx, s);
1170 file_progress_show_target (ctx, d);
1171 if (check_progress_buttons (ctx) == FILE_ABORT)
1172 return FILE_ABORT;
1174 mc_refresh ();
1176 mc_stat (s, &sbuf);
1177 dstat_ok = (mc_stat (d, &dbuf) == 0);
1179 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
1180 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same directory "), s, d);
1182 if (!dstat_ok)
1183 destdir = g_strdup (d); /* destination doesn't exist */
1184 else if (!ctx->dive_into_subdirs) {
1185 destdir = g_strdup (d);
1186 move_over = TRUE;
1187 } else
1188 destdir = concat_dir_and_file (d, x_basename (s));
1190 /* Check if the user inputted an existing dir */
1191 retry_dst_stat:
1192 if (!mc_stat (destdir, &destbuf)) {
1193 if (move_over) {
1194 return_status = copy_dir_dir (tctx, ctx, s, destdir, FALSE, TRUE, TRUE, NULL);
1196 if (return_status != FILE_CONT)
1197 goto ret;
1198 goto oktoret;
1199 } else {
1200 if (S_ISDIR (destbuf.st_mode))
1201 return_status =
1202 file_error (_
1203 (" Cannot overwrite directory \"%s\" %s "),
1204 destdir);
1205 else
1206 return_status =
1207 file_error (_(" Cannot overwrite file \"%s\" %s "),
1208 destdir);
1209 if (return_status == FILE_RETRY)
1210 goto retry_dst_stat;
1212 g_free (destdir);
1213 return return_status;
1216 retry_rename:
1217 if (mc_rename (s, destdir) == 0) {
1218 return_status = FILE_CONT;
1219 goto ret;
1222 if (errno != EXDEV) {
1223 return_status =
1224 files_error (_
1225 (" Cannot move directory \"%s\" to \"%s\" \n %s "),
1226 s, d);
1227 if (return_status == FILE_RETRY)
1228 goto retry_rename;
1229 goto ret;
1231 /* Failed because of filesystem boundary -> copy dir instead */
1232 return_status =
1233 copy_dir_dir (tctx, ctx, s, destdir, FALSE, FALSE, TRUE, NULL);
1235 if (return_status != FILE_CONT)
1236 goto ret;
1237 oktoret:
1238 file_progress_show_source (ctx, NULL);
1239 file_progress_show (ctx, 0, 0, "", FALSE);
1241 return_status = check_progress_buttons (ctx);
1242 if (return_status != FILE_CONT)
1243 goto ret;
1245 mc_refresh ();
1246 if (ctx->erase_at_end) {
1247 for (; erase_list && return_status != FILE_ABORT;) {
1248 if (S_ISDIR (erase_list->st_mode)) {
1249 return_status =
1250 erase_dir_iff_empty (ctx, erase_list->name);
1251 } else
1252 return_status =
1253 erase_file (tctx, ctx, erase_list->name, FALSE);
1254 lp = erase_list;
1255 erase_list = erase_list->next;
1256 g_free (lp);
1259 erase_dir_iff_empty (ctx, s);
1261 ret:
1262 g_free (destdir);
1263 while (erase_list) {
1264 lp = erase_list;
1265 erase_list = erase_list->next;
1266 g_free (lp);
1268 return return_status;
1271 /* }}} */
1273 /* {{{ Erase routines */
1274 /* Don't update progress status if progress_count==NULL */
1275 static FileProgressStatus
1276 erase_file (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, gboolean is_toplevel_file)
1278 int return_status;
1279 struct stat buf;
1281 file_progress_show_deleting (ctx, s);
1282 if (check_progress_buttons (ctx) == FILE_ABORT)
1283 return FILE_ABORT;
1284 mc_refresh ();
1286 if (tctx->progress_count && mc_lstat (s, &buf)) {
1287 /* ignore, most likely the mc_unlink fails, too */
1288 buf.st_size = 0;
1291 while (mc_unlink (s)) {
1292 return_status =
1293 file_error (_(" Cannot delete file \"%s\" \n %s "), s);
1294 if (return_status != FILE_RETRY)
1295 return return_status;
1298 if (tctx->progress_count)
1299 return progress_update_one (tctx, ctx, buf.st_size, is_toplevel_file);
1300 else
1301 return FILE_CONT;
1304 static FileProgressStatus
1305 recursive_erase (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s)
1307 struct dirent *next;
1308 struct stat buf;
1309 DIR *reading;
1310 char *path;
1311 FileProgressStatus return_status = FILE_CONT;
1313 if (!strcmp (s, ".."))
1314 return FILE_RETRY;
1316 reading = mc_opendir (s);
1318 if (!reading)
1319 return FILE_RETRY;
1321 while ((next = mc_readdir (reading)) && return_status == FILE_CONT) {
1322 if (!strcmp (next->d_name, "."))
1323 continue;
1324 if (!strcmp (next->d_name, ".."))
1325 continue;
1326 path = concat_dir_and_file (s, next->d_name);
1327 if (mc_lstat (path, &buf)) {
1328 g_free (path);
1329 mc_closedir (reading);
1330 return FILE_RETRY;
1332 if (S_ISDIR (buf.st_mode))
1333 return_status =
1334 (recursive_erase (tctx, ctx, path) != FILE_CONT) ? FILE_RETRY : FILE_CONT;
1335 else
1336 return_status =
1337 erase_file (tctx, ctx, path, 0);
1338 g_free (path);
1340 mc_closedir (reading);
1341 if (return_status != FILE_CONT)
1342 return return_status;
1343 file_progress_show_deleting (ctx, s);
1344 if (check_progress_buttons (ctx) == FILE_ABORT)
1345 return FILE_ABORT;
1346 mc_refresh ();
1348 while (my_rmdir (s)) {
1349 return_status =
1350 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1351 if (return_status != FILE_RETRY)
1352 return return_status;
1355 return FILE_CONT;
1358 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1359 in the directory path points to, 0 else. */
1360 static int
1361 check_dir_is_empty (const char *path)
1363 DIR *dir;
1364 struct dirent *d;
1365 int i;
1367 dir = mc_opendir (path);
1368 if (!dir)
1369 return -1;
1371 for (i = 1, d = mc_readdir (dir); d; d = mc_readdir (dir)) {
1372 if (d->d_name[0] == '.' && (d->d_name[1] == '\0' ||
1373 (d->d_name[1] == '.'
1374 && d->d_name[2] == '\0')))
1375 continue; /* "." or ".." */
1376 i = 0;
1377 break;
1380 mc_closedir (dir);
1381 return i;
1384 FileProgressStatus
1385 erase_dir (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s)
1387 FileProgressStatus error;
1389 if (strcmp (s, "..") == 0)
1390 return FILE_SKIP;
1392 if (strcmp (s, ".") == 0)
1393 return FILE_SKIP;
1395 file_progress_show_deleting (ctx, s);
1396 if (check_progress_buttons (ctx) == FILE_ABORT)
1397 return FILE_ABORT;
1398 mc_refresh ();
1400 /* The old way to detect a non empty directory was:
1401 error = my_rmdir (s);
1402 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1403 For the linux user space nfs server (nfs-server-2.2beta29-2)
1404 we would have to check also for EIO. I hope the new way is
1405 fool proof. (Norbert)
1407 error = check_dir_is_empty (s);
1408 if (error == 0) { /* not empty */
1409 error = query_recursive (ctx, s);
1410 if (error == FILE_CONT)
1411 return recursive_erase (tctx, ctx, s);
1412 else
1413 return error;
1416 while (my_rmdir (s) == -1) {
1417 error =
1418 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1419 if (error != FILE_RETRY)
1420 return error;
1423 return FILE_CONT;
1426 static FileProgressStatus
1427 erase_dir_iff_empty (FileOpContext *ctx, const char *s)
1429 FileProgressStatus error;
1431 if (strcmp (s, "..") == 0)
1432 return FILE_SKIP;
1434 if (strcmp (s, ".") == 0)
1435 return FILE_SKIP;
1437 file_progress_show_deleting (ctx, s);
1438 if (check_progress_buttons (ctx) == FILE_ABORT)
1439 return FILE_ABORT;
1440 mc_refresh ();
1442 if (1 != check_dir_is_empty (s)) /* not empty or error */
1443 return FILE_CONT;
1445 while (my_rmdir (s)) {
1446 error =
1447 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1448 if (error != FILE_RETRY)
1449 return error;
1452 return FILE_CONT;
1455 /* }}} */
1457 /* {{{ Panel operate routines */
1460 * Return currently selected entry name or the name of the first marked
1461 * entry if there is one.
1463 static char *
1464 panel_get_file (WPanel *panel, struct stat *stat_buf)
1466 int i;
1468 if (get_current_type () == view_tree) {
1469 WTree *tree = (WTree *) get_panel_widget (get_current_index ());
1470 char *tree_name = tree_selected_name (tree);
1472 mc_stat (tree_name, stat_buf);
1473 return tree_name;
1476 if (panel->marked) {
1477 for (i = 0; i < panel->count; i++)
1478 if (panel->dir.list[i].f.marked) {
1479 *stat_buf = panel->dir.list[i].st;
1480 return panel->dir.list[i].fname;
1482 } else {
1483 *stat_buf = panel->dir.list[panel->selected].st;
1484 return panel->dir.list[panel->selected].fname;
1486 g_assert_not_reached ();
1487 return NULL;
1491 ComputeDirSizeUI *
1492 compute_dir_size_create_ui (void)
1494 ComputeDirSizeUI *ui;
1496 const char *b_name = N_("&Abort");
1498 #ifdef ENABLE_NLS
1499 b_name = _(b_name);
1500 #endif
1502 ui = g_new (ComputeDirSizeUI, 1);
1504 ui->dlg = create_dlg (0, 0, 8, COLS/2, dialog_colors, NULL,
1505 NULL, _("Directory scanning"), DLG_CENTER);
1506 ui->dirname = label_new (3, 3, "");
1507 add_widget (ui->dlg, ui->dirname);
1509 add_widget (ui->dlg,
1510 button_new (5, (ui->dlg->cols - strlen (b_name))/2,
1511 FILE_ABORT, NORMAL_BUTTON, b_name, NULL));
1513 /* We will manage the dialog without any help,
1514 that's why we have to call init_dlg */
1515 init_dlg (ui->dlg);
1517 return ui;
1520 void
1521 compute_dir_size_destroy_ui (ComputeDirSizeUI *ui)
1523 if (ui != NULL) {
1524 /* schedule to update passive panel */
1525 other_panel->dirty = 1;
1527 /* close and destroy dialog */
1528 dlg_run_done (ui->dlg);
1529 destroy_dlg (ui->dlg);
1530 g_free (ui);
1534 FileProgressStatus
1535 compute_dir_size_update_ui (const void *ui, const char *dirname)
1537 const ComputeDirSizeUI *this = (const ComputeDirSizeUI *) ui;
1538 int c;
1539 Gpm_Event event;
1541 if (ui == NULL)
1542 return FILE_CONT;
1544 label_set_text (this->dirname, name_trunc (dirname, this->dlg->cols - 6));
1546 event.x = -1; /* Don't show the GPM cursor */
1547 c = tty_get_event (&event, FALSE, FALSE);
1548 if (c == EV_NONE)
1549 return FILE_CONT;
1551 /* Reinitialize to avoid old values after events other than
1552 selecting a button */
1553 this->dlg->ret_value = FILE_CONT;
1555 dlg_process_event (this->dlg, c, &event);
1557 switch (this->dlg->ret_value) {
1558 case B_CANCEL:
1559 case FILE_ABORT:
1560 return FILE_ABORT;
1561 default:
1562 return FILE_CONT;
1567 * compute_dir_size:
1569 * Computes the number of bytes used by the files in a directory
1571 FileProgressStatus
1572 compute_dir_size (const char *dirname, const void *ui,
1573 compute_dir_size_callback cback,
1574 off_t *ret_marked, double *ret_total,
1575 gboolean compute_symlinks)
1577 int res;
1578 struct stat s;
1579 DIR *dir;
1580 struct dirent *dirent;
1581 FileProgressStatus ret = FILE_CONT;
1583 if (!compute_symlinks)
1585 res = mc_lstat (dirname, &s);
1586 if (res != 0)
1587 return ret;
1589 /* don't scan symlink to directory */
1590 if (S_ISLNK (s.st_mode)) {
1591 (*ret_marked)++;
1592 *ret_total += s.st_size;
1593 return ret;
1597 dir = mc_opendir (dirname);
1599 if (dir == NULL)
1600 return ret;
1602 while ((dirent = mc_readdir (dir)) != NULL) {
1603 char *fullname;
1605 ret = (cback != NULL) ? cback (ui, dirname) : FILE_CONT;
1607 if (ret != FILE_CONT)
1608 break;
1610 if (strcmp (dirent->d_name, ".") == 0)
1611 continue;
1612 if (strcmp (dirent->d_name, "..") == 0)
1613 continue;
1615 fullname = concat_dir_and_file (dirname, dirent->d_name);
1616 res = mc_lstat (fullname, &s);
1618 if (res != 0) {
1619 g_free (fullname);
1620 continue;
1623 if (S_ISDIR (s.st_mode)) {
1624 off_t subdir_count = 0;
1625 double subdir_bytes = 0;
1627 ret = compute_dir_size (fullname, ui, cback, &subdir_count, &subdir_bytes, compute_symlinks);
1629 if (ret != FILE_CONT) {
1630 g_free (fullname);
1631 break;
1634 *ret_marked += subdir_count;
1635 *ret_total += subdir_bytes;
1636 } else {
1637 (*ret_marked)++;
1638 *ret_total += s.st_size;
1641 g_free (fullname);
1644 mc_closedir (dir);
1646 return ret;
1650 * panel_compute_totals:
1652 * compute the number of files and the number of bytes
1653 * used up by the whole selection, recursing directories
1654 * as required. In addition, it checks to see if it will
1655 * overwrite any files by doing the copy.
1657 static FileProgressStatus
1658 panel_compute_totals (const WPanel *panel, const void *ui,
1659 compute_dir_size_callback cback,
1660 off_t *ret_marked, double *ret_total, gboolean compute_symlinks)
1662 int i;
1664 *ret_marked = 0;
1665 *ret_total = 0.0;
1667 for (i = 0; i < panel->count; i++) {
1668 struct stat *s;
1670 if (!panel->dir.list[i].f.marked)
1671 continue;
1673 s = &panel->dir.list[i].st;
1675 if (S_ISDIR (s->st_mode)) {
1676 char *dir_name;
1677 off_t subdir_count = 0;
1678 double subdir_bytes = 0;
1679 FileProgressStatus status;
1681 dir_name =
1682 concat_dir_and_file (panel->cwd, panel->dir.list[i].fname);
1684 status = compute_dir_size (dir_name, ui, cback,
1685 &subdir_count, &subdir_bytes, compute_symlinks);
1686 g_free (dir_name);
1688 if (status != FILE_CONT)
1689 return FILE_ABORT;
1691 *ret_marked += subdir_count;
1692 *ret_total += subdir_bytes;
1693 } else {
1694 (*ret_marked)++;
1695 *ret_total += s->st_size;
1699 return FILE_CONT;
1702 /* Initialize variables for progress bars */
1703 static FileProgressStatus
1704 panel_operate_init_totals (FileOperation operation,
1705 const WPanel *panel, const char *source,
1706 FileOpContext *ctx)
1708 FileProgressStatus status;
1710 if (operation != OP_MOVE && verbose && file_op_compute_totals) {
1711 ComputeDirSizeUI *ui;
1713 ui = compute_dir_size_create_ui ();
1715 if (source != NULL)
1716 status = compute_dir_size (source, ui, compute_dir_size_update_ui,
1717 &ctx->progress_count, &ctx->progress_bytes, ctx->follow_links);
1718 else
1719 status = panel_compute_totals (panel, ui, compute_dir_size_update_ui,
1720 &ctx->progress_count, &ctx->progress_bytes, ctx->follow_links);
1722 compute_dir_size_destroy_ui (ui);
1724 ctx->progress_totals_computed = (status == FILE_CONT);
1725 } else {
1726 status = FILE_CONT;
1727 ctx->progress_count = panel->marked;
1728 ctx->progress_bytes = panel->total;
1729 ctx->progress_totals_computed = FALSE;
1732 return status;
1736 * This array introduced to avoid translation problems. The former (op_names)
1737 * is assumed to be nouns, suitable in dialog box titles; this one should
1738 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
1739 * (I don't use spaces around the words, because someday they could be
1740 * dropped, when widgets get smarter)
1743 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
1744 static const char *op_names1[] = {
1745 N_("FileOperation|Copy"),
1746 N_("FileOperation|Move"),
1747 N_("FileOperation|Delete")
1751 * These are formats for building a prompt. Parts encoded as follows:
1752 * %o - operation from op_names1
1753 * %f - file/files or files/directories, as appropriate
1754 * %m - "with source mask" or question mark for delete
1755 * %s - source name (truncated)
1756 * %d - number of marked files
1757 * %e - "to:" or question mark for delete
1759 * xgettext:no-c-format */
1760 static const char *one_format = N_("%o %f \"%s\"%m");
1761 /* xgettext:no-c-format */
1762 static const char *many_format = N_("%o %d %f%m");
1764 static const char *prompt_parts[] = {
1765 N_("file"),
1766 N_("files"),
1767 N_("directory"),
1768 N_("directories"),
1769 N_("files/directories"),
1770 N_(" with source mask:"),
1771 N_(" to:")
1774 static const char *question_format = N_("%s?");
1777 * Generate user prompt for panel operation.
1778 * single_source is the name if the source entry or NULL for multiple
1779 * entries.
1780 * src_stat is only used when single_source is not NULL.
1782 static char *
1783 panel_operate_generate_prompt (const WPanel *panel, FileOperation operation,
1784 gboolean single_source,
1785 const struct stat *src_stat)
1787 const char *sp, *cp;
1788 char format_string[BUF_MEDIUM];
1789 char *dp = format_string;
1790 gboolean build_question = FALSE;
1792 #ifdef ENABLE_NLS
1793 static gboolean i18n_flag = FALSE;
1794 if (!i18n_flag) {
1795 size_t i;
1797 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1798 op_names1[i] = Q_(op_names1[i]);
1800 for (i = sizeof (prompt_parts) / sizeof (prompt_parts[0]); i--;)
1801 prompt_parts[i] = _(prompt_parts[i]);
1803 one_format = _(one_format);
1804 many_format = _(many_format);
1805 question_format = _(question_format);
1806 i18n_flag = TRUE;
1808 #endif /* ENABLE_NLS */
1810 sp = single_source ? one_format : many_format;
1812 while (*sp != '\0') {
1813 switch (*sp) {
1814 case '%':
1815 cp = NULL;
1816 switch (sp[1]) {
1817 case 'o':
1818 cp = op_names1[operation];
1819 break;
1820 case 'm':
1821 if (operation == OP_DELETE) {
1822 cp = "";
1823 build_question = TRUE;
1824 } else
1825 cp = prompt_parts[5];
1826 break;
1827 case 'e':
1828 if (operation == OP_DELETE) {
1829 cp = "";
1830 build_question = TRUE;
1831 } else
1832 cp = prompt_parts[6];
1833 break;
1834 case 'f':
1835 if (single_source) {
1836 cp = S_ISDIR (src_stat->
1837 st_mode) ? prompt_parts[2] : prompt_parts[0];
1838 } else {
1839 cp = (panel->marked == panel->dirs_marked)
1840 ? prompt_parts[3]
1841 : (panel->dirs_marked ? prompt_parts[4] : prompt_parts[1]);
1843 break;
1844 default:
1845 *dp++ = *sp++;
1847 if (cp != NULL) {
1848 sp += 2;
1849 while (*cp != '\0')
1850 *dp++ = *cp++;
1852 break;
1853 default:
1854 *dp++ = *sp++;
1857 *dp = '\0';
1859 if (build_question) {
1860 char tmp[BUF_MEDIUM];
1862 memmove (tmp, format_string, sizeof (tmp));
1863 g_snprintf (format_string, sizeof (format_string),
1864 question_format, tmp);
1867 return g_strdup (format_string);
1870 #ifdef WITH_BACKGROUND
1871 static int
1872 end_bg_process (FileOpContext *ctx, enum OperationMode mode) {
1873 int pid = ctx->pid;
1875 (void) mode;
1876 ctx->pid = 0;
1878 unregister_task_with_pid(pid);
1879 // file_op_context_destroy(ctx);
1880 return 1;
1882 #endif
1885 * panel_operate:
1887 * Performs one of the operations on the selection on the source_panel
1888 * (copy, delete, move).
1890 * Returns TRUE if did change the directory
1891 * structure, Returns FALSE if user aborted
1893 * force_single forces operation on the current entry and affects
1894 * default destination. Current filename is used as default.
1896 gboolean
1897 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
1899 WPanel *panel = (WPanel *) source_panel;
1900 const gboolean single_entry = force_single || (panel->marked <= 1)
1901 || (get_current_type () == view_tree);
1903 char *source = NULL;
1904 #ifdef WITH_FULL_PATHS
1905 char *source_with_path = NULL;
1906 #else
1907 # define source_with_path source
1908 #endif /* !WITH_FULL_PATHS */
1909 char *dest = NULL;
1910 char *temp = NULL;
1911 char *save_cwd = NULL, *save_dest = NULL;
1912 struct stat src_stat;
1913 int i;
1914 FileProgressStatus value;
1915 FileOpContext *ctx;
1916 FileOpTotalContext *tctx;
1918 gboolean do_bg = FALSE; /* do background operation? */
1920 #ifdef ENABLE_NLS
1921 static gboolean i18n_flag = FALSE;
1922 if (!i18n_flag) {
1923 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1924 op_names[i] = Q_(op_names[i]);
1925 i18n_flag = TRUE;
1927 #endif /* ENABLE_NLS */
1929 free_linklist (&linklist);
1930 free_linklist (&dest_dirs);
1932 /* Update panel contents to avoid actions on deleted files */
1933 if (!panel->is_panelized) {
1934 update_panels (UP_RELOAD, UP_KEEPSEL);
1935 repaint_screen ();
1938 if (single_entry) {
1939 if (force_single) {
1940 source = selection (panel)->fname;
1941 src_stat = selection (panel)->st;
1942 } else {
1943 source = panel_get_file (panel, &src_stat);
1946 if (!strcmp (source, "..")) {
1947 message (D_ERROR, MSG_ERROR, _(" Cannot operate on \"..\"! "));
1948 return FALSE;
1952 ctx = file_op_context_new (operation);
1953 tctx = file_op_total_context_new ();
1954 gettimeofday (&(tctx->transfer_start), (struct timezone *) NULL);
1956 /* Show confirmation dialog */
1957 if (operation != OP_DELETE) {
1958 char *dest_dir;
1959 char *dest_dir_;
1960 char *format;
1962 /* Forced single operations default to the original name */
1963 if (force_single)
1964 dest_dir = source;
1965 else if (get_other_type () == view_listing)
1966 dest_dir = other_panel->cwd;
1967 else
1968 dest_dir = panel->cwd;
1970 * Add trailing backslash only when do non-local ops.
1971 * It saves user from occasional file renames (when destination
1972 * dir is deleted)
1974 if (!force_single
1975 && dest_dir[0] != '\0'
1976 && dest_dir[strlen (dest_dir) - 1] != PATH_SEP) {
1977 /* add trailing separator */
1978 dest_dir_ = g_strconcat (dest_dir, PATH_SEP_STR, (char *) NULL);
1979 } else {
1980 /* just copy */
1981 dest_dir_ = g_strdup (dest_dir);
1983 if (dest_dir_ == NULL) {
1984 file_op_total_context_destroy (tctx);
1985 file_op_context_destroy (ctx);
1986 return FALSE;
1989 /* Generate confirmation prompt */
1990 format = panel_operate_generate_prompt (panel, operation,
1991 source != NULL, &src_stat);
1993 dest = file_mask_dialog (ctx, operation, source != NULL, format,
1994 source != NULL ? (void *) source
1995 : (void *) &panel->marked,
1996 dest_dir_, &do_bg);
1998 g_free (format);
1999 g_free (dest_dir_);
2001 if (dest == NULL || dest[0] == '\0') {
2002 file_op_total_context_destroy (tctx);
2003 file_op_context_destroy (ctx);
2004 g_free (dest);
2005 return FALSE;
2007 } else if (confirm_delete) {
2008 char *format;
2009 char fmd_buf [BUF_MEDIUM];
2011 /* Generate confirmation prompt */
2012 format = panel_operate_generate_prompt (panel, OP_DELETE,
2013 source != NULL, &src_stat);
2015 if (source == NULL)
2016 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
2017 else {
2018 const int fmd_xlen = 64;
2019 i = fmd_xlen - str_term_width1 (format) - 4;
2020 g_snprintf (fmd_buf, sizeof (fmd_buf),
2021 format, str_trunc (source, i));
2024 g_free (format);
2026 if (safe_delete)
2027 query_set_sel (1);
2029 i = query_dialog (op_names[operation], fmd_buf, D_ERROR, 2,
2030 _("&Yes"), _("&No"));
2032 if (i != 0) {
2033 file_op_total_context_destroy (tctx);
2034 file_op_context_destroy (ctx);
2035 return FALSE;
2040 filegui_dialog_type_t dialog_type;
2042 if (operation == OP_DELETE) {
2043 dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
2044 } else {
2045 dialog_type = !((operation != OP_COPY) || (single_entry) || (force_single))
2046 ? FILEGUI_DIALOG_MULTI_ITEM
2047 : FILEGUI_DIALOG_ONE_ITEM;
2049 if ((single_entry) && (operation == OP_COPY) && S_ISDIR (selection (panel)->st.st_mode))
2050 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
2053 /* Background also need ctx->ui, but not full */
2054 if (do_bg)
2055 file_op_context_create_ui_without_init (ctx, 1, dialog_type);
2056 else
2057 file_op_context_create_ui (ctx, 1, dialog_type);
2060 #ifdef WITH_BACKGROUND
2061 /* Did the user select to do a background operation? */
2062 if (do_bg) {
2063 int v;
2065 v = do_background (ctx,
2066 g_strconcat (op_names[operation], ": ",
2067 panel->cwd, (char *) NULL));
2068 if (v == -1) {
2069 message (D_ERROR, MSG_ERROR,
2070 _(" Sorry, I could not put the job in background "));
2073 /* If we are the parent */
2074 if (v == 1) {
2075 mc_setctl (panel->cwd, VFS_SETCTL_FORGET, NULL);
2076 mc_setctl (dest, VFS_SETCTL_FORGET, NULL);
2077 /* file_op_context_destroy (ctx); */
2078 return FALSE;
2081 #endif /* WITH_BACKGROUND */
2083 /* Initialize things */
2084 /* We do not want to trash cache every time file is
2085 created/touched. However, this will make our cache contain
2086 invalid data. */
2087 if ((dest != NULL)
2088 && (mc_setctl (dest, VFS_SETCTL_STALE_DATA, (void *) 1)))
2089 save_dest = g_strdup (dest);
2091 if ((panel->cwd[0] != '\0')
2092 && (mc_setctl (panel->cwd, VFS_SETCTL_STALE_DATA, (void *) 1)))
2093 save_cwd = g_strdup (panel->cwd);
2095 /* Now, let's do the job */
2097 /* This code is only called by the tree and panel code */
2098 if (single_entry) {
2099 /* We now have ETA in all cases */
2101 /* One file: FIXME mc_chdir will take user out of any vfs */
2102 if (operation != OP_COPY && get_current_type () == view_tree)
2103 mc_chdir (PATH_SEP_STR);
2105 /* The source and src_stat variables have been initialized before */
2106 #ifdef WITH_FULL_PATHS
2107 source_with_path = concat_dir_and_file (panel->cwd, source);
2108 #endif /* WITH_FULL_PATHS */
2110 if (panel_operate_init_totals (operation, panel,
2111 source_with_path, ctx) == FILE_CONT) {
2112 if (operation == OP_DELETE) {
2113 if (S_ISDIR (src_stat.st_mode))
2114 value = erase_dir (tctx, ctx, source_with_path);
2115 else
2116 value = erase_file (tctx, ctx, source_with_path, 1);
2117 } else {
2118 temp = transform_source (ctx, source_with_path);
2119 if (temp == NULL)
2120 value = transform_error;
2121 else {
2122 char *repl_dest, *temp2;
2124 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2125 temp2 = concat_dir_and_file (repl_dest, temp);
2126 g_free (temp);
2127 g_free (repl_dest);
2128 g_free (dest);
2129 dest = temp2;
2131 switch (operation) {
2132 case OP_COPY:
2133 /* we use file_mask_op_follow_links only with OP_COPY */
2134 ctx->stat_func (source_with_path, &src_stat);
2136 if (S_ISDIR (src_stat.st_mode))
2137 value = copy_dir_dir (tctx, ctx, source_with_path, dest,
2138 TRUE, FALSE, FALSE, NULL);
2139 else
2140 value = copy_file_file (tctx, ctx, source_with_path, dest);
2141 break;
2143 case OP_MOVE:
2144 if (S_ISDIR (src_stat.st_mode))
2145 value = move_dir_dir (tctx, ctx, source_with_path, dest);
2146 else
2147 value = move_file_file (tctx, ctx, source_with_path, dest);
2148 break;
2150 default:
2151 /* Unknown file operation */
2152 abort ();
2155 } /* Copy or move operation */
2157 if ((value == FILE_CONT) && !force_single)
2158 unmark_files (panel);
2160 } else {
2161 /* Many files */
2163 /* Check destination for copy or move operation */
2164 while (operation != OP_DELETE) {
2165 int dst_result;
2166 struct stat dst_stat;
2168 dst_result = mc_stat (dest, &dst_stat);
2170 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
2171 break;
2173 if (file_error (_(" Destination \"%s\" must be a directory \n %s "),
2174 dest) != FILE_RETRY)
2175 goto clean_up;
2178 if (panel_operate_init_totals (operation, panel, NULL, ctx) == FILE_CONT) {
2179 /* Loop for every file, perform the actual copy operation */
2180 for (i = 0; i < panel->count; i++) {
2181 if (!panel->dir.list[i].f.marked)
2182 continue; /* Skip the unmarked ones */
2184 source = panel->dir.list[i].fname;
2185 src_stat = panel->dir.list[i].st;
2187 #ifdef WITH_FULL_PATHS
2188 g_free (source_with_path);
2189 source_with_path = concat_dir_and_file (panel->cwd, source);
2190 #endif /* WITH_FULL_PATHS */
2192 if (operation == OP_DELETE) {
2193 if (S_ISDIR (src_stat.st_mode))
2194 value = erase_dir (tctx, ctx, source_with_path);
2195 else
2196 value = erase_file (tctx, ctx, source_with_path, 1);
2197 } else {
2198 temp = transform_source (ctx, source_with_path);
2200 if (temp == NULL)
2201 value = transform_error;
2202 else {
2203 char *temp2, *temp3, *repl_dest;
2205 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2206 temp2 = concat_dir_and_file (repl_dest, temp);
2207 g_free (temp);
2208 g_free (repl_dest);
2209 temp3 = source_with_path;
2210 source_with_path = strutils_shell_unescape (source_with_path);
2211 g_free (temp3);
2212 temp3 = temp2;
2213 temp2 = strutils_shell_unescape (temp2);
2214 g_free (temp3);
2216 switch (operation) {
2217 case OP_COPY:
2218 /* we use file_mask_op_follow_links only with OP_COPY */
2219 ctx->stat_func (source_with_path, &src_stat);
2220 if (S_ISDIR (src_stat.st_mode))
2221 value = copy_dir_dir (tctx, ctx, source_with_path, temp2,
2222 TRUE, FALSE, FALSE, NULL);
2223 else
2224 value = copy_file_file (tctx, ctx, source_with_path, temp2);
2225 free_linklist (&dest_dirs);
2226 break;
2228 case OP_MOVE:
2229 if (S_ISDIR (src_stat.st_mode))
2230 value = move_dir_dir (tctx, ctx, source_with_path, temp2);
2231 else
2232 value = move_file_file (tctx, ctx, source_with_path, temp2);
2233 break;
2235 default:
2236 /* Unknown file operation */
2237 abort ();
2240 g_free (temp2);
2242 } /* Copy or move operation */
2244 if (value == FILE_ABORT)
2245 break;
2247 if (value == FILE_CONT)
2248 do_file_mark (panel, i, 0);
2250 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2252 if (verbose) {
2253 file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
2255 if (operation != OP_DELETE)
2256 file_progress_show (ctx, 0, 0, "", FALSE);
2259 if (check_progress_buttons (ctx) == FILE_ABORT)
2260 break;
2262 mc_refresh ();
2263 } /* Loop for every file */
2265 } /* Many entries */
2267 clean_up:
2268 /* Clean up */
2269 if (save_cwd != NULL) {
2270 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
2271 g_free (save_cwd);
2274 if (save_dest != NULL) {
2275 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
2276 g_free (save_dest);
2279 free_linklist (&linklist);
2280 free_linklist (&dest_dirs);
2281 #ifdef WITH_FULL_PATHS
2282 g_free (source_with_path);
2283 #endif /* WITH_FULL_PATHS */
2284 g_free (dest);
2285 g_free (ctx->dest_mask);
2286 ctx->dest_mask = NULL;
2288 #ifdef WITH_BACKGROUND
2289 /* Let our parent know we are saying bye bye */
2290 if (we_are_background) {
2291 int cur_pid = getpid();
2292 /* Send pid to parent with child context, it is fork and
2293 don't modify real parent ctx */
2294 ctx->pid = cur_pid;
2295 parent_call ((void *) end_bg_process, ctx, 0);
2297 vfs_shut ();
2298 _exit (0);
2300 #endif /* WITH_BACKGROUND */
2302 file_op_context_destroy (ctx);
2303 return TRUE;
2306 /* }}} */
2308 /* {{{ Query/status report routines */
2310 static FileProgressStatus
2311 real_do_file_error (enum OperationMode mode, const char *error)
2313 int result;
2314 const char *msg;
2316 msg = mode == Foreground ? MSG_ERROR : _(" Background process error ");
2317 result =
2318 query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("&Retry"),
2319 _("&Abort"));
2321 switch (result) {
2322 case 0:
2323 do_refresh ();
2324 return FILE_SKIP;
2326 case 1:
2327 do_refresh ();
2328 return FILE_RETRY;
2330 case 2:
2331 default:
2332 return FILE_ABORT;
2336 /* Report error with one file */
2337 FileProgressStatus
2338 file_error (const char *format, const char *file)
2340 char buf [BUF_MEDIUM];
2342 g_snprintf (buf, sizeof (buf), format,
2343 path_trunc (file, 30), unix_error_string (errno));
2345 return do_file_error (buf);
2348 /* Report error with two files */
2349 static FileProgressStatus
2350 files_error (const char *format, const char *file1, const char *file2)
2352 char buf [BUF_MEDIUM];
2353 char *nfile1 = g_strdup (path_trunc (file1, 15));
2354 char *nfile2 = g_strdup (path_trunc (file2, 15));
2356 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2,
2357 unix_error_string (errno));
2359 g_free (nfile1);
2360 g_free (nfile2);
2362 return do_file_error (buf);
2365 static FileProgressStatus
2366 real_query_recursive (FileOpContext *ctx, enum OperationMode mode, const char *s)
2368 gchar *text;
2370 if (ctx->recursive_result < RECURSIVE_ALWAYS) {
2371 const char *msg = mode == Foreground
2372 ? _("\n Directory not empty. \n"
2373 " Delete it recursively? ")
2374 : _("\n Background process: Directory not empty \n"
2375 " Delete it recursively? ");
2376 text = g_strconcat (_(" Delete: "), path_trunc (s, 30), " ", (char *) NULL);
2378 if (safe_delete)
2379 query_set_sel (1);
2381 ctx->recursive_result =
2382 (FileCopyMode) query_dialog (text, msg, D_ERROR, 5,
2383 _("&Yes"), _("&No"),
2384 _("A&ll"), _("Non&e"),
2385 _("&Abort"));
2387 if (ctx->recursive_result != RECURSIVE_ABORT)
2388 do_refresh ();
2389 g_free (text);
2392 switch (ctx->recursive_result) {
2393 case RECURSIVE_YES:
2394 case RECURSIVE_ALWAYS:
2395 return FILE_CONT;
2397 case RECURSIVE_NO:
2398 case RECURSIVE_NEVER:
2399 return FILE_SKIP;
2401 case RECURSIVE_ABORT:
2402 default:
2403 return FILE_ABORT;
2407 #ifdef WITH_BACKGROUND
2408 static FileProgressStatus
2409 do_file_error (const char *str)
2411 union {
2412 void *p;
2413 FileProgressStatus (*f) (enum OperationMode, const char *);
2414 } pntr;
2415 pntr.f = real_do_file_error;
2417 if (we_are_background)
2418 return parent_call (pntr.p, NULL, 1, strlen (str),
2419 str);
2420 else
2421 return real_do_file_error (Foreground, str);
2424 static FileProgressStatus
2425 query_recursive (FileOpContext *ctx, const char *s)
2427 union {
2428 void *p;
2429 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *);
2430 } pntr;
2431 pntr.f = real_query_recursive;
2433 if (we_are_background)
2434 return parent_call (pntr.p, ctx, 1, strlen (s), s);
2435 else
2436 return real_query_recursive (ctx, Foreground, s);
2439 static FileProgressStatus
2440 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2441 struct stat *_d_stat)
2443 union {
2444 void *p;
2445 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *,
2446 struct stat *, struct stat *);
2447 } pntr;
2448 pntr.f = file_progress_real_query_replace;
2450 if (we_are_background)
2451 return parent_call (pntr.p,
2452 ctx,
2454 strlen (destname), destname,
2455 sizeof (struct stat), _s_stat,
2456 sizeof (struct stat), _d_stat);
2457 else
2458 return file_progress_real_query_replace (ctx, Foreground, destname,
2459 _s_stat, _d_stat);
2462 #else
2463 static FileProgressStatus
2464 do_file_error (const char *str)
2466 return real_do_file_error (Foreground, str);
2469 static FileProgressStatus
2470 query_recursive (FileOpContext *ctx, const char *s)
2472 return real_query_recursive (ctx, Foreground, s);
2475 static FileProgressStatus
2476 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2477 struct stat *_d_stat)
2479 return file_progress_real_query_replace (ctx, Foreground, destname,
2480 _s_stat, _d_stat);
2483 #endif /* !WITH_BACKGROUND */
2486 Cause emacs to enter folding mode for this file:
2487 Local variables:
2488 end: