Moved dir $(srcdir)/syntax into $(srcdir)/misc/syntax
[midnight-commander.git] / src / file.c
blob04444d6fea2ebac71cb2488d0d1072c59d1d734d
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 "../../lib/vfs/mc-vfs/vfs-impl.h"
84 #include "../../lib/vfs/mc-vfs/vfs.h"
86 /* }}} */
88 /* Hack: the vfs code should not rely on this */
89 #define WITH_FULL_PATHS 1
91 int verbose = 1;
94 * Whether the Midnight Commander tries to provide more
95 * information about copy/move sizes and bytes transfered
96 * at the expense of some speed
98 int file_op_compute_totals = 1;
100 /* This is a hard link cache */
101 struct link {
102 struct link *next;
103 struct vfs_class *vfs;
104 dev_t dev;
105 ino_t ino;
106 short linkcount;
107 mode_t st_mode;
108 char name[1];
111 /* the hard link cache */
112 static struct link *linklist = NULL;
114 /* the files-to-be-erased list */
115 static struct link *erase_list;
118 * In copy_dir_dir we use two additional single linked lists: The first -
119 * variable name `parent_dirs' - holds information about already copied
120 * directories and is used to detect cyclic symbolic links.
121 * The second (`dest_dirs' below) holds information about just created
122 * target directories and is used to detect when an directory is copied
123 * into itself (we don't want to copy infinitly).
124 * Both lists don't use the linkcount and name structure members of struct
125 * link.
127 static struct link *dest_dirs = NULL;
129 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
130 const char *op_names[3] = {
131 N_("DialogTitle|Copy"),
132 N_("DialogTitle|Move"),
133 N_("DialogTitle|Delete")
136 /* }}} */
138 static FileProgressStatus query_replace (FileOpContext * ctx, const char *destname,
139 struct stat *_s_stat, struct stat *_d_stat);
140 static FileProgressStatus query_recursive (FileOpContext * ctx, const char *s);
141 static FileProgressStatus do_file_error (const char *str);
142 static FileProgressStatus erase_dir_iff_empty (FileOpContext *ctx, const char *s);
143 static FileProgressStatus erase_file (FileOpContext *ctx, const char *s,
144 off_t *progress_count, double *progress_bytes,
145 int is_toplevel_file);
146 static FileProgressStatus files_error (const char *format, const char *file1,
147 const char *file2);
149 static FileProgressStatus transform_error = FILE_CONT;
151 static char *
152 transform_source (FileOpContext *ctx, const char *source)
154 char *s, *q;
155 char *fnsource;
157 s = g_strdup (source);
159 /* We remove \n from the filename since regex routines would use \n as an anchor */
160 /* this is just to be allowed to maniupulate file names with \n on it */
161 for (q = s; *q != '\0'; q++)
162 if (*q == '\n')
163 *q = ' ';
165 fnsource = (char *) x_basename (s);
167 if (mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
168 q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
169 else {
170 q = NULL;
171 transform_error = FILE_SKIP;
174 g_free (s);
175 return q;
178 static void
179 free_linklist (struct link **lc_linklist)
181 struct link *lp, *lp2;
183 for (lp = *lc_linklist; lp != NULL; lp = lp2) {
184 lp2 = lp->next;
185 g_free (lp);
187 *lc_linklist = NULL;
190 static int
191 is_in_linklist (struct link *lp, const char *path, struct stat *sb)
193 ino_t ino = sb->st_ino;
194 dev_t dev = sb->st_dev;
195 #ifdef ENABLE_VFS
196 struct vfs_class *vfs = vfs_get_class (path);
197 #endif /* ENABLE_VFS */
199 (void) path;
201 while (lp) {
202 #ifdef ENABLE_VFS
203 if (lp->vfs == vfs)
204 #endif /* ENABLE_VFS */
205 if (lp->ino == ino && lp->dev == dev)
206 return 1;
207 lp = lp->next;
209 return 0;
213 * Returns 0 if the inode wasn't found in the cache and 1 if it was found
214 * and a hardlink was succesfully made
216 static int
217 check_hardlinks (const char *src_name, const char *dst_name, struct stat *pstat)
219 struct link *lp;
220 struct vfs_class *my_vfs = vfs_get_class (src_name);
221 ino_t ino = pstat->st_ino;
222 dev_t dev = pstat->st_dev;
223 struct stat link_stat;
224 const char *p;
226 if (vfs_file_class_flags (src_name) & VFSF_NOLINKS)
227 return 0;
229 for (lp = linklist; lp != NULL; lp = lp->next)
230 if (lp->vfs == my_vfs && lp->ino == ino && lp->dev == dev) {
231 if (!mc_stat (lp->name, &link_stat) && link_stat.st_ino == ino
232 && link_stat.st_dev == dev
233 && vfs_get_class (lp->name) == my_vfs) {
234 p = strchr (lp->name, 0) + 1; /* i.e. where the `name' file
235 was copied to */
236 if (vfs_get_class (dst_name) == vfs_get_class (p)) {
237 if (!mc_stat (p, &link_stat)) {
238 if (!mc_link (p, dst_name))
239 return 1;
243 message (D_ERROR, MSG_ERROR, _(" Cannot make the hardlink "));
244 return 0;
246 lp = (struct link *) g_try_malloc (sizeof (struct link) + strlen (src_name)
247 + strlen (dst_name) + 1);
248 if (lp) {
249 char *lpdstname;
250 lp->vfs = my_vfs;
251 lp->ino = ino;
252 lp->dev = dev;
253 strcpy (lp->name, src_name);
254 lpdstname = lp->name + strlen(lp->name) + 1;
255 strcpy (lpdstname, dst_name);
256 lp->next = linklist;
257 linklist = lp;
259 return 0;
263 * Duplicate the contents of the symbolic link src_path in dst_path.
264 * Try to make a stable symlink if the option "stable symlink" was
265 * set in the file mask dialog.
266 * If dst_path is an existing symlink it will be deleted silently
267 * (upper levels take already care of existing files at dst_path).
269 static FileProgressStatus
270 make_symlink (FileOpContext *ctx, const char *src_path, const char *dst_path)
272 char link_target[MC_MAXPATHLEN];
273 int len;
274 FileProgressStatus return_status;
275 struct stat sb;
276 int dst_is_symlink;
278 if (mc_lstat (dst_path, &sb) == 0 && S_ISLNK (sb.st_mode))
279 dst_is_symlink = 1;
280 else
281 dst_is_symlink = 0;
283 retry_src_readlink:
284 len = mc_readlink (src_path, link_target, MC_MAXPATHLEN - 1);
285 if (len < 0) {
286 return_status =
287 file_error (_(" Cannot read source link \"%s\" \n %s "),
288 src_path);
289 if (return_status == FILE_RETRY)
290 goto retry_src_readlink;
291 return return_status;
293 link_target[len] = 0;
295 if (ctx->stable_symlinks)
296 if (!vfs_file_is_local (src_path) || !vfs_file_is_local (dst_path)) {
297 message (D_ERROR, MSG_ERROR,
298 _(" Cannot make stable symlinks across "
299 "non-local filesystems: \n\n"
300 " Option Stable Symlinks will be disabled "));
301 ctx->stable_symlinks = 0;
304 if (ctx->stable_symlinks && *link_target != PATH_SEP) {
305 char *p, *q, *s;
307 const char *r = strrchr (src_path, PATH_SEP);
309 if (r) {
310 p = g_strndup (src_path, r - src_path + 1);
311 if (*dst_path == PATH_SEP)
312 q = g_strdup (dst_path);
313 else
314 q = g_strconcat (p, dst_path, (char *) NULL);
315 s = strrchr (q, PATH_SEP);
316 if (s) {
317 s[1] = 0;
318 s = g_strconcat (p, link_target, (char *) NULL);
319 g_free (p);
320 g_strlcpy (link_target, s, sizeof (link_target));
321 g_free (s);
322 s = diff_two_paths (q, link_target);
323 if (s) {
324 g_strlcpy (link_target, s, sizeof (link_target));
325 g_free (s);
327 } else
328 g_free (p);
329 g_free (q);
332 retry_dst_symlink:
333 if (mc_symlink (link_target, dst_path) == 0)
334 /* Success */
335 return FILE_CONT;
337 * if dst_exists, it is obvious that this had failed.
338 * We can delete the old symlink and try again...
340 if (dst_is_symlink) {
341 if (!mc_unlink (dst_path))
342 if (mc_symlink (link_target, dst_path) == 0)
343 /* Success */
344 return FILE_CONT;
346 return_status =
347 file_error (_(" Cannot create target symlink \"%s\" \n %s "),
348 dst_path);
349 if (return_status == FILE_RETRY)
350 goto retry_dst_symlink;
351 return return_status;
354 static int
355 progress_update_one (FileOpContext *ctx,
356 off_t *progress_count,
357 double *progress_bytes, off_t add, int is_toplevel_file)
359 int ret;
361 if (is_toplevel_file || ctx->progress_totals_computed) {
362 (*progress_count)++;
363 (*progress_bytes) += add;
366 /* Apply some heuristic here to not call the update stuff very often */
367 ret =
368 file_progress_show_count (ctx, *progress_count,
369 ctx->progress_count);
370 if (ret != FILE_CONT)
371 return ret;
372 ret =
373 file_progress_show_bytes (ctx, *progress_bytes,
374 ctx->progress_bytes);
376 return ret;
379 /* Status of the destination file */
380 enum {
381 DEST_NONE, /* Not created */
382 DEST_SHORT, /* Created, not fully copied */
383 DEST_FULL /* Created, fully copied */
386 static FileProgressStatus
387 real_warn_same_file (enum OperationMode mode, const char *fmt,
388 const char *a, const char *b)
390 char *msg;
391 int result = 0;
392 const char *head_msg;
394 head_msg = mode == Foreground ? MSG_ERROR :
395 _(" Background process error ");
397 msg = g_strdup_printf (fmt, a, b);
398 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
399 g_free(msg);
400 do_refresh ();
401 if ( result ) { /* 1 == Abort */
402 return FILE_ABORT;
403 } else {
404 return FILE_SKIP;
408 #ifdef WITH_BACKGROUND
409 static FileProgressStatus
410 warn_same_file (const char *fmt, const char *a, const char *b)
412 union {
413 void *p;
414 FileProgressStatus (*f) (enum OperationMode, const char *fmt,
415 const char *a, const char *b);
416 } pntr;
417 pntr.f = real_warn_same_file;
419 if (we_are_background)
420 return parent_call (pntr.p, NULL, 3, strlen (fmt),
421 fmt, strlen(a), a, strlen(b), b);
422 else
423 return real_warn_same_file (Foreground, fmt, a, b);
425 #else
426 static FileProgressStatus
427 warn_same_file (const char *fmt, const char *a, const char *b)
429 return real_warn_same_file (Foreground, fmt, a, b);
431 #endif
433 FileProgressStatus
434 copy_file_file (FileOpContext *ctx, const char *src_path, const char *dst_path,
435 int ask_overwrite, off_t *progress_count,
436 double *progress_bytes, int is_toplevel_file)
438 uid_t src_uid = (uid_t) - 1;
439 gid_t src_gid = (gid_t) - 1;
441 char *buf = NULL;
442 int buf_size = BUF_8K;
443 int src_desc, dest_desc = -1;
444 int n_read, n_written;
445 mode_t src_mode = 0; /* The mode of the source file */
446 struct stat sb, sb2;
447 struct utimbuf utb;
448 int dst_exists = 0, appending = 0;
449 off_t n_read_total = 0, file_size = -1;
450 FileProgressStatus return_status, temp_status;
451 struct timeval tv_transfer_start;
452 int dst_status = DEST_NONE; /* 1 if the file is not fully copied */
453 int open_flags;
455 /* FIXME: We should not be using global variables! */
456 ctx->do_reget = 0;
457 return_status = FILE_RETRY;
459 if (file_progress_show_source (ctx, src_path) == FILE_ABORT ||
460 file_progress_show_target (ctx, dst_path) == FILE_ABORT)
461 return FILE_ABORT;
463 mc_refresh ();
465 while (mc_stat (dst_path, &sb2) == 0) {
466 if (S_ISDIR (sb2.st_mode)) {
467 return_status =
468 file_error (_(" Cannot overwrite directory \"%s\" \n %s "),
469 dst_path);
470 if (return_status == FILE_RETRY)
471 continue;
472 return return_status;
474 dst_exists = 1;
475 break;
478 while ((*ctx->stat_func) (src_path, &sb)) {
479 return_status =
480 file_error (_(" Cannot stat source file \"%s\" \n %s "),
481 src_path);
482 if (return_status != FILE_RETRY)
483 return return_status;
486 if (dst_exists) {
487 /* Destination already exists */
488 if (sb.st_dev == sb2.st_dev && sb.st_ino == sb2.st_ino)
489 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "),
490 src_path, dst_path);
491 /* Should we replace destination? */
492 if (ask_overwrite) {
493 ctx->do_reget = 0;
494 return_status = query_replace (ctx, dst_path, &sb, &sb2);
495 if (return_status != FILE_CONT)
496 return return_status;
500 if (!ctx->do_append) {
501 /* Check the hardlinks */
502 if (!ctx->follow_links && sb.st_nlink > 1 &&
503 check_hardlinks (src_path, dst_path, &sb) == 1) {
504 /* We have made a hardlink - no more processing is necessary */
505 return FILE_CONT;
508 if (S_ISLNK (sb.st_mode))
509 return make_symlink (ctx, src_path, dst_path);
511 if (S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode) ||
512 S_ISFIFO (sb.st_mode) || S_ISNAM (sb.st_mode) ||
513 S_ISSOCK (sb.st_mode)) {
514 while (mc_mknod (dst_path, sb.st_mode & ctx->umask_kill,
515 sb.st_rdev) < 0) {
516 return_status = file_error (
517 _(" Cannot create special file \"%s\" \n %s "), dst_path);
518 if (return_status == FILE_RETRY)
519 continue;
520 return return_status;
522 /* Success */
524 while (ctx->preserve_uidgid
525 && mc_chown (dst_path, sb.st_uid, sb.st_gid)) {
526 temp_status = file_error (
527 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
528 if (temp_status == FILE_RETRY)
529 continue;
530 return temp_status;
532 while (ctx->preserve &&
533 mc_chmod (dst_path, sb.st_mode & ctx->umask_kill)) {
534 temp_status = file_error (
535 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
536 if (temp_status == FILE_RETRY)
537 continue;
538 return temp_status;
540 return FILE_CONT;
544 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
546 while ((src_desc = mc_open (src_path, O_RDONLY | O_LINEAR)) < 0) {
547 return_status = file_error (
548 _(" Cannot open source file \"%s\" \n %s "), src_path);
549 if (return_status == FILE_RETRY)
550 continue;
551 ctx->do_append = 0;
552 return return_status;
555 if (ctx->do_reget) {
556 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget) {
557 message (D_ERROR, _("Warning"),
558 _(" Reget failed, about to overwrite file "));
559 ctx->do_reget = ctx->do_append = 0;
563 while (mc_fstat (src_desc, &sb)) {
564 return_status = file_error (
565 _(" Cannot fstat source file \"%s\" \n %s "), src_path);
566 if (return_status == FILE_RETRY)
567 continue;
568 ctx->do_append = 0;
569 goto ret;
571 src_mode = sb.st_mode;
572 src_uid = sb.st_uid;
573 src_gid = sb.st_gid;
574 utb.actime = sb.st_atime;
575 utb.modtime = sb.st_mtime;
576 file_size = sb.st_size;
578 open_flags = O_WRONLY;
579 if (dst_exists != 0) {
580 if (ctx->do_append != 0)
581 open_flags |= O_APPEND;
582 else
583 open_flags |= O_CREAT | O_TRUNC;
584 } else {
585 open_flags |= O_CREAT | O_EXCL;
587 while ((dest_desc = mc_open (dst_path, open_flags, src_mode)) < 0) {
588 if (errno == EEXIST) {
589 goto ret;
591 return_status = file_error (
592 _(" Cannot create target file \"%s\" \n %s "), dst_path);
593 if (return_status == FILE_RETRY)
594 continue;
595 ctx->do_append = 0;
596 goto ret;
598 dst_status = DEST_SHORT; /* file opened, but not fully copied */
600 appending = ctx->do_append;
601 ctx->do_append = 0;
603 /* Find out the optimal buffer size. */
604 while (mc_fstat (dest_desc, &sb)) {
605 return_status = file_error (
606 _(" Cannot fstat target file \"%s\" \n %s "), dst_path);
607 if (return_status == FILE_RETRY)
608 continue;
609 goto ret;
611 buf = g_malloc (buf_size);
613 ctx->eta_secs = 0.0;
614 ctx->bps = 0;
616 return_status = file_progress_show (ctx, 0, file_size);
618 mc_refresh ();
620 if (return_status != FILE_CONT)
621 goto ret;
624 struct timeval tv_current, tv_last_update, tv_last_input;
625 int secs, update_secs;
626 long dt;
627 const char *stalled_msg;
629 tv_last_update = tv_transfer_start;
631 for (;;) {
632 /* src_read */
633 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0))
634 n_read = -1;
635 else
636 while ((n_read = mc_read (src_desc, buf, buf_size)) < 0) {
637 return_status = file_error (
638 _(" Cannot read source file \"%s\" \n %s "), src_path);
639 if (return_status == FILE_RETRY)
640 continue;
641 goto ret;
643 if (n_read == 0)
644 break;
646 gettimeofday (&tv_current, NULL);
648 if (n_read > 0) {
649 char *t = buf;
650 n_read_total += n_read;
652 /* Windows NT ftp servers report that files have no
653 * permissions: -------, so if we happen to have actually
654 * read something, we should fix the permissions.
656 if (!(src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)))
657 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
658 gettimeofday (&tv_last_input, NULL);
660 /* dst_write */
661 while ((n_written =
662 mc_write (dest_desc, t, n_read)) < n_read) {
663 if (n_written > 0) {
664 n_read -= n_written;
665 t += n_written;
666 continue;
668 return_status =
669 file_error (_(" Cannot write target file \"%s\" \n %s "),
670 dst_path);
671 if (return_status != FILE_RETRY)
672 goto ret;
676 /* 1. Update rotating dash after some time (hardcoded to 2 seconds) */
677 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
678 if (secs > 2) {
679 rotate_dash ();
680 tv_last_update = tv_current;
683 /* 2. Check for a stalled condition */
684 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
685 stalled_msg = "";
686 if (update_secs > 4) {
687 stalled_msg = _("(stalled)");
690 /* 3. Compute ETA */
691 if (secs > 2) {
692 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
694 if (n_read_total) {
695 ctx->eta_secs =
696 ((dt / (double) n_read_total) * file_size) - dt;
697 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
698 } else
699 ctx->eta_secs = 0.0;
702 /* 4. Compute BPS rate */
703 if (secs > 2) {
704 ctx->bps_time =
705 (tv_current.tv_sec - tv_transfer_start.tv_sec);
706 if (ctx->bps_time < 1)
707 ctx->bps_time = 1;
708 ctx->bps = n_read_total / ctx->bps_time;
711 file_progress_set_stalled_label (ctx, stalled_msg);
712 return_status = file_progress_show_bytes (ctx, *progress_bytes +
713 n_read_total + ctx->do_reget, ctx->progress_bytes);
714 if (return_status == FILE_CONT) {
715 return_status =
716 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size);
718 mc_refresh ();
719 if (return_status != FILE_CONT)
720 goto ret;
724 dst_status = DEST_FULL; /* copy successful, don't remove target file */
726 ret:
727 g_free (buf);
729 while (src_desc != -1 && mc_close (src_desc) < 0) {
730 temp_status = file_error (
731 _(" Cannot close source file \"%s\" \n %s "), src_path);
732 if (temp_status == FILE_RETRY)
733 continue;
734 if (temp_status == FILE_ABORT)
735 return_status = temp_status;
736 break;
739 while (dest_desc != -1 && mc_close (dest_desc) < 0) {
740 temp_status = file_error (
741 _(" Cannot close target file \"%s\" \n %s "), dst_path);
742 if (temp_status == FILE_RETRY)
743 continue;
744 return_status = temp_status;
745 break;
748 if (dst_status == DEST_SHORT) {
749 /* Remove short file */
750 int result;
751 result = query_dialog (Q_("DialogTitle|Copy"),
752 _("Incomplete file was retrieved. Keep it?"),
753 D_ERROR, 2, _("&Delete"), _("&Keep"));
754 if (!result)
755 mc_unlink (dst_path);
756 } else if (dst_status == DEST_FULL) {
757 /* Copy has succeeded */
758 if (!appending && ctx->preserve_uidgid) {
759 while (mc_chown (dst_path, src_uid, src_gid)) {
760 temp_status = file_error (
761 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
762 if (temp_status == FILE_RETRY)
763 continue;
764 return_status = temp_status;
765 break;
769 if (!appending) {
770 if (ctx->preserve){
771 while (mc_chmod (dst_path, (src_mode & ctx->umask_kill))) {
772 temp_status = file_error (
773 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
774 if (temp_status != FILE_RETRY) {
775 return_status = temp_status;
776 break;
779 } else {
780 src_mode = umask(-1);
781 umask(src_mode);
782 src_mode = 0100666 & ~src_mode;
783 mc_chmod (dst_path, (src_mode & ctx->umask_kill));
785 mc_utime (dst_path, &utb);
789 if (return_status == FILE_CONT)
790 return_status =
791 progress_update_one (ctx, progress_count, progress_bytes,
792 file_size, is_toplevel_file);
794 return return_status;
798 * I think these copy_*_* functions should have a return type.
799 * anyway, this function *must* have two directories as arguments.
801 /* FIXME: This function needs to check the return values of the
802 function calls */
803 FileProgressStatus
804 copy_dir_dir (FileOpContext *ctx, const char *s, const char *_d, int toplevel,
805 int move_over, int delete, struct link *parent_dirs,
806 off_t *progress_count, double *progress_bytes)
808 struct dirent *next;
809 struct stat buf, cbuf;
810 DIR *reading;
811 char *dest_dir = NULL;
812 FileProgressStatus return_status = FILE_CONT;
813 struct utimbuf utb;
814 struct link *lp;
815 char *d;
817 d = strutils_shell_unescape (_d);
819 /* First get the mode of the source dir */
820 retry_src_stat:
821 if ((*ctx->stat_func) (s, &cbuf)) {
822 return_status =
823 file_error (_(" Cannot stat source directory \"%s\" \n %s "), s);
824 if (return_status == FILE_RETRY)
825 goto retry_src_stat;
826 goto ret_fast;
829 if (is_in_linklist (dest_dirs, s, &cbuf)) {
830 /* Don't copy a directory we created before (we don't want to copy
831 infinitely if a directory is copied into itself) */
832 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
833 return_status = FILE_CONT;
834 goto ret_fast;
837 /* Hmm, hardlink to directory??? - Norbert */
838 /* FIXME: In this step we should do something
839 in case the destination already exist */
840 /* Check the hardlinks */
841 if (ctx->preserve && cbuf.st_nlink > 1
842 && check_hardlinks (s, d, &cbuf) == 1) {
843 /* We have made a hardlink - no more processing is necessary */
844 goto ret_fast;
847 if (!S_ISDIR (cbuf.st_mode)) {
848 return_status =
849 file_error (_(" Source \"%s\" is not a directory \n %s "), s);
850 if (return_status == FILE_RETRY)
851 goto retry_src_stat;
852 goto ret_fast;
855 if (is_in_linklist (parent_dirs, s, &cbuf)) {
856 /* we found a cyclic symbolic link */
857 message (D_ERROR, MSG_ERROR,
858 _(" Cannot copy cyclic symbolic link \n `%s' "), s);
859 return_status = FILE_SKIP;
860 goto ret_fast;
863 lp = g_new (struct link, 1);
864 lp->vfs = vfs_get_class (s);
865 lp->ino = cbuf.st_ino;
866 lp->dev = cbuf.st_dev;
867 lp->next = parent_dirs;
868 parent_dirs = lp;
870 retry_dst_stat:
871 /* Now, check if the dest dir exists, if not, create it. */
872 if (mc_stat (d, &buf)) {
873 /* Here the dir doesn't exist : make it ! */
874 if (move_over) {
875 if (mc_rename (s, d) == 0) {
876 return_status = FILE_CONT;
877 goto ret;
880 dest_dir = d;
881 d = NULL;
882 } else {
884 * If the destination directory exists, we want to copy the whole
885 * directory, but we only want this to happen once.
887 * Escape sequences added to the * to compiler warnings.
888 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
889 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
891 if (!S_ISDIR (buf.st_mode)) {
892 return_status = file_error(
893 _(" Destination \"%s\" must be a directory \n %s "), d);
894 if (return_status == FILE_RETRY)
895 goto retry_dst_stat;
896 goto ret;
898 /* Dive into subdir if exists */
899 if (toplevel && ctx->dive_into_subdirs) {
900 dest_dir = concat_dir_and_file (d, x_basename (s));
901 } else {
902 dest_dir = d;
903 d = NULL;
904 goto dont_mkdir;
907 while (my_mkdir (dest_dir, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU)) {
908 return_status = file_error (
909 _(" Cannot create target directory \"%s\" \n %s "), dest_dir);
910 if (return_status != FILE_RETRY)
911 goto ret;
914 lp = g_new (struct link, 1);
915 mc_stat (dest_dir, &buf);
916 lp->vfs = vfs_get_class (dest_dir);
917 lp->ino = buf.st_ino;
918 lp->dev = buf.st_dev;
919 lp->next = dest_dirs;
920 dest_dirs = lp;
922 if (ctx->preserve_uidgid) {
923 while (mc_chown (dest_dir, cbuf.st_uid, cbuf.st_gid)) {
924 return_status = file_error (
925 _(" Cannot chown target directory \"%s\" \n %s "), dest_dir);
926 if (return_status != FILE_RETRY)
927 goto ret;
931 dont_mkdir:
932 /* open the source dir for reading */
933 reading = mc_opendir (s);
934 if (reading == NULL)
935 goto ret;
937 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) {
938 char *path;
940 * Now, we don't want '.' and '..' to be created / copied at any time
942 if (!strcmp (next->d_name, "."))
943 continue;
944 if (!strcmp (next->d_name, ".."))
945 continue;
947 /* get the filename and add it to the src directory */
948 path = concat_dir_and_file (s, next->d_name);
950 (*ctx->stat_func) (path, &buf);
951 if (S_ISDIR (buf.st_mode)) {
952 char *mdpath;
954 mdpath = concat_dir_and_file (dest_dir, next->d_name);
956 * From here, we just intend to recursively copy subdirs, not
957 * the double functionality of copying different when the target
958 * dir already exists. So, we give the recursive call the flag 0
959 * meaning no toplevel.
961 return_status = copy_dir_dir (ctx, path, mdpath, 0, 0, delete,
962 parent_dirs, progress_count, progress_bytes);
963 g_free (mdpath);
964 } else {
965 char *dest_file;
967 dest_file = concat_dir_and_file (dest_dir, x_basename (path));
968 return_status = copy_file_file (ctx, path, dest_file, 1,
969 progress_count, progress_bytes, 0);
970 g_free (dest_file);
972 if (delete && return_status == FILE_CONT) {
973 if (ctx->erase_at_end) {
974 static struct link *tail;
975 size_t len = strlen (path);
976 lp = g_malloc (sizeof (struct link) + len);
977 strncpy (lp->name, path, len + 1);
978 lp->st_mode = buf.st_mode;
979 lp->next = NULL;
980 if (erase_list != NULL) {
981 tail->next = lp;
982 tail = lp;
983 } else
984 erase_list = tail = lp;
985 } else {
986 if (S_ISDIR (buf.st_mode)) {
987 return_status = erase_dir_iff_empty (ctx, path);
988 } else
989 return_status = erase_file (ctx, path, 0, 0, 0);
992 g_free (path);
994 mc_closedir (reading);
996 if (ctx->preserve) {
997 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
998 utb.actime = cbuf.st_atime;
999 utb.modtime = cbuf.st_mtime;
1000 mc_utime (dest_dir, &utb);
1001 } else {
1002 cbuf.st_mode = umask(-1);
1003 umask(cbuf.st_mode);
1004 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
1005 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
1008 ret:
1009 g_free (dest_dir);
1010 g_free (parent_dirs);
1011 ret_fast:
1012 g_free (d);
1013 return return_status;
1016 /* }}} */
1018 /* {{{ Move routines */
1020 static FileProgressStatus
1021 move_file_file (FileOpContext *ctx, const char *s, const char *d,
1022 off_t *progress_count, double *progress_bytes)
1024 struct stat src_stats, dst_stats;
1025 FileProgressStatus return_status = FILE_CONT;
1026 gboolean copy_done = FALSE;
1028 if (file_progress_show_source (ctx, s) == FILE_ABORT
1029 || file_progress_show_target (ctx, d) == FILE_ABORT)
1030 return FILE_ABORT;
1032 mc_refresh ();
1034 while (mc_lstat (s, &src_stats) != 0) {
1035 /* Source doesn't exist */
1036 return_status =
1037 file_error (_(" Cannot stat file \"%s\" \n %s "), s);
1038 if (return_status != FILE_RETRY)
1039 return return_status;
1042 if (mc_lstat (d, &dst_stats) == 0) {
1043 if (src_stats.st_dev == dst_stats.st_dev
1044 && src_stats.st_ino == dst_stats.st_ino)
1045 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "), s, d);
1047 if (S_ISDIR (dst_stats.st_mode)) {
1048 message (D_ERROR, MSG_ERROR,
1049 _(" Cannot overwrite directory `%s' "), d);
1050 do_refresh ();
1051 return FILE_SKIP;
1054 if (confirm_overwrite) {
1055 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
1056 if (return_status != FILE_CONT)
1057 return return_status;
1059 /* Ok to overwrite */
1062 if (!ctx->do_append) {
1063 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks) {
1064 if ((return_status = make_symlink (ctx, s, d)) == FILE_CONT) {
1065 goto retry_src_remove;
1066 } else
1067 return return_status;
1070 if (mc_rename (s, d) == 0) {
1071 return progress_update_one (ctx, progress_count,
1072 progress_bytes,
1073 src_stats.st_size, 1);
1076 #if 0
1077 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1078 one nfs to the same, but on the server it is on two different
1079 filesystems. Then nfs returns EIO instead of EXDEV.
1080 Hope it will not hurt if we always in case of error try to copy/delete. */
1081 else
1082 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1084 if (errno != EXDEV) {
1085 return_status =
1086 files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s,
1088 if (return_status == FILE_RETRY)
1089 goto retry_rename;
1090 return return_status;
1092 #endif
1094 /* Failed because filesystem boundary -> copy the file instead */
1095 return_status =
1096 copy_file_file (ctx, s, d, 0, progress_count, progress_bytes, 1);
1097 if (return_status != FILE_CONT)
1098 return return_status;
1100 copy_done = TRUE;
1102 if ((return_status =
1103 file_progress_show_source (ctx, NULL)) != FILE_CONT
1104 || (return_status = file_progress_show (ctx, 0, 0)) != FILE_CONT)
1105 return return_status;
1107 mc_refresh ();
1109 retry_src_remove:
1110 if (mc_unlink (s)) {
1111 return_status =
1112 file_error (_(" Cannot remove file \"%s\" \n %s "), s);
1113 if (return_status == FILE_RETRY)
1114 goto retry_src_remove;
1115 return return_status;
1118 if (!copy_done) {
1119 return_status = progress_update_one (ctx,
1120 progress_count,
1121 progress_bytes,
1122 src_stats.st_size, 1);
1125 return return_status;
1128 FileProgressStatus
1129 move_dir_dir (FileOpContext *ctx, const char *s, const char *d,
1130 off_t *progress_count, double *progress_bytes)
1132 struct stat sbuf, dbuf, destbuf;
1133 struct link *lp;
1134 char *destdir;
1135 FileProgressStatus return_status;
1136 gboolean move_over = FALSE;
1137 gboolean dstat_ok;
1139 if (file_progress_show_source (ctx, s) == FILE_ABORT ||
1140 file_progress_show_target (ctx, d) == FILE_ABORT)
1141 return FILE_ABORT;
1143 mc_refresh ();
1145 mc_stat (s, &sbuf);
1146 dstat_ok = (mc_stat (d, &dbuf) == 0);
1148 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
1149 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same directory "), s, d);
1151 if (!dstat_ok)
1152 destdir = g_strdup (d); /* destination doesn't exist */
1153 else if (!ctx->dive_into_subdirs) {
1154 destdir = g_strdup (d);
1155 move_over = TRUE;
1156 } else
1157 destdir = concat_dir_and_file (d, x_basename (s));
1159 /* Check if the user inputted an existing dir */
1160 retry_dst_stat:
1161 if (!mc_stat (destdir, &destbuf)) {
1162 if (move_over) {
1163 return_status = copy_dir_dir (ctx, s, destdir, 0, 1, 1, 0,
1164 progress_count, progress_bytes);
1166 if (return_status != FILE_CONT)
1167 goto ret;
1168 goto oktoret;
1169 } else {
1170 if (S_ISDIR (destbuf.st_mode))
1171 return_status =
1172 file_error (_
1173 (" Cannot overwrite directory \"%s\" %s "),
1174 destdir);
1175 else
1176 return_status =
1177 file_error (_(" Cannot overwrite file \"%s\" %s "),
1178 destdir);
1179 if (return_status == FILE_RETRY)
1180 goto retry_dst_stat;
1182 g_free (destdir);
1183 return return_status;
1186 retry_rename:
1187 if (mc_rename (s, destdir) == 0) {
1188 return_status = FILE_CONT;
1189 goto ret;
1192 if (errno != EXDEV) {
1193 return_status =
1194 files_error (_
1195 (" Cannot move directory \"%s\" to \"%s\" \n %s "),
1196 s, d);
1197 if (return_status == FILE_RETRY)
1198 goto retry_rename;
1199 goto ret;
1201 /* Failed because of filesystem boundary -> copy dir instead */
1202 return_status =
1203 copy_dir_dir (ctx, s, destdir, 0, 0, 1, 0, progress_count,
1204 progress_bytes);
1206 if (return_status != FILE_CONT)
1207 goto ret;
1208 oktoret:
1209 if ((return_status =
1210 file_progress_show_source (ctx, NULL)) != FILE_CONT
1211 || (return_status = file_progress_show (ctx, 0, 0)) != FILE_CONT)
1212 goto ret;
1214 mc_refresh ();
1215 if (ctx->erase_at_end) {
1216 for (; erase_list && return_status != FILE_ABORT;) {
1217 if (S_ISDIR (erase_list->st_mode)) {
1218 return_status =
1219 erase_dir_iff_empty (ctx, erase_list->name);
1220 } else
1221 return_status =
1222 erase_file (ctx, erase_list->name, 0, 0, 0);
1223 lp = erase_list;
1224 erase_list = erase_list->next;
1225 g_free (lp);
1228 erase_dir_iff_empty (ctx, s);
1230 ret:
1231 g_free (destdir);
1232 while (erase_list) {
1233 lp = erase_list;
1234 erase_list = erase_list->next;
1235 g_free (lp);
1237 return return_status;
1240 /* }}} */
1242 /* {{{ Erase routines */
1243 /* Don't update progress status if progress_count==NULL */
1244 static FileProgressStatus
1245 erase_file (FileOpContext *ctx, const char *s, off_t *progress_count,
1246 double *progress_bytes, int is_toplevel_file)
1248 int return_status;
1249 struct stat buf;
1251 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1252 return FILE_ABORT;
1253 mc_refresh ();
1255 if (progress_count && mc_lstat (s, &buf)) {
1256 /* ignore, most likely the mc_unlink fails, too */
1257 buf.st_size = 0;
1260 while (mc_unlink (s)) {
1261 return_status =
1262 file_error (_(" Cannot delete file \"%s\" \n %s "), s);
1263 if (return_status != FILE_RETRY)
1264 return return_status;
1267 if (progress_count)
1268 return progress_update_one (ctx, progress_count, progress_bytes,
1269 buf.st_size, is_toplevel_file);
1270 else
1271 return FILE_CONT;
1274 static FileProgressStatus
1275 recursive_erase (FileOpContext *ctx, const char *s, off_t *progress_count,
1276 double *progress_bytes)
1278 struct dirent *next;
1279 struct stat buf;
1280 DIR *reading;
1281 char *path;
1282 FileProgressStatus return_status = FILE_CONT;
1284 if (!strcmp (s, ".."))
1285 return FILE_RETRY;
1287 reading = mc_opendir (s);
1289 if (!reading)
1290 return FILE_RETRY;
1292 while ((next = mc_readdir (reading)) && return_status == FILE_CONT) {
1293 if (!strcmp (next->d_name, "."))
1294 continue;
1295 if (!strcmp (next->d_name, ".."))
1296 continue;
1297 path = concat_dir_and_file (s, next->d_name);
1298 if (mc_lstat (path, &buf)) {
1299 g_free (path);
1300 mc_closedir (reading);
1301 return FILE_RETRY;
1303 if (S_ISDIR (buf.st_mode))
1304 return_status =
1305 (recursive_erase
1306 (ctx, path, progress_count, progress_bytes)
1307 != FILE_CONT) ? FILE_RETRY : FILE_CONT;
1308 else
1309 return_status =
1310 erase_file (ctx, path, progress_count, progress_bytes, 0);
1311 g_free (path);
1313 mc_closedir (reading);
1314 if (return_status != FILE_CONT)
1315 return return_status;
1316 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1317 return FILE_ABORT;
1318 mc_refresh ();
1320 while (my_rmdir (s)) {
1321 return_status =
1322 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1323 if (return_status != FILE_RETRY)
1324 return return_status;
1327 return FILE_CONT;
1330 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1331 in the directory path points to, 0 else. */
1332 static int
1333 check_dir_is_empty (const char *path)
1335 DIR *dir;
1336 struct dirent *d;
1337 int i;
1339 dir = mc_opendir (path);
1340 if (!dir)
1341 return -1;
1343 for (i = 1, d = mc_readdir (dir); d; d = mc_readdir (dir)) {
1344 if (d->d_name[0] == '.' && (d->d_name[1] == '\0' ||
1345 (d->d_name[1] == '.'
1346 && d->d_name[2] == '\0')))
1347 continue; /* "." or ".." */
1348 i = 0;
1349 break;
1352 mc_closedir (dir);
1353 return i;
1356 FileProgressStatus
1357 erase_dir (FileOpContext *ctx, const char *s, off_t *progress_count,
1358 double *progress_bytes)
1360 FileProgressStatus error;
1362 if (strcmp (s, "..") == 0)
1363 return FILE_SKIP;
1365 if (strcmp (s, ".") == 0)
1366 return FILE_SKIP;
1368 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1369 return FILE_ABORT;
1370 mc_refresh ();
1372 /* The old way to detect a non empty directory was:
1373 error = my_rmdir (s);
1374 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1375 For the linux user space nfs server (nfs-server-2.2beta29-2)
1376 we would have to check also for EIO. I hope the new way is
1377 fool proof. (Norbert)
1379 error = check_dir_is_empty (s);
1380 if (error == 0) { /* not empty */
1381 error = query_recursive (ctx, s);
1382 if (error == FILE_CONT)
1383 return recursive_erase (ctx, s, progress_count,
1384 progress_bytes);
1385 else
1386 return error;
1389 while (my_rmdir (s) == -1) {
1390 error =
1391 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1392 if (error != FILE_RETRY)
1393 return error;
1396 return FILE_CONT;
1399 static FileProgressStatus
1400 erase_dir_iff_empty (FileOpContext *ctx, const char *s)
1402 FileProgressStatus error;
1404 if (strcmp (s, "..") == 0)
1405 return FILE_SKIP;
1407 if (strcmp (s, ".") == 0)
1408 return FILE_SKIP;
1410 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1411 return FILE_ABORT;
1412 mc_refresh ();
1414 if (1 != check_dir_is_empty (s)) /* not empty or error */
1415 return FILE_CONT;
1417 while (my_rmdir (s)) {
1418 error =
1419 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1420 if (error != FILE_RETRY)
1421 return error;
1424 return FILE_CONT;
1427 /* }}} */
1429 /* {{{ Panel operate routines */
1432 * Return currently selected entry name or the name of the first marked
1433 * entry if there is one.
1435 static char *
1436 panel_get_file (WPanel *panel, struct stat *stat_buf)
1438 int i;
1440 if (get_current_type () == view_tree) {
1441 WTree *tree = (WTree *) get_panel_widget (get_current_index ());
1442 char *tree_name = tree_selected_name (tree);
1444 mc_stat (tree_name, stat_buf);
1445 return tree_name;
1448 if (panel->marked) {
1449 for (i = 0; i < panel->count; i++)
1450 if (panel->dir.list[i].f.marked) {
1451 *stat_buf = panel->dir.list[i].st;
1452 return panel->dir.list[i].fname;
1454 } else {
1455 *stat_buf = panel->dir.list[panel->selected].st;
1456 return panel->dir.list[panel->selected].fname;
1458 g_assert_not_reached ();
1459 return NULL;
1463 ComputeDirSizeUI *
1464 compute_dir_size_create_ui (void)
1466 ComputeDirSizeUI *ui;
1468 const char *b_name = N_("&Abort");
1470 #ifdef ENABLE_NLS
1471 b_name = _(b_name);
1472 #endif
1474 ui = g_new (ComputeDirSizeUI, 1);
1476 ui->dlg = create_dlg (0, 0, 8, COLS/2, dialog_colors, NULL,
1477 NULL, _("Directory scanning"), DLG_CENTER);
1478 ui->dirname = label_new (3, 3, "");
1479 add_widget (ui->dlg, ui->dirname);
1481 add_widget (ui->dlg,
1482 button_new (5, (ui->dlg->cols - strlen (b_name))/2,
1483 FILE_ABORT, NORMAL_BUTTON, b_name, NULL));
1485 /* We will manage the dialog without any help,
1486 that's why we have to call init_dlg */
1487 init_dlg (ui->dlg);
1489 return ui;
1492 void
1493 compute_dir_size_destroy_ui (ComputeDirSizeUI *ui)
1495 if (ui != NULL) {
1496 /* schedule to update passive panel */
1497 other_panel->dirty = 1;
1499 /* close and destroy dialog */
1500 dlg_run_done (ui->dlg);
1501 destroy_dlg (ui->dlg);
1502 g_free (ui);
1506 FileProgressStatus
1507 compute_dir_size_update_ui (const void *ui, const char *dirname)
1509 const ComputeDirSizeUI *this = (const ComputeDirSizeUI *) ui;
1510 int c;
1511 Gpm_Event event;
1513 if (ui == NULL)
1514 return FILE_CONT;
1516 label_set_text (this->dirname, name_trunc (dirname, this->dlg->cols - 6));
1518 event.x = -1; /* Don't show the GPM cursor */
1519 c = tty_get_event (&event, FALSE, FALSE);
1520 if (c == EV_NONE)
1521 return FILE_CONT;
1523 /* Reinitialize to avoid old values after events other than
1524 selecting a button */
1525 this->dlg->ret_value = FILE_CONT;
1527 dlg_process_event (this->dlg, c, &event);
1529 switch (this->dlg->ret_value) {
1530 case B_CANCEL:
1531 case FILE_ABORT:
1532 return FILE_ABORT;
1533 default:
1534 return FILE_CONT;
1539 * compute_dir_size:
1541 * Computes the number of bytes used by the files in a directory
1543 FileProgressStatus
1544 compute_dir_size (const char *dirname, const void *ui,
1545 compute_dir_size_callback cback,
1546 off_t *ret_marked, double *ret_total)
1548 DIR *dir;
1549 struct dirent *dirent;
1550 FileProgressStatus ret = FILE_CONT;
1552 dir = mc_opendir (dirname);
1554 if (dir == NULL)
1555 return ret;
1557 while ((dirent = mc_readdir (dir)) != NULL) {
1558 char *fullname;
1559 int res;
1560 struct stat s;
1562 ret = (cback != NULL) ? cback (ui, dirname) : FILE_CONT;
1564 if (ret != FILE_CONT)
1565 break;
1567 if (strcmp (dirent->d_name, ".") == 0)
1568 continue;
1569 if (strcmp (dirent->d_name, "..") == 0)
1570 continue;
1572 fullname = concat_dir_and_file (dirname, dirent->d_name);
1573 res = mc_lstat (fullname, &s);
1575 if (res != 0) {
1576 g_free (fullname);
1577 continue;
1580 if (S_ISDIR (s.st_mode)) {
1581 off_t subdir_count = 0;
1582 double subdir_bytes = 0;
1584 ret = compute_dir_size (fullname, ui, cback, &subdir_count, &subdir_bytes);
1586 if (ret != FILE_CONT) {
1587 g_free (fullname);
1588 break;
1591 *ret_marked += subdir_count;
1592 *ret_total += subdir_bytes;
1593 } else {
1594 (*ret_marked)++;
1595 *ret_total += s.st_size;
1598 g_free (fullname);
1601 mc_closedir (dir);
1603 return ret;
1607 * panel_compute_totals:
1609 * compute the number of files and the number of bytes
1610 * used up by the whole selection, recursing directories
1611 * as required. In addition, it checks to see if it will
1612 * overwrite any files by doing the copy.
1614 static FileProgressStatus
1615 panel_compute_totals (WPanel *panel, const void *ui,
1616 compute_dir_size_callback cback,
1617 off_t *ret_marked, double *ret_total)
1619 int i;
1621 *ret_marked = 0;
1622 *ret_total = 0.0;
1624 for (i = 0; i < panel->count; i++) {
1625 struct stat *s;
1627 if (!panel->dir.list[i].f.marked)
1628 continue;
1630 s = &panel->dir.list[i].st;
1632 if (S_ISDIR (s->st_mode)) {
1633 char *dir_name;
1634 off_t subdir_count = 0;
1635 double subdir_bytes = 0;
1636 FileProgressStatus status;
1638 dir_name =
1639 concat_dir_and_file (panel->cwd, panel->dir.list[i].fname);
1641 status = compute_dir_size (dir_name, ui, cback,
1642 &subdir_count, &subdir_bytes);
1643 g_free (dir_name);
1645 if (status != FILE_CONT)
1646 return FILE_ABORT;
1648 *ret_marked += subdir_count;
1649 *ret_total += subdir_bytes;
1650 } else {
1651 (*ret_marked)++;
1652 *ret_total += s->st_size;
1656 return FILE_CONT;
1660 * This array introduced to avoid translation problems. The former (op_names)
1661 * is assumed to be nouns, suitable in dialog box titles; this one should
1662 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
1663 * (I don't use spaces around the words, because someday they could be
1664 * dropped, when widgets get smarter)
1667 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
1668 static const char *op_names1[] = {
1669 N_("FileOperation|Copy"),
1670 N_("FileOperation|Move"),
1671 N_("FileOperation|Delete")
1675 * These are formats for building a prompt. Parts encoded as follows:
1676 * %o - operation from op_names1
1677 * %f - file/files or files/directories, as appropriate
1678 * %m - "with source mask" or question mark for delete
1679 * %s - source name (truncated)
1680 * %d - number of marked files
1681 * %e - "to:" or question mark for delete
1683 * xgettext:no-c-format */
1684 static const char *one_format = N_("%o %f \"%s\"%m");
1685 /* xgettext:no-c-format */
1686 static const char *many_format = N_("%o %d %f%m");
1688 static const char *prompt_parts[] = {
1689 N_("file"),
1690 N_("files"),
1691 N_("directory"),
1692 N_("directories"),
1693 N_("files/directories"),
1694 N_(" with source mask:"),
1695 N_(" to:")
1698 static const char *question_format = N_("%s?");
1701 * Generate user prompt for panel operation.
1702 * single_source is the name if the source entry or NULL for multiple
1703 * entries.
1704 * src_stat is only used when single_source is not NULL.
1706 static char *
1707 panel_operate_generate_prompt (const WPanel *panel, const int operation,
1708 gboolean single_source,
1709 const struct stat *src_stat)
1711 const char *sp, *cp;
1712 char format_string[BUF_MEDIUM];
1713 char *dp = format_string;
1714 gboolean build_question = FALSE;
1716 #ifdef ENABLE_NLS
1717 static gboolean i18n_flag = FALSE;
1718 if (!i18n_flag) {
1719 size_t i;
1721 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1722 op_names1[i] = Q_(op_names1[i]);
1724 for (i = sizeof (prompt_parts) / sizeof (prompt_parts[0]); i--;)
1725 prompt_parts[i] = _(prompt_parts[i]);
1727 one_format = _(one_format);
1728 many_format = _(many_format);
1729 question_format = _(question_format);
1730 i18n_flag = TRUE;
1732 #endif /* ENABLE_NLS */
1734 sp = single_source ? one_format : many_format;
1736 while (*sp != '\0') {
1737 switch (*sp) {
1738 case '%':
1739 cp = NULL;
1740 switch (sp[1]) {
1741 case 'o':
1742 cp = op_names1[operation];
1743 break;
1744 case 'm':
1745 if (operation == OP_DELETE) {
1746 cp = "";
1747 build_question = TRUE;
1748 } else
1749 cp = prompt_parts[5];
1750 break;
1751 case 'e':
1752 if (operation == OP_DELETE) {
1753 cp = "";
1754 build_question = TRUE;
1755 } else
1756 cp = prompt_parts[6];
1757 break;
1758 case 'f':
1759 if (single_source) {
1760 cp = S_ISDIR (src_stat->
1761 st_mode) ? prompt_parts[2] : prompt_parts[0];
1762 } else {
1763 cp = (panel->marked == panel->dirs_marked)
1764 ? prompt_parts[3]
1765 : (panel->dirs_marked ? prompt_parts[4] : prompt_parts[1]);
1767 break;
1768 default:
1769 *dp++ = *sp++;
1771 if (cp != NULL) {
1772 sp += 2;
1773 while (*cp != '\0')
1774 *dp++ = *cp++;
1776 break;
1777 default:
1778 *dp++ = *sp++;
1781 *dp = '\0';
1783 if (build_question) {
1784 char tmp[BUF_MEDIUM];
1786 memmove (tmp, format_string, sizeof (tmp));
1787 g_snprintf (format_string, sizeof (format_string),
1788 question_format, tmp);
1791 return g_strdup (format_string);
1794 #ifdef WITH_BACKGROUND
1795 static int
1796 end_bg_process (FileOpContext *ctx, enum OperationMode mode) {
1797 int pid = ctx->pid;
1799 (void) mode;
1800 ctx->pid = 0;
1802 unregister_task_with_pid(pid);
1803 // file_op_context_destroy(ctx);
1804 return 1;
1806 #endif
1809 * panel_operate:
1811 * Performs one of the operations on the selection on the source_panel
1812 * (copy, delete, move).
1814 * Returns 1 if did change the directory
1815 * structure, Returns 0 if user aborted
1817 * force_single forces operation on the current entry and affects
1818 * default destination. Current filename is used as default.
1821 panel_operate (void *source_panel, FileOperation operation, int force_single)
1823 WPanel *panel = (WPanel *) source_panel;
1824 const gboolean single_entry = force_single || (panel->marked <= 1)
1825 || (get_current_type () == view_tree);
1827 char *source = NULL;
1828 #ifdef WITH_FULL_PATHS
1829 char *source_with_path = NULL;
1830 #else
1831 # define source_with_path source
1832 #endif /* !WITH_FULL_PATHS */
1833 char *dest = NULL;
1834 char *temp = NULL;
1835 char *save_cwd = NULL, *save_dest = NULL;
1836 struct stat src_stat, dst_stat;
1837 int i;
1838 FileProgressStatus value;
1839 FileOpContext *ctx;
1841 off_t count = 0;
1842 double bytes = 0;
1844 int dst_result;
1845 int do_bg = 0; /* do background operation? */
1847 #ifdef ENABLE_NLS
1848 static gboolean i18n_flag = FALSE;
1849 if (!i18n_flag) {
1850 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1851 op_names[i] = Q_(op_names[i]);
1852 i18n_flag = TRUE;
1854 #endif /* ENABLE_NLS */
1856 free_linklist (&linklist);
1857 free_linklist (&dest_dirs);
1859 /* Update panel contents to avoid actions on deleted files */
1860 if (!panel->is_panelized) {
1861 update_panels (UP_RELOAD, UP_KEEPSEL);
1862 repaint_screen ();
1865 if (single_entry) {
1866 if (force_single) {
1867 source = selection (panel)->fname;
1868 src_stat = selection (panel)->st;
1869 } else {
1870 source = panel_get_file (panel, &src_stat);
1873 if (!strcmp (source, "..")) {
1874 message (D_ERROR, MSG_ERROR, _(" Cannot operate on \"..\"! "));
1875 return 0;
1879 ctx = file_op_context_new (operation);
1881 /* Show confirmation dialog */
1882 if (operation != OP_DELETE) {
1883 char *dest_dir;
1884 char *dest_dir_;
1885 char *format;
1887 /* Forced single operations default to the original name */
1888 if (force_single)
1889 dest_dir = source;
1890 else if (get_other_type () == view_listing)
1891 dest_dir = other_panel->cwd;
1892 else
1893 dest_dir = panel->cwd;
1895 * Add trailing backslash only when do non-local ops.
1896 * It saves user from occasional file renames (when destination
1897 * dir is deleted)
1899 if (!force_single
1900 && dest_dir[0] != '\0'
1901 && dest_dir[strlen (dest_dir) - 1] != PATH_SEP) {
1902 /* add trailing separator */
1903 dest_dir_ = g_strconcat (dest_dir, PATH_SEP_STR, (char *) NULL);
1904 } else {
1905 /* just copy */
1906 dest_dir_ = g_strdup (dest_dir);
1908 if (dest_dir_ == NULL) {
1909 file_op_context_destroy (ctx);
1910 return 0;
1913 /* Generate confirmation prompt */
1914 format = panel_operate_generate_prompt (panel, operation,
1915 source != NULL, &src_stat);
1917 dest = file_mask_dialog (ctx, operation, source != NULL, format,
1918 source != NULL ? (void *) source
1919 : (void *) &panel->marked,
1920 dest_dir_, &do_bg);
1922 g_free (format);
1923 g_free (dest_dir_);
1925 if (dest == NULL || dest[0] == '\0') {
1926 file_op_context_destroy (ctx);
1927 g_free (dest);
1928 return 0;
1930 } else if (confirm_delete) {
1931 char *format;
1932 char fmd_buf [BUF_MEDIUM];
1934 /* Generate confirmation prompt */
1935 format = panel_operate_generate_prompt (panel, OP_DELETE,
1936 source != NULL, &src_stat);
1938 if (source == NULL)
1939 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1940 else {
1941 const int fmd_xlen = 64;
1942 i = fmd_xlen - str_term_width1 (format) - 4;
1943 g_snprintf (fmd_buf, sizeof (fmd_buf),
1944 format, str_trunc (source, i));
1947 g_free (format);
1949 if (safe_delete)
1950 query_set_sel (1);
1952 i = query_dialog (op_names[operation], fmd_buf, D_ERROR, 2,
1953 _("&Yes"), _("&No"));
1955 if (i != 0) {
1956 file_op_context_destroy (ctx);
1957 return 0;
1961 /* Background also need ctx->ui, but not full */
1962 if (do_bg)
1963 file_op_context_create_ui_without_init (ctx, 1);
1964 else
1965 file_op_context_create_ui (ctx, 1);
1967 #ifdef WITH_BACKGROUND
1968 /* Did the user select to do a background operation? */
1969 if (do_bg) {
1970 int v;
1972 v = do_background (ctx,
1973 g_strconcat (op_names[operation], ": ",
1974 panel->cwd, (char *) NULL));
1975 if (v == -1) {
1976 message (D_ERROR, MSG_ERROR,
1977 _(" Sorry, I could not put the job in background "));
1980 /* If we are the parent */
1981 if (v == 1) {
1982 mc_setctl (panel->cwd, VFS_SETCTL_FORGET, NULL);
1983 mc_setctl (dest, VFS_SETCTL_FORGET, NULL);
1984 /* file_op_context_destroy (ctx); */
1985 return 0;
1988 #endif /* WITH_BACKGROUND */
1990 /* Initialize things */
1991 /* We do not want to trash cache every time file is
1992 created/touched. However, this will make our cache contain
1993 invalid data. */
1994 if (dest) {
1995 if (mc_setctl (dest, VFS_SETCTL_STALE_DATA, (void *) 1))
1996 save_dest = g_strdup (dest);
1998 if (panel->cwd) {
1999 if (mc_setctl (panel->cwd, VFS_SETCTL_STALE_DATA, (void *) 1))
2000 save_cwd = g_strdup (panel->cwd);
2003 /* Now, let's do the job */
2005 /* This code is only called by the tree and panel code */
2006 if (single_entry) {
2007 /* We now have ETA in all cases */
2009 /* One file: FIXME mc_chdir will take user out of any vfs */
2010 if (operation != OP_COPY && get_current_type () == view_tree)
2011 mc_chdir (PATH_SEP_STR);
2013 /* The source and src_stat variables have been initialized before */
2014 #ifdef WITH_FULL_PATHS
2015 source_with_path = concat_dir_and_file (panel->cwd, source);
2016 #endif /* WITH_FULL_PATHS */
2018 if (operation == OP_DELETE) {
2019 if (S_ISDIR (src_stat.st_mode))
2020 value = erase_dir (ctx, source_with_path, &count, &bytes);
2021 else
2022 value =
2023 erase_file (ctx, source_with_path, &count, &bytes, 1);
2024 } else {
2025 temp = transform_source (ctx, source_with_path);
2026 if (temp == NULL) {
2027 value = transform_error;
2028 } else {
2029 char *repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2030 char *temp2 = concat_dir_and_file (repl_dest, temp);
2031 g_free (repl_dest);
2032 g_free (temp);
2033 g_free(dest);
2034 dest = temp2;
2036 switch (operation) {
2037 case OP_COPY:
2039 * we use file_mask_op_follow_links only with OP_COPY,
2041 (*ctx->stat_func) (source_with_path, &src_stat);
2043 if (S_ISDIR (src_stat.st_mode)) {
2044 value =
2045 copy_dir_dir (ctx, source_with_path, dest, 1,
2046 0, 0, 0, &count, &bytes);
2047 } else {
2048 value =
2049 copy_file_file (ctx, source_with_path, dest, 1,
2050 &count, &bytes, 1);
2052 break;
2054 case OP_MOVE:
2055 if (S_ISDIR (src_stat.st_mode))
2056 value =
2057 move_dir_dir (ctx, source_with_path, dest,
2058 &count, &bytes);
2059 else
2060 value =
2061 move_file_file (ctx, source_with_path, dest,
2062 &count, &bytes);
2063 break;
2065 default:
2066 /* Unknown file operation */
2067 abort ();
2070 } /* Copy or move operation */
2072 if ((value == FILE_CONT) && !force_single)
2073 unmark_files (panel);
2074 } else {
2075 /* Many files */
2076 /* Check destination for copy or move operation */
2077 if (operation != OP_DELETE) {
2078 retry_many_dst_stat:
2079 dst_result = mc_stat (dest, &dst_stat);
2080 if (dst_result == 0 && !S_ISDIR (dst_stat.st_mode)) {
2081 if (file_error
2082 (_(" Destination \"%s\" must be a directory \n %s "),
2083 dest) == FILE_RETRY)
2084 goto retry_many_dst_stat;
2085 goto clean_up;
2089 /* Initialize variables for progress bars */
2090 if (operation != OP_MOVE && verbose && file_op_compute_totals) {
2091 ComputeDirSizeUI *ui;
2092 FileProgressStatus status;
2094 ui = compute_dir_size_create_ui ();
2095 status = panel_compute_totals (panel,
2096 ui, compute_dir_size_update_ui,
2097 &ctx->progress_count, &ctx->progress_bytes);
2098 compute_dir_size_destroy_ui (ui);
2100 if (status != FILE_CONT)
2101 goto clean_up;
2103 ctx->progress_totals_computed = 1;
2104 } else {
2105 ctx->progress_totals_computed = 0;
2106 ctx->progress_count = panel->marked;
2107 ctx->progress_bytes = panel->total;
2110 /* Loop for every file, perform the actual copy operation */
2111 for (i = 0; i < panel->count; i++) {
2112 if (!panel->dir.list[i].f.marked)
2113 continue; /* Skip the unmarked ones */
2115 source = panel->dir.list[i].fname;
2116 src_stat = panel->dir.list[i].st;
2118 #ifdef WITH_FULL_PATHS
2119 g_free (source_with_path);
2120 source_with_path = concat_dir_and_file (panel->cwd, source);
2121 #endif /* WITH_FULL_PATHS */
2123 if (operation == OP_DELETE) {
2124 if (S_ISDIR (src_stat.st_mode))
2125 value =
2126 erase_dir (ctx, source_with_path, &count, &bytes);
2127 else
2128 value =
2129 erase_file (ctx, source_with_path, &count, &bytes,
2131 } else {
2132 temp = transform_source (ctx, source_with_path);
2133 if (temp == NULL)
2134 value = transform_error;
2135 else {
2136 char *temp3;
2137 char *repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2138 char *temp2 = concat_dir_and_file (repl_dest, temp);
2139 g_free(repl_dest);
2141 g_free(temp);
2142 temp3 = source_with_path;
2143 source_with_path = strutils_shell_unescape(source_with_path);
2144 g_free(temp3);
2145 temp3 = temp2;
2146 temp2 = strutils_shell_unescape(temp2);
2147 g_free(temp3);
2149 switch (operation) {
2150 case OP_COPY:
2152 * we use file_mask_op_follow_links only with OP_COPY
2154 (*ctx->stat_func) (source_with_path, &src_stat);
2155 if (S_ISDIR (src_stat.st_mode))
2156 value =
2157 copy_dir_dir (ctx, source_with_path, temp2,
2158 1, 0, 0, 0, &count, &bytes);
2159 else
2160 value =
2161 copy_file_file (ctx, source_with_path,
2162 temp2, 1, &count, &bytes,
2164 free_linklist (&dest_dirs);
2165 break;
2167 case OP_MOVE:
2168 if (S_ISDIR (src_stat.st_mode))
2169 value =
2170 move_dir_dir (ctx, source_with_path, temp2,
2171 &count, &bytes);
2172 else
2173 value =
2174 move_file_file (ctx, source_with_path,
2175 temp2, &count, &bytes);
2176 break;
2178 default:
2179 /* Unknown file operation */
2180 abort ();
2182 g_free (temp2);
2184 } /* Copy or move operation */
2186 if (value == FILE_ABORT)
2187 goto clean_up;
2189 if (value == FILE_CONT)
2190 do_file_mark (panel, i, 0);
2192 if (file_progress_show_count (ctx, count, ctx->progress_count)
2193 == FILE_ABORT)
2194 goto clean_up;
2196 if (verbose
2197 && file_progress_show_bytes (ctx, bytes,
2198 ctx->progress_bytes) ==
2199 FILE_ABORT)
2200 goto clean_up;
2202 if (operation != OP_DELETE && verbose
2203 && file_progress_show (ctx, 0, 0) == FILE_ABORT)
2204 goto clean_up;
2206 mc_refresh ();
2207 } /* Loop for every file */
2208 } /* Many entries */
2209 clean_up:
2210 /* Clean up */
2212 if (save_cwd) {
2213 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
2214 g_free (save_cwd);
2216 if (save_dest) {
2217 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
2218 g_free (save_dest);
2221 free_linklist (&linklist);
2222 free_linklist (&dest_dirs);
2223 #ifdef WITH_FULL_PATHS
2224 g_free (source_with_path);
2225 #endif /* WITH_FULL_PATHS */
2226 g_free (dest);
2227 g_free (ctx->dest_mask);
2228 ctx->dest_mask = NULL;
2229 #ifdef WITH_BACKGROUND
2230 /* Let our parent know we are saying bye bye */
2231 if (we_are_background) {
2232 int cur_pid = getpid();
2233 /* Send pid to parent with child context, it is fork and
2234 don't modify real parent ctx */
2235 ctx->pid = cur_pid;
2236 parent_call ((void *) end_bg_process, ctx, 0);
2238 vfs_shut ();
2239 _exit (0);
2241 #endif /* WITH_BACKGROUND */
2243 file_op_context_destroy (ctx);
2244 return 1;
2247 /* }}} */
2249 /* {{{ Query/status report routines */
2251 static FileProgressStatus
2252 real_do_file_error (enum OperationMode mode, const char *error)
2254 int result;
2255 const char *msg;
2257 msg = mode == Foreground ? MSG_ERROR : _(" Background process error ");
2258 result =
2259 query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("&Retry"),
2260 _("&Abort"));
2262 switch (result) {
2263 case 0:
2264 do_refresh ();
2265 return FILE_SKIP;
2267 case 1:
2268 do_refresh ();
2269 return FILE_RETRY;
2271 case 2:
2272 default:
2273 return FILE_ABORT;
2277 /* Report error with one file */
2278 FileProgressStatus
2279 file_error (const char *format, const char *file)
2281 char buf [BUF_MEDIUM];
2283 g_snprintf (buf, sizeof (buf), format,
2284 path_trunc (file, 30), unix_error_string (errno));
2286 return do_file_error (buf);
2289 /* Report error with two files */
2290 static FileProgressStatus
2291 files_error (const char *format, const char *file1, const char *file2)
2293 char buf [BUF_MEDIUM];
2294 char *nfile1 = g_strdup (path_trunc (file1, 15));
2295 char *nfile2 = g_strdup (path_trunc (file2, 15));
2297 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2,
2298 unix_error_string (errno));
2300 g_free (nfile1);
2301 g_free (nfile2);
2303 return do_file_error (buf);
2306 static FileProgressStatus
2307 real_query_recursive (FileOpContext *ctx, enum OperationMode mode, const char *s)
2309 gchar *text;
2311 if (ctx->recursive_result < RECURSIVE_ALWAYS) {
2312 const char *msg =
2313 mode ==
2314 Foreground ?
2315 _("\n Directory not empty. \n"
2316 " Delete it recursively? ")
2317 : _("\n Background process: Directory not empty \n"
2318 " Delete it recursively? ");
2319 text = g_strconcat (_(" Delete: "), path_trunc (s, 30), " ", (char *) NULL);
2321 if (safe_delete)
2322 query_set_sel (1);
2323 ctx->recursive_result = query_dialog (text, msg, D_ERROR, 5,
2324 _("&Yes"), _("&No"),
2325 _("A&ll"), _("Non&e"),
2326 _("&Abort"));
2328 if (ctx->recursive_result != RECURSIVE_ABORT)
2329 do_refresh ();
2330 g_free (text);
2333 switch (ctx->recursive_result) {
2334 case RECURSIVE_YES:
2335 case RECURSIVE_ALWAYS:
2336 return FILE_CONT;
2338 case RECURSIVE_NO:
2339 case RECURSIVE_NEVER:
2340 return FILE_SKIP;
2342 case RECURSIVE_ABORT:
2344 default:
2345 return FILE_ABORT;
2349 #ifdef WITH_BACKGROUND
2350 static FileProgressStatus
2351 do_file_error (const char *str)
2353 union {
2354 void *p;
2355 FileProgressStatus (*f) (enum OperationMode, const char *);
2356 } pntr;
2357 pntr.f = real_do_file_error;
2359 if (we_are_background)
2360 return parent_call (pntr.p, NULL, 1, strlen (str),
2361 str);
2362 else
2363 return real_do_file_error (Foreground, str);
2366 static FileProgressStatus
2367 query_recursive (FileOpContext *ctx, const char *s)
2369 union {
2370 void *p;
2371 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *);
2372 } pntr;
2373 pntr.f = real_query_recursive;
2375 if (we_are_background)
2376 return parent_call (pntr.p, ctx, 1, strlen (s), s);
2377 else
2378 return real_query_recursive (ctx, Foreground, s);
2381 static FileProgressStatus
2382 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2383 struct stat *_d_stat)
2385 union {
2386 void *p;
2387 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *,
2388 struct stat *, struct stat *);
2389 } pntr;
2390 pntr.f = file_progress_real_query_replace;
2392 if (we_are_background)
2393 return parent_call (pntr.p,
2394 ctx,
2396 strlen (destname), destname,
2397 sizeof (struct stat), _s_stat,
2398 sizeof (struct stat), _d_stat);
2399 else
2400 return file_progress_real_query_replace (ctx, Foreground, destname,
2401 _s_stat, _d_stat);
2404 #else
2405 static FileProgressStatus
2406 do_file_error (const char *str)
2408 return real_do_file_error (Foreground, str);
2411 static FileProgressStatus
2412 query_recursive (FileOpContext *ctx, const char *s)
2414 return real_query_recursive (ctx, Foreground, s);
2417 static FileProgressStatus
2418 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2419 struct stat *_d_stat)
2421 return file_progress_real_query_replace (ctx, Foreground, destname,
2422 _s_stat, _d_stat);
2425 #endif /* !WITH_BACKGROUND */
2428 Cause emacs to enter folding mode for this file:
2429 Local variables:
2430 end: