Change progressbar dialog.
[midnight-commander.git] / src / file.c
blob68a94496ef7985a1518b39450a129f979f7a2540
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;
1058 file_progress_show_source (ctx, s);
1059 file_progress_show_target (ctx, d);
1060 if (check_progress_buttons (ctx) == FILE_ABORT)
1061 return FILE_ABORT;
1063 mc_refresh ();
1065 while (mc_lstat (s, &src_stats) != 0) {
1066 /* Source doesn't exist */
1067 return_status =
1068 file_error (_(" Cannot stat file \"%s\" \n %s "), s);
1069 if (return_status != FILE_RETRY)
1070 return return_status;
1073 if (mc_lstat (d, &dst_stats) == 0) {
1074 if (src_stats.st_dev == dst_stats.st_dev
1075 && src_stats.st_ino == dst_stats.st_ino)
1076 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "), s, d);
1078 if (S_ISDIR (dst_stats.st_mode)) {
1079 message (D_ERROR, MSG_ERROR,
1080 _(" Cannot overwrite directory `%s' "), d);
1081 do_refresh ();
1082 return FILE_SKIP;
1085 if (confirm_overwrite) {
1086 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
1087 if (return_status != FILE_CONT)
1088 return return_status;
1090 /* Ok to overwrite */
1093 if (!ctx->do_append) {
1094 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks) {
1095 if ((return_status = make_symlink (ctx, s, d)) == FILE_CONT) {
1096 goto retry_src_remove;
1097 } else
1098 return return_status;
1101 if (mc_rename (s, d) == 0) {
1102 return progress_update_one (tctx, ctx, src_stats.st_size, TRUE);
1105 #if 0
1106 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1107 one nfs to the same, but on the server it is on two different
1108 filesystems. Then nfs returns EIO instead of EXDEV.
1109 Hope it will not hurt if we always in case of error try to copy/delete. */
1110 else
1111 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1113 if (errno != EXDEV) {
1114 return_status =
1115 files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s,
1117 if (return_status == FILE_RETRY)
1118 goto retry_rename;
1119 return return_status;
1121 #endif
1123 /* Failed because filesystem boundary -> copy the file instead */
1124 return_status = copy_file_file (tctx, ctx, s, d);
1125 if (return_status != FILE_CONT)
1126 return return_status;
1128 copy_done = TRUE;
1130 file_progress_show_source (ctx, NULL);
1131 file_progress_show (ctx, 0, 0, "", FALSE);
1133 return_status = check_progress_buttons (ctx);
1134 if (return_status != FILE_CONT)
1135 return return_status;
1137 mc_refresh ();
1139 retry_src_remove:
1140 if (mc_unlink (s)) {
1141 return_status =
1142 file_error (_(" Cannot remove file \"%s\" \n %s "), s);
1143 if (return_status == FILE_RETRY)
1144 goto retry_src_remove;
1145 return return_status;
1148 if (!copy_done) {
1149 return_status = progress_update_one (tctx, ctx, src_stats.st_size, TRUE);
1152 return return_status;
1155 FileProgressStatus
1156 move_dir_dir (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, const char *d)
1158 struct stat sbuf, dbuf, destbuf;
1159 struct link *lp;
1160 char *destdir;
1161 FileProgressStatus return_status;
1162 gboolean move_over = FALSE;
1163 gboolean dstat_ok;
1165 file_progress_show_source (ctx, s);
1166 file_progress_show_target (ctx, d);
1167 if (check_progress_buttons (ctx) == FILE_ABORT)
1168 return FILE_ABORT;
1170 mc_refresh ();
1172 mc_stat (s, &sbuf);
1173 dstat_ok = (mc_stat (d, &dbuf) == 0);
1175 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
1176 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same directory "), s, d);
1178 if (!dstat_ok)
1179 destdir = g_strdup (d); /* destination doesn't exist */
1180 else if (!ctx->dive_into_subdirs) {
1181 destdir = g_strdup (d);
1182 move_over = TRUE;
1183 } else
1184 destdir = concat_dir_and_file (d, x_basename (s));
1186 /* Check if the user inputted an existing dir */
1187 retry_dst_stat:
1188 if (!mc_stat (destdir, &destbuf)) {
1189 if (move_over) {
1190 return_status = copy_dir_dir (tctx, ctx, s, destdir, FALSE, TRUE, TRUE, NULL);
1192 if (return_status != FILE_CONT)
1193 goto ret;
1194 goto oktoret;
1195 } else {
1196 if (S_ISDIR (destbuf.st_mode))
1197 return_status =
1198 file_error (_
1199 (" Cannot overwrite directory \"%s\" %s "),
1200 destdir);
1201 else
1202 return_status =
1203 file_error (_(" Cannot overwrite file \"%s\" %s "),
1204 destdir);
1205 if (return_status == FILE_RETRY)
1206 goto retry_dst_stat;
1208 g_free (destdir);
1209 return return_status;
1212 retry_rename:
1213 if (mc_rename (s, destdir) == 0) {
1214 return_status = FILE_CONT;
1215 goto ret;
1218 if (errno != EXDEV) {
1219 return_status =
1220 files_error (_
1221 (" Cannot move directory \"%s\" to \"%s\" \n %s "),
1222 s, d);
1223 if (return_status == FILE_RETRY)
1224 goto retry_rename;
1225 goto ret;
1227 /* Failed because of filesystem boundary -> copy dir instead */
1228 return_status =
1229 copy_dir_dir (tctx, ctx, s, destdir, FALSE, FALSE, TRUE, NULL);
1231 if (return_status != FILE_CONT)
1232 goto ret;
1233 oktoret:
1234 file_progress_show_source (ctx, NULL);
1235 file_progress_show (ctx, 0, 0, "", FALSE);
1237 return_status = check_progress_buttons (ctx);
1238 if (return_status != FILE_CONT)
1239 goto ret;
1241 mc_refresh ();
1242 if (ctx->erase_at_end) {
1243 for (; erase_list && return_status != FILE_ABORT;) {
1244 if (S_ISDIR (erase_list->st_mode)) {
1245 return_status =
1246 erase_dir_iff_empty (ctx, erase_list->name);
1247 } else
1248 return_status =
1249 erase_file (tctx, ctx, erase_list->name, FALSE);
1250 lp = erase_list;
1251 erase_list = erase_list->next;
1252 g_free (lp);
1255 erase_dir_iff_empty (ctx, s);
1257 ret:
1258 g_free (destdir);
1259 while (erase_list) {
1260 lp = erase_list;
1261 erase_list = erase_list->next;
1262 g_free (lp);
1264 return return_status;
1267 /* }}} */
1269 /* {{{ Erase routines */
1270 /* Don't update progress status if progress_count==NULL */
1271 static FileProgressStatus
1272 erase_file (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, gboolean is_toplevel_file)
1274 int return_status;
1275 struct stat buf;
1277 file_progress_show_deleting (ctx, s);
1278 if (check_progress_buttons (ctx) == FILE_ABORT)
1279 return FILE_ABORT;
1280 mc_refresh ();
1282 if (tctx->progress_count && mc_lstat (s, &buf)) {
1283 /* ignore, most likely the mc_unlink fails, too */
1284 buf.st_size = 0;
1287 while (mc_unlink (s)) {
1288 return_status =
1289 file_error (_(" Cannot delete file \"%s\" \n %s "), s);
1290 if (return_status != FILE_RETRY)
1291 return return_status;
1294 if (tctx->progress_count)
1295 return progress_update_one (tctx, ctx, buf.st_size, is_toplevel_file);
1296 else
1297 return FILE_CONT;
1300 static FileProgressStatus
1301 recursive_erase (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s)
1303 struct dirent *next;
1304 struct stat buf;
1305 DIR *reading;
1306 char *path;
1307 FileProgressStatus return_status = FILE_CONT;
1309 if (!strcmp (s, ".."))
1310 return FILE_RETRY;
1312 reading = mc_opendir (s);
1314 if (!reading)
1315 return FILE_RETRY;
1317 while ((next = mc_readdir (reading)) && return_status == FILE_CONT) {
1318 if (!strcmp (next->d_name, "."))
1319 continue;
1320 if (!strcmp (next->d_name, ".."))
1321 continue;
1322 path = concat_dir_and_file (s, next->d_name);
1323 if (mc_lstat (path, &buf)) {
1324 g_free (path);
1325 mc_closedir (reading);
1326 return FILE_RETRY;
1328 if (S_ISDIR (buf.st_mode))
1329 return_status =
1330 (recursive_erase (tctx, ctx, path) != FILE_CONT) ? FILE_RETRY : FILE_CONT;
1331 else
1332 return_status =
1333 erase_file (tctx, ctx, path, 0);
1334 g_free (path);
1336 mc_closedir (reading);
1337 if (return_status != FILE_CONT)
1338 return return_status;
1339 file_progress_show_deleting (ctx, s);
1340 if (check_progress_buttons (ctx) == FILE_ABORT)
1341 return FILE_ABORT;
1342 mc_refresh ();
1344 while (my_rmdir (s)) {
1345 return_status =
1346 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1347 if (return_status != FILE_RETRY)
1348 return return_status;
1351 return FILE_CONT;
1354 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1355 in the directory path points to, 0 else. */
1356 static int
1357 check_dir_is_empty (const char *path)
1359 DIR *dir;
1360 struct dirent *d;
1361 int i;
1363 dir = mc_opendir (path);
1364 if (!dir)
1365 return -1;
1367 for (i = 1, d = mc_readdir (dir); d; d = mc_readdir (dir)) {
1368 if (d->d_name[0] == '.' && (d->d_name[1] == '\0' ||
1369 (d->d_name[1] == '.'
1370 && d->d_name[2] == '\0')))
1371 continue; /* "." or ".." */
1372 i = 0;
1373 break;
1376 mc_closedir (dir);
1377 return i;
1380 FileProgressStatus
1381 erase_dir (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s)
1383 FileProgressStatus error;
1385 if (strcmp (s, "..") == 0)
1386 return FILE_SKIP;
1388 if (strcmp (s, ".") == 0)
1389 return FILE_SKIP;
1391 file_progress_show_deleting (ctx, s);
1392 if (check_progress_buttons (ctx) == FILE_ABORT)
1393 return FILE_ABORT;
1394 mc_refresh ();
1396 /* The old way to detect a non empty directory was:
1397 error = my_rmdir (s);
1398 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1399 For the linux user space nfs server (nfs-server-2.2beta29-2)
1400 we would have to check also for EIO. I hope the new way is
1401 fool proof. (Norbert)
1403 error = check_dir_is_empty (s);
1404 if (error == 0) { /* not empty */
1405 error = query_recursive (ctx, s);
1406 if (error == FILE_CONT)
1407 return recursive_erase (tctx, ctx, s);
1408 else
1409 return error;
1412 while (my_rmdir (s) == -1) {
1413 error =
1414 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1415 if (error != FILE_RETRY)
1416 return error;
1419 return FILE_CONT;
1422 static FileProgressStatus
1423 erase_dir_iff_empty (FileOpContext *ctx, const char *s)
1425 FileProgressStatus error;
1427 if (strcmp (s, "..") == 0)
1428 return FILE_SKIP;
1430 if (strcmp (s, ".") == 0)
1431 return FILE_SKIP;
1433 file_progress_show_deleting (ctx, s);
1434 if (check_progress_buttons (ctx) == FILE_ABORT)
1435 return FILE_ABORT;
1436 mc_refresh ();
1438 if (1 != check_dir_is_empty (s)) /* not empty or error */
1439 return FILE_CONT;
1441 while (my_rmdir (s)) {
1442 error =
1443 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1444 if (error != FILE_RETRY)
1445 return error;
1448 return FILE_CONT;
1451 /* }}} */
1453 /* {{{ Panel operate routines */
1456 * Return currently selected entry name or the name of the first marked
1457 * entry if there is one.
1459 static char *
1460 panel_get_file (WPanel *panel, struct stat *stat_buf)
1462 int i;
1464 if (get_current_type () == view_tree) {
1465 WTree *tree = (WTree *) get_panel_widget (get_current_index ());
1466 char *tree_name = tree_selected_name (tree);
1468 mc_stat (tree_name, stat_buf);
1469 return tree_name;
1472 if (panel->marked) {
1473 for (i = 0; i < panel->count; i++)
1474 if (panel->dir.list[i].f.marked) {
1475 *stat_buf = panel->dir.list[i].st;
1476 return panel->dir.list[i].fname;
1478 } else {
1479 *stat_buf = panel->dir.list[panel->selected].st;
1480 return panel->dir.list[panel->selected].fname;
1482 g_assert_not_reached ();
1483 return NULL;
1487 ComputeDirSizeUI *
1488 compute_dir_size_create_ui (void)
1490 ComputeDirSizeUI *ui;
1492 const char *b_name = N_("&Abort");
1494 #ifdef ENABLE_NLS
1495 b_name = _(b_name);
1496 #endif
1498 ui = g_new (ComputeDirSizeUI, 1);
1500 ui->dlg = create_dlg (0, 0, 8, COLS/2, dialog_colors, NULL,
1501 NULL, _("Directory scanning"), DLG_CENTER);
1502 ui->dirname = label_new (3, 3, "");
1503 add_widget (ui->dlg, ui->dirname);
1505 add_widget (ui->dlg,
1506 button_new (5, (ui->dlg->cols - strlen (b_name))/2,
1507 FILE_ABORT, NORMAL_BUTTON, b_name, NULL));
1509 /* We will manage the dialog without any help,
1510 that's why we have to call init_dlg */
1511 init_dlg (ui->dlg);
1513 return ui;
1516 void
1517 compute_dir_size_destroy_ui (ComputeDirSizeUI *ui)
1519 if (ui != NULL) {
1520 /* schedule to update passive panel */
1521 other_panel->dirty = 1;
1523 /* close and destroy dialog */
1524 dlg_run_done (ui->dlg);
1525 destroy_dlg (ui->dlg);
1526 g_free (ui);
1530 FileProgressStatus
1531 compute_dir_size_update_ui (const void *ui, const char *dirname)
1533 const ComputeDirSizeUI *this = (const ComputeDirSizeUI *) ui;
1534 int c;
1535 Gpm_Event event;
1537 if (ui == NULL)
1538 return FILE_CONT;
1540 label_set_text (this->dirname, name_trunc (dirname, this->dlg->cols - 6));
1542 event.x = -1; /* Don't show the GPM cursor */
1543 c = tty_get_event (&event, FALSE, FALSE);
1544 if (c == EV_NONE)
1545 return FILE_CONT;
1547 /* Reinitialize to avoid old values after events other than
1548 selecting a button */
1549 this->dlg->ret_value = FILE_CONT;
1551 dlg_process_event (this->dlg, c, &event);
1553 switch (this->dlg->ret_value) {
1554 case B_CANCEL:
1555 case FILE_ABORT:
1556 return FILE_ABORT;
1557 default:
1558 return FILE_CONT;
1563 * compute_dir_size:
1565 * Computes the number of bytes used by the files in a directory
1567 FileProgressStatus
1568 compute_dir_size (const char *dirname, const void *ui,
1569 compute_dir_size_callback cback,
1570 off_t *ret_marked, double *ret_total)
1572 DIR *dir;
1573 struct dirent *dirent;
1574 FileProgressStatus ret = FILE_CONT;
1576 dir = mc_opendir (dirname);
1578 if (dir == NULL)
1579 return ret;
1581 while ((dirent = mc_readdir (dir)) != NULL) {
1582 char *fullname;
1583 int res;
1584 struct stat s;
1586 ret = (cback != NULL) ? cback (ui, dirname) : FILE_CONT;
1588 if (ret != FILE_CONT)
1589 break;
1591 if (strcmp (dirent->d_name, ".") == 0)
1592 continue;
1593 if (strcmp (dirent->d_name, "..") == 0)
1594 continue;
1596 fullname = concat_dir_and_file (dirname, dirent->d_name);
1597 res = mc_lstat (fullname, &s);
1599 if (res != 0) {
1600 g_free (fullname);
1601 continue;
1604 if (S_ISDIR (s.st_mode)) {
1605 off_t subdir_count = 0;
1606 double subdir_bytes = 0;
1608 ret = compute_dir_size (fullname, ui, cback, &subdir_count, &subdir_bytes);
1610 if (ret != FILE_CONT) {
1611 g_free (fullname);
1612 break;
1615 *ret_marked += subdir_count;
1616 *ret_total += subdir_bytes;
1617 } else {
1618 (*ret_marked)++;
1619 *ret_total += s.st_size;
1622 g_free (fullname);
1625 mc_closedir (dir);
1627 return ret;
1631 * panel_compute_totals:
1633 * compute the number of files and the number of bytes
1634 * used up by the whole selection, recursing directories
1635 * as required. In addition, it checks to see if it will
1636 * overwrite any files by doing the copy.
1638 static FileProgressStatus
1639 panel_compute_totals (const WPanel *panel, const void *ui,
1640 compute_dir_size_callback cback,
1641 off_t *ret_marked, double *ret_total)
1643 int i;
1645 *ret_marked = 0;
1646 *ret_total = 0.0;
1648 for (i = 0; i < panel->count; i++) {
1649 struct stat *s;
1651 if (!panel->dir.list[i].f.marked)
1652 continue;
1654 s = &panel->dir.list[i].st;
1656 if (S_ISDIR (s->st_mode)) {
1657 char *dir_name;
1658 off_t subdir_count = 0;
1659 double subdir_bytes = 0;
1660 FileProgressStatus status;
1662 dir_name =
1663 concat_dir_and_file (panel->cwd, panel->dir.list[i].fname);
1665 status = compute_dir_size (dir_name, ui, cback,
1666 &subdir_count, &subdir_bytes);
1667 g_free (dir_name);
1669 if (status != FILE_CONT)
1670 return FILE_ABORT;
1672 *ret_marked += subdir_count;
1673 *ret_total += subdir_bytes;
1674 } else {
1675 (*ret_marked)++;
1676 *ret_total += s->st_size;
1680 return FILE_CONT;
1683 /* Initialize variables for progress bars */
1684 static FileProgressStatus
1685 panel_operate_init_totals (FileOperation operation,
1686 const WPanel *panel, const char *source,
1687 FileOpContext *ctx)
1689 FileProgressStatus status;
1691 if (operation != OP_MOVE && verbose && file_op_compute_totals) {
1692 ComputeDirSizeUI *ui;
1694 ui = compute_dir_size_create_ui ();
1696 if (source != NULL)
1697 status = compute_dir_size (source, ui, compute_dir_size_update_ui,
1698 &ctx->progress_count, &ctx->progress_bytes);
1699 else
1700 status = panel_compute_totals (panel, ui, compute_dir_size_update_ui,
1701 &ctx->progress_count, &ctx->progress_bytes);
1703 compute_dir_size_destroy_ui (ui);
1705 ctx->progress_totals_computed = (status == FILE_CONT);
1706 } else {
1707 status = FILE_CONT;
1708 ctx->progress_count = panel->marked;
1709 ctx->progress_bytes = panel->total;
1710 ctx->progress_totals_computed = FALSE;
1713 return status;
1717 * This array introduced to avoid translation problems. The former (op_names)
1718 * is assumed to be nouns, suitable in dialog box titles; this one should
1719 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
1720 * (I don't use spaces around the words, because someday they could be
1721 * dropped, when widgets get smarter)
1724 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
1725 static const char *op_names1[] = {
1726 N_("FileOperation|Copy"),
1727 N_("FileOperation|Move"),
1728 N_("FileOperation|Delete")
1732 * These are formats for building a prompt. Parts encoded as follows:
1733 * %o - operation from op_names1
1734 * %f - file/files or files/directories, as appropriate
1735 * %m - "with source mask" or question mark for delete
1736 * %s - source name (truncated)
1737 * %d - number of marked files
1738 * %e - "to:" or question mark for delete
1740 * xgettext:no-c-format */
1741 static const char *one_format = N_("%o %f \"%s\"%m");
1742 /* xgettext:no-c-format */
1743 static const char *many_format = N_("%o %d %f%m");
1745 static const char *prompt_parts[] = {
1746 N_("file"),
1747 N_("files"),
1748 N_("directory"),
1749 N_("directories"),
1750 N_("files/directories"),
1751 N_(" with source mask:"),
1752 N_(" to:")
1755 static const char *question_format = N_("%s?");
1758 * Generate user prompt for panel operation.
1759 * single_source is the name if the source entry or NULL for multiple
1760 * entries.
1761 * src_stat is only used when single_source is not NULL.
1763 static char *
1764 panel_operate_generate_prompt (const WPanel *panel, FileOperation operation,
1765 gboolean single_source,
1766 const struct stat *src_stat)
1768 const char *sp, *cp;
1769 char format_string[BUF_MEDIUM];
1770 char *dp = format_string;
1771 gboolean build_question = FALSE;
1773 #ifdef ENABLE_NLS
1774 static gboolean i18n_flag = FALSE;
1775 if (!i18n_flag) {
1776 size_t i;
1778 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1779 op_names1[i] = Q_(op_names1[i]);
1781 for (i = sizeof (prompt_parts) / sizeof (prompt_parts[0]); i--;)
1782 prompt_parts[i] = _(prompt_parts[i]);
1784 one_format = _(one_format);
1785 many_format = _(many_format);
1786 question_format = _(question_format);
1787 i18n_flag = TRUE;
1789 #endif /* ENABLE_NLS */
1791 sp = single_source ? one_format : many_format;
1793 while (*sp != '\0') {
1794 switch (*sp) {
1795 case '%':
1796 cp = NULL;
1797 switch (sp[1]) {
1798 case 'o':
1799 cp = op_names1[operation];
1800 break;
1801 case 'm':
1802 if (operation == OP_DELETE) {
1803 cp = "";
1804 build_question = TRUE;
1805 } else
1806 cp = prompt_parts[5];
1807 break;
1808 case 'e':
1809 if (operation == OP_DELETE) {
1810 cp = "";
1811 build_question = TRUE;
1812 } else
1813 cp = prompt_parts[6];
1814 break;
1815 case 'f':
1816 if (single_source) {
1817 cp = S_ISDIR (src_stat->
1818 st_mode) ? prompt_parts[2] : prompt_parts[0];
1819 } else {
1820 cp = (panel->marked == panel->dirs_marked)
1821 ? prompt_parts[3]
1822 : (panel->dirs_marked ? prompt_parts[4] : prompt_parts[1]);
1824 break;
1825 default:
1826 *dp++ = *sp++;
1828 if (cp != NULL) {
1829 sp += 2;
1830 while (*cp != '\0')
1831 *dp++ = *cp++;
1833 break;
1834 default:
1835 *dp++ = *sp++;
1838 *dp = '\0';
1840 if (build_question) {
1841 char tmp[BUF_MEDIUM];
1843 memmove (tmp, format_string, sizeof (tmp));
1844 g_snprintf (format_string, sizeof (format_string),
1845 question_format, tmp);
1848 return g_strdup (format_string);
1851 #ifdef WITH_BACKGROUND
1852 static int
1853 end_bg_process (FileOpContext *ctx, enum OperationMode mode) {
1854 int pid = ctx->pid;
1856 (void) mode;
1857 ctx->pid = 0;
1859 unregister_task_with_pid(pid);
1860 // file_op_context_destroy(ctx);
1861 return 1;
1863 #endif
1866 * panel_operate:
1868 * Performs one of the operations on the selection on the source_panel
1869 * (copy, delete, move).
1871 * Returns TRUE if did change the directory
1872 * structure, Returns FALSE if user aborted
1874 * force_single forces operation on the current entry and affects
1875 * default destination. Current filename is used as default.
1877 gboolean
1878 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
1880 WPanel *panel = (WPanel *) source_panel;
1881 const gboolean single_entry = force_single || (panel->marked <= 1)
1882 || (get_current_type () == view_tree);
1884 char *source = NULL;
1885 #ifdef WITH_FULL_PATHS
1886 char *source_with_path = NULL;
1887 #else
1888 # define source_with_path source
1889 #endif /* !WITH_FULL_PATHS */
1890 char *dest = NULL;
1891 char *temp = NULL;
1892 char *save_cwd = NULL, *save_dest = NULL;
1893 struct stat src_stat;
1894 int i;
1895 FileProgressStatus value;
1896 FileOpContext *ctx;
1897 FileOpTotalContext *tctx;
1899 gboolean do_bg = FALSE; /* do background operation? */
1901 #ifdef ENABLE_NLS
1902 static gboolean i18n_flag = FALSE;
1903 if (!i18n_flag) {
1904 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1905 op_names[i] = Q_(op_names[i]);
1906 i18n_flag = TRUE;
1908 #endif /* ENABLE_NLS */
1910 free_linklist (&linklist);
1911 free_linklist (&dest_dirs);
1913 /* Update panel contents to avoid actions on deleted files */
1914 if (!panel->is_panelized) {
1915 update_panels (UP_RELOAD, UP_KEEPSEL);
1916 repaint_screen ();
1919 if (single_entry) {
1920 if (force_single) {
1921 source = selection (panel)->fname;
1922 src_stat = selection (panel)->st;
1923 } else {
1924 source = panel_get_file (panel, &src_stat);
1927 if (!strcmp (source, "..")) {
1928 message (D_ERROR, MSG_ERROR, _(" Cannot operate on \"..\"! "));
1929 return FALSE;
1933 ctx = file_op_context_new (operation);
1934 tctx = file_op_total_context_new ();
1935 gettimeofday (&(tctx->transfer_start), (struct timezone *) NULL);
1937 /* Show confirmation dialog */
1938 if (operation != OP_DELETE) {
1939 char *dest_dir;
1940 char *dest_dir_;
1941 char *format;
1943 /* Forced single operations default to the original name */
1944 if (force_single)
1945 dest_dir = source;
1946 else if (get_other_type () == view_listing)
1947 dest_dir = other_panel->cwd;
1948 else
1949 dest_dir = panel->cwd;
1951 * Add trailing backslash only when do non-local ops.
1952 * It saves user from occasional file renames (when destination
1953 * dir is deleted)
1955 if (!force_single
1956 && dest_dir[0] != '\0'
1957 && dest_dir[strlen (dest_dir) - 1] != PATH_SEP) {
1958 /* add trailing separator */
1959 dest_dir_ = g_strconcat (dest_dir, PATH_SEP_STR, (char *) NULL);
1960 } else {
1961 /* just copy */
1962 dest_dir_ = g_strdup (dest_dir);
1964 if (dest_dir_ == NULL) {
1965 file_op_total_context_destroy (tctx);
1966 file_op_context_destroy (ctx);
1967 return FALSE;
1970 /* Generate confirmation prompt */
1971 format = panel_operate_generate_prompt (panel, operation,
1972 source != NULL, &src_stat);
1974 dest = file_mask_dialog (ctx, operation, source != NULL, format,
1975 source != NULL ? (void *) source
1976 : (void *) &panel->marked,
1977 dest_dir_, &do_bg);
1979 g_free (format);
1980 g_free (dest_dir_);
1982 if (dest == NULL || dest[0] == '\0') {
1983 file_op_total_context_destroy (tctx);
1984 file_op_context_destroy (ctx);
1985 g_free (dest);
1986 return FALSE;
1988 } else if (confirm_delete) {
1989 char *format;
1990 char fmd_buf [BUF_MEDIUM];
1992 /* Generate confirmation prompt */
1993 format = panel_operate_generate_prompt (panel, OP_DELETE,
1994 source != NULL, &src_stat);
1996 if (source == NULL)
1997 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1998 else {
1999 const int fmd_xlen = 64;
2000 i = fmd_xlen - str_term_width1 (format) - 4;
2001 g_snprintf (fmd_buf, sizeof (fmd_buf),
2002 format, str_trunc (source, i));
2005 g_free (format);
2007 if (safe_delete)
2008 query_set_sel (1);
2010 i = query_dialog (op_names[operation], fmd_buf, D_ERROR, 2,
2011 _("&Yes"), _("&No"));
2013 if (i != 0) {
2014 file_op_total_context_destroy (tctx);
2015 file_op_context_destroy (ctx);
2016 return FALSE;
2021 filegui_dialog_type_t dialog_type;
2023 if (operation == OP_DELETE) {
2024 dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
2025 } else {
2026 dialog_type = !((operation != OP_COPY) || (single_entry) || (force_single))
2027 ? FILEGUI_DIALOG_MULTI_ITEM
2028 : FILEGUI_DIALOG_ONE_ITEM;
2030 if ((single_entry) && (operation == OP_COPY) && S_ISDIR (selection (panel)->st.st_mode))
2031 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
2034 /* Background also need ctx->ui, but not full */
2035 if (do_bg)
2036 file_op_context_create_ui_without_init (ctx, 1, dialog_type);
2037 else
2038 file_op_context_create_ui (ctx, 1, dialog_type);
2041 #ifdef WITH_BACKGROUND
2042 /* Did the user select to do a background operation? */
2043 if (do_bg) {
2044 int v;
2046 v = do_background (ctx,
2047 g_strconcat (op_names[operation], ": ",
2048 panel->cwd, (char *) NULL));
2049 if (v == -1) {
2050 message (D_ERROR, MSG_ERROR,
2051 _(" Sorry, I could not put the job in background "));
2054 /* If we are the parent */
2055 if (v == 1) {
2056 mc_setctl (panel->cwd, VFS_SETCTL_FORGET, NULL);
2057 mc_setctl (dest, VFS_SETCTL_FORGET, NULL);
2058 /* file_op_context_destroy (ctx); */
2059 return FALSE;
2062 #endif /* WITH_BACKGROUND */
2064 /* Initialize things */
2065 /* We do not want to trash cache every time file is
2066 created/touched. However, this will make our cache contain
2067 invalid data. */
2068 if ((dest != NULL)
2069 && (mc_setctl (dest, VFS_SETCTL_STALE_DATA, (void *) 1)))
2070 save_dest = g_strdup (dest);
2072 if ((panel->cwd[0] != '\0')
2073 && (mc_setctl (panel->cwd, VFS_SETCTL_STALE_DATA, (void *) 1)))
2074 save_cwd = g_strdup (panel->cwd);
2076 /* Now, let's do the job */
2078 /* This code is only called by the tree and panel code */
2079 if (single_entry) {
2080 /* We now have ETA in all cases */
2082 /* One file: FIXME mc_chdir will take user out of any vfs */
2083 if (operation != OP_COPY && get_current_type () == view_tree)
2084 mc_chdir (PATH_SEP_STR);
2086 /* The source and src_stat variables have been initialized before */
2087 #ifdef WITH_FULL_PATHS
2088 source_with_path = concat_dir_and_file (panel->cwd, source);
2089 #endif /* WITH_FULL_PATHS */
2091 if (panel_operate_init_totals (operation, panel,
2092 source_with_path, ctx) == FILE_CONT) {
2093 if (operation == OP_DELETE) {
2094 if (S_ISDIR (src_stat.st_mode))
2095 value = erase_dir (tctx, ctx, source_with_path);
2096 else
2097 value = erase_file (tctx, ctx, source_with_path, 1);
2098 } else {
2099 temp = transform_source (ctx, source_with_path);
2100 if (temp == NULL)
2101 value = transform_error;
2102 else {
2103 char *repl_dest, *temp2;
2105 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2106 temp2 = concat_dir_and_file (repl_dest, temp);
2107 g_free (temp);
2108 g_free (repl_dest);
2109 g_free (dest);
2110 dest = temp2;
2112 switch (operation) {
2113 case OP_COPY:
2114 /* we use file_mask_op_follow_links only with OP_COPY */
2115 ctx->stat_func (source_with_path, &src_stat);
2117 if (S_ISDIR (src_stat.st_mode))
2118 value = copy_dir_dir (tctx, ctx, source_with_path, dest,
2119 TRUE, FALSE, FALSE, NULL);
2120 else
2121 value = copy_file_file (tctx, ctx, source_with_path, dest);
2122 break;
2124 case OP_MOVE:
2125 if (S_ISDIR (src_stat.st_mode))
2126 value = move_dir_dir (tctx, ctx, source_with_path, dest);
2127 else
2128 value = move_file_file (tctx, ctx, source_with_path, dest);
2129 break;
2131 default:
2132 /* Unknown file operation */
2133 abort ();
2136 } /* Copy or move operation */
2138 if ((value == FILE_CONT) && !force_single)
2139 unmark_files (panel);
2141 } else {
2142 /* Many files */
2144 /* Check destination for copy or move operation */
2145 while (operation != OP_DELETE) {
2146 int dst_result;
2147 struct stat dst_stat;
2149 dst_result = mc_stat (dest, &dst_stat);
2151 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
2152 break;
2154 if (file_error (_(" Destination \"%s\" must be a directory \n %s "),
2155 dest) != FILE_RETRY)
2156 goto clean_up;
2159 if (panel_operate_init_totals (operation, panel, NULL, ctx) == FILE_CONT) {
2160 /* Loop for every file, perform the actual copy operation */
2161 for (i = 0; i < panel->count; i++) {
2162 if (!panel->dir.list[i].f.marked)
2163 continue; /* Skip the unmarked ones */
2165 source = panel->dir.list[i].fname;
2166 src_stat = panel->dir.list[i].st;
2168 #ifdef WITH_FULL_PATHS
2169 g_free (source_with_path);
2170 source_with_path = concat_dir_and_file (panel->cwd, source);
2171 #endif /* WITH_FULL_PATHS */
2173 if (operation == OP_DELETE) {
2174 if (S_ISDIR (src_stat.st_mode))
2175 value = erase_dir (tctx, ctx, source_with_path);
2176 else
2177 value = erase_file (tctx, ctx, source_with_path, 1);
2178 } else {
2179 temp = transform_source (ctx, source_with_path);
2181 if (temp == NULL)
2182 value = transform_error;
2183 else {
2184 char *temp2, *temp3, *repl_dest;
2186 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2187 temp2 = concat_dir_and_file (repl_dest, temp);
2188 g_free (temp);
2189 g_free (repl_dest);
2190 temp3 = source_with_path;
2191 source_with_path = strutils_shell_unescape (source_with_path);
2192 g_free (temp3);
2193 temp3 = temp2;
2194 temp2 = strutils_shell_unescape (temp2);
2195 g_free (temp3);
2197 switch (operation) {
2198 case OP_COPY:
2199 /* we use file_mask_op_follow_links only with OP_COPY */
2200 ctx->stat_func (source_with_path, &src_stat);
2201 if (S_ISDIR (src_stat.st_mode))
2202 value = copy_dir_dir (tctx, ctx, source_with_path, temp2,
2203 TRUE, FALSE, FALSE, NULL);
2204 else
2205 value = copy_file_file (tctx, ctx, source_with_path, temp2);
2206 free_linklist (&dest_dirs);
2207 break;
2209 case OP_MOVE:
2210 if (S_ISDIR (src_stat.st_mode))
2211 value = move_dir_dir (tctx, ctx, source_with_path, temp2);
2212 else
2213 value = move_file_file (tctx, ctx, source_with_path, temp2);
2214 break;
2216 default:
2217 /* Unknown file operation */
2218 abort ();
2221 g_free (temp2);
2223 } /* Copy or move operation */
2225 if (value == FILE_ABORT)
2226 break;
2228 if (value == FILE_CONT)
2229 do_file_mark (panel, i, 0);
2231 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2233 if (verbose) {
2234 file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
2236 if (operation != OP_DELETE)
2237 file_progress_show (ctx, 0, 0, "", FALSE);
2240 if (check_progress_buttons (ctx) == FILE_ABORT)
2241 break;
2243 mc_refresh ();
2244 } /* Loop for every file */
2246 } /* Many entries */
2248 clean_up:
2249 /* Clean up */
2250 if (save_cwd != NULL) {
2251 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
2252 g_free (save_cwd);
2255 if (save_dest != NULL) {
2256 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
2257 g_free (save_dest);
2260 free_linklist (&linklist);
2261 free_linklist (&dest_dirs);
2262 #ifdef WITH_FULL_PATHS
2263 g_free (source_with_path);
2264 #endif /* WITH_FULL_PATHS */
2265 g_free (dest);
2266 g_free (ctx->dest_mask);
2267 ctx->dest_mask = NULL;
2269 #ifdef WITH_BACKGROUND
2270 /* Let our parent know we are saying bye bye */
2271 if (we_are_background) {
2272 int cur_pid = getpid();
2273 /* Send pid to parent with child context, it is fork and
2274 don't modify real parent ctx */
2275 ctx->pid = cur_pid;
2276 parent_call ((void *) end_bg_process, ctx, 0);
2278 vfs_shut ();
2279 _exit (0);
2281 #endif /* WITH_BACKGROUND */
2283 file_op_context_destroy (ctx);
2284 return TRUE;
2287 /* }}} */
2289 /* {{{ Query/status report routines */
2291 static FileProgressStatus
2292 real_do_file_error (enum OperationMode mode, const char *error)
2294 int result;
2295 const char *msg;
2297 msg = mode == Foreground ? MSG_ERROR : _(" Background process error ");
2298 result =
2299 query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("&Retry"),
2300 _("&Abort"));
2302 switch (result) {
2303 case 0:
2304 do_refresh ();
2305 return FILE_SKIP;
2307 case 1:
2308 do_refresh ();
2309 return FILE_RETRY;
2311 case 2:
2312 default:
2313 return FILE_ABORT;
2317 /* Report error with one file */
2318 FileProgressStatus
2319 file_error (const char *format, const char *file)
2321 char buf [BUF_MEDIUM];
2323 g_snprintf (buf, sizeof (buf), format,
2324 path_trunc (file, 30), unix_error_string (errno));
2326 return do_file_error (buf);
2329 /* Report error with two files */
2330 static FileProgressStatus
2331 files_error (const char *format, const char *file1, const char *file2)
2333 char buf [BUF_MEDIUM];
2334 char *nfile1 = g_strdup (path_trunc (file1, 15));
2335 char *nfile2 = g_strdup (path_trunc (file2, 15));
2337 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2,
2338 unix_error_string (errno));
2340 g_free (nfile1);
2341 g_free (nfile2);
2343 return do_file_error (buf);
2346 static FileProgressStatus
2347 real_query_recursive (FileOpContext *ctx, enum OperationMode mode, const char *s)
2349 gchar *text;
2351 if (ctx->recursive_result < RECURSIVE_ALWAYS) {
2352 const char *msg = mode == Foreground
2353 ? _("\n Directory not empty. \n"
2354 " Delete it recursively? ")
2355 : _("\n Background process: Directory not empty \n"
2356 " Delete it recursively? ");
2357 text = g_strconcat (_(" Delete: "), path_trunc (s, 30), " ", (char *) NULL);
2359 if (safe_delete)
2360 query_set_sel (1);
2362 ctx->recursive_result =
2363 (FileCopyMode) query_dialog (text, msg, D_ERROR, 5,
2364 _("&Yes"), _("&No"),
2365 _("A&ll"), _("Non&e"),
2366 _("&Abort"));
2368 if (ctx->recursive_result != RECURSIVE_ABORT)
2369 do_refresh ();
2370 g_free (text);
2373 switch (ctx->recursive_result) {
2374 case RECURSIVE_YES:
2375 case RECURSIVE_ALWAYS:
2376 return FILE_CONT;
2378 case RECURSIVE_NO:
2379 case RECURSIVE_NEVER:
2380 return FILE_SKIP;
2382 case RECURSIVE_ABORT:
2383 default:
2384 return FILE_ABORT;
2388 #ifdef WITH_BACKGROUND
2389 static FileProgressStatus
2390 do_file_error (const char *str)
2392 union {
2393 void *p;
2394 FileProgressStatus (*f) (enum OperationMode, const char *);
2395 } pntr;
2396 pntr.f = real_do_file_error;
2398 if (we_are_background)
2399 return parent_call (pntr.p, NULL, 1, strlen (str),
2400 str);
2401 else
2402 return real_do_file_error (Foreground, str);
2405 static FileProgressStatus
2406 query_recursive (FileOpContext *ctx, const char *s)
2408 union {
2409 void *p;
2410 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *);
2411 } pntr;
2412 pntr.f = real_query_recursive;
2414 if (we_are_background)
2415 return parent_call (pntr.p, ctx, 1, strlen (s), s);
2416 else
2417 return real_query_recursive (ctx, Foreground, s);
2420 static FileProgressStatus
2421 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2422 struct stat *_d_stat)
2424 union {
2425 void *p;
2426 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *,
2427 struct stat *, struct stat *);
2428 } pntr;
2429 pntr.f = file_progress_real_query_replace;
2431 if (we_are_background)
2432 return parent_call (pntr.p,
2433 ctx,
2435 strlen (destname), destname,
2436 sizeof (struct stat), _s_stat,
2437 sizeof (struct stat), _d_stat);
2438 else
2439 return file_progress_real_query_replace (ctx, Foreground, destname,
2440 _s_stat, _d_stat);
2443 #else
2444 static FileProgressStatus
2445 do_file_error (const char *str)
2447 return real_do_file_error (Foreground, str);
2450 static FileProgressStatus
2451 query_recursive (FileOpContext *ctx, const char *s)
2453 return real_query_recursive (ctx, Foreground, s);
2456 static FileProgressStatus
2457 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2458 struct stat *_d_stat)
2460 return file_progress_real_query_replace (ctx, Foreground, destname,
2461 _s_stat, _d_stat);
2464 #endif /* !WITH_BACKGROUND */
2467 Cause emacs to enter folding mode for this file:
2468 Local variables:
2469 end: