Merge branch '1450_fish_timestamps'
[midnight-commander.git] / src / file.c
blob4d7e3fb0a0173a597a3ce072acb84769145b3853
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>
57 #include <sys/time.h>
59 #include "global.h"
61 #include "../src/tty/tty.h"
62 #include "../src/tty/key.h"
64 #include "../src/search/search.h"
66 #include "setup.h"
67 #include "dialog.h"
68 #include "widget.h"
69 #include "main.h"
70 #include "layout.h"
71 #include "widget.h"
72 #include "wtools.h"
73 #include "background.h" /* we_are_background */
74 #include "../src/strescape.h"
75 #include "strutil.h"
77 /* Needed for current_panel, other_panel and WTree */
78 #include "dir.h"
79 #include "panel.h"
80 #include "file.h"
81 #include "filegui.h"
82 #include "tree.h"
83 #include "../vfs/vfs-impl.h"
85 /* }}} */
87 /* Hack: the vfs code should not rely on this */
88 #define WITH_FULL_PATHS 1
90 int verbose = 1;
93 * Whether the Midnight Commander tries to provide more
94 * information about copy/move sizes and bytes transfered
95 * at the expense of some speed
97 int file_op_compute_totals = 1;
99 /* This is a hard link cache */
100 struct link {
101 struct link *next;
102 struct vfs_class *vfs;
103 dev_t dev;
104 ino_t ino;
105 short linkcount;
106 mode_t st_mode;
107 char name[1];
110 /* the hard link cache */
111 static struct link *linklist = NULL;
113 /* the files-to-be-erased list */
114 static struct link *erase_list;
117 * In copy_dir_dir we use two additional single linked lists: The first -
118 * variable name `parent_dirs' - holds information about already copied
119 * directories and is used to detect cyclic symbolic links.
120 * The second (`dest_dirs' below) holds information about just created
121 * target directories and is used to detect when an directory is copied
122 * into itself (we don't want to copy infinitly).
123 * Both lists don't use the linkcount and name structure members of struct
124 * link.
126 static struct link *dest_dirs = NULL;
128 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
129 const char *op_names[3] = {
130 N_("DialogTitle|Copy"),
131 N_("DialogTitle|Move"),
132 N_("DialogTitle|Delete")
135 /* }}} */
137 static FileProgressStatus query_replace (FileOpContext * ctx, const char *destname,
138 struct stat *_s_stat, struct stat *_d_stat);
139 static FileProgressStatus query_recursive (FileOpContext * ctx, const char *s);
140 static FileProgressStatus do_file_error (const char *str);
141 static FileProgressStatus erase_dir_iff_empty (FileOpContext *ctx, const char *s);
142 static FileProgressStatus erase_file (FileOpContext *ctx, const char *s,
143 off_t *progress_count, double *progress_bytes,
144 int 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 USE_VFS
195 struct vfs_class *vfs = vfs_get_class (path);
196 #endif /* USE_VFS */
198 while (lp) {
199 #ifdef USE_VFS
200 if (lp->vfs == vfs)
201 #endif /* USE_VFS */
202 if (lp->ino == ino && lp->dev == dev)
203 return 1;
204 lp = lp->next;
206 return 0;
210 * Returns 0 if the inode wasn't found in the cache and 1 if it was found
211 * and a hardlink was succesfully made
213 static int
214 check_hardlinks (const char *src_name, const char *dst_name, struct stat *pstat)
216 struct link *lp;
217 struct vfs_class *my_vfs = vfs_get_class (src_name);
218 ino_t ino = pstat->st_ino;
219 dev_t dev = pstat->st_dev;
220 struct stat link_stat;
221 const char *p;
223 if (vfs_file_class_flags (src_name) & VFSF_NOLINKS)
224 return 0;
226 for (lp = linklist; lp != NULL; lp = lp->next)
227 if (lp->vfs == my_vfs && lp->ino == ino && lp->dev == dev) {
228 if (!mc_stat (lp->name, &link_stat) && link_stat.st_ino == ino
229 && link_stat.st_dev == dev
230 && vfs_get_class (lp->name) == my_vfs) {
231 p = strchr (lp->name, 0) + 1; /* i.e. where the `name' file
232 was copied to */
233 if (vfs_get_class (dst_name) == vfs_get_class (p)) {
234 if (!mc_stat (p, &link_stat)) {
235 if (!mc_link (p, dst_name))
236 return 1;
240 message (D_ERROR, MSG_ERROR, _(" Cannot make the hardlink "));
241 return 0;
243 lp = (struct link *) g_malloc (sizeof (struct link) + strlen (src_name)
244 + strlen (dst_name) + 1);
245 if (lp) {
246 char *lpdstname;
247 lp->vfs = my_vfs;
248 lp->ino = ino;
249 lp->dev = dev;
250 strcpy (lp->name, src_name);
251 lpdstname = lp->name + strlen(lp->name) + 1;
252 strcpy (lpdstname, dst_name);
253 lp->next = linklist;
254 linklist = lp;
256 return 0;
260 * Duplicate the contents of the symbolic link src_path in dst_path.
261 * Try to make a stable symlink if the option "stable symlink" was
262 * set in the file mask dialog.
263 * If dst_path is an existing symlink it will be deleted silently
264 * (upper levels take already care of existing files at dst_path).
266 static FileProgressStatus
267 make_symlink (FileOpContext *ctx, const char *src_path, const char *dst_path)
269 char link_target[MC_MAXPATHLEN];
270 int len;
271 FileProgressStatus return_status;
272 struct stat sb;
273 int dst_is_symlink;
275 if (mc_lstat (dst_path, &sb) == 0 && S_ISLNK (sb.st_mode))
276 dst_is_symlink = 1;
277 else
278 dst_is_symlink = 0;
280 retry_src_readlink:
281 len = mc_readlink (src_path, link_target, MC_MAXPATHLEN - 1);
282 if (len < 0) {
283 return_status =
284 file_error (_(" Cannot read source link \"%s\" \n %s "),
285 src_path);
286 if (return_status == FILE_RETRY)
287 goto retry_src_readlink;
288 return return_status;
290 link_target[len] = 0;
292 if (ctx->stable_symlinks)
293 if (!vfs_file_is_local (src_path) || !vfs_file_is_local (dst_path)) {
294 message (D_ERROR, MSG_ERROR,
295 _(" Cannot make stable symlinks across "
296 "non-local filesystems: \n\n"
297 " Option Stable Symlinks will be disabled "));
298 ctx->stable_symlinks = 0;
301 if (ctx->stable_symlinks && *link_target != PATH_SEP) {
302 char *p, *q, *s;
304 const char *r = strrchr (src_path, PATH_SEP);
306 if (r) {
307 p = g_strndup (src_path, r - src_path + 1);
308 if (*dst_path == PATH_SEP)
309 q = g_strdup (dst_path);
310 else
311 q = g_strconcat (p, dst_path, (char *) NULL);
312 s = strrchr (q, PATH_SEP);
313 if (s) {
314 s[1] = 0;
315 s = g_strconcat (p, link_target, (char *) NULL);
316 g_free (p);
317 g_strlcpy (link_target, s, sizeof (link_target));
318 g_free (s);
319 s = diff_two_paths (q, link_target);
320 if (s) {
321 g_strlcpy (link_target, s, sizeof (link_target));
322 g_free (s);
324 } else
325 g_free (p);
326 g_free (q);
329 retry_dst_symlink:
330 if (mc_symlink (link_target, dst_path) == 0)
331 /* Success */
332 return FILE_CONT;
334 * if dst_exists, it is obvious that this had failed.
335 * We can delete the old symlink and try again...
337 if (dst_is_symlink) {
338 if (!mc_unlink (dst_path))
339 if (mc_symlink (link_target, dst_path) == 0)
340 /* Success */
341 return FILE_CONT;
343 return_status =
344 file_error (_(" Cannot create target symlink \"%s\" \n %s "),
345 dst_path);
346 if (return_status == FILE_RETRY)
347 goto retry_dst_symlink;
348 return return_status;
351 static int
352 progress_update_one (FileOpContext *ctx,
353 off_t *progress_count,
354 double *progress_bytes, off_t add, int is_toplevel_file)
356 int ret;
358 if (is_toplevel_file || ctx->progress_totals_computed) {
359 (*progress_count)++;
360 (*progress_bytes) += add;
363 /* Apply some heuristic here to not call the update stuff very often */
364 ret =
365 file_progress_show_count (ctx, *progress_count,
366 ctx->progress_count);
367 if (ret != FILE_CONT)
368 return ret;
369 ret =
370 file_progress_show_bytes (ctx, *progress_bytes,
371 ctx->progress_bytes);
373 return ret;
376 /* Status of the destination file */
377 enum {
378 DEST_NONE, /* Not created */
379 DEST_SHORT, /* Created, not fully copied */
380 DEST_FULL /* Created, fully copied */
383 static FileProgressStatus
384 real_warn_same_file (enum OperationMode mode, const char *fmt,
385 const char *a, const char *b)
387 char *msg;
388 int result = 0;
389 const char *head_msg;
391 head_msg = mode == Foreground ? MSG_ERROR :
392 _(" Background process error ");
394 msg = g_strdup_printf (fmt, a, b);
395 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
396 g_free(msg);
397 do_refresh ();
398 if ( result ) { /* 1 == Abort */
399 return FILE_ABORT;
400 } else {
401 return FILE_SKIP;
405 #ifdef WITH_BACKGROUND
406 static FileProgressStatus
407 warn_same_file (const char *fmt, const char *a, const char *b)
409 union {
410 void *p;
411 FileProgressStatus (*f) (enum OperationMode, const char *fmt,
412 const char *a, const char *b);
413 } pntr;
414 pntr.f = real_warn_same_file;
416 if (we_are_background)
417 return parent_call (pntr.p, NULL, 3, strlen (fmt),
418 fmt, strlen(a), a, strlen(b), b);
419 else
420 return real_warn_same_file (Foreground, fmt, a, b);
422 #else
423 static FileProgressStatus
424 warn_same_file (const char *fmt, const char *a, const char *b)
426 return real_warn_same_file (Foreground, fmt, a, b);
428 #endif
430 FileProgressStatus
431 copy_file_file (FileOpContext *ctx, const char *src_path, const char *dst_path,
432 int ask_overwrite, off_t *progress_count,
433 double *progress_bytes, int is_toplevel_file)
435 uid_t src_uid = (uid_t) - 1;
436 gid_t src_gid = (gid_t) - 1;
438 char *buf = NULL;
439 int buf_size = BUF_8K;
440 int src_desc, dest_desc = -1;
441 int n_read, n_written;
442 mode_t src_mode = 0; /* The mode of the source file */
443 struct stat sb, sb2;
444 struct utimbuf utb;
445 int dst_exists = 0, appending = 0;
446 off_t n_read_total = 0, file_size = -1;
447 FileProgressStatus return_status, temp_status;
448 struct timeval tv_transfer_start;
449 int dst_status = DEST_NONE; /* 1 if the file is not fully copied */
450 int open_flags;
452 /* FIXME: We should not be using global variables! */
453 ctx->do_reget = 0;
454 return_status = FILE_RETRY;
456 if (file_progress_show_source (ctx, src_path) == FILE_ABORT ||
457 file_progress_show_target (ctx, dst_path) == FILE_ABORT)
458 return FILE_ABORT;
460 mc_refresh ();
462 while (mc_stat (dst_path, &sb2) == 0) {
463 if (S_ISDIR (sb2.st_mode)) {
464 return_status =
465 file_error (_(" Cannot overwrite directory \"%s\" \n %s "),
466 dst_path);
467 if (return_status == FILE_RETRY)
468 continue;
469 return return_status;
471 dst_exists = 1;
472 break;
475 while ((*ctx->stat_func) (src_path, &sb)) {
476 return_status =
477 file_error (_(" Cannot stat source file \"%s\" \n %s "),
478 src_path);
479 if (return_status != FILE_RETRY)
480 return return_status;
483 if (dst_exists) {
484 /* Destination already exists */
485 if (sb.st_dev == sb2.st_dev && sb.st_ino == sb2.st_ino)
486 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "),
487 src_path, dst_path);
488 /* Should we replace destination? */
489 if (ask_overwrite) {
490 ctx->do_reget = 0;
491 return_status = query_replace (ctx, dst_path, &sb, &sb2);
492 if (return_status != FILE_CONT)
493 return return_status;
497 if (!ctx->do_append) {
498 /* Check the hardlinks */
499 if (!ctx->follow_links && sb.st_nlink > 1 &&
500 check_hardlinks (src_path, dst_path, &sb) == 1) {
501 /* We have made a hardlink - no more processing is necessary */
502 return FILE_CONT;
505 if (S_ISLNK (sb.st_mode))
506 return make_symlink (ctx, src_path, dst_path);
508 if (S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode) ||
509 S_ISFIFO (sb.st_mode) || S_ISNAM (sb.st_mode) ||
510 S_ISSOCK (sb.st_mode)) {
511 while (mc_mknod (dst_path, sb.st_mode & ctx->umask_kill,
512 sb.st_rdev) < 0) {
513 return_status = file_error (
514 _(" Cannot create special file \"%s\" \n %s "), dst_path);
515 if (return_status == FILE_RETRY)
516 continue;
517 return return_status;
519 /* Success */
521 while (ctx->preserve_uidgid
522 && mc_chown (dst_path, sb.st_uid, sb.st_gid)) {
523 temp_status = file_error (
524 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
525 if (temp_status == FILE_RETRY)
526 continue;
527 return temp_status;
529 while (ctx->preserve &&
530 mc_chmod (dst_path, sb.st_mode & ctx->umask_kill)) {
531 temp_status = file_error (
532 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
533 if (temp_status == FILE_RETRY)
534 continue;
535 return temp_status;
537 return FILE_CONT;
541 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
543 while ((src_desc = mc_open (src_path, O_RDONLY | O_LINEAR)) < 0) {
544 return_status = file_error (
545 _(" Cannot open source file \"%s\" \n %s "), src_path);
546 if (return_status == FILE_RETRY)
547 continue;
548 ctx->do_append = 0;
549 return return_status;
552 if (ctx->do_reget) {
553 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget) {
554 message (D_ERROR, _("Warning"),
555 _(" Reget failed, about to overwrite file "));
556 ctx->do_reget = ctx->do_append = 0;
560 while (mc_fstat (src_desc, &sb)) {
561 return_status = file_error (
562 _(" Cannot fstat source file \"%s\" \n %s "), src_path);
563 if (return_status == FILE_RETRY)
564 continue;
565 ctx->do_append = 0;
566 goto ret;
568 src_mode = sb.st_mode;
569 src_uid = sb.st_uid;
570 src_gid = sb.st_gid;
571 utb.actime = sb.st_atime;
572 utb.modtime = sb.st_mtime;
573 file_size = sb.st_size;
575 open_flags = O_WRONLY;
576 if (dst_exists != 0) {
577 if (ctx->do_append != 0)
578 open_flags |= O_APPEND;
579 else
580 open_flags |= O_CREAT | O_TRUNC;
581 } else {
582 open_flags |= O_CREAT | O_EXCL;
584 while ((dest_desc = mc_open (dst_path, open_flags, src_mode)) < 0) {
585 if (errno == EEXIST) {
586 goto ret;
588 return_status = file_error (
589 _(" Cannot create target file \"%s\" \n %s "), dst_path);
590 if (return_status == FILE_RETRY)
591 continue;
592 ctx->do_append = 0;
593 goto ret;
595 dst_status = DEST_SHORT; /* file opened, but not fully copied */
597 appending = ctx->do_append;
598 ctx->do_append = 0;
600 /* Find out the optimal buffer size. */
601 while (mc_fstat (dest_desc, &sb)) {
602 return_status = file_error (
603 _(" Cannot fstat target file \"%s\" \n %s "), dst_path);
604 if (return_status == FILE_RETRY)
605 continue;
606 goto ret;
608 buf = g_malloc (buf_size);
610 ctx->eta_secs = 0.0;
611 ctx->bps = 0;
613 return_status = file_progress_show (ctx, 0, file_size);
615 mc_refresh ();
617 if (return_status != FILE_CONT)
618 goto ret;
621 struct timeval tv_current, tv_last_update, tv_last_input;
622 int secs, update_secs;
623 long dt;
624 const char *stalled_msg;
626 tv_last_update = tv_transfer_start;
628 for (;;) {
629 /* src_read */
630 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0))
631 n_read = -1;
632 else
633 while ((n_read = mc_read (src_desc, buf, buf_size)) < 0) {
634 return_status = file_error (
635 _(" Cannot read source file \"%s\" \n %s "), src_path);
636 if (return_status == FILE_RETRY)
637 continue;
638 goto ret;
640 if (n_read == 0)
641 break;
643 gettimeofday (&tv_current, NULL);
645 if (n_read > 0) {
646 char *t = buf;
647 n_read_total += n_read;
649 /* Windows NT ftp servers report that files have no
650 * permissions: -------, so if we happen to have actually
651 * read something, we should fix the permissions.
653 if (!(src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)))
654 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
655 gettimeofday (&tv_last_input, NULL);
657 /* dst_write */
658 while ((n_written =
659 mc_write (dest_desc, t, n_read)) < n_read) {
660 if (n_written > 0) {
661 n_read -= n_written;
662 t += n_written;
663 continue;
665 return_status =
666 file_error (_(" Cannot write target file \"%s\" \n %s "),
667 dst_path);
668 if (return_status != FILE_RETRY)
669 goto ret;
673 /* 1. Update rotating dash after some time (hardcoded to 2 seconds) */
674 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
675 if (secs > 2) {
676 rotate_dash ();
677 tv_last_update = tv_current;
680 /* 2. Check for a stalled condition */
681 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
682 stalled_msg = "";
683 if (update_secs > 4) {
684 stalled_msg = _("(stalled)");
687 /* 3. Compute ETA */
688 if (secs > 2) {
689 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
691 if (n_read_total) {
692 ctx->eta_secs =
693 ((dt / (double) n_read_total) * file_size) - dt;
694 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
695 } else
696 ctx->eta_secs = 0.0;
699 /* 4. Compute BPS rate */
700 if (secs > 2) {
701 ctx->bps_time =
702 (tv_current.tv_sec - tv_transfer_start.tv_sec);
703 if (ctx->bps_time < 1)
704 ctx->bps_time = 1;
705 ctx->bps = n_read_total / ctx->bps_time;
708 file_progress_set_stalled_label (ctx, stalled_msg);
709 return_status = file_progress_show_bytes (ctx, *progress_bytes +
710 n_read_total + ctx->do_reget, ctx->progress_bytes);
711 if (return_status == FILE_CONT) {
712 return_status =
713 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size);
715 mc_refresh ();
716 if (return_status != FILE_CONT)
717 goto ret;
721 dst_status = DEST_FULL; /* copy successful, don't remove target file */
723 ret:
724 g_free (buf);
726 while (src_desc != -1 && mc_close (src_desc) < 0) {
727 temp_status = file_error (
728 _(" Cannot close source file \"%s\" \n %s "), src_path);
729 if (temp_status == FILE_RETRY)
730 continue;
731 if (temp_status == FILE_ABORT)
732 return_status = temp_status;
733 break;
736 while (dest_desc != -1 && mc_close (dest_desc) < 0) {
737 temp_status = file_error (
738 _(" Cannot close target file \"%s\" \n %s "), dst_path);
739 if (temp_status == FILE_RETRY)
740 continue;
741 return_status = temp_status;
742 break;
745 if (dst_status == DEST_SHORT) {
746 /* Remove short file */
747 int result;
748 result = query_dialog (Q_("DialogTitle|Copy"),
749 _("Incomplete file was retrieved. Keep it?"),
750 D_ERROR, 2, _("&Delete"), _("&Keep"));
751 if (!result)
752 mc_unlink (dst_path);
753 } else if (dst_status == DEST_FULL) {
754 /* Copy has succeeded */
755 if (!appending && ctx->preserve_uidgid) {
756 while (mc_chown (dst_path, src_uid, src_gid)) {
757 temp_status = file_error (
758 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
759 if (temp_status == FILE_RETRY)
760 continue;
761 return_status = temp_status;
762 break;
766 if (!appending) {
767 if (ctx->preserve){
768 while (mc_chmod (dst_path, (src_mode & ctx->umask_kill))) {
769 temp_status = file_error (
770 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
771 if (temp_status != FILE_RETRY) {
772 return_status = temp_status;
773 break;
776 } else {
777 src_mode = umask(-1);
778 umask(src_mode);
779 src_mode = 0100666 & ~src_mode;
780 mc_chmod (dst_path, (src_mode & ctx->umask_kill));
782 mc_utime (dst_path, &utb);
786 if (return_status == FILE_CONT)
787 return_status =
788 progress_update_one (ctx, progress_count, progress_bytes,
789 file_size, is_toplevel_file);
791 return return_status;
795 * I think these copy_*_* functions should have a return type.
796 * anyway, this function *must* have two directories as arguments.
798 /* FIXME: This function needs to check the return values of the
799 function calls */
800 FileProgressStatus
801 copy_dir_dir (FileOpContext *ctx, const char *s, const char *_d, int toplevel,
802 int move_over, int delete, struct link *parent_dirs,
803 off_t *progress_count, double *progress_bytes)
805 struct dirent *next;
806 struct stat buf, cbuf;
807 DIR *reading;
808 char *dest_dir = NULL;
809 FileProgressStatus return_status = FILE_CONT;
810 struct utimbuf utb;
811 struct link *lp;
812 char *d;
814 d = strutils_shell_unescape (_d);
816 /* First get the mode of the source dir */
817 retry_src_stat:
818 if ((*ctx->stat_func) (s, &cbuf)) {
819 return_status =
820 file_error (_(" Cannot stat source directory \"%s\" \n %s "), s);
821 if (return_status == FILE_RETRY)
822 goto retry_src_stat;
823 goto ret_fast;
826 if (is_in_linklist (dest_dirs, s, &cbuf)) {
827 /* Don't copy a directory we created before (we don't want to copy
828 infinitely if a directory is copied into itself) */
829 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
830 return_status = FILE_CONT;
831 goto ret_fast;
834 /* Hmm, hardlink to directory??? - Norbert */
835 /* FIXME: In this step we should do something
836 in case the destination already exist */
837 /* Check the hardlinks */
838 if (ctx->preserve && cbuf.st_nlink > 1
839 && check_hardlinks (s, d, &cbuf) == 1) {
840 /* We have made a hardlink - no more processing is necessary */
841 goto ret_fast;
844 if (!S_ISDIR (cbuf.st_mode)) {
845 return_status =
846 file_error (_(" Source \"%s\" is not a directory \n %s "), s);
847 if (return_status == FILE_RETRY)
848 goto retry_src_stat;
849 goto ret_fast;
852 if (is_in_linklist (parent_dirs, s, &cbuf)) {
853 /* we found a cyclic symbolic link */
854 message (D_ERROR, MSG_ERROR,
855 _(" Cannot copy cyclic symbolic link \n `%s' "), s);
856 return_status = FILE_SKIP;
857 goto ret_fast;
860 lp = g_new (struct link, 1);
861 lp->vfs = vfs_get_class (s);
862 lp->ino = cbuf.st_ino;
863 lp->dev = cbuf.st_dev;
864 lp->next = parent_dirs;
865 parent_dirs = lp;
867 retry_dst_stat:
868 /* Now, check if the dest dir exists, if not, create it. */
869 if (mc_stat (d, &buf)) {
870 /* Here the dir doesn't exist : make it ! */
871 if (move_over) {
872 if (mc_rename (s, d) == 0) {
873 return_status = FILE_CONT;
874 goto ret;
877 dest_dir = d;
878 d = NULL;
879 } else {
881 * If the destination directory exists, we want to copy the whole
882 * directory, but we only want this to happen once.
884 * Escape sequences added to the * to compiler warnings.
885 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
886 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
888 if (!S_ISDIR (buf.st_mode)) {
889 return_status = file_error(
890 _(" Destination \"%s\" must be a directory \n %s "), d);
891 if (return_status == FILE_RETRY)
892 goto retry_dst_stat;
893 goto ret;
895 /* Dive into subdir if exists */
896 if (toplevel && ctx->dive_into_subdirs) {
897 dest_dir = concat_dir_and_file (d, x_basename (s));
898 } else {
899 dest_dir = d;
900 d = NULL;
901 goto dont_mkdir;
904 while (my_mkdir (dest_dir, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU)) {
905 return_status = file_error (
906 _(" Cannot create target directory \"%s\" \n %s "), dest_dir);
907 if (return_status != FILE_RETRY)
908 goto ret;
911 lp = g_new (struct link, 1);
912 mc_stat (dest_dir, &buf);
913 lp->vfs = vfs_get_class (dest_dir);
914 lp->ino = buf.st_ino;
915 lp->dev = buf.st_dev;
916 lp->next = dest_dirs;
917 dest_dirs = lp;
919 if (ctx->preserve_uidgid) {
920 while (mc_chown (dest_dir, cbuf.st_uid, cbuf.st_gid)) {
921 return_status = file_error (
922 _(" Cannot chown target directory \"%s\" \n %s "), dest_dir);
923 if (return_status != FILE_RETRY)
924 goto ret;
928 dont_mkdir:
929 /* open the source dir for reading */
930 reading = mc_opendir (s);
931 if (reading == NULL)
932 goto ret;
934 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) {
935 char *path;
937 * Now, we don't want '.' and '..' to be created / copied at any time
939 if (!strcmp (next->d_name, "."))
940 continue;
941 if (!strcmp (next->d_name, ".."))
942 continue;
944 /* get the filename and add it to the src directory */
945 path = concat_dir_and_file (s, next->d_name);
947 (*ctx->stat_func) (path, &buf);
948 if (S_ISDIR (buf.st_mode)) {
949 char *mdpath;
951 mdpath = concat_dir_and_file (dest_dir, next->d_name);
953 * From here, we just intend to recursively copy subdirs, not
954 * the double functionality of copying different when the target
955 * dir already exists. So, we give the recursive call the flag 0
956 * meaning no toplevel.
958 return_status = copy_dir_dir (ctx, path, mdpath, 0, 0, delete,
959 parent_dirs, progress_count, progress_bytes);
960 g_free (mdpath);
961 } else {
962 char *dest_file;
964 dest_file = concat_dir_and_file (dest_dir, x_basename (path));
965 return_status = copy_file_file (ctx, path, dest_file, 1,
966 progress_count, progress_bytes, 0);
967 g_free (dest_file);
969 if (delete && return_status == FILE_CONT) {
970 if (ctx->erase_at_end) {
971 static struct link *tail;
972 size_t len = strlen (path);
973 lp = g_malloc (sizeof (struct link) + len);
974 strncpy (lp->name, path, len + 1);
975 lp->st_mode = buf.st_mode;
976 lp->next = NULL;
977 if (erase_list != NULL) {
978 tail->next = lp;
979 tail = lp;
980 } else
981 erase_list = tail = lp;
982 } else {
983 if (S_ISDIR (buf.st_mode)) {
984 return_status = erase_dir_iff_empty (ctx, path);
985 } else
986 return_status = erase_file (ctx, path, 0, 0, 0);
989 g_free (path);
991 mc_closedir (reading);
993 if (ctx->preserve) {
994 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
995 utb.actime = cbuf.st_atime;
996 utb.modtime = cbuf.st_mtime;
997 mc_utime (dest_dir, &utb);
998 } else {
999 cbuf.st_mode = umask(-1);
1000 umask(cbuf.st_mode);
1001 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
1002 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
1005 ret:
1006 g_free (dest_dir);
1007 g_free (parent_dirs);
1008 ret_fast:
1009 g_free (d);
1010 return return_status;
1013 /* }}} */
1015 /* {{{ Move routines */
1017 static FileProgressStatus
1018 move_file_file (FileOpContext *ctx, const char *s, const char *d,
1019 off_t *progress_count, double *progress_bytes)
1021 struct stat src_stats, dst_stats;
1022 FileProgressStatus return_status = FILE_CONT;
1023 gboolean copy_done = FALSE;
1025 if (file_progress_show_source (ctx, s) == FILE_ABORT
1026 || file_progress_show_target (ctx, d) == FILE_ABORT)
1027 return FILE_ABORT;
1029 mc_refresh ();
1031 while (mc_lstat (s, &src_stats) != 0) {
1032 /* Source doesn't exist */
1033 return_status =
1034 file_error (_(" Cannot stat file \"%s\" \n %s "), s);
1035 if (return_status != FILE_RETRY)
1036 return return_status;
1039 if (mc_lstat (d, &dst_stats) == 0) {
1040 if (src_stats.st_dev == dst_stats.st_dev
1041 && src_stats.st_ino == dst_stats.st_ino)
1042 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "), s, d);
1044 if (S_ISDIR (dst_stats.st_mode)) {
1045 message (D_ERROR, MSG_ERROR,
1046 _(" Cannot overwrite directory `%s' "), d);
1047 do_refresh ();
1048 return FILE_SKIP;
1051 if (confirm_overwrite) {
1052 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
1053 if (return_status != FILE_CONT)
1054 return return_status;
1056 /* Ok to overwrite */
1059 if (!ctx->do_append) {
1060 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks) {
1061 if ((return_status = make_symlink (ctx, s, d)) == FILE_CONT) {
1062 goto retry_src_remove;
1063 } else
1064 return return_status;
1067 if (mc_rename (s, d) == 0) {
1068 return progress_update_one (ctx, progress_count,
1069 progress_bytes,
1070 src_stats.st_size, 1);
1073 #if 0
1074 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1075 one nfs to the same, but on the server it is on two different
1076 filesystems. Then nfs returns EIO instead of EXDEV.
1077 Hope it will not hurt if we always in case of error try to copy/delete. */
1078 else
1079 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1081 if (errno != EXDEV) {
1082 return_status =
1083 files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s,
1085 if (return_status == FILE_RETRY)
1086 goto retry_rename;
1087 return return_status;
1089 #endif
1091 /* Failed because filesystem boundary -> copy the file instead */
1092 return_status =
1093 copy_file_file (ctx, s, d, 0, progress_count, progress_bytes, 1);
1094 if (return_status != FILE_CONT)
1095 return return_status;
1097 copy_done = TRUE;
1099 if ((return_status =
1100 file_progress_show_source (ctx, NULL)) != FILE_CONT
1101 || (return_status = file_progress_show (ctx, 0, 0)) != FILE_CONT)
1102 return return_status;
1104 mc_refresh ();
1106 retry_src_remove:
1107 if (mc_unlink (s)) {
1108 return_status =
1109 file_error (_(" Cannot remove file \"%s\" \n %s "), s);
1110 if (return_status == FILE_RETRY)
1111 goto retry_src_remove;
1112 return return_status;
1115 if (!copy_done) {
1116 return_status = progress_update_one (ctx,
1117 progress_count,
1118 progress_bytes,
1119 src_stats.st_size, 1);
1122 return return_status;
1125 FileProgressStatus
1126 move_dir_dir (FileOpContext *ctx, const char *s, const char *d,
1127 off_t *progress_count, double *progress_bytes)
1129 struct stat sbuf, dbuf, destbuf;
1130 struct link *lp;
1131 char *destdir;
1132 FileProgressStatus return_status;
1133 gboolean move_over = FALSE;
1134 gboolean dstat_ok;
1136 if (file_progress_show_source (ctx, s) == FILE_ABORT ||
1137 file_progress_show_target (ctx, d) == FILE_ABORT)
1138 return FILE_ABORT;
1140 mc_refresh ();
1142 mc_stat (s, &sbuf);
1143 dstat_ok = (mc_stat (d, &dbuf) == 0);
1145 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
1146 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same directory "), s, d);
1148 if (!dstat_ok)
1149 destdir = g_strdup (d); /* destination doesn't exist */
1150 else if (!ctx->dive_into_subdirs) {
1151 destdir = g_strdup (d);
1152 move_over = TRUE;
1153 } else
1154 destdir = concat_dir_and_file (d, x_basename (s));
1156 /* Check if the user inputted an existing dir */
1157 retry_dst_stat:
1158 if (!mc_stat (destdir, &destbuf)) {
1159 if (move_over) {
1160 return_status = copy_dir_dir (ctx, s, destdir, 0, 1, 1, 0,
1161 progress_count, progress_bytes);
1163 if (return_status != FILE_CONT)
1164 goto ret;
1165 goto oktoret;
1166 } else {
1167 if (S_ISDIR (destbuf.st_mode))
1168 return_status =
1169 file_error (_
1170 (" Cannot overwrite directory \"%s\" %s "),
1171 destdir);
1172 else
1173 return_status =
1174 file_error (_(" Cannot overwrite file \"%s\" %s "),
1175 destdir);
1176 if (return_status == FILE_RETRY)
1177 goto retry_dst_stat;
1179 g_free (destdir);
1180 return return_status;
1183 retry_rename:
1184 if (mc_rename (s, destdir) == 0) {
1185 return_status = FILE_CONT;
1186 goto ret;
1189 if (errno != EXDEV) {
1190 return_status =
1191 files_error (_
1192 (" Cannot move directory \"%s\" to \"%s\" \n %s "),
1193 s, d);
1194 if (return_status == FILE_RETRY)
1195 goto retry_rename;
1196 goto ret;
1198 /* Failed because of filesystem boundary -> copy dir instead */
1199 return_status =
1200 copy_dir_dir (ctx, s, destdir, 0, 0, 1, 0, progress_count,
1201 progress_bytes);
1203 if (return_status != FILE_CONT)
1204 goto ret;
1205 oktoret:
1206 if ((return_status =
1207 file_progress_show_source (ctx, NULL)) != FILE_CONT
1208 || (return_status = file_progress_show (ctx, 0, 0)) != FILE_CONT)
1209 goto ret;
1211 mc_refresh ();
1212 if (ctx->erase_at_end) {
1213 for (; erase_list && return_status != FILE_ABORT;) {
1214 if (S_ISDIR (erase_list->st_mode)) {
1215 return_status =
1216 erase_dir_iff_empty (ctx, erase_list->name);
1217 } else
1218 return_status =
1219 erase_file (ctx, erase_list->name, 0, 0, 0);
1220 lp = erase_list;
1221 erase_list = erase_list->next;
1222 g_free (lp);
1225 erase_dir_iff_empty (ctx, s);
1227 ret:
1228 g_free (destdir);
1229 while (erase_list) {
1230 lp = erase_list;
1231 erase_list = erase_list->next;
1232 g_free (lp);
1234 return return_status;
1237 /* }}} */
1239 /* {{{ Erase routines */
1240 /* Don't update progress status if progress_count==NULL */
1241 static FileProgressStatus
1242 erase_file (FileOpContext *ctx, const char *s, off_t *progress_count,
1243 double *progress_bytes, int is_toplevel_file)
1245 int return_status;
1246 struct stat buf;
1248 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1249 return FILE_ABORT;
1250 mc_refresh ();
1252 if (progress_count && mc_lstat (s, &buf)) {
1253 /* ignore, most likely the mc_unlink fails, too */
1254 buf.st_size = 0;
1257 while (mc_unlink (s)) {
1258 return_status =
1259 file_error (_(" Cannot delete file \"%s\" \n %s "), s);
1260 if (return_status != FILE_RETRY)
1261 return return_status;
1264 if (progress_count)
1265 return progress_update_one (ctx, progress_count, progress_bytes,
1266 buf.st_size, is_toplevel_file);
1267 else
1268 return FILE_CONT;
1271 static FileProgressStatus
1272 recursive_erase (FileOpContext *ctx, const char *s, off_t *progress_count,
1273 double *progress_bytes)
1275 struct dirent *next;
1276 struct stat buf;
1277 DIR *reading;
1278 char *path;
1279 FileProgressStatus return_status = FILE_CONT;
1281 if (!strcmp (s, ".."))
1282 return FILE_RETRY;
1284 reading = mc_opendir (s);
1286 if (!reading)
1287 return FILE_RETRY;
1289 while ((next = mc_readdir (reading)) && return_status == FILE_CONT) {
1290 if (!strcmp (next->d_name, "."))
1291 continue;
1292 if (!strcmp (next->d_name, ".."))
1293 continue;
1294 path = concat_dir_and_file (s, next->d_name);
1295 if (mc_lstat (path, &buf)) {
1296 g_free (path);
1297 mc_closedir (reading);
1298 return FILE_RETRY;
1300 if (S_ISDIR (buf.st_mode))
1301 return_status =
1302 (recursive_erase
1303 (ctx, path, progress_count, progress_bytes)
1304 != FILE_CONT) ? FILE_RETRY : FILE_CONT;
1305 else
1306 return_status =
1307 erase_file (ctx, path, progress_count, progress_bytes, 0);
1308 g_free (path);
1310 mc_closedir (reading);
1311 if (return_status != FILE_CONT)
1312 return return_status;
1313 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1314 return FILE_ABORT;
1315 mc_refresh ();
1317 while (my_rmdir (s)) {
1318 return_status =
1319 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1320 if (return_status != FILE_RETRY)
1321 return return_status;
1324 return FILE_CONT;
1327 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1328 in the directory path points to, 0 else. */
1329 static int
1330 check_dir_is_empty (const char *path)
1332 DIR *dir;
1333 struct dirent *d;
1334 int i;
1336 dir = mc_opendir (path);
1337 if (!dir)
1338 return -1;
1340 for (i = 1, d = mc_readdir (dir); d; d = mc_readdir (dir)) {
1341 if (d->d_name[0] == '.' && (d->d_name[1] == '\0' ||
1342 (d->d_name[1] == '.'
1343 && d->d_name[2] == '\0')))
1344 continue; /* "." or ".." */
1345 i = 0;
1346 break;
1349 mc_closedir (dir);
1350 return i;
1353 FileProgressStatus
1354 erase_dir (FileOpContext *ctx, const char *s, off_t *progress_count,
1355 double *progress_bytes)
1357 FileProgressStatus error;
1359 if (strcmp (s, "..") == 0)
1360 return FILE_SKIP;
1362 if (strcmp (s, ".") == 0)
1363 return FILE_SKIP;
1365 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1366 return FILE_ABORT;
1367 mc_refresh ();
1369 /* The old way to detect a non empty directory was:
1370 error = my_rmdir (s);
1371 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1372 For the linux user space nfs server (nfs-server-2.2beta29-2)
1373 we would have to check also for EIO. I hope the new way is
1374 fool proof. (Norbert)
1376 error = check_dir_is_empty (s);
1377 if (error == 0) { /* not empty */
1378 error = query_recursive (ctx, s);
1379 if (error == FILE_CONT)
1380 return recursive_erase (ctx, s, progress_count,
1381 progress_bytes);
1382 else
1383 return error;
1386 while (my_rmdir (s) == -1) {
1387 error =
1388 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1389 if (error != FILE_RETRY)
1390 return error;
1393 return FILE_CONT;
1396 static FileProgressStatus
1397 erase_dir_iff_empty (FileOpContext *ctx, const char *s)
1399 FileProgressStatus error;
1401 if (strcmp (s, "..") == 0)
1402 return FILE_SKIP;
1404 if (strcmp (s, ".") == 0)
1405 return FILE_SKIP;
1407 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1408 return FILE_ABORT;
1409 mc_refresh ();
1411 if (1 != check_dir_is_empty (s)) /* not empty or error */
1412 return FILE_CONT;
1414 while (my_rmdir (s)) {
1415 error =
1416 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1417 if (error != FILE_RETRY)
1418 return error;
1421 return FILE_CONT;
1424 /* }}} */
1426 /* {{{ Panel operate routines */
1429 * Return currently selected entry name or the name of the first marked
1430 * entry if there is one.
1432 static char *
1433 panel_get_file (WPanel *panel, struct stat *stat_buf)
1435 int i;
1437 if (get_current_type () == view_tree) {
1438 WTree *tree = (WTree *) get_panel_widget (get_current_index ());
1439 char *tree_name = tree_selected_name (tree);
1441 mc_stat (tree_name, stat_buf);
1442 return tree_name;
1445 if (panel->marked) {
1446 for (i = 0; i < panel->count; i++)
1447 if (panel->dir.list[i].f.marked) {
1448 *stat_buf = panel->dir.list[i].st;
1449 return panel->dir.list[i].fname;
1451 } else {
1452 *stat_buf = panel->dir.list[panel->selected].st;
1453 return panel->dir.list[panel->selected].fname;
1455 g_assert_not_reached ();
1456 return NULL;
1460 ComputeDirSizeUI *
1461 compute_dir_size_create_ui (void)
1463 ComputeDirSizeUI *ui;
1465 const char *b_name = N_("&Abort");
1467 #ifdef ENABLE_NLS
1468 b_name = _(b_name);
1469 #endif
1471 ui = g_new (ComputeDirSizeUI, 1);
1473 ui->dlg = create_dlg (0, 0, 8, COLS/2, dialog_colors, NULL,
1474 NULL, _("Directory scanning"), DLG_CENTER);
1475 ui->dirname = label_new (3, 3, "");
1476 add_widget (ui->dlg, ui->dirname);
1478 add_widget (ui->dlg,
1479 button_new (5, (ui->dlg->cols - strlen (b_name))/2,
1480 FILE_ABORT, NORMAL_BUTTON, b_name, NULL));
1482 /* We will manage the dialog without any help,
1483 that's why we have to call init_dlg */
1484 init_dlg (ui->dlg);
1486 return ui;
1489 void
1490 compute_dir_size_destroy_ui (ComputeDirSizeUI *ui)
1492 if (ui != NULL) {
1493 /* schedule to update passive panel */
1494 other_panel->dirty = 1;
1496 /* close and destroy dialog */
1497 dlg_run_done (ui->dlg);
1498 destroy_dlg (ui->dlg);
1499 g_free (ui);
1503 FileProgressStatus
1504 compute_dir_size_update_ui (const void *ui, const char *dirname)
1506 const ComputeDirSizeUI *this = (const ComputeDirSizeUI *) ui;
1507 int c;
1508 Gpm_Event event;
1510 if (ui == NULL)
1511 return FILE_CONT;
1513 label_set_text (this->dirname, name_trunc (dirname, this->dlg->cols - 6));
1515 event.x = -1; /* Don't show the GPM cursor */
1516 c = tty_get_event (&event, FALSE, FALSE);
1517 if (c == EV_NONE)
1518 return FILE_CONT;
1520 /* Reinitialize to avoid old values after events other than
1521 selecting a button */
1522 this->dlg->ret_value = FILE_CONT;
1524 dlg_process_event (this->dlg, c, &event);
1526 switch (this->dlg->ret_value) {
1527 case B_CANCEL:
1528 case FILE_ABORT:
1529 return FILE_ABORT;
1530 default:
1531 return FILE_CONT;
1536 * compute_dir_size:
1538 * Computes the number of bytes used by the files in a directory
1540 FileProgressStatus
1541 compute_dir_size (const char *dirname, const void *ui,
1542 compute_dir_size_callback cback,
1543 off_t *ret_marked, double *ret_total)
1545 DIR *dir;
1546 struct dirent *dirent;
1547 FileProgressStatus ret = FILE_CONT;
1549 dir = mc_opendir (dirname);
1551 if (dir == NULL)
1552 return ret;
1554 while ((dirent = mc_readdir (dir)) != NULL) {
1555 char *fullname;
1556 int res;
1557 struct stat s;
1559 ret = (cback != NULL) ? cback (ui, dirname) : FILE_CONT;
1561 if (ret != FILE_CONT)
1562 break;
1564 if (strcmp (dirent->d_name, ".") == 0)
1565 continue;
1566 if (strcmp (dirent->d_name, "..") == 0)
1567 continue;
1569 fullname = concat_dir_and_file (dirname, dirent->d_name);
1570 res = mc_lstat (fullname, &s);
1572 if (res != 0) {
1573 g_free (fullname);
1574 continue;
1577 if (S_ISDIR (s.st_mode)) {
1578 off_t subdir_count = 0;
1579 double subdir_bytes = 0;
1581 ret = compute_dir_size (fullname, ui, cback, &subdir_count, &subdir_bytes);
1583 if (ret != FILE_CONT) {
1584 g_free (fullname);
1585 break;
1588 *ret_marked += subdir_count;
1589 *ret_total += subdir_bytes;
1590 } else {
1591 (*ret_marked)++;
1592 *ret_total += s.st_size;
1595 g_free (fullname);
1598 mc_closedir (dir);
1600 return ret;
1604 * panel_compute_totals:
1606 * compute the number of files and the number of bytes
1607 * used up by the whole selection, recursing directories
1608 * as required. In addition, it checks to see if it will
1609 * overwrite any files by doing the copy.
1611 static FileProgressStatus
1612 panel_compute_totals (WPanel *panel, const void *ui,
1613 compute_dir_size_callback cback,
1614 off_t *ret_marked, double *ret_total)
1616 int i;
1618 *ret_marked = 0;
1619 *ret_total = 0.0;
1621 for (i = 0; i < panel->count; i++) {
1622 struct stat *s;
1624 if (!panel->dir.list[i].f.marked)
1625 continue;
1627 s = &panel->dir.list[i].st;
1629 if (S_ISDIR (s->st_mode)) {
1630 char *dir_name;
1631 off_t subdir_count = 0;
1632 double subdir_bytes = 0;
1633 FileProgressStatus status;
1635 dir_name =
1636 concat_dir_and_file (panel->cwd, panel->dir.list[i].fname);
1638 status = compute_dir_size (dir_name, ui, cback,
1639 &subdir_count, &subdir_bytes);
1640 g_free (dir_name);
1642 if (status != FILE_CONT)
1643 return FILE_ABORT;
1645 *ret_marked += subdir_count;
1646 *ret_total += subdir_bytes;
1647 } else {
1648 (*ret_marked)++;
1649 *ret_total += s->st_size;
1653 return FILE_CONT;
1657 * This array introduced to avoid translation problems. The former (op_names)
1658 * is assumed to be nouns, suitable in dialog box titles; this one should
1659 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
1660 * (I don't use spaces around the words, because someday they could be
1661 * dropped, when widgets get smarter)
1664 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
1665 static const char *op_names1[] = {
1666 N_("FileOperation|Copy"),
1667 N_("FileOperation|Move"),
1668 N_("FileOperation|Delete")
1672 * These are formats for building a prompt. Parts encoded as follows:
1673 * %o - operation from op_names1
1674 * %f - file/files or files/directories, as appropriate
1675 * %m - "with source mask" or question mark for delete
1676 * %s - source name (truncated)
1677 * %d - number of marked files
1678 * %e - "to:" or question mark for delete
1680 * xgettext:no-c-format */
1681 static const char *one_format = N_("%o %f \"%s\"%m");
1682 /* xgettext:no-c-format */
1683 static const char *many_format = N_("%o %d %f%m");
1685 static const char *prompt_parts[] = {
1686 N_("file"),
1687 N_("files"),
1688 N_("directory"),
1689 N_("directories"),
1690 N_("files/directories"),
1691 N_(" with source mask:"),
1692 N_(" to:")
1695 static const char *question_format = N_("%s?");
1698 * Generate user prompt for panel operation.
1699 * single_source is the name if the source entry or NULL for multiple
1700 * entries.
1701 * src_stat is only used when single_source is not NULL.
1703 static char *
1704 panel_operate_generate_prompt (const WPanel *panel, const int operation,
1705 gboolean single_source,
1706 const struct stat *src_stat)
1708 const char *sp, *cp;
1709 char format_string[BUF_MEDIUM];
1710 char *dp = format_string;
1711 gboolean build_question = FALSE;
1713 #ifdef ENABLE_NLS
1714 static gboolean i18n_flag = FALSE;
1715 if (!i18n_flag) {
1716 int i;
1718 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1719 op_names1[i] = Q_(op_names1[i]);
1721 for (i = sizeof (prompt_parts) / sizeof (prompt_parts[0]); i--;)
1722 prompt_parts[i] = _(prompt_parts[i]);
1724 one_format = _(one_format);
1725 many_format = _(many_format);
1726 question_format = _(question_format);
1727 i18n_flag = TRUE;
1729 #endif /* ENABLE_NLS */
1731 sp = single_source ? one_format : many_format;
1733 while (*sp != '\0') {
1734 switch (*sp) {
1735 case '%':
1736 cp = NULL;
1737 switch (sp[1]) {
1738 case 'o':
1739 cp = op_names1[operation];
1740 break;
1741 case 'm':
1742 if (operation == OP_DELETE) {
1743 cp = "";
1744 build_question = TRUE;
1745 } else
1746 cp = prompt_parts[5];
1747 break;
1748 case 'e':
1749 if (operation == OP_DELETE) {
1750 cp = "";
1751 build_question = TRUE;
1752 } else
1753 cp = prompt_parts[6];
1754 break;
1755 case 'f':
1756 if (single_source) {
1757 cp = S_ISDIR (src_stat->
1758 st_mode) ? prompt_parts[2] : prompt_parts[0];
1759 } else {
1760 cp = (panel->marked == panel->dirs_marked)
1761 ? prompt_parts[3]
1762 : (panel->dirs_marked ? prompt_parts[4] : prompt_parts[1]);
1764 break;
1765 default:
1766 *dp++ = *sp++;
1768 if (cp != NULL) {
1769 sp += 2;
1770 while (*cp != '\0')
1771 *dp++ = *cp++;
1773 break;
1774 default:
1775 *dp++ = *sp++;
1778 *dp = '\0';
1780 if (build_question) {
1781 char tmp[BUF_MEDIUM];
1783 memmove (tmp, format_string, sizeof (tmp));
1784 g_snprintf (format_string, sizeof (format_string),
1785 question_format, tmp);
1788 return g_strdup (format_string);
1791 #ifdef WITH_BACKGROUND
1792 static int
1793 end_bg_process (FileOpContext *ctx, enum OperationMode mode) {
1794 int pid = ctx->pid;
1796 (void) mode;
1797 ctx->pid = 0;
1799 unregister_task_with_pid(pid);
1800 // file_op_context_destroy(ctx);
1801 return 1;
1803 #endif
1806 * panel_operate:
1808 * Performs one of the operations on the selection on the source_panel
1809 * (copy, delete, move).
1811 * Returns 1 if did change the directory
1812 * structure, Returns 0 if user aborted
1814 * force_single forces operation on the current entry and affects
1815 * default destination. Current filename is used as default.
1818 panel_operate (void *source_panel, FileOperation operation,
1819 int force_single)
1821 WPanel *panel = source_panel;
1822 char *source = NULL;
1823 #ifdef WITH_FULL_PATHS
1824 char *source_with_path = NULL;
1825 #else
1826 # define source_with_path source
1827 #endif /* !WITH_FULL_PATHS */
1828 char *dest = NULL;
1829 char *temp = NULL;
1830 char *save_cwd = NULL, *save_dest = NULL;
1831 int single_entry = (get_current_type () == view_tree)
1832 || (panel->marked <= 1) || force_single;
1833 struct stat src_stat, dst_stat;
1834 int i;
1835 FileProgressStatus value;
1836 FileOpContext *ctx;
1838 off_t count = 0;
1839 double bytes = 0;
1841 int dst_result;
1842 int do_bg = 0; /* do background operation? */
1844 free_linklist (&linklist);
1845 free_linklist (&dest_dirs);
1847 /* Update panel contents to avoid actions on deleted files */
1848 if (!panel->is_panelized) {
1849 update_panels (UP_RELOAD, UP_KEEPSEL);
1850 repaint_screen ();
1853 if (single_entry) {
1854 if (force_single) {
1855 source = selection (panel)->fname;
1856 src_stat = selection (panel)->st;
1857 } else {
1858 source = panel_get_file (panel, &src_stat);
1861 if (!strcmp (source, "..")) {
1862 message (D_ERROR, MSG_ERROR, _(" Cannot operate on \"..\"! "));
1863 return 0;
1867 ctx = file_op_context_new (operation);
1869 /* Show confirmation dialog */
1870 if (operation != OP_DELETE) {
1871 char *dest_dir;
1872 char *dest_dir_;
1873 char *format;
1875 /* Forced single operations default to the original name */
1876 if (force_single)
1877 dest_dir = source;
1878 else if (get_other_type () == view_listing)
1879 dest_dir = other_panel->cwd;
1880 else
1881 dest_dir = panel->cwd;
1883 * Add trailing backslash only when do non-local ops.
1884 * It saves user from occasional file renames (when destination
1885 * dir is deleted)
1887 if (!force_single
1888 && dest_dir[0] != '\0'
1889 && dest_dir[strlen (dest_dir) - 1] != PATH_SEP) {
1890 /* add trailing separator */
1891 dest_dir_ = g_strconcat (dest_dir, PATH_SEP_STR, (char *) NULL);
1892 } else {
1893 /* just copy */
1894 dest_dir_ = g_strdup (dest_dir);
1896 if (dest_dir_ == NULL) {
1897 file_op_context_destroy (ctx);
1898 return 0;
1901 /* Generate confirmation prompt */
1902 format = panel_operate_generate_prompt (panel, operation,
1903 source != NULL, &src_stat);
1905 dest = file_mask_dialog (ctx, operation, source != NULL, format,
1906 source != NULL ? (void *) source
1907 : (void *) &panel->marked,
1908 dest_dir_, &do_bg);
1910 g_free (format);
1911 g_free (dest_dir_);
1913 if (dest == NULL || dest[0] == '\0') {
1914 file_op_context_destroy (ctx);
1915 g_free (dest);
1916 return 0;
1918 } else if (confirm_delete) {
1919 char *format;
1920 char fmd_buf [BUF_MEDIUM];
1922 /* Generate confirmation prompt */
1923 format = panel_operate_generate_prompt (panel, OP_DELETE,
1924 source != NULL, &src_stat);
1926 if (source == NULL)
1927 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1928 else {
1929 const int fmd_xlen = 64;
1930 i = fmd_xlen - str_term_width1 (format) - 4;
1931 g_snprintf (fmd_buf, sizeof (fmd_buf),
1932 format, str_trunc (source, i));
1935 g_free (format);
1937 if (safe_delete)
1938 query_set_sel (1);
1940 i = query_dialog (Q_(op_names[operation]), fmd_buf, D_ERROR, 2,
1941 _("&Yes"), _("&No"));
1943 if (i != 0) {
1944 file_op_context_destroy (ctx);
1945 return 0;
1949 /* Background also need ctx->ui, but not full */
1950 if (do_bg)
1951 file_op_context_create_ui_without_init (ctx, 1);
1952 else
1953 file_op_context_create_ui (ctx, 1);
1955 #ifdef WITH_BACKGROUND
1956 /* Did the user select to do a background operation? */
1957 if (do_bg) {
1958 int v;
1960 v = do_background (ctx,
1961 g_strconcat (op_names[operation], ": ",
1962 panel->cwd, NULL));
1963 if (v == -1) {
1964 message (D_ERROR, MSG_ERROR,
1965 _(" Sorry, I could not put the job in background "));
1968 /* If we are the parent */
1969 if (v == 1) {
1970 mc_setctl (panel->cwd, VFS_SETCTL_FORGET, NULL);
1971 mc_setctl (dest, VFS_SETCTL_FORGET, NULL);
1972 /* file_op_context_destroy (ctx); */
1973 return 0;
1976 #endif /* WITH_BACKGROUND */
1978 /* Initialize things */
1979 /* We do not want to trash cache every time file is
1980 created/touched. However, this will make our cache contain
1981 invalid data. */
1982 if (dest) {
1983 if (mc_setctl (dest, VFS_SETCTL_STALE_DATA, (void *) 1))
1984 save_dest = g_strdup (dest);
1986 if (panel->cwd) {
1987 if (mc_setctl (panel->cwd, VFS_SETCTL_STALE_DATA, (void *) 1))
1988 save_cwd = g_strdup (panel->cwd);
1991 /* Now, let's do the job */
1993 /* This code is only called by the tree and panel code */
1994 if (single_entry) {
1995 /* We now have ETA in all cases */
1997 /* One file: FIXME mc_chdir will take user out of any vfs */
1998 if (operation != OP_COPY && get_current_type () == view_tree)
1999 mc_chdir (PATH_SEP_STR);
2001 /* The source and src_stat variables have been initialized before */
2002 #ifdef WITH_FULL_PATHS
2003 source_with_path = concat_dir_and_file (panel->cwd, source);
2004 #endif /* WITH_FULL_PATHS */
2006 if (operation == OP_DELETE) {
2007 if (S_ISDIR (src_stat.st_mode))
2008 value = erase_dir (ctx, source_with_path, &count, &bytes);
2009 else
2010 value =
2011 erase_file (ctx, source_with_path, &count, &bytes, 1);
2012 } else {
2013 temp = transform_source (ctx, source_with_path);
2014 if (temp == NULL) {
2015 value = transform_error;
2016 } else {
2017 char *repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2018 char *temp2 = concat_dir_and_file (repl_dest, temp);
2019 g_free (repl_dest);
2020 g_free (temp);
2021 g_free(dest);
2022 dest = temp2;
2024 switch (operation) {
2025 case OP_COPY:
2027 * we use file_mask_op_follow_links only with OP_COPY,
2029 (*ctx->stat_func) (source_with_path, &src_stat);
2031 if (S_ISDIR (src_stat.st_mode)) {
2032 value =
2033 copy_dir_dir (ctx, source_with_path, dest, 1,
2034 0, 0, 0, &count, &bytes);
2035 } else {
2036 value =
2037 copy_file_file (ctx, source_with_path, dest, 1,
2038 &count, &bytes, 1);
2040 break;
2042 case OP_MOVE:
2043 if (S_ISDIR (src_stat.st_mode))
2044 value =
2045 move_dir_dir (ctx, source_with_path, dest,
2046 &count, &bytes);
2047 else
2048 value =
2049 move_file_file (ctx, source_with_path, dest,
2050 &count, &bytes);
2051 break;
2053 default:
2054 /* Unknown file operation */
2055 abort ();
2058 } /* Copy or move operation */
2060 if ((value == FILE_CONT) && !force_single)
2061 unmark_files (panel);
2062 } else {
2063 /* Many files */
2064 /* Check destination for copy or move operation */
2065 if (operation != OP_DELETE) {
2066 retry_many_dst_stat:
2067 dst_result = mc_stat (dest, &dst_stat);
2068 if (dst_result == 0 && !S_ISDIR (dst_stat.st_mode)) {
2069 if (file_error
2070 (_(" Destination \"%s\" must be a directory \n %s "),
2071 dest) == FILE_RETRY)
2072 goto retry_many_dst_stat;
2073 goto clean_up;
2077 /* Initialize variables for progress bars */
2078 if (operation != OP_MOVE && verbose && file_op_compute_totals) {
2079 ComputeDirSizeUI *ui;
2080 FileProgressStatus status;
2082 ui = compute_dir_size_create_ui ();
2083 status = panel_compute_totals (panel,
2084 ui, compute_dir_size_update_ui,
2085 &ctx->progress_count, &ctx->progress_bytes);
2086 compute_dir_size_destroy_ui (ui);
2088 if (status != FILE_CONT)
2089 goto clean_up;
2091 ctx->progress_totals_computed = 1;
2092 } else {
2093 ctx->progress_totals_computed = 0;
2094 ctx->progress_count = panel->marked;
2095 ctx->progress_bytes = panel->total;
2098 /* Loop for every file, perform the actual copy operation */
2099 for (i = 0; i < panel->count; i++) {
2100 if (!panel->dir.list[i].f.marked)
2101 continue; /* Skip the unmarked ones */
2103 source = panel->dir.list[i].fname;
2104 src_stat = panel->dir.list[i].st;
2106 #ifdef WITH_FULL_PATHS
2107 g_free (source_with_path);
2108 source_with_path = concat_dir_and_file (panel->cwd, source);
2109 #endif /* WITH_FULL_PATHS */
2111 if (operation == OP_DELETE) {
2112 if (S_ISDIR (src_stat.st_mode))
2113 value =
2114 erase_dir (ctx, source_with_path, &count, &bytes);
2115 else
2116 value =
2117 erase_file (ctx, source_with_path, &count, &bytes,
2119 } else {
2120 temp = transform_source (ctx, source_with_path);
2121 if (temp == NULL)
2122 value = transform_error;
2123 else {
2124 char *temp3;
2125 char *repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2126 char *temp2 = concat_dir_and_file (repl_dest, temp);
2127 g_free(repl_dest);
2129 g_free(temp);
2130 temp3 = source_with_path;
2131 source_with_path = strutils_shell_unescape(source_with_path);
2132 g_free(temp3);
2133 temp3 = temp2;
2134 temp2 = strutils_shell_unescape(temp2);
2135 g_free(temp3);
2137 switch (operation) {
2138 case OP_COPY:
2140 * we use file_mask_op_follow_links only with OP_COPY
2142 (*ctx->stat_func) (source_with_path, &src_stat);
2143 if (S_ISDIR (src_stat.st_mode))
2144 value =
2145 copy_dir_dir (ctx, source_with_path, temp2,
2146 1, 0, 0, 0, &count, &bytes);
2147 else
2148 value =
2149 copy_file_file (ctx, source_with_path,
2150 temp2, 1, &count, &bytes,
2152 free_linklist (&dest_dirs);
2153 break;
2155 case OP_MOVE:
2156 if (S_ISDIR (src_stat.st_mode))
2157 value =
2158 move_dir_dir (ctx, source_with_path, temp2,
2159 &count, &bytes);
2160 else
2161 value =
2162 move_file_file (ctx, source_with_path,
2163 temp2, &count, &bytes);
2164 break;
2166 default:
2167 /* Unknown file operation */
2168 abort ();
2170 g_free (temp2);
2172 } /* Copy or move operation */
2174 if (value == FILE_ABORT)
2175 goto clean_up;
2177 if (value == FILE_CONT)
2178 do_file_mark (panel, i, 0);
2180 if (file_progress_show_count (ctx, count, ctx->progress_count)
2181 == FILE_ABORT)
2182 goto clean_up;
2184 if (verbose
2185 && file_progress_show_bytes (ctx, bytes,
2186 ctx->progress_bytes) ==
2187 FILE_ABORT)
2188 goto clean_up;
2190 if (operation != OP_DELETE && verbose
2191 && file_progress_show (ctx, 0, 0) == FILE_ABORT)
2192 goto clean_up;
2194 mc_refresh ();
2195 } /* Loop for every file */
2196 } /* Many entries */
2197 clean_up:
2198 /* Clean up */
2200 if (save_cwd) {
2201 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
2202 g_free (save_cwd);
2204 if (save_dest) {
2205 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
2206 g_free (save_dest);
2209 free_linklist (&linklist);
2210 free_linklist (&dest_dirs);
2211 #ifdef WITH_FULL_PATHS
2212 g_free (source_with_path);
2213 #endif /* WITH_FULL_PATHS */
2214 g_free (dest);
2215 g_free (ctx->dest_mask);
2216 ctx->dest_mask = NULL;
2217 #ifdef WITH_BACKGROUND
2218 /* Let our parent know we are saying bye bye */
2219 if (we_are_background) {
2220 int cur_pid = getpid();
2221 /* Send pid to parent with child context, it is fork and
2222 don't modify real parent ctx */
2223 ctx->pid = cur_pid;
2224 parent_call ((void *) end_bg_process, ctx, 0);
2226 vfs_shut ();
2227 _exit (0);
2229 #endif /* WITH_BACKGROUND */
2231 file_op_context_destroy (ctx);
2232 return 1;
2235 /* }}} */
2237 /* {{{ Query/status report routines */
2239 static FileProgressStatus
2240 real_do_file_error (enum OperationMode mode, const char *error)
2242 int result;
2243 const char *msg;
2245 msg = mode == Foreground ? MSG_ERROR : _(" Background process error ");
2246 result =
2247 query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("&Retry"),
2248 _("&Abort"));
2250 switch (result) {
2251 case 0:
2252 do_refresh ();
2253 return FILE_SKIP;
2255 case 1:
2256 do_refresh ();
2257 return FILE_RETRY;
2259 case 2:
2260 default:
2261 return FILE_ABORT;
2265 /* Report error with one file */
2266 FileProgressStatus
2267 file_error (const char *format, const char *file)
2269 char buf [BUF_MEDIUM];
2271 g_snprintf (buf, sizeof (buf), format,
2272 path_trunc (file, 30), unix_error_string (errno));
2274 return do_file_error (buf);
2277 /* Report error with two files */
2278 static FileProgressStatus
2279 files_error (const char *format, const char *file1, const char *file2)
2281 char buf [BUF_MEDIUM];
2282 char *nfile1 = g_strdup (path_trunc (file1, 15));
2283 char *nfile2 = g_strdup (path_trunc (file2, 15));
2285 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2,
2286 unix_error_string (errno));
2288 g_free (nfile1);
2289 g_free (nfile2);
2291 return do_file_error (buf);
2294 static FileProgressStatus
2295 real_query_recursive (FileOpContext *ctx, enum OperationMode mode, const char *s)
2297 gchar *text;
2299 if (ctx->recursive_result < RECURSIVE_ALWAYS) {
2300 const char *msg =
2301 mode ==
2302 Foreground ?
2303 _("\n Directory not empty. \n"
2304 " Delete it recursively? ")
2305 : _("\n Background process: Directory not empty \n"
2306 " Delete it recursively? ");
2307 text = g_strconcat (_(" Delete: "), path_trunc (s, 30), " ", (char *) NULL);
2309 if (safe_delete)
2310 query_set_sel (1);
2311 ctx->recursive_result = query_dialog (text, msg, D_ERROR, 5,
2312 _("&Yes"), _("&No"),
2313 _("A&ll"), _("Non&e"),
2314 _("&Abort"));
2316 if (ctx->recursive_result != RECURSIVE_ABORT)
2317 do_refresh ();
2318 g_free (text);
2321 switch (ctx->recursive_result) {
2322 case RECURSIVE_YES:
2323 case RECURSIVE_ALWAYS:
2324 return FILE_CONT;
2326 case RECURSIVE_NO:
2327 case RECURSIVE_NEVER:
2328 return FILE_SKIP;
2330 case RECURSIVE_ABORT:
2332 default:
2333 return FILE_ABORT;
2337 #ifdef WITH_BACKGROUND
2338 static FileProgressStatus
2339 do_file_error (const char *str)
2341 union {
2342 void *p;
2343 FileProgressStatus (*f) (enum OperationMode, const char *);
2344 } pntr;
2345 pntr.f = real_do_file_error;
2347 if (we_are_background)
2348 return parent_call (pntr.p, NULL, 1, strlen (str),
2349 str);
2350 else
2351 return real_do_file_error (Foreground, str);
2354 static FileProgressStatus
2355 query_recursive (FileOpContext *ctx, const char *s)
2357 union {
2358 void *p;
2359 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *);
2360 } pntr;
2361 pntr.f = real_query_recursive;
2363 if (we_are_background)
2364 return parent_call (pntr.p, ctx, 1, strlen (s), s);
2365 else
2366 return real_query_recursive (ctx, Foreground, s);
2369 static FileProgressStatus
2370 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2371 struct stat *_d_stat)
2373 union {
2374 void *p;
2375 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *,
2376 struct stat *, struct stat *);
2377 } pntr;
2378 pntr.f = file_progress_real_query_replace;
2380 if (we_are_background)
2381 return parent_call (pntr.p,
2382 ctx,
2384 strlen (destname), destname,
2385 sizeof (struct stat), _s_stat,
2386 sizeof (struct stat), _d_stat);
2387 else
2388 return file_progress_real_query_replace (ctx, Foreground, destname,
2389 _s_stat, _d_stat);
2392 #else
2393 static FileProgressStatus
2394 do_file_error (const char *str)
2396 return real_do_file_error (Foreground, str);
2399 static FileProgressStatus
2400 query_recursive (FileOpContext *ctx, const char *s)
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 return file_progress_real_query_replace (ctx, Foreground, destname,
2410 _s_stat, _d_stat);
2413 #endif /* !WITH_BACKGROUND */
2416 Cause emacs to enter folding mode for this file:
2417 Local variables:
2418 end: