Added indication of total BPS and ETA for file operations.
[midnight-commander.git] / src / file.c
blob1341f01a5ca33223a26c2fd2a3fb126bab113b9a
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 int verbose = 1;
91 * Whether the Midnight Commander tries to provide more
92 * information about copy/move sizes and bytes transfered
93 * at the expense of some speed
95 int file_op_compute_totals = 1;
97 /* This is a hard link cache */
98 struct link {
99 struct link *next;
100 struct vfs_class *vfs;
101 dev_t dev;
102 ino_t ino;
103 short linkcount;
104 mode_t st_mode;
105 char name[1];
108 /* the hard link cache */
109 static struct link *linklist = NULL;
111 /* the files-to-be-erased list */
112 static struct link *erase_list;
115 * In copy_dir_dir we use two additional single linked lists: The first -
116 * variable name `parent_dirs' - holds information about already copied
117 * directories and is used to detect cyclic symbolic links.
118 * The second (`dest_dirs' below) holds information about just created
119 * target directories and is used to detect when an directory is copied
120 * into itself (we don't want to copy infinitly).
121 * Both lists don't use the linkcount and name structure members of struct
122 * link.
124 static struct link *dest_dirs = NULL;
126 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
127 const char *op_names[3] = {
128 N_("DialogTitle|Copy"),
129 N_("DialogTitle|Move"),
130 N_("DialogTitle|Delete")
133 /* }}} */
135 static FileProgressStatus query_replace (FileOpContext * ctx, const char *destname,
136 struct stat *_s_stat, struct stat *_d_stat);
137 static FileProgressStatus query_recursive (FileOpContext * ctx, const char *s);
138 static FileProgressStatus do_file_error (const char *str);
139 static FileProgressStatus erase_dir_iff_empty (FileOpContext *ctx, const char *s);
140 static FileProgressStatus erase_file (FileOpTotalContext *tctx, FileOpContext *ctx,
141 const char *s, gboolean is_toplevel_file);
142 static FileProgressStatus files_error (const char *format, const char *file1,
143 const char *file2);
145 static FileProgressStatus transform_error = FILE_CONT;
147 static char *
148 transform_source (FileOpContext *ctx, const char *source)
150 char *s, *q;
151 char *fnsource;
153 s = g_strdup (source);
155 /* We remove \n from the filename since regex routines would use \n as an anchor */
156 /* this is just to be allowed to maniupulate file names with \n on it */
157 for (q = s; *q != '\0'; q++)
158 if (*q == '\n')
159 *q = ' ';
161 fnsource = (char *) x_basename (s);
163 if (mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
164 q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
165 else {
166 q = NULL;
167 transform_error = FILE_SKIP;
170 g_free (s);
171 return q;
174 static void
175 free_linklist (struct link **lc_linklist)
177 struct link *lp, *lp2;
179 for (lp = *lc_linklist; lp != NULL; lp = lp2) {
180 lp2 = lp->next;
181 g_free (lp);
183 *lc_linklist = NULL;
186 static int
187 is_in_linklist (struct link *lp, const char *path, struct stat *sb)
189 ino_t ino = sb->st_ino;
190 dev_t dev = sb->st_dev;
191 #ifdef ENABLE_VFS
192 struct vfs_class *vfs = vfs_get_class (path);
193 #endif /* ENABLE_VFS */
195 (void) path;
197 while (lp) {
198 #ifdef ENABLE_VFS
199 if (lp->vfs == vfs)
200 #endif /* ENABLE_VFS */
201 if (lp->ino == ino && lp->dev == dev)
202 return 1;
203 lp = lp->next;
205 return 0;
209 * Returns 0 if the inode wasn't found in the cache and 1 if it was found
210 * and a hardlink was succesfully made
212 static int
213 check_hardlinks (const char *src_name, const char *dst_name, struct stat *pstat)
215 struct link *lp;
216 struct vfs_class *my_vfs = vfs_get_class (src_name);
217 ino_t ino = pstat->st_ino;
218 dev_t dev = pstat->st_dev;
219 struct stat link_stat;
220 const char *p;
222 if (vfs_file_class_flags (src_name) & VFSF_NOLINKS)
223 return 0;
225 for (lp = linklist; lp != NULL; lp = lp->next)
226 if (lp->vfs == my_vfs && lp->ino == ino && lp->dev == dev) {
227 if (!mc_stat (lp->name, &link_stat) && link_stat.st_ino == ino
228 && link_stat.st_dev == dev
229 && vfs_get_class (lp->name) == my_vfs) {
230 p = strchr (lp->name, 0) + 1; /* i.e. where the `name' file
231 was copied to */
232 if (vfs_get_class (dst_name) == vfs_get_class (p)) {
233 if (!mc_stat (p, &link_stat)) {
234 if (!mc_link (p, dst_name))
235 return 1;
239 message (D_ERROR, MSG_ERROR, _(" Cannot make the hardlink "));
240 return 0;
242 lp = (struct link *) g_try_malloc (sizeof (struct link) + strlen (src_name)
243 + strlen (dst_name) + 1);
244 if (lp) {
245 char *lpdstname;
246 lp->vfs = my_vfs;
247 lp->ino = ino;
248 lp->dev = dev;
249 strcpy (lp->name, src_name);
250 lpdstname = lp->name + strlen(lp->name) + 1;
251 strcpy (lpdstname, dst_name);
252 lp->next = linklist;
253 linklist = lp;
255 return 0;
259 * Duplicate the contents of the symbolic link src_path in dst_path.
260 * Try to make a stable symlink if the option "stable symlink" was
261 * set in the file mask dialog.
262 * If dst_path is an existing symlink it will be deleted silently
263 * (upper levels take already care of existing files at dst_path).
265 static FileProgressStatus
266 make_symlink (FileOpContext *ctx, const char *src_path, const char *dst_path)
268 char link_target[MC_MAXPATHLEN];
269 int len;
270 FileProgressStatus return_status;
271 struct stat sb;
272 gboolean dst_is_symlink;
274 dst_is_symlink = (mc_lstat (dst_path, &sb) == 0) && S_ISLNK (sb.st_mode);
276 retry_src_readlink:
277 len = mc_readlink (src_path, link_target, MC_MAXPATHLEN - 1);
278 if (len < 0) {
279 return_status =
280 file_error (_(" Cannot read source link \"%s\" \n %s "),
281 src_path);
282 if (return_status == FILE_RETRY)
283 goto retry_src_readlink;
284 return return_status;
286 link_target[len] = 0;
288 if (ctx->stable_symlinks)
289 if (!vfs_file_is_local (src_path) || !vfs_file_is_local (dst_path)) {
290 message (D_ERROR, MSG_ERROR,
291 _(" Cannot make stable symlinks across "
292 "non-local filesystems: \n\n"
293 " Option Stable Symlinks will be disabled "));
294 ctx->stable_symlinks = FALSE;
297 if (ctx->stable_symlinks && !g_path_is_absolute (link_target)) {
298 char *p, *q, *s;
300 const char *r = strrchr (src_path, PATH_SEP);
302 if (r) {
303 p = g_strndup (src_path, r - src_path + 1);
304 if (g_path_is_absolute (dst_path))
305 q = g_strdup (dst_path);
306 else
307 q = g_strconcat (p, dst_path, (char *) NULL);
308 s = strrchr (q, PATH_SEP);
309 if (s) {
310 s[1] = 0;
311 s = g_strconcat (p, link_target, (char *) NULL);
312 g_free (p);
313 g_strlcpy (link_target, s, sizeof (link_target));
314 g_free (s);
315 s = diff_two_paths (q, link_target);
316 if (s) {
317 g_strlcpy (link_target, s, sizeof (link_target));
318 g_free (s);
320 } else
321 g_free (p);
322 g_free (q);
325 retry_dst_symlink:
326 if (mc_symlink (link_target, dst_path) == 0)
327 /* Success */
328 return FILE_CONT;
330 * if dst_exists, it is obvious that this had failed.
331 * We can delete the old symlink and try again...
333 if (dst_is_symlink) {
334 if (!mc_unlink (dst_path))
335 if (mc_symlink (link_target, dst_path) == 0)
336 /* Success */
337 return FILE_CONT;
339 return_status =
340 file_error (_(" Cannot create target symlink \"%s\" \n %s "),
341 dst_path);
342 if (return_status == FILE_RETRY)
343 goto retry_dst_symlink;
344 return return_status;
347 static FileProgressStatus
348 progress_update_one (FileOpTotalContext *tctx, FileOpContext *ctx, off_t add, gboolean is_toplevel_file)
351 if (is_toplevel_file || ctx->progress_totals_computed) {
352 tctx->progress_count++;
353 tctx->progress_bytes += add;
356 /* Apply some heuristic here to not call the update stuff very often */
357 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
358 file_progress_show_bytes (ctx, tctx->progress_bytes, ctx->progress_bytes);
360 return check_progress_buttons (ctx);
363 /* Status of the destination file */
364 typedef enum {
365 DEST_NONE = 0, /* Not created */
366 DEST_SHORT = 1, /* Created, not fully copied */
367 DEST_FULL = 2 /* Created, fully copied */
368 } dest_status_t;
370 static FileProgressStatus
371 real_warn_same_file (enum OperationMode mode, const char *fmt,
372 const char *a, const char *b)
374 char *msg;
375 int result = 0;
376 const char *head_msg;
378 head_msg = mode == Foreground ? MSG_ERROR :
379 _(" Background process error ");
381 msg = g_strdup_printf (fmt, a, b);
382 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
383 g_free(msg);
384 do_refresh ();
385 if ( result ) { /* 1 == Abort */
386 return FILE_ABORT;
387 } else {
388 return FILE_SKIP;
392 #ifdef WITH_BACKGROUND
393 static FileProgressStatus
394 warn_same_file (const char *fmt, const char *a, const char *b)
396 union {
397 void *p;
398 FileProgressStatus (*f) (enum OperationMode, const char *fmt,
399 const char *a, const char *b);
400 } pntr;
401 pntr.f = real_warn_same_file;
403 if (we_are_background)
404 return parent_call (pntr.p, NULL, 3, strlen (fmt),
405 fmt, strlen(a), a, strlen(b), b);
406 else
407 return real_warn_same_file (Foreground, fmt, a, b);
409 #else
410 static FileProgressStatus
411 warn_same_file (const char *fmt, const char *a, const char *b)
413 return real_warn_same_file (Foreground, fmt, a, b);
415 #endif
417 #define FILEOP_UPDATE_INTERVAL 2
418 #define FILEOP_STALLING_INTERVAL 4
419 static void
420 copy_file_file_display_progress (FileOpTotalContext *tctx, FileOpContext *ctx,
421 struct timeval tv_current, struct timeval tv_transfer_start,
422 off_t file_size, off_t n_read_total)
424 long dt;
426 /* 1. Update rotating dash after some time */
427 rotate_dash ();
429 /* 3. Compute ETA */
430 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
432 if (n_read_total) {
433 ctx->eta_secs = ((dt / (double) n_read_total) * file_size) - dt;
434 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
435 } else
436 ctx->eta_secs = 0.0;
438 /* 4. Compute BPS rate */
439 ctx->bps_time = (tv_current.tv_sec - tv_transfer_start.tv_sec);
440 if (ctx->bps_time < 1)
441 ctx->bps_time = 1;
442 ctx->bps = n_read_total / ctx->bps_time;
444 /* 5. Compute total ETA and BPS*/
445 if (ctx->progress_bytes != 0) {
446 double remain_bytes;
447 tctx->copyed_bytes = tctx->progress_bytes + n_read_total + ctx->do_reget;
448 remain_bytes = ctx->progress_bytes - tctx->copyed_bytes;
449 #if 1
451 int total_secs = tv_current.tv_sec - tctx->transfer_start.tv_sec;
453 if (total_secs < 1)
454 total_secs = 1;
455 tctx->bps = tctx->copyed_bytes / total_secs;
456 tctx->eta_secs = remain_bytes / tctx->bps;
458 #else
459 /* broken on lot of little files */
460 tctx->bps_count++;
461 tctx->bps = ( tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count;
462 tctx->eta_secs = remain_bytes / tctx->bps;
463 #endif
467 FileProgressStatus
468 copy_file_file (FileOpTotalContext *tctx, FileOpContext *ctx,
469 const char *src_path, const char *dst_path)
471 uid_t src_uid = (uid_t) -1;
472 gid_t src_gid = (gid_t) -1;
474 int src_desc, dest_desc = -1;
475 int n_read, n_written;
476 mode_t src_mode = 0; /* The mode of the source file */
477 struct stat sb, sb2;
478 struct utimbuf utb;
479 gboolean dst_exists = FALSE, appending = FALSE;
480 off_t n_read_total = 0, file_size = -1;
481 FileProgressStatus return_status, temp_status;
482 struct timeval tv_transfer_start;
483 dest_status_t dst_status = DEST_NONE;
484 int open_flags;
485 gboolean is_first_time=TRUE;
487 /* FIXME: We should not be using global variables! */
488 ctx->do_reget = 0;
489 return_status = FILE_RETRY;
491 file_progress_show_source (ctx, src_path);
492 file_progress_show_target (ctx, dst_path);
493 if (check_progress_buttons (ctx) == FILE_ABORT)
494 return FILE_ABORT;
496 mc_refresh ();
498 while (mc_stat (dst_path, &sb2) == 0) {
499 if (S_ISDIR (sb2.st_mode)) {
500 return_status =
501 file_error (_(" Cannot overwrite directory \"%s\" \n %s "),
502 dst_path);
503 if (return_status == FILE_RETRY)
504 continue;
505 return return_status;
507 dst_exists = TRUE;
508 break;
511 while ((*ctx->stat_func) (src_path, &sb)) {
512 return_status =
513 file_error (_(" Cannot stat source file \"%s\" \n %s "),
514 src_path);
515 if (return_status != FILE_RETRY)
516 return return_status;
519 if (dst_exists) {
520 /* Destination already exists */
521 if (sb.st_dev == sb2.st_dev && sb.st_ino == sb2.st_ino)
522 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "),
523 src_path, dst_path);
524 /* Should we replace destination? */
525 if (tctx->ask_overwrite) {
526 ctx->do_reget = 0;
527 return_status = query_replace (ctx, dst_path, &sb, &sb2);
528 if (return_status != FILE_CONT)
529 return return_status;
533 if (!ctx->do_append) {
534 /* Check the hardlinks */
535 if (!ctx->follow_links && sb.st_nlink > 1 &&
536 check_hardlinks (src_path, dst_path, &sb) == 1) {
537 /* We have made a hardlink - no more processing is necessary */
538 return FILE_CONT;
541 if (S_ISLNK (sb.st_mode))
542 return make_symlink (ctx, src_path, dst_path);
544 if (S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode) ||
545 S_ISFIFO (sb.st_mode) || S_ISNAM (sb.st_mode) ||
546 S_ISSOCK (sb.st_mode)) {
547 while (mc_mknod (dst_path, sb.st_mode & ctx->umask_kill,
548 sb.st_rdev) < 0) {
549 return_status = file_error (
550 _(" Cannot create special file \"%s\" \n %s "), dst_path);
551 if (return_status == FILE_RETRY)
552 continue;
553 return return_status;
555 /* Success */
557 while (ctx->preserve_uidgid
558 && mc_chown (dst_path, sb.st_uid, sb.st_gid)) {
559 temp_status = file_error (
560 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
561 if (temp_status == FILE_RETRY)
562 continue;
563 return temp_status;
565 while (ctx->preserve &&
566 mc_chmod (dst_path, sb.st_mode & ctx->umask_kill)) {
567 temp_status = file_error (
568 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
569 if (temp_status == FILE_RETRY)
570 continue;
571 return temp_status;
573 return FILE_CONT;
577 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
579 while ((src_desc = mc_open (src_path, O_RDONLY | O_LINEAR)) < 0) {
580 return_status = file_error (
581 _(" Cannot open source file \"%s\" \n %s "), src_path);
582 if (return_status == FILE_RETRY)
583 continue;
584 ctx->do_append = 0;
585 return return_status;
588 if (ctx->do_reget != 0) {
589 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget) {
590 message (D_ERROR, _("Warning"),
591 _(" Reget failed, about to overwrite file "));
592 ctx->do_reget = 0;
593 ctx->do_append = FALSE;
597 while (mc_fstat (src_desc, &sb)) {
598 return_status = file_error (
599 _(" Cannot fstat source file \"%s\" \n %s "), src_path);
600 if (return_status == FILE_RETRY)
601 continue;
602 ctx->do_append = FALSE;
603 goto ret;
605 src_mode = sb.st_mode;
606 src_uid = sb.st_uid;
607 src_gid = sb.st_gid;
608 utb.actime = sb.st_atime;
609 utb.modtime = sb.st_mtime;
610 file_size = sb.st_size;
612 open_flags = O_WRONLY;
613 if (dst_exists) {
614 if (ctx->do_append != 0)
615 open_flags |= O_APPEND;
616 else
617 open_flags |= O_CREAT | O_TRUNC;
618 } else {
619 open_flags |= O_CREAT | O_EXCL;
622 while ((dest_desc = mc_open (dst_path, open_flags, src_mode)) < 0) {
623 if (errno == EEXIST) {
624 goto ret;
626 return_status = file_error (
627 _(" Cannot create target file \"%s\" \n %s "), dst_path);
628 if (return_status == FILE_RETRY)
629 continue;
630 ctx->do_append = FALSE;
631 goto ret;
633 dst_status = DEST_SHORT; /* file opened, but not fully copied */
635 appending = ctx->do_append;
636 ctx->do_append = FALSE;
638 /* Find out the optimal buffer size. */
639 while (mc_fstat (dest_desc, &sb)) {
640 return_status = file_error (
641 _(" Cannot fstat target file \"%s\" \n %s "), dst_path);
642 if (return_status == FILE_RETRY)
643 continue;
644 goto ret;
647 ctx->eta_secs = 0.0;
648 ctx->bps = 0;
650 if (tctx->bps == 0 || (file_size/(tctx->bps)) > FILEOP_UPDATE_INTERVAL) {
651 file_progress_show (ctx, 0, file_size);
652 } else {
653 file_progress_show (ctx, 1, 1);
655 return_status = check_progress_buttons (ctx);
656 mc_refresh ();
658 if (return_status != FILE_CONT)
659 goto ret;
662 struct timeval tv_current, tv_last_update, tv_last_input;
663 int secs, update_secs;
664 const char *stalled_msg="";
666 tv_last_update = tv_transfer_start;
668 for (;;) {
669 char buf[BUF_8K];
671 /* src_read */
672 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0))
673 n_read = -1;
674 else
675 while ((n_read = mc_read (src_desc, buf, sizeof (buf))) < 0) {
676 return_status = file_error (
677 _(" Cannot read source file \"%s\" \n %s "), src_path);
678 if (return_status == FILE_RETRY)
679 continue;
680 goto ret;
682 if (n_read == 0)
683 break;
685 gettimeofday (&tv_current, NULL);
687 if (n_read > 0) {
688 char *t = buf;
689 n_read_total += n_read;
691 /* Windows NT ftp servers report that files have no
692 * permissions: -------, so if we happen to have actually
693 * read something, we should fix the permissions.
695 if ((src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == 0)
696 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
697 gettimeofday (&tv_last_input, NULL);
699 /* dst_write */
700 while ((n_written =
701 mc_write (dest_desc, t, n_read)) < n_read) {
702 if (n_written > 0) {
703 n_read -= n_written;
704 t += n_written;
705 continue;
707 return_status =
708 file_error (_(" Cannot write target file \"%s\" \n %s "),
709 dst_path);
710 if (return_status != FILE_RETRY)
711 goto ret;
714 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
715 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
717 if (is_first_time || secs > FILEOP_UPDATE_INTERVAL )
719 copy_file_file_display_progress(tctx, ctx,
720 tv_current,
721 tv_transfer_start,
722 file_size,
723 n_read_total);
724 tv_last_update = tv_current;
726 is_first_time = FALSE;
728 if (update_secs > FILEOP_STALLING_INTERVAL) {
729 stalled_msg = _("(stalled)");
733 file_progress_set_stalled_label (ctx, stalled_msg);
734 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
735 file_progress_show_bytes (ctx, tctx->progress_bytes + n_read_total + ctx->do_reget,
736 ctx->progress_bytes);
738 if ((ctx->progress_bytes != 0) && (tv_current.tv_sec - tctx->transfer_start.tv_sec) > FILEOP_UPDATE_INTERVAL) {
739 file_progress_show_total (tctx, ctx);
742 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size);
743 mc_refresh ();
745 return_status = check_progress_buttons (ctx);
747 if (return_status != FILE_CONT) {
748 mc_refresh ();
749 goto ret;
754 dst_status = DEST_FULL; /* copy successful, don't remove target file */
756 ret:
757 while (src_desc != -1 && mc_close (src_desc) < 0) {
758 temp_status = file_error (
759 _(" Cannot close source file \"%s\" \n %s "), src_path);
760 if (temp_status == FILE_RETRY)
761 continue;
762 if (temp_status == FILE_ABORT)
763 return_status = temp_status;
764 break;
767 while (dest_desc != -1 && mc_close (dest_desc) < 0) {
768 temp_status = file_error (
769 _(" Cannot close target file \"%s\" \n %s "), dst_path);
770 if (temp_status == FILE_RETRY)
771 continue;
772 return_status = temp_status;
773 break;
776 if (dst_status == DEST_SHORT) {
777 /* Remove short file */
778 int result;
779 result = query_dialog (Q_("DialogTitle|Copy"),
780 _("Incomplete file was retrieved. Keep it?"),
781 D_ERROR, 2, _("&Delete"), _("&Keep"));
782 if (result == 0)
783 mc_unlink (dst_path);
784 } else if (dst_status == DEST_FULL) {
785 /* Copy has succeeded */
786 if (!appending && ctx->preserve_uidgid) {
787 while (mc_chown (dst_path, src_uid, src_gid)) {
788 temp_status = file_error (
789 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
790 if (temp_status == FILE_RETRY)
791 continue;
792 return_status = temp_status;
793 break;
797 if (!appending) {
798 if (ctx->preserve){
799 while (mc_chmod (dst_path, (src_mode & ctx->umask_kill))) {
800 temp_status = file_error (
801 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
802 if (temp_status != FILE_RETRY) {
803 return_status = temp_status;
804 break;
807 } else {
808 src_mode = umask(-1);
809 umask(src_mode);
810 src_mode = 0100666 & ~src_mode;
811 mc_chmod (dst_path, (src_mode & ctx->umask_kill));
813 mc_utime (dst_path, &utb);
817 if (return_status == FILE_CONT)
818 return_status = progress_update_one (tctx, ctx, file_size, tctx->is_toplevel_file);
820 return return_status;
822 #undef FILEOP_UPDATE_INTERVAL
824 * I think these copy_*_* functions should have a return type.
825 * anyway, this function *must* have two directories as arguments.
827 /* FIXME: This function needs to check the return values of the
828 function calls */
829 FileProgressStatus
830 copy_dir_dir (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, const char *_d,
831 gboolean toplevel, gboolean move_over, gboolean do_delete,
832 struct link *parent_dirs)
834 struct dirent *next;
835 struct stat buf, cbuf;
836 DIR *reading;
837 char *dest_dir = NULL;
838 FileProgressStatus return_status = FILE_CONT;
839 struct utimbuf utb;
840 struct link *lp;
841 char *d;
843 d = strutils_shell_unescape (_d);
845 /* First get the mode of the source dir */
846 retry_src_stat:
847 if ((*ctx->stat_func) (s, &cbuf)) {
848 return_status =
849 file_error (_(" Cannot stat source directory \"%s\" \n %s "), s);
850 if (return_status == FILE_RETRY)
851 goto retry_src_stat;
852 goto ret_fast;
855 if (is_in_linklist (dest_dirs, s, &cbuf)) {
856 /* Don't copy a directory we created before (we don't want to copy
857 infinitely if a directory is copied into itself) */
858 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
859 return_status = FILE_CONT;
860 goto ret_fast;
863 /* Hmm, hardlink to directory??? - Norbert */
864 /* FIXME: In this step we should do something
865 in case the destination already exist */
866 /* Check the hardlinks */
867 if (ctx->preserve && cbuf.st_nlink > 1
868 && check_hardlinks (s, d, &cbuf) == 1) {
869 /* We have made a hardlink - no more processing is necessary */
870 goto ret_fast;
873 if (!S_ISDIR (cbuf.st_mode)) {
874 return_status =
875 file_error (_(" Source \"%s\" is not a directory \n %s "), s);
876 if (return_status == FILE_RETRY)
877 goto retry_src_stat;
878 goto ret_fast;
881 if (is_in_linklist (parent_dirs, s, &cbuf)) {
882 /* we found a cyclic symbolic link */
883 message (D_ERROR, MSG_ERROR,
884 _(" Cannot copy cyclic symbolic link \n `%s' "), s);
885 return_status = FILE_SKIP;
886 goto ret_fast;
889 lp = g_new (struct link, 1);
890 lp->vfs = vfs_get_class (s);
891 lp->ino = cbuf.st_ino;
892 lp->dev = cbuf.st_dev;
893 lp->next = parent_dirs;
894 parent_dirs = lp;
896 retry_dst_stat:
897 /* Now, check if the dest dir exists, if not, create it. */
898 if (mc_stat (d, &buf)) {
899 /* Here the dir doesn't exist : make it ! */
900 if (move_over) {
901 if (mc_rename (s, d) == 0) {
902 return_status = FILE_CONT;
903 goto ret;
906 dest_dir = d;
907 d = NULL;
908 } else {
910 * If the destination directory exists, we want to copy the whole
911 * directory, but we only want this to happen once.
913 * Escape sequences added to the * to compiler warnings.
914 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
915 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
917 if (!S_ISDIR (buf.st_mode)) {
918 return_status = file_error(
919 _(" Destination \"%s\" must be a directory \n %s "), d);
920 if (return_status == FILE_RETRY)
921 goto retry_dst_stat;
922 goto ret;
924 /* Dive into subdir if exists */
925 if (toplevel && ctx->dive_into_subdirs) {
926 dest_dir = concat_dir_and_file (d, x_basename (s));
927 } else {
928 dest_dir = d;
929 d = NULL;
930 goto dont_mkdir;
933 while (my_mkdir (dest_dir, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU)) {
934 return_status = file_error (
935 _(" Cannot create target directory \"%s\" \n %s "), dest_dir);
936 if (return_status != FILE_RETRY)
937 goto ret;
940 lp = g_new (struct link, 1);
941 mc_stat (dest_dir, &buf);
942 lp->vfs = vfs_get_class (dest_dir);
943 lp->ino = buf.st_ino;
944 lp->dev = buf.st_dev;
945 lp->next = dest_dirs;
946 dest_dirs = lp;
948 if (ctx->preserve_uidgid) {
949 while (mc_chown (dest_dir, cbuf.st_uid, cbuf.st_gid)) {
950 return_status = file_error (
951 _(" Cannot chown target directory \"%s\" \n %s "), dest_dir);
952 if (return_status != FILE_RETRY)
953 goto ret;
957 dont_mkdir:
958 /* open the source dir for reading */
959 reading = mc_opendir (s);
960 if (reading == NULL)
961 goto ret;
963 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) {
964 char *path;
966 * Now, we don't want '.' and '..' to be created / copied at any time
968 if (!strcmp (next->d_name, "."))
969 continue;
970 if (!strcmp (next->d_name, ".."))
971 continue;
973 /* get the filename and add it to the src directory */
974 path = concat_dir_and_file (s, next->d_name);
976 (*ctx->stat_func) (path, &buf);
977 if (S_ISDIR (buf.st_mode)) {
978 char *mdpath;
980 mdpath = concat_dir_and_file (dest_dir, next->d_name);
982 * From here, we just intend to recursively copy subdirs, not
983 * the double functionality of copying different when the target
984 * dir already exists. So, we give the recursive call the flag 0
985 * meaning no toplevel.
987 return_status = copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
988 g_free (mdpath);
989 } else {
990 char *dest_file;
992 dest_file = concat_dir_and_file (dest_dir, x_basename (path));
993 return_status = copy_file_file (tctx, ctx, path, dest_file);
994 g_free (dest_file);
996 if (do_delete && return_status == FILE_CONT) {
997 if (ctx->erase_at_end) {
998 static struct link *tail;
999 size_t len = strlen (path);
1000 lp = g_malloc (sizeof (struct link) + len);
1001 strncpy (lp->name, path, len + 1);
1002 lp->st_mode = buf.st_mode;
1003 lp->next = NULL;
1004 if (erase_list != NULL) {
1005 tail->next = lp;
1006 tail = lp;
1007 } else
1008 erase_list = tail = lp;
1009 } else {
1010 if (S_ISDIR (buf.st_mode)) {
1011 return_status = erase_dir_iff_empty (ctx, path);
1012 } else
1013 return_status = erase_file (tctx, ctx, path, FALSE);
1016 g_free (path);
1018 mc_closedir (reading);
1020 if (ctx->preserve) {
1021 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
1022 utb.actime = cbuf.st_atime;
1023 utb.modtime = cbuf.st_mtime;
1024 mc_utime (dest_dir, &utb);
1025 } else {
1026 cbuf.st_mode = umask(-1);
1027 umask(cbuf.st_mode);
1028 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
1029 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
1032 ret:
1033 g_free (dest_dir);
1034 g_free (parent_dirs);
1035 ret_fast:
1036 g_free (d);
1037 return return_status;
1040 /* }}} */
1042 /* {{{ Move routines */
1044 static FileProgressStatus
1045 move_file_file (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, const char *d)
1047 struct stat src_stats, dst_stats;
1048 FileProgressStatus return_status = FILE_CONT;
1049 gboolean copy_done = FALSE;
1051 file_progress_show_source (ctx, s);
1052 file_progress_show_target (ctx, d);
1053 if (check_progress_buttons (ctx) == FILE_ABORT)
1054 return FILE_ABORT;
1056 mc_refresh ();
1058 while (mc_lstat (s, &src_stats) != 0) {
1059 /* Source doesn't exist */
1060 return_status =
1061 file_error (_(" Cannot stat file \"%s\" \n %s "), s);
1062 if (return_status != FILE_RETRY)
1063 return return_status;
1066 if (mc_lstat (d, &dst_stats) == 0) {
1067 if (src_stats.st_dev == dst_stats.st_dev
1068 && src_stats.st_ino == dst_stats.st_ino)
1069 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "), s, d);
1071 if (S_ISDIR (dst_stats.st_mode)) {
1072 message (D_ERROR, MSG_ERROR,
1073 _(" Cannot overwrite directory `%s' "), d);
1074 do_refresh ();
1075 return FILE_SKIP;
1078 if (confirm_overwrite) {
1079 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
1080 if (return_status != FILE_CONT)
1081 return return_status;
1083 /* Ok to overwrite */
1086 if (!ctx->do_append) {
1087 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks) {
1088 if ((return_status = make_symlink (ctx, s, d)) == FILE_CONT) {
1089 goto retry_src_remove;
1090 } else
1091 return return_status;
1094 if (mc_rename (s, d) == 0) {
1095 return progress_update_one (tctx, ctx, src_stats.st_size, TRUE);
1098 #if 0
1099 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1100 one nfs to the same, but on the server it is on two different
1101 filesystems. Then nfs returns EIO instead of EXDEV.
1102 Hope it will not hurt if we always in case of error try to copy/delete. */
1103 else
1104 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1106 if (errno != EXDEV) {
1107 return_status =
1108 files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s,
1110 if (return_status == FILE_RETRY)
1111 goto retry_rename;
1112 return return_status;
1114 #endif
1116 /* Failed because filesystem boundary -> copy the file instead */
1117 return_status = copy_file_file (tctx, ctx, s, d);
1118 if (return_status != FILE_CONT)
1119 return return_status;
1121 copy_done = TRUE;
1123 file_progress_show_source (ctx, NULL);
1124 file_progress_show (ctx, 0, 0);
1126 return_status = check_progress_buttons (ctx);
1127 if (return_status != FILE_CONT)
1128 return return_status;
1130 mc_refresh ();
1132 retry_src_remove:
1133 if (mc_unlink (s)) {
1134 return_status =
1135 file_error (_(" Cannot remove file \"%s\" \n %s "), s);
1136 if (return_status == FILE_RETRY)
1137 goto retry_src_remove;
1138 return return_status;
1141 if (!copy_done) {
1142 return_status = progress_update_one (tctx, ctx, src_stats.st_size, TRUE);
1145 return return_status;
1148 FileProgressStatus
1149 move_dir_dir (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, const char *d)
1151 struct stat sbuf, dbuf, destbuf;
1152 struct link *lp;
1153 char *destdir;
1154 FileProgressStatus return_status;
1155 gboolean move_over = FALSE;
1156 gboolean dstat_ok;
1158 file_progress_show_source (ctx, s);
1159 file_progress_show_target (ctx, d);
1160 if (check_progress_buttons (ctx) == FILE_ABORT)
1161 return FILE_ABORT;
1163 mc_refresh ();
1165 mc_stat (s, &sbuf);
1166 dstat_ok = (mc_stat (d, &dbuf) == 0);
1168 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
1169 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same directory "), s, d);
1171 if (!dstat_ok)
1172 destdir = g_strdup (d); /* destination doesn't exist */
1173 else if (!ctx->dive_into_subdirs) {
1174 destdir = g_strdup (d);
1175 move_over = TRUE;
1176 } else
1177 destdir = concat_dir_and_file (d, x_basename (s));
1179 /* Check if the user inputted an existing dir */
1180 retry_dst_stat:
1181 if (!mc_stat (destdir, &destbuf)) {
1182 if (move_over) {
1183 return_status = copy_dir_dir (tctx, ctx, s, destdir, FALSE, TRUE, TRUE, NULL);
1185 if (return_status != FILE_CONT)
1186 goto ret;
1187 goto oktoret;
1188 } else {
1189 if (S_ISDIR (destbuf.st_mode))
1190 return_status =
1191 file_error (_
1192 (" Cannot overwrite directory \"%s\" %s "),
1193 destdir);
1194 else
1195 return_status =
1196 file_error (_(" Cannot overwrite file \"%s\" %s "),
1197 destdir);
1198 if (return_status == FILE_RETRY)
1199 goto retry_dst_stat;
1201 g_free (destdir);
1202 return return_status;
1205 retry_rename:
1206 if (mc_rename (s, destdir) == 0) {
1207 return_status = FILE_CONT;
1208 goto ret;
1211 if (errno != EXDEV) {
1212 return_status =
1213 files_error (_
1214 (" Cannot move directory \"%s\" to \"%s\" \n %s "),
1215 s, d);
1216 if (return_status == FILE_RETRY)
1217 goto retry_rename;
1218 goto ret;
1220 /* Failed because of filesystem boundary -> copy dir instead */
1221 return_status =
1222 copy_dir_dir (tctx, ctx, s, destdir, FALSE, FALSE, TRUE, NULL);
1224 if (return_status != FILE_CONT)
1225 goto ret;
1226 oktoret:
1227 file_progress_show_source (ctx, NULL);
1228 file_progress_show (ctx, 0, 0);
1230 return_status = check_progress_buttons (ctx);
1231 if (return_status != FILE_CONT)
1232 goto ret;
1234 mc_refresh ();
1235 if (ctx->erase_at_end) {
1236 for (; erase_list && return_status != FILE_ABORT;) {
1237 if (S_ISDIR (erase_list->st_mode)) {
1238 return_status =
1239 erase_dir_iff_empty (ctx, erase_list->name);
1240 } else
1241 return_status =
1242 erase_file (tctx, ctx, erase_list->name, FALSE);
1243 lp = erase_list;
1244 erase_list = erase_list->next;
1245 g_free (lp);
1248 erase_dir_iff_empty (ctx, s);
1250 ret:
1251 g_free (destdir);
1252 while (erase_list) {
1253 lp = erase_list;
1254 erase_list = erase_list->next;
1255 g_free (lp);
1257 return return_status;
1260 /* }}} */
1262 /* {{{ Erase routines */
1263 /* Don't update progress status if progress_count==NULL */
1264 static FileProgressStatus
1265 erase_file (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s, gboolean is_toplevel_file)
1267 int return_status;
1268 struct stat buf;
1270 file_progress_show_deleting (ctx, s);
1271 if (check_progress_buttons (ctx) == FILE_ABORT)
1272 return FILE_ABORT;
1273 mc_refresh ();
1275 if (tctx->progress_count && mc_lstat (s, &buf)) {
1276 /* ignore, most likely the mc_unlink fails, too */
1277 buf.st_size = 0;
1280 while (mc_unlink (s)) {
1281 return_status =
1282 file_error (_(" Cannot delete file \"%s\" \n %s "), s);
1283 if (return_status != FILE_RETRY)
1284 return return_status;
1287 if (tctx->progress_count)
1288 return progress_update_one (tctx, ctx, buf.st_size, is_toplevel_file);
1289 else
1290 return FILE_CONT;
1293 static FileProgressStatus
1294 recursive_erase (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s)
1296 struct dirent *next;
1297 struct stat buf;
1298 DIR *reading;
1299 char *path;
1300 FileProgressStatus return_status = FILE_CONT;
1302 if (!strcmp (s, ".."))
1303 return FILE_RETRY;
1305 reading = mc_opendir (s);
1307 if (!reading)
1308 return FILE_RETRY;
1310 while ((next = mc_readdir (reading)) && return_status == FILE_CONT) {
1311 if (!strcmp (next->d_name, "."))
1312 continue;
1313 if (!strcmp (next->d_name, ".."))
1314 continue;
1315 path = concat_dir_and_file (s, next->d_name);
1316 if (mc_lstat (path, &buf)) {
1317 g_free (path);
1318 mc_closedir (reading);
1319 return FILE_RETRY;
1321 if (S_ISDIR (buf.st_mode))
1322 return_status =
1323 (recursive_erase (tctx, ctx, path) != FILE_CONT) ? FILE_RETRY : FILE_CONT;
1324 else
1325 return_status =
1326 erase_file (tctx, ctx, path, 0);
1327 g_free (path);
1329 mc_closedir (reading);
1330 if (return_status != FILE_CONT)
1331 return return_status;
1332 file_progress_show_deleting (ctx, s);
1333 if (check_progress_buttons (ctx) == FILE_ABORT)
1334 return FILE_ABORT;
1335 mc_refresh ();
1337 while (my_rmdir (s)) {
1338 return_status =
1339 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1340 if (return_status != FILE_RETRY)
1341 return return_status;
1344 return FILE_CONT;
1347 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1348 in the directory path points to, 0 else. */
1349 static int
1350 check_dir_is_empty (const char *path)
1352 DIR *dir;
1353 struct dirent *d;
1354 int i;
1356 dir = mc_opendir (path);
1357 if (!dir)
1358 return -1;
1360 for (i = 1, d = mc_readdir (dir); d; d = mc_readdir (dir)) {
1361 if (d->d_name[0] == '.' && (d->d_name[1] == '\0' ||
1362 (d->d_name[1] == '.'
1363 && d->d_name[2] == '\0')))
1364 continue; /* "." or ".." */
1365 i = 0;
1366 break;
1369 mc_closedir (dir);
1370 return i;
1373 FileProgressStatus
1374 erase_dir (FileOpTotalContext *tctx, FileOpContext *ctx, const char *s)
1376 FileProgressStatus error;
1378 if (strcmp (s, "..") == 0)
1379 return FILE_SKIP;
1381 if (strcmp (s, ".") == 0)
1382 return FILE_SKIP;
1384 file_progress_show_deleting (ctx, s);
1385 if (check_progress_buttons (ctx) == FILE_ABORT)
1386 return FILE_ABORT;
1387 mc_refresh ();
1389 /* The old way to detect a non empty directory was:
1390 error = my_rmdir (s);
1391 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1392 For the linux user space nfs server (nfs-server-2.2beta29-2)
1393 we would have to check also for EIO. I hope the new way is
1394 fool proof. (Norbert)
1396 error = check_dir_is_empty (s);
1397 if (error == 0) { /* not empty */
1398 error = query_recursive (ctx, s);
1399 if (error == FILE_CONT)
1400 return recursive_erase (tctx, ctx, s);
1401 else
1402 return error;
1405 while (my_rmdir (s) == -1) {
1406 error =
1407 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1408 if (error != FILE_RETRY)
1409 return error;
1412 return FILE_CONT;
1415 static FileProgressStatus
1416 erase_dir_iff_empty (FileOpContext *ctx, const char *s)
1418 FileProgressStatus error;
1420 if (strcmp (s, "..") == 0)
1421 return FILE_SKIP;
1423 if (strcmp (s, ".") == 0)
1424 return FILE_SKIP;
1426 file_progress_show_deleting (ctx, s);
1427 if (check_progress_buttons (ctx) == FILE_ABORT)
1428 return FILE_ABORT;
1429 mc_refresh ();
1431 if (1 != check_dir_is_empty (s)) /* not empty or error */
1432 return FILE_CONT;
1434 while (my_rmdir (s)) {
1435 error =
1436 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1437 if (error != FILE_RETRY)
1438 return error;
1441 return FILE_CONT;
1444 /* }}} */
1446 /* {{{ Panel operate routines */
1449 * Return currently selected entry name or the name of the first marked
1450 * entry if there is one.
1452 static char *
1453 panel_get_file (WPanel *panel, struct stat *stat_buf)
1455 int i;
1457 if (get_current_type () == view_tree) {
1458 WTree *tree = (WTree *) get_panel_widget (get_current_index ());
1459 char *tree_name = tree_selected_name (tree);
1461 mc_stat (tree_name, stat_buf);
1462 return tree_name;
1465 if (panel->marked) {
1466 for (i = 0; i < panel->count; i++)
1467 if (panel->dir.list[i].f.marked) {
1468 *stat_buf = panel->dir.list[i].st;
1469 return panel->dir.list[i].fname;
1471 } else {
1472 *stat_buf = panel->dir.list[panel->selected].st;
1473 return panel->dir.list[panel->selected].fname;
1475 g_assert_not_reached ();
1476 return NULL;
1480 ComputeDirSizeUI *
1481 compute_dir_size_create_ui (void)
1483 ComputeDirSizeUI *ui;
1485 const char *b_name = N_("&Abort");
1487 #ifdef ENABLE_NLS
1488 b_name = _(b_name);
1489 #endif
1491 ui = g_new (ComputeDirSizeUI, 1);
1493 ui->dlg = create_dlg (0, 0, 8, COLS/2, dialog_colors, NULL,
1494 NULL, _("Directory scanning"), DLG_CENTER);
1495 ui->dirname = label_new (3, 3, "");
1496 add_widget (ui->dlg, ui->dirname);
1498 add_widget (ui->dlg,
1499 button_new (5, (ui->dlg->cols - strlen (b_name))/2,
1500 FILE_ABORT, NORMAL_BUTTON, b_name, NULL));
1502 /* We will manage the dialog without any help,
1503 that's why we have to call init_dlg */
1504 init_dlg (ui->dlg);
1506 return ui;
1509 void
1510 compute_dir_size_destroy_ui (ComputeDirSizeUI *ui)
1512 if (ui != NULL) {
1513 /* schedule to update passive panel */
1514 other_panel->dirty = 1;
1516 /* close and destroy dialog */
1517 dlg_run_done (ui->dlg);
1518 destroy_dlg (ui->dlg);
1519 g_free (ui);
1523 FileProgressStatus
1524 compute_dir_size_update_ui (const void *ui, const char *dirname)
1526 const ComputeDirSizeUI *this = (const ComputeDirSizeUI *) ui;
1527 int c;
1528 Gpm_Event event;
1530 if (ui == NULL)
1531 return FILE_CONT;
1533 label_set_text (this->dirname, name_trunc (dirname, this->dlg->cols - 6));
1535 event.x = -1; /* Don't show the GPM cursor */
1536 c = tty_get_event (&event, FALSE, FALSE);
1537 if (c == EV_NONE)
1538 return FILE_CONT;
1540 /* Reinitialize to avoid old values after events other than
1541 selecting a button */
1542 this->dlg->ret_value = FILE_CONT;
1544 dlg_process_event (this->dlg, c, &event);
1546 switch (this->dlg->ret_value) {
1547 case B_CANCEL:
1548 case FILE_ABORT:
1549 return FILE_ABORT;
1550 default:
1551 return FILE_CONT;
1556 * compute_dir_size:
1558 * Computes the number of bytes used by the files in a directory
1560 FileProgressStatus
1561 compute_dir_size (const char *dirname, const void *ui,
1562 compute_dir_size_callback cback,
1563 off_t *ret_marked, double *ret_total)
1565 DIR *dir;
1566 struct dirent *dirent;
1567 FileProgressStatus ret = FILE_CONT;
1569 dir = mc_opendir (dirname);
1571 if (dir == NULL)
1572 return ret;
1574 while ((dirent = mc_readdir (dir)) != NULL) {
1575 char *fullname;
1576 int res;
1577 struct stat s;
1579 ret = (cback != NULL) ? cback (ui, dirname) : FILE_CONT;
1581 if (ret != FILE_CONT)
1582 break;
1584 if (strcmp (dirent->d_name, ".") == 0)
1585 continue;
1586 if (strcmp (dirent->d_name, "..") == 0)
1587 continue;
1589 fullname = concat_dir_and_file (dirname, dirent->d_name);
1590 res = mc_lstat (fullname, &s);
1592 if (res != 0) {
1593 g_free (fullname);
1594 continue;
1597 if (S_ISDIR (s.st_mode)) {
1598 off_t subdir_count = 0;
1599 double subdir_bytes = 0;
1601 ret = compute_dir_size (fullname, ui, cback, &subdir_count, &subdir_bytes);
1603 if (ret != FILE_CONT) {
1604 g_free (fullname);
1605 break;
1608 *ret_marked += subdir_count;
1609 *ret_total += subdir_bytes;
1610 } else {
1611 (*ret_marked)++;
1612 *ret_total += s.st_size;
1615 g_free (fullname);
1618 mc_closedir (dir);
1620 return ret;
1624 * panel_compute_totals:
1626 * compute the number of files and the number of bytes
1627 * used up by the whole selection, recursing directories
1628 * as required. In addition, it checks to see if it will
1629 * overwrite any files by doing the copy.
1631 static FileProgressStatus
1632 panel_compute_totals (const WPanel *panel, const void *ui,
1633 compute_dir_size_callback cback,
1634 off_t *ret_marked, double *ret_total)
1636 int i;
1638 *ret_marked = 0;
1639 *ret_total = 0.0;
1641 for (i = 0; i < panel->count; i++) {
1642 struct stat *s;
1644 if (!panel->dir.list[i].f.marked)
1645 continue;
1647 s = &panel->dir.list[i].st;
1649 if (S_ISDIR (s->st_mode)) {
1650 char *dir_name;
1651 off_t subdir_count = 0;
1652 double subdir_bytes = 0;
1653 FileProgressStatus status;
1655 dir_name =
1656 concat_dir_and_file (panel->cwd, panel->dir.list[i].fname);
1658 status = compute_dir_size (dir_name, ui, cback,
1659 &subdir_count, &subdir_bytes);
1660 g_free (dir_name);
1662 if (status != FILE_CONT)
1663 return FILE_ABORT;
1665 *ret_marked += subdir_count;
1666 *ret_total += subdir_bytes;
1667 } else {
1668 (*ret_marked)++;
1669 *ret_total += s->st_size;
1673 return FILE_CONT;
1676 /* Initialize variables for progress bars */
1677 static FileProgressStatus
1678 panel_operate_init_totals (FileOperation operation,
1679 const WPanel *panel, const char *source,
1680 FileOpContext *ctx)
1682 FileProgressStatus status;
1684 if (operation != OP_MOVE && verbose && file_op_compute_totals) {
1685 ComputeDirSizeUI *ui;
1687 ui = compute_dir_size_create_ui ();
1689 if (source != NULL)
1690 status = compute_dir_size (source, ui, compute_dir_size_update_ui,
1691 &ctx->progress_count, &ctx->progress_bytes);
1692 else
1693 status = panel_compute_totals (panel, ui, compute_dir_size_update_ui,
1694 &ctx->progress_count, &ctx->progress_bytes);
1696 compute_dir_size_destroy_ui (ui);
1698 ctx->progress_totals_computed = (status == FILE_CONT);
1699 } else {
1700 status = FILE_CONT;
1701 ctx->progress_count = panel->marked;
1702 ctx->progress_bytes = panel->total;
1703 ctx->progress_totals_computed = FALSE;
1706 return status;
1710 * This array introduced to avoid translation problems. The former (op_names)
1711 * is assumed to be nouns, suitable in dialog box titles; this one should
1712 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
1713 * (I don't use spaces around the words, because someday they could be
1714 * dropped, when widgets get smarter)
1717 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
1718 static const char *op_names1[] = {
1719 N_("FileOperation|Copy"),
1720 N_("FileOperation|Move"),
1721 N_("FileOperation|Delete")
1725 * These are formats for building a prompt. Parts encoded as follows:
1726 * %o - operation from op_names1
1727 * %f - file/files or files/directories, as appropriate
1728 * %m - "with source mask" or question mark for delete
1729 * %s - source name (truncated)
1730 * %d - number of marked files
1731 * %e - "to:" or question mark for delete
1733 * xgettext:no-c-format */
1734 static const char *one_format = N_("%o %f \"%s\"%m");
1735 /* xgettext:no-c-format */
1736 static const char *many_format = N_("%o %d %f%m");
1738 static const char *prompt_parts[] = {
1739 N_("file"),
1740 N_("files"),
1741 N_("directory"),
1742 N_("directories"),
1743 N_("files/directories"),
1744 N_(" with source mask:"),
1745 N_(" to:")
1748 static const char *question_format = N_("%s?");
1751 * Generate user prompt for panel operation.
1752 * single_source is the name if the source entry or NULL for multiple
1753 * entries.
1754 * src_stat is only used when single_source is not NULL.
1756 static char *
1757 panel_operate_generate_prompt (const WPanel *panel, FileOperation operation,
1758 gboolean single_source,
1759 const struct stat *src_stat)
1761 const char *sp, *cp;
1762 char format_string[BUF_MEDIUM];
1763 char *dp = format_string;
1764 gboolean build_question = FALSE;
1766 #ifdef ENABLE_NLS
1767 static gboolean i18n_flag = FALSE;
1768 if (!i18n_flag) {
1769 size_t i;
1771 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1772 op_names1[i] = Q_(op_names1[i]);
1774 for (i = sizeof (prompt_parts) / sizeof (prompt_parts[0]); i--;)
1775 prompt_parts[i] = _(prompt_parts[i]);
1777 one_format = _(one_format);
1778 many_format = _(many_format);
1779 question_format = _(question_format);
1780 i18n_flag = TRUE;
1782 #endif /* ENABLE_NLS */
1784 sp = single_source ? one_format : many_format;
1786 while (*sp != '\0') {
1787 switch (*sp) {
1788 case '%':
1789 cp = NULL;
1790 switch (sp[1]) {
1791 case 'o':
1792 cp = op_names1[operation];
1793 break;
1794 case 'm':
1795 if (operation == OP_DELETE) {
1796 cp = "";
1797 build_question = TRUE;
1798 } else
1799 cp = prompt_parts[5];
1800 break;
1801 case 'e':
1802 if (operation == OP_DELETE) {
1803 cp = "";
1804 build_question = TRUE;
1805 } else
1806 cp = prompt_parts[6];
1807 break;
1808 case 'f':
1809 if (single_source) {
1810 cp = S_ISDIR (src_stat->
1811 st_mode) ? prompt_parts[2] : prompt_parts[0];
1812 } else {
1813 cp = (panel->marked == panel->dirs_marked)
1814 ? prompt_parts[3]
1815 : (panel->dirs_marked ? prompt_parts[4] : prompt_parts[1]);
1817 break;
1818 default:
1819 *dp++ = *sp++;
1821 if (cp != NULL) {
1822 sp += 2;
1823 while (*cp != '\0')
1824 *dp++ = *cp++;
1826 break;
1827 default:
1828 *dp++ = *sp++;
1831 *dp = '\0';
1833 if (build_question) {
1834 char tmp[BUF_MEDIUM];
1836 memmove (tmp, format_string, sizeof (tmp));
1837 g_snprintf (format_string, sizeof (format_string),
1838 question_format, tmp);
1841 return g_strdup (format_string);
1844 #ifdef WITH_BACKGROUND
1845 static int
1846 end_bg_process (FileOpContext *ctx, enum OperationMode mode) {
1847 int pid = ctx->pid;
1849 (void) mode;
1850 ctx->pid = 0;
1852 unregister_task_with_pid(pid);
1853 // file_op_context_destroy(ctx);
1854 return 1;
1856 #endif
1859 * panel_operate:
1861 * Performs one of the operations on the selection on the source_panel
1862 * (copy, delete, move).
1864 * Returns TRUE if did change the directory
1865 * structure, Returns FALSE if user aborted
1867 * force_single forces operation on the current entry and affects
1868 * default destination. Current filename is used as default.
1870 gboolean
1871 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
1873 WPanel *panel = (WPanel *) source_panel;
1874 const gboolean single_entry = force_single || (panel->marked <= 1)
1875 || (get_current_type () == view_tree);
1877 char *source = NULL;
1878 #ifdef WITH_FULL_PATHS
1879 char *source_with_path = NULL;
1880 #else
1881 # define source_with_path source
1882 #endif /* !WITH_FULL_PATHS */
1883 char *dest = NULL;
1884 char *temp = NULL;
1885 char *save_cwd = NULL, *save_dest = NULL;
1886 struct stat src_stat;
1887 int i;
1888 FileProgressStatus value;
1889 FileOpContext *ctx;
1890 FileOpTotalContext *tctx;
1892 gboolean do_bg = FALSE; /* do background operation? */
1894 #ifdef ENABLE_NLS
1895 static gboolean i18n_flag = FALSE;
1896 if (!i18n_flag) {
1897 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1898 op_names[i] = Q_(op_names[i]);
1899 i18n_flag = TRUE;
1901 #endif /* ENABLE_NLS */
1903 free_linklist (&linklist);
1904 free_linklist (&dest_dirs);
1906 /* Update panel contents to avoid actions on deleted files */
1907 if (!panel->is_panelized) {
1908 update_panels (UP_RELOAD, UP_KEEPSEL);
1909 repaint_screen ();
1912 if (single_entry) {
1913 if (force_single) {
1914 source = selection (panel)->fname;
1915 src_stat = selection (panel)->st;
1916 } else {
1917 source = panel_get_file (panel, &src_stat);
1920 if (!strcmp (source, "..")) {
1921 message (D_ERROR, MSG_ERROR, _(" Cannot operate on \"..\"! "));
1922 return FALSE;
1926 ctx = file_op_context_new (operation);
1927 tctx = file_op_total_context_new ();
1928 gettimeofday (&(tctx->transfer_start), (struct timezone *) NULL);
1930 /* Show confirmation dialog */
1931 if (operation != OP_DELETE) {
1932 char *dest_dir;
1933 char *dest_dir_;
1934 char *format;
1936 /* Forced single operations default to the original name */
1937 if (force_single)
1938 dest_dir = source;
1939 else if (get_other_type () == view_listing)
1940 dest_dir = other_panel->cwd;
1941 else
1942 dest_dir = panel->cwd;
1944 * Add trailing backslash only when do non-local ops.
1945 * It saves user from occasional file renames (when destination
1946 * dir is deleted)
1948 if (!force_single
1949 && dest_dir[0] != '\0'
1950 && dest_dir[strlen (dest_dir) - 1] != PATH_SEP) {
1951 /* add trailing separator */
1952 dest_dir_ = g_strconcat (dest_dir, PATH_SEP_STR, (char *) NULL);
1953 } else {
1954 /* just copy */
1955 dest_dir_ = g_strdup (dest_dir);
1957 if (dest_dir_ == NULL) {
1958 file_op_total_context_destroy (tctx);
1959 file_op_context_destroy (ctx);
1960 return FALSE;
1963 /* Generate confirmation prompt */
1964 format = panel_operate_generate_prompt (panel, operation,
1965 source != NULL, &src_stat);
1967 dest = file_mask_dialog (ctx, operation, source != NULL, format,
1968 source != NULL ? (void *) source
1969 : (void *) &panel->marked,
1970 dest_dir_, &do_bg);
1972 g_free (format);
1973 g_free (dest_dir_);
1975 if (dest == NULL || dest[0] == '\0') {
1976 file_op_total_context_destroy (tctx);
1977 file_op_context_destroy (ctx);
1978 g_free (dest);
1979 return FALSE;
1981 } else if (confirm_delete) {
1982 char *format;
1983 char fmd_buf [BUF_MEDIUM];
1985 /* Generate confirmation prompt */
1986 format = panel_operate_generate_prompt (panel, OP_DELETE,
1987 source != NULL, &src_stat);
1989 if (source == NULL)
1990 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1991 else {
1992 const int fmd_xlen = 64;
1993 i = fmd_xlen - str_term_width1 (format) - 4;
1994 g_snprintf (fmd_buf, sizeof (fmd_buf),
1995 format, str_trunc (source, i));
1998 g_free (format);
2000 if (safe_delete)
2001 query_set_sel (1);
2003 i = query_dialog (op_names[operation], fmd_buf, D_ERROR, 2,
2004 _("&Yes"), _("&No"));
2006 if (i != 0) {
2007 file_op_total_context_destroy (tctx);
2008 file_op_context_destroy (ctx);
2009 return FALSE;
2014 gboolean show_total = !((operation != OP_COPY) || (single_entry) || (force_single));
2016 if ((single_entry) && (operation == OP_COPY) && S_ISDIR (selection (panel)->st.st_mode))
2017 show_total = TRUE;
2018 /* Background also need ctx->ui, but not full */
2019 if (do_bg)
2020 file_op_context_create_ui_without_init (ctx, 1, show_total);
2021 else
2022 file_op_context_create_ui (ctx, 1, show_total);
2025 #ifdef WITH_BACKGROUND
2026 /* Did the user select to do a background operation? */
2027 if (do_bg) {
2028 int v;
2030 v = do_background (ctx,
2031 g_strconcat (op_names[operation], ": ",
2032 panel->cwd, (char *) NULL));
2033 if (v == -1) {
2034 message (D_ERROR, MSG_ERROR,
2035 _(" Sorry, I could not put the job in background "));
2038 /* If we are the parent */
2039 if (v == 1) {
2040 mc_setctl (panel->cwd, VFS_SETCTL_FORGET, NULL);
2041 mc_setctl (dest, VFS_SETCTL_FORGET, NULL);
2042 /* file_op_context_destroy (ctx); */
2043 return FALSE;
2046 #endif /* WITH_BACKGROUND */
2048 /* Initialize things */
2049 /* We do not want to trash cache every time file is
2050 created/touched. However, this will make our cache contain
2051 invalid data. */
2052 if ((dest != NULL)
2053 && (mc_setctl (dest, VFS_SETCTL_STALE_DATA, (void *) 1)))
2054 save_dest = g_strdup (dest);
2056 if ((panel->cwd[0] != '\0')
2057 && (mc_setctl (panel->cwd, VFS_SETCTL_STALE_DATA, (void *) 1)))
2058 save_cwd = g_strdup (panel->cwd);
2060 /* Now, let's do the job */
2062 /* This code is only called by the tree and panel code */
2063 if (single_entry) {
2064 /* We now have ETA in all cases */
2066 /* One file: FIXME mc_chdir will take user out of any vfs */
2067 if (operation != OP_COPY && get_current_type () == view_tree)
2068 mc_chdir (PATH_SEP_STR);
2070 /* The source and src_stat variables have been initialized before */
2071 #ifdef WITH_FULL_PATHS
2072 source_with_path = concat_dir_and_file (panel->cwd, source);
2073 #endif /* WITH_FULL_PATHS */
2075 if (panel_operate_init_totals (operation, panel,
2076 source_with_path, ctx) == FILE_CONT) {
2077 if (operation == OP_DELETE) {
2078 if (S_ISDIR (src_stat.st_mode))
2079 value = erase_dir (tctx, ctx, source_with_path);
2080 else
2081 value = erase_file (tctx, ctx, source_with_path, 1);
2082 } else {
2083 temp = transform_source (ctx, source_with_path);
2084 if (temp == NULL)
2085 value = transform_error;
2086 else {
2087 char *repl_dest, *temp2;
2089 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2090 temp2 = concat_dir_and_file (repl_dest, temp);
2091 g_free (temp);
2092 g_free (repl_dest);
2093 g_free (dest);
2094 dest = temp2;
2096 switch (operation) {
2097 case OP_COPY:
2098 /* we use file_mask_op_follow_links only with OP_COPY */
2099 ctx->stat_func (source_with_path, &src_stat);
2101 if (S_ISDIR (src_stat.st_mode))
2102 value = copy_dir_dir (tctx, ctx, source_with_path, dest,
2103 TRUE, FALSE, FALSE, NULL);
2104 else
2105 value = copy_file_file (tctx, ctx, source_with_path, dest);
2106 break;
2108 case OP_MOVE:
2109 if (S_ISDIR (src_stat.st_mode))
2110 value = move_dir_dir (tctx, ctx, source_with_path, dest);
2111 else
2112 value = move_file_file (tctx, ctx, source_with_path, dest);
2113 break;
2115 default:
2116 /* Unknown file operation */
2117 abort ();
2120 } /* Copy or move operation */
2122 if ((value == FILE_CONT) && !force_single)
2123 unmark_files (panel);
2125 } else {
2126 /* Many files */
2128 /* Check destination for copy or move operation */
2129 while (operation != OP_DELETE) {
2130 int dst_result;
2131 struct stat dst_stat;
2133 dst_result = mc_stat (dest, &dst_stat);
2135 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
2136 break;
2138 if (file_error (_(" Destination \"%s\" must be a directory \n %s "),
2139 dest) != FILE_RETRY)
2140 goto clean_up;
2143 if (panel_operate_init_totals (operation, panel, NULL, ctx) == FILE_CONT) {
2144 /* Loop for every file, perform the actual copy operation */
2145 for (i = 0; i < panel->count; i++) {
2146 if (!panel->dir.list[i].f.marked)
2147 continue; /* Skip the unmarked ones */
2149 source = panel->dir.list[i].fname;
2150 src_stat = panel->dir.list[i].st;
2152 #ifdef WITH_FULL_PATHS
2153 g_free (source_with_path);
2154 source_with_path = concat_dir_and_file (panel->cwd, source);
2155 #endif /* WITH_FULL_PATHS */
2157 if (operation == OP_DELETE) {
2158 if (S_ISDIR (src_stat.st_mode))
2159 value = erase_dir (tctx, ctx, source_with_path);
2160 else
2161 value = erase_file (tctx, ctx, source_with_path, 1);
2162 } else {
2163 temp = transform_source (ctx, source_with_path);
2165 if (temp == NULL)
2166 value = transform_error;
2167 else {
2168 char *temp2, *temp3, *repl_dest;
2170 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2171 temp2 = concat_dir_and_file (repl_dest, temp);
2172 g_free (temp);
2173 g_free (repl_dest);
2174 temp3 = source_with_path;
2175 source_with_path = strutils_shell_unescape (source_with_path);
2176 g_free (temp3);
2177 temp3 = temp2;
2178 temp2 = strutils_shell_unescape (temp2);
2179 g_free (temp3);
2181 switch (operation) {
2182 case OP_COPY:
2183 /* we use file_mask_op_follow_links only with OP_COPY */
2184 ctx->stat_func (source_with_path, &src_stat);
2185 if (S_ISDIR (src_stat.st_mode))
2186 value = copy_dir_dir (tctx, ctx, source_with_path, temp2,
2187 TRUE, FALSE, FALSE, NULL);
2188 else
2189 value = copy_file_file (tctx, ctx, source_with_path, temp2);
2190 free_linklist (&dest_dirs);
2191 break;
2193 case OP_MOVE:
2194 if (S_ISDIR (src_stat.st_mode))
2195 value = move_dir_dir (tctx, ctx, source_with_path, temp2);
2196 else
2197 value = move_file_file (tctx, ctx, source_with_path, temp2);
2198 break;
2200 default:
2201 /* Unknown file operation */
2202 abort ();
2205 g_free (temp2);
2207 } /* Copy or move operation */
2209 if (value == FILE_ABORT)
2210 break;
2212 if (value == FILE_CONT)
2213 do_file_mark (panel, i, 0);
2215 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2218 if (verbose) {
2219 file_progress_show_bytes (ctx, tctx->progress_bytes, ctx->progress_bytes);
2221 if (operation != OP_DELETE)
2222 file_progress_show (ctx, 0, 0);
2225 if (check_progress_buttons (ctx) == FILE_ABORT)
2226 break;
2228 mc_refresh ();
2229 } /* Loop for every file */
2231 } /* Many entries */
2233 clean_up:
2234 /* Clean up */
2235 if (save_cwd != NULL) {
2236 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
2237 g_free (save_cwd);
2240 if (save_dest != NULL) {
2241 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
2242 g_free (save_dest);
2245 free_linklist (&linklist);
2246 free_linklist (&dest_dirs);
2247 #ifdef WITH_FULL_PATHS
2248 g_free (source_with_path);
2249 #endif /* WITH_FULL_PATHS */
2250 g_free (dest);
2251 g_free (ctx->dest_mask);
2252 ctx->dest_mask = NULL;
2254 #ifdef WITH_BACKGROUND
2255 /* Let our parent know we are saying bye bye */
2256 if (we_are_background) {
2257 int cur_pid = getpid();
2258 /* Send pid to parent with child context, it is fork and
2259 don't modify real parent ctx */
2260 ctx->pid = cur_pid;
2261 parent_call ((void *) end_bg_process, ctx, 0);
2263 vfs_shut ();
2264 _exit (0);
2266 #endif /* WITH_BACKGROUND */
2268 file_op_context_destroy (ctx);
2269 return TRUE;
2272 /* }}} */
2274 /* {{{ Query/status report routines */
2276 static FileProgressStatus
2277 real_do_file_error (enum OperationMode mode, const char *error)
2279 int result;
2280 const char *msg;
2282 msg = mode == Foreground ? MSG_ERROR : _(" Background process error ");
2283 result =
2284 query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("&Retry"),
2285 _("&Abort"));
2287 switch (result) {
2288 case 0:
2289 do_refresh ();
2290 return FILE_SKIP;
2292 case 1:
2293 do_refresh ();
2294 return FILE_RETRY;
2296 case 2:
2297 default:
2298 return FILE_ABORT;
2302 /* Report error with one file */
2303 FileProgressStatus
2304 file_error (const char *format, const char *file)
2306 char buf [BUF_MEDIUM];
2308 g_snprintf (buf, sizeof (buf), format,
2309 path_trunc (file, 30), unix_error_string (errno));
2311 return do_file_error (buf);
2314 /* Report error with two files */
2315 static FileProgressStatus
2316 files_error (const char *format, const char *file1, const char *file2)
2318 char buf [BUF_MEDIUM];
2319 char *nfile1 = g_strdup (path_trunc (file1, 15));
2320 char *nfile2 = g_strdup (path_trunc (file2, 15));
2322 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2,
2323 unix_error_string (errno));
2325 g_free (nfile1);
2326 g_free (nfile2);
2328 return do_file_error (buf);
2331 static FileProgressStatus
2332 real_query_recursive (FileOpContext *ctx, enum OperationMode mode, const char *s)
2334 gchar *text;
2336 if (ctx->recursive_result < RECURSIVE_ALWAYS) {
2337 const char *msg = mode == Foreground
2338 ? _("\n Directory not empty. \n"
2339 " Delete it recursively? ")
2340 : _("\n Background process: Directory not empty \n"
2341 " Delete it recursively? ");
2342 text = g_strconcat (_(" Delete: "), path_trunc (s, 30), " ", (char *) NULL);
2344 if (safe_delete)
2345 query_set_sel (1);
2347 ctx->recursive_result =
2348 (FileCopyMode) query_dialog (text, msg, D_ERROR, 5,
2349 _("&Yes"), _("&No"),
2350 _("A&ll"), _("Non&e"),
2351 _("&Abort"));
2353 if (ctx->recursive_result != RECURSIVE_ABORT)
2354 do_refresh ();
2355 g_free (text);
2358 switch (ctx->recursive_result) {
2359 case RECURSIVE_YES:
2360 case RECURSIVE_ALWAYS:
2361 return FILE_CONT;
2363 case RECURSIVE_NO:
2364 case RECURSIVE_NEVER:
2365 return FILE_SKIP;
2367 case RECURSIVE_ABORT:
2368 default:
2369 return FILE_ABORT;
2373 #ifdef WITH_BACKGROUND
2374 static FileProgressStatus
2375 do_file_error (const char *str)
2377 union {
2378 void *p;
2379 FileProgressStatus (*f) (enum OperationMode, const char *);
2380 } pntr;
2381 pntr.f = real_do_file_error;
2383 if (we_are_background)
2384 return parent_call (pntr.p, NULL, 1, strlen (str),
2385 str);
2386 else
2387 return real_do_file_error (Foreground, str);
2390 static FileProgressStatus
2391 query_recursive (FileOpContext *ctx, const char *s)
2393 union {
2394 void *p;
2395 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *);
2396 } pntr;
2397 pntr.f = real_query_recursive;
2399 if (we_are_background)
2400 return parent_call (pntr.p, ctx, 1, strlen (s), s);
2401 else
2402 return real_query_recursive (ctx, Foreground, s);
2405 static FileProgressStatus
2406 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2407 struct stat *_d_stat)
2409 union {
2410 void *p;
2411 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *,
2412 struct stat *, struct stat *);
2413 } pntr;
2414 pntr.f = file_progress_real_query_replace;
2416 if (we_are_background)
2417 return parent_call (pntr.p,
2418 ctx,
2420 strlen (destname), destname,
2421 sizeof (struct stat), _s_stat,
2422 sizeof (struct stat), _d_stat);
2423 else
2424 return file_progress_real_query_replace (ctx, Foreground, destname,
2425 _s_stat, _d_stat);
2428 #else
2429 static FileProgressStatus
2430 do_file_error (const char *str)
2432 return real_do_file_error (Foreground, str);
2435 static FileProgressStatus
2436 query_recursive (FileOpContext *ctx, const char *s)
2438 return real_query_recursive (ctx, Foreground, s);
2441 static FileProgressStatus
2442 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2443 struct stat *_d_stat)
2445 return file_progress_real_query_replace (ctx, Foreground, destname,
2446 _s_stat, _d_stat);
2449 #endif /* !WITH_BACKGROUND */
2452 Cause emacs to enter folding mode for this file:
2453 Local variables:
2454 end: