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