Merge branch '1708_ftp_permission'
[midnight-commander.git] / src / file.c
blob7e1f61ebe8048fed448f4b3cfb23295cf3a677da
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 int i;
1710 char format_string[BUF_MEDIUM];
1711 char *dp = format_string;
1712 gboolean build_question = FALSE;
1714 #ifdef ENABLE_NLS
1715 static gboolean i18n_flag = FALSE;
1716 if (!i18n_flag) {
1717 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1718 op_names1[i] = Q_(op_names1[i]);
1720 for (i = sizeof (prompt_parts) / sizeof (prompt_parts[0]); i--;)
1721 prompt_parts[i] = _(prompt_parts[i]);
1723 one_format = _(one_format);
1724 many_format = _(many_format);
1725 question_format = _(question_format);
1726 i18n_flag = TRUE;
1728 #endif /* ENABLE_NLS */
1730 sp = single_source ? one_format : many_format;
1732 while (*sp != '\0') {
1733 switch (*sp) {
1734 case '%':
1735 cp = NULL;
1736 switch (sp[1]) {
1737 case 'o':
1738 cp = op_names1[operation];
1739 break;
1740 case 'm':
1741 if (operation == OP_DELETE) {
1742 cp = "";
1743 build_question = TRUE;
1744 } else
1745 cp = prompt_parts[5];
1746 break;
1747 case 'e':
1748 if (operation == OP_DELETE) {
1749 cp = "";
1750 build_question = TRUE;
1751 } else
1752 cp = prompt_parts[6];
1753 break;
1754 case 'f':
1755 if (single_source) {
1756 cp = S_ISDIR (src_stat->
1757 st_mode) ? prompt_parts[2] : prompt_parts[0];
1758 } else {
1759 cp = (panel->marked == panel->dirs_marked)
1760 ? prompt_parts[3]
1761 : (panel->dirs_marked ? prompt_parts[4] : prompt_parts[1]);
1763 break;
1764 default:
1765 *dp++ = *sp++;
1767 if (cp != NULL) {
1768 sp += 2;
1769 while (*cp != '\0')
1770 *dp++ = *cp++;
1772 break;
1773 default:
1774 *dp++ = *sp++;
1777 *dp = '\0';
1779 if (build_question) {
1780 char tmp[BUF_MEDIUM];
1782 memmove (tmp, format_string, sizeof (tmp));
1783 g_snprintf (format_string, sizeof (format_string),
1784 question_format, tmp);
1787 return g_strdup (format_string);
1790 #ifdef WITH_BACKGROUND
1791 static int
1792 end_bg_process (FileOpContext *ctx, enum OperationMode mode) {
1793 int pid = ctx->pid;
1795 (void) mode;
1796 ctx->pid = 0;
1798 unregister_task_with_pid(pid);
1799 // file_op_context_destroy(ctx);
1800 return 1;
1802 #endif
1805 * panel_operate:
1807 * Performs one of the operations on the selection on the source_panel
1808 * (copy, delete, move).
1810 * Returns 1 if did change the directory
1811 * structure, Returns 0 if user aborted
1813 * force_single forces operation on the current entry and affects
1814 * default destination. Current filename is used as default.
1817 panel_operate (void *source_panel, FileOperation operation,
1818 int force_single)
1820 WPanel *panel = source_panel;
1821 char *source = NULL;
1822 #ifdef WITH_FULL_PATHS
1823 char *source_with_path = NULL;
1824 #else
1825 # define source_with_path source
1826 #endif /* !WITH_FULL_PATHS */
1827 char *dest = NULL;
1828 char *temp = NULL;
1829 char *save_cwd = NULL, *save_dest = NULL;
1830 int single_entry = (get_current_type () == view_tree)
1831 || (panel->marked <= 1) || force_single;
1832 struct stat src_stat, dst_stat;
1833 int i;
1834 FileProgressStatus value;
1835 FileOpContext *ctx;
1837 off_t count = 0;
1838 double bytes = 0;
1840 int dst_result;
1841 int do_bg = 0; /* do background operation? */
1843 free_linklist (&linklist);
1844 free_linklist (&dest_dirs);
1846 /* Update panel contents to avoid actions on deleted files */
1847 if (!panel->is_panelized) {
1848 update_panels (UP_RELOAD, UP_KEEPSEL);
1849 repaint_screen ();
1852 if (single_entry) {
1853 if (force_single) {
1854 source = selection (panel)->fname;
1855 src_stat = selection (panel)->st;
1856 } else {
1857 source = panel_get_file (panel, &src_stat);
1860 if (!strcmp (source, "..")) {
1861 message (D_ERROR, MSG_ERROR, _(" Cannot operate on \"..\"! "));
1862 return 0;
1866 ctx = file_op_context_new (operation);
1868 /* Show confirmation dialog */
1869 if (operation != OP_DELETE) {
1870 char *dest_dir;
1871 char *dest_dir_;
1872 char *format;
1874 /* Forced single operations default to the original name */
1875 if (force_single)
1876 dest_dir = source;
1877 else if (get_other_type () == view_listing)
1878 dest_dir = other_panel->cwd;
1879 else
1880 dest_dir = panel->cwd;
1882 * Add trailing backslash only when do non-local ops.
1883 * It saves user from occasional file renames (when destination
1884 * dir is deleted)
1886 if (!force_single
1887 && dest_dir[0] != '\0'
1888 && dest_dir[strlen (dest_dir) - 1] != PATH_SEP) {
1889 /* add trailing separator */
1890 dest_dir_ = g_strconcat (dest_dir, PATH_SEP_STR, (char *) NULL);
1891 } else {
1892 /* just copy */
1893 dest_dir_ = g_strdup (dest_dir);
1895 if (dest_dir_ == NULL) {
1896 file_op_context_destroy (ctx);
1897 return 0;
1900 /* Generate confirmation prompt */
1901 format = panel_operate_generate_prompt (panel, operation,
1902 source != NULL, &src_stat);
1904 dest = file_mask_dialog (ctx, operation, source != NULL, format,
1905 source != NULL ? (void *) source
1906 : (void *) &panel->marked,
1907 dest_dir_, &do_bg);
1909 g_free (format);
1910 g_free (dest_dir_);
1912 if (dest == NULL || dest[0] == '\0') {
1913 file_op_context_destroy (ctx);
1914 g_free (dest);
1915 return 0;
1917 } else if (confirm_delete) {
1918 char *format;
1919 char fmd_buf [BUF_MEDIUM];
1921 /* Generate confirmation prompt */
1922 format = panel_operate_generate_prompt (panel, OP_DELETE,
1923 source != NULL, &src_stat);
1925 if (source == NULL)
1926 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1927 else {
1928 const int fmd_xlen = 64;
1929 i = fmd_xlen - str_term_width1 (format) - 4;
1930 g_snprintf (fmd_buf, sizeof (fmd_buf),
1931 format, str_trunc (source, i));
1934 g_free (format);
1936 if (safe_delete)
1937 query_set_sel (1);
1939 i = query_dialog (Q_(op_names[operation]), fmd_buf, D_ERROR, 2,
1940 _("&Yes"), _("&No"));
1942 if (i != 0) {
1943 file_op_context_destroy (ctx);
1944 return 0;
1948 /* Background also need ctx->ui, but not full */
1949 if (do_bg)
1950 file_op_context_create_ui_without_init (ctx, 1);
1951 else
1952 file_op_context_create_ui (ctx, 1);
1954 #ifdef WITH_BACKGROUND
1955 /* Did the user select to do a background operation? */
1956 if (do_bg) {
1957 int v;
1959 v = do_background (ctx,
1960 g_strconcat (op_names[operation], ": ",
1961 panel->cwd, NULL));
1962 if (v == -1) {
1963 message (D_ERROR, MSG_ERROR,
1964 _(" Sorry, I could not put the job in background "));
1967 /* If we are the parent */
1968 if (v == 1) {
1969 mc_setctl (panel->cwd, VFS_SETCTL_FORGET, NULL);
1970 mc_setctl (dest, VFS_SETCTL_FORGET, NULL);
1971 /* file_op_context_destroy (ctx); */
1972 return 0;
1975 #endif /* WITH_BACKGROUND */
1977 /* Initialize things */
1978 /* We do not want to trash cache every time file is
1979 created/touched. However, this will make our cache contain
1980 invalid data. */
1981 if (dest) {
1982 if (mc_setctl (dest, VFS_SETCTL_STALE_DATA, (void *) 1))
1983 save_dest = g_strdup (dest);
1985 if (panel->cwd) {
1986 if (mc_setctl (panel->cwd, VFS_SETCTL_STALE_DATA, (void *) 1))
1987 save_cwd = g_strdup (panel->cwd);
1990 /* Now, let's do the job */
1992 /* This code is only called by the tree and panel code */
1993 if (single_entry) {
1994 /* We now have ETA in all cases */
1996 /* One file: FIXME mc_chdir will take user out of any vfs */
1997 if (operation != OP_COPY && get_current_type () == view_tree)
1998 mc_chdir (PATH_SEP_STR);
2000 /* The source and src_stat variables have been initialized before */
2001 #ifdef WITH_FULL_PATHS
2002 source_with_path = concat_dir_and_file (panel->cwd, source);
2003 #endif /* WITH_FULL_PATHS */
2005 if (operation == OP_DELETE) {
2006 if (S_ISDIR (src_stat.st_mode))
2007 value = erase_dir (ctx, source_with_path, &count, &bytes);
2008 else
2009 value =
2010 erase_file (ctx, source_with_path, &count, &bytes, 1);
2011 } else {
2012 temp = transform_source (ctx, source_with_path);
2013 if (temp == NULL) {
2014 value = transform_error;
2015 } else {
2016 char *repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2017 char *temp2 = concat_dir_and_file (repl_dest, temp);
2018 g_free (repl_dest);
2019 g_free (temp);
2020 g_free(dest);
2021 dest = temp2;
2023 switch (operation) {
2024 case OP_COPY:
2026 * we use file_mask_op_follow_links only with OP_COPY,
2028 (*ctx->stat_func) (source_with_path, &src_stat);
2030 if (S_ISDIR (src_stat.st_mode)) {
2031 value =
2032 copy_dir_dir (ctx, source_with_path, dest, 1,
2033 0, 0, 0, &count, &bytes);
2034 } else {
2035 value =
2036 copy_file_file (ctx, source_with_path, dest, 1,
2037 &count, &bytes, 1);
2039 break;
2041 case OP_MOVE:
2042 if (S_ISDIR (src_stat.st_mode))
2043 value =
2044 move_dir_dir (ctx, source_with_path, dest,
2045 &count, &bytes);
2046 else
2047 value =
2048 move_file_file (ctx, source_with_path, dest,
2049 &count, &bytes);
2050 break;
2052 default:
2053 /* Unknown file operation */
2054 abort ();
2057 } /* Copy or move operation */
2059 if ((value == FILE_CONT) && !force_single)
2060 unmark_files (panel);
2061 } else {
2062 /* Many files */
2063 /* Check destination for copy or move operation */
2064 if (operation != OP_DELETE) {
2065 retry_many_dst_stat:
2066 dst_result = mc_stat (dest, &dst_stat);
2067 if (dst_result == 0 && !S_ISDIR (dst_stat.st_mode)) {
2068 if (file_error
2069 (_(" Destination \"%s\" must be a directory \n %s "),
2070 dest) == FILE_RETRY)
2071 goto retry_many_dst_stat;
2072 goto clean_up;
2076 /* Initialize variables for progress bars */
2077 if (operation != OP_MOVE && verbose && file_op_compute_totals) {
2078 ComputeDirSizeUI *ui;
2079 FileProgressStatus status;
2081 ui = compute_dir_size_create_ui ();
2082 status = panel_compute_totals (panel,
2083 ui, compute_dir_size_update_ui,
2084 &ctx->progress_count, &ctx->progress_bytes);
2085 compute_dir_size_destroy_ui (ui);
2087 if (status != FILE_CONT)
2088 goto clean_up;
2090 ctx->progress_totals_computed = 1;
2091 } else {
2092 ctx->progress_totals_computed = 0;
2093 ctx->progress_count = panel->marked;
2094 ctx->progress_bytes = panel->total;
2097 /* Loop for every file, perform the actual copy operation */
2098 for (i = 0; i < panel->count; i++) {
2099 if (!panel->dir.list[i].f.marked)
2100 continue; /* Skip the unmarked ones */
2102 source = panel->dir.list[i].fname;
2103 src_stat = panel->dir.list[i].st;
2105 #ifdef WITH_FULL_PATHS
2106 g_free (source_with_path);
2107 source_with_path = concat_dir_and_file (panel->cwd, source);
2108 #endif /* WITH_FULL_PATHS */
2110 if (operation == OP_DELETE) {
2111 if (S_ISDIR (src_stat.st_mode))
2112 value =
2113 erase_dir (ctx, source_with_path, &count, &bytes);
2114 else
2115 value =
2116 erase_file (ctx, source_with_path, &count, &bytes,
2118 } else {
2119 temp = transform_source (ctx, source_with_path);
2120 if (temp == NULL)
2121 value = transform_error;
2122 else {
2123 char *temp3;
2124 char *repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2125 char *temp2 = concat_dir_and_file (repl_dest, temp);
2126 g_free(repl_dest);
2128 g_free(temp);
2129 temp3 = source_with_path;
2130 source_with_path = strutils_shell_unescape(source_with_path);
2131 g_free(temp3);
2132 temp3 = temp2;
2133 temp2 = strutils_shell_unescape(temp2);
2134 g_free(temp3);
2136 switch (operation) {
2137 case OP_COPY:
2139 * we use file_mask_op_follow_links only with OP_COPY
2141 (*ctx->stat_func) (source_with_path, &src_stat);
2142 if (S_ISDIR (src_stat.st_mode))
2143 value =
2144 copy_dir_dir (ctx, source_with_path, temp2,
2145 1, 0, 0, 0, &count, &bytes);
2146 else
2147 value =
2148 copy_file_file (ctx, source_with_path,
2149 temp2, 1, &count, &bytes,
2151 free_linklist (&dest_dirs);
2152 break;
2154 case OP_MOVE:
2155 if (S_ISDIR (src_stat.st_mode))
2156 value =
2157 move_dir_dir (ctx, source_with_path, temp2,
2158 &count, &bytes);
2159 else
2160 value =
2161 move_file_file (ctx, source_with_path,
2162 temp2, &count, &bytes);
2163 break;
2165 default:
2166 /* Unknown file operation */
2167 abort ();
2169 g_free (temp2);
2171 } /* Copy or move operation */
2173 if (value == FILE_ABORT)
2174 goto clean_up;
2176 if (value == FILE_CONT)
2177 do_file_mark (panel, i, 0);
2179 if (file_progress_show_count (ctx, count, ctx->progress_count)
2180 == FILE_ABORT)
2181 goto clean_up;
2183 if (verbose
2184 && file_progress_show_bytes (ctx, bytes,
2185 ctx->progress_bytes) ==
2186 FILE_ABORT)
2187 goto clean_up;
2189 if (operation != OP_DELETE && verbose
2190 && file_progress_show (ctx, 0, 0) == FILE_ABORT)
2191 goto clean_up;
2193 mc_refresh ();
2194 } /* Loop for every file */
2195 } /* Many entries */
2196 clean_up:
2197 /* Clean up */
2199 if (save_cwd) {
2200 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
2201 g_free (save_cwd);
2203 if (save_dest) {
2204 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
2205 g_free (save_dest);
2208 free_linklist (&linklist);
2209 free_linklist (&dest_dirs);
2210 #ifdef WITH_FULL_PATHS
2211 g_free (source_with_path);
2212 #endif /* WITH_FULL_PATHS */
2213 g_free (dest);
2214 g_free (ctx->dest_mask);
2215 ctx->dest_mask = NULL;
2216 #ifdef WITH_BACKGROUND
2217 /* Let our parent know we are saying bye bye */
2218 if (we_are_background) {
2219 int cur_pid = getpid();
2220 /* Send pid to parent with child context, it is fork and
2221 don't modify real parent ctx */
2222 ctx->pid = cur_pid;
2223 parent_call ((void *) end_bg_process, ctx, 0);
2225 vfs_shut ();
2226 _exit (0);
2228 #endif /* WITH_BACKGROUND */
2230 file_op_context_destroy (ctx);
2231 return 1;
2234 /* }}} */
2236 /* {{{ Query/status report routines */
2238 static FileProgressStatus
2239 real_do_file_error (enum OperationMode mode, const char *error)
2241 int result;
2242 const char *msg;
2244 msg = mode == Foreground ? MSG_ERROR : _(" Background process error ");
2245 result =
2246 query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("&Retry"),
2247 _("&Abort"));
2249 switch (result) {
2250 case 0:
2251 do_refresh ();
2252 return FILE_SKIP;
2254 case 1:
2255 do_refresh ();
2256 return FILE_RETRY;
2258 case 2:
2259 default:
2260 return FILE_ABORT;
2264 /* Report error with one file */
2265 FileProgressStatus
2266 file_error (const char *format, const char *file)
2268 char buf [BUF_MEDIUM];
2270 g_snprintf (buf, sizeof (buf), format,
2271 path_trunc (file, 30), unix_error_string (errno));
2273 return do_file_error (buf);
2276 /* Report error with two files */
2277 static FileProgressStatus
2278 files_error (const char *format, const char *file1, const char *file2)
2280 char buf [BUF_MEDIUM];
2281 char *nfile1 = g_strdup (path_trunc (file1, 15));
2282 char *nfile2 = g_strdup (path_trunc (file2, 15));
2284 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2,
2285 unix_error_string (errno));
2287 g_free (nfile1);
2288 g_free (nfile2);
2290 return do_file_error (buf);
2293 static FileProgressStatus
2294 real_query_recursive (FileOpContext *ctx, enum OperationMode mode, const char *s)
2296 gchar *text;
2298 if (ctx->recursive_result < RECURSIVE_ALWAYS) {
2299 const char *msg =
2300 mode ==
2301 Foreground ?
2302 _("\n Directory not empty. \n"
2303 " Delete it recursively? ")
2304 : _("\n Background process: Directory not empty \n"
2305 " Delete it recursively? ");
2306 text = g_strconcat (_(" Delete: "), path_trunc (s, 30), " ", (char *) NULL);
2308 if (safe_delete)
2309 query_set_sel (1);
2310 ctx->recursive_result = query_dialog (text, msg, D_ERROR, 5,
2311 _("&Yes"), _("&No"),
2312 _("A&ll"), _("Non&e"),
2313 _("&Abort"));
2315 if (ctx->recursive_result != RECURSIVE_ABORT)
2316 do_refresh ();
2317 g_free (text);
2320 switch (ctx->recursive_result) {
2321 case RECURSIVE_YES:
2322 case RECURSIVE_ALWAYS:
2323 return FILE_CONT;
2325 case RECURSIVE_NO:
2326 case RECURSIVE_NEVER:
2327 return FILE_SKIP;
2329 case RECURSIVE_ABORT:
2331 default:
2332 return FILE_ABORT;
2336 #ifdef WITH_BACKGROUND
2337 static FileProgressStatus
2338 do_file_error (const char *str)
2340 union {
2341 void *p;
2342 FileProgressStatus (*f) (enum OperationMode, const char *);
2343 } pntr;
2344 pntr.f = real_do_file_error;
2346 if (we_are_background)
2347 return parent_call (pntr.p, NULL, 1, strlen (str),
2348 str);
2349 else
2350 return real_do_file_error (Foreground, str);
2353 static FileProgressStatus
2354 query_recursive (FileOpContext *ctx, const char *s)
2356 union {
2357 void *p;
2358 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *);
2359 } pntr;
2360 pntr.f = real_query_recursive;
2362 if (we_are_background)
2363 return parent_call (pntr.p, ctx, 1, strlen (s), s);
2364 else
2365 return real_query_recursive (ctx, Foreground, s);
2368 static FileProgressStatus
2369 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2370 struct stat *_d_stat)
2372 union {
2373 void *p;
2374 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *,
2375 struct stat *, struct stat *);
2376 } pntr;
2377 pntr.f = file_progress_real_query_replace;
2379 if (we_are_background)
2380 return parent_call (pntr.p,
2381 ctx,
2383 strlen (destname), destname,
2384 sizeof (struct stat), _s_stat,
2385 sizeof (struct stat), _d_stat);
2386 else
2387 return file_progress_real_query_replace (ctx, Foreground, destname,
2388 _s_stat, _d_stat);
2391 #else
2392 static FileProgressStatus
2393 do_file_error (const char *str)
2395 return real_do_file_error (Foreground, str);
2398 static FileProgressStatus
2399 query_recursive (FileOpContext *ctx, const char *s)
2401 return real_query_recursive (ctx, Foreground, s);
2404 static FileProgressStatus
2405 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2406 struct stat *_d_stat)
2408 return file_progress_real_query_replace (ctx, Foreground, destname,
2409 _s_stat, _d_stat);
2412 #endif /* !WITH_BACKGROUND */
2415 Cause emacs to enter folding mode for this file:
2416 Local variables:
2417 end: