lib/Makefile.am: added lib/skin.h into distribution
[midnight-commander.git] / src / file.c
blobe7293e528d84c809659472aa1e9defaff8c744bc
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 "lib/global.h"
60 #include "lib/tty/tty.h"
61 #include "lib/tty/key.h"
62 #include "lib/search.h"
63 #include "lib/vfs/mc-vfs/vfs-impl.h"
64 #include "lib/vfs/mc-vfs/vfs.h"
65 #include "lib/strescape.h"
66 #include "lib/strutil.h"
68 #include "setup.h"
69 #include "dialog.h"
70 #include "widget.h"
71 #include "main.h"
72 #include "layout.h"
73 #include "widget.h"
74 #include "wtools.h"
75 #include "background.h" /* we_are_background */
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"
84 /* }}} */
86 /* Hack: the vfs code should not rely on this */
87 #define WITH_FULL_PATHS 1
89 int verbose = 1;
92 * Whether the Midnight Commander tries to provide more
93 * information about copy/move sizes and bytes transfered
94 * at the expense of some speed
96 int file_op_compute_totals = 1;
98 /* This is a hard link cache */
99 struct link {
100 struct link *next;
101 struct vfs_class *vfs;
102 dev_t dev;
103 ino_t ino;
104 short linkcount;
105 mode_t st_mode;
106 char name[1];
109 /* the hard link cache */
110 static struct link *linklist = NULL;
112 /* the files-to-be-erased list */
113 static struct link *erase_list;
116 * In copy_dir_dir we use two additional single linked lists: The first -
117 * variable name `parent_dirs' - holds information about already copied
118 * directories and is used to detect cyclic symbolic links.
119 * The second (`dest_dirs' below) holds information about just created
120 * target directories and is used to detect when an directory is copied
121 * into itself (we don't want to copy infinitly).
122 * Both lists don't use the linkcount and name structure members of struct
123 * link.
125 static struct link *dest_dirs = NULL;
127 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
128 const char *op_names[3] = {
129 N_("DialogTitle|Copy"),
130 N_("DialogTitle|Move"),
131 N_("DialogTitle|Delete")
134 /* }}} */
136 static FileProgressStatus query_replace (FileOpContext * ctx, const char *destname,
137 struct stat *_s_stat, struct stat *_d_stat);
138 static FileProgressStatus query_recursive (FileOpContext * ctx, const char *s);
139 static FileProgressStatus do_file_error (const char *str);
140 static FileProgressStatus erase_dir_iff_empty (FileOpContext *ctx, const char *s);
141 static FileProgressStatus erase_file (FileOpContext *ctx, const char *s,
142 off_t *progress_count, double *progress_bytes,
143 int is_toplevel_file);
144 static FileProgressStatus files_error (const char *format, const char *file1,
145 const char *file2);
147 static FileProgressStatus transform_error = FILE_CONT;
149 static char *
150 transform_source (FileOpContext *ctx, const char *source)
152 char *s, *q;
153 char *fnsource;
155 s = g_strdup (source);
157 /* We remove \n from the filename since regex routines would use \n as an anchor */
158 /* this is just to be allowed to maniupulate file names with \n on it */
159 for (q = s; *q != '\0'; q++)
160 if (*q == '\n')
161 *q = ' ';
163 fnsource = (char *) x_basename (s);
165 if (mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
166 q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
167 else {
168 q = NULL;
169 transform_error = FILE_SKIP;
172 g_free (s);
173 return q;
176 static void
177 free_linklist (struct link **lc_linklist)
179 struct link *lp, *lp2;
181 for (lp = *lc_linklist; lp != NULL; lp = lp2) {
182 lp2 = lp->next;
183 g_free (lp);
185 *lc_linklist = NULL;
188 static int
189 is_in_linklist (struct link *lp, const char *path, struct stat *sb)
191 ino_t ino = sb->st_ino;
192 dev_t dev = sb->st_dev;
193 #ifdef ENABLE_VFS
194 struct vfs_class *vfs = vfs_get_class (path);
195 #endif /* ENABLE_VFS */
197 (void) path;
199 while (lp) {
200 #ifdef ENABLE_VFS
201 if (lp->vfs == vfs)
202 #endif /* ENABLE_VFS */
203 if (lp->ino == ino && lp->dev == dev)
204 return 1;
205 lp = lp->next;
207 return 0;
211 * Returns 0 if the inode wasn't found in the cache and 1 if it was found
212 * and a hardlink was succesfully made
214 static int
215 check_hardlinks (const char *src_name, const char *dst_name, struct stat *pstat)
217 struct link *lp;
218 struct vfs_class *my_vfs = vfs_get_class (src_name);
219 ino_t ino = pstat->st_ino;
220 dev_t dev = pstat->st_dev;
221 struct stat link_stat;
222 const char *p;
224 if (vfs_file_class_flags (src_name) & VFSF_NOLINKS)
225 return 0;
227 for (lp = linklist; lp != NULL; lp = lp->next)
228 if (lp->vfs == my_vfs && lp->ino == ino && lp->dev == dev) {
229 if (!mc_stat (lp->name, &link_stat) && link_stat.st_ino == ino
230 && link_stat.st_dev == dev
231 && vfs_get_class (lp->name) == my_vfs) {
232 p = strchr (lp->name, 0) + 1; /* i.e. where the `name' file
233 was copied to */
234 if (vfs_get_class (dst_name) == vfs_get_class (p)) {
235 if (!mc_stat (p, &link_stat)) {
236 if (!mc_link (p, dst_name))
237 return 1;
241 message (D_ERROR, MSG_ERROR, _(" Cannot make the hardlink "));
242 return 0;
244 lp = (struct link *) g_try_malloc (sizeof (struct link) + strlen (src_name)
245 + strlen (dst_name) + 1);
246 if (lp) {
247 char *lpdstname;
248 lp->vfs = my_vfs;
249 lp->ino = ino;
250 lp->dev = dev;
251 strcpy (lp->name, src_name);
252 lpdstname = lp->name + strlen(lp->name) + 1;
253 strcpy (lpdstname, dst_name);
254 lp->next = linklist;
255 linklist = lp;
257 return 0;
261 * Duplicate the contents of the symbolic link src_path in dst_path.
262 * Try to make a stable symlink if the option "stable symlink" was
263 * set in the file mask dialog.
264 * If dst_path is an existing symlink it will be deleted silently
265 * (upper levels take already care of existing files at dst_path).
267 static FileProgressStatus
268 make_symlink (FileOpContext *ctx, const char *src_path, const char *dst_path)
270 char link_target[MC_MAXPATHLEN];
271 int len;
272 FileProgressStatus return_status;
273 struct stat sb;
274 int dst_is_symlink;
276 if (mc_lstat (dst_path, &sb) == 0 && S_ISLNK (sb.st_mode))
277 dst_is_symlink = 1;
278 else
279 dst_is_symlink = 0;
281 retry_src_readlink:
282 len = mc_readlink (src_path, link_target, MC_MAXPATHLEN - 1);
283 if (len < 0) {
284 return_status =
285 file_error (_(" Cannot read source link \"%s\" \n %s "),
286 src_path);
287 if (return_status == FILE_RETRY)
288 goto retry_src_readlink;
289 return return_status;
291 link_target[len] = 0;
293 if (ctx->stable_symlinks)
294 if (!vfs_file_is_local (src_path) || !vfs_file_is_local (dst_path)) {
295 message (D_ERROR, MSG_ERROR,
296 _(" Cannot make stable symlinks across "
297 "non-local filesystems: \n\n"
298 " Option Stable Symlinks will be disabled "));
299 ctx->stable_symlinks = 0;
302 if (ctx->stable_symlinks && *link_target != PATH_SEP) {
303 char *p, *q, *s;
305 const char *r = strrchr (src_path, PATH_SEP);
307 if (r) {
308 p = g_strndup (src_path, r - src_path + 1);
309 if (*dst_path == PATH_SEP)
310 q = g_strdup (dst_path);
311 else
312 q = g_strconcat (p, dst_path, (char *) NULL);
313 s = strrchr (q, PATH_SEP);
314 if (s) {
315 s[1] = 0;
316 s = g_strconcat (p, link_target, (char *) NULL);
317 g_free (p);
318 g_strlcpy (link_target, s, sizeof (link_target));
319 g_free (s);
320 s = diff_two_paths (q, link_target);
321 if (s) {
322 g_strlcpy (link_target, s, sizeof (link_target));
323 g_free (s);
325 } else
326 g_free (p);
327 g_free (q);
330 retry_dst_symlink:
331 if (mc_symlink (link_target, dst_path) == 0)
332 /* Success */
333 return FILE_CONT;
335 * if dst_exists, it is obvious that this had failed.
336 * We can delete the old symlink and try again...
338 if (dst_is_symlink) {
339 if (!mc_unlink (dst_path))
340 if (mc_symlink (link_target, dst_path) == 0)
341 /* Success */
342 return FILE_CONT;
344 return_status =
345 file_error (_(" Cannot create target symlink \"%s\" \n %s "),
346 dst_path);
347 if (return_status == FILE_RETRY)
348 goto retry_dst_symlink;
349 return return_status;
352 static int
353 progress_update_one (FileOpContext *ctx,
354 off_t *progress_count,
355 double *progress_bytes, off_t add, int is_toplevel_file)
357 int ret;
359 if (is_toplevel_file || ctx->progress_totals_computed) {
360 (*progress_count)++;
361 (*progress_bytes) += add;
364 /* Apply some heuristic here to not call the update stuff very often */
365 ret =
366 file_progress_show_count (ctx, *progress_count,
367 ctx->progress_count);
368 if (ret != FILE_CONT)
369 return ret;
370 ret =
371 file_progress_show_bytes (ctx, *progress_bytes,
372 ctx->progress_bytes);
374 return ret;
377 /* Status of the destination file */
378 enum {
379 DEST_NONE, /* Not created */
380 DEST_SHORT, /* Created, not fully copied */
381 DEST_FULL /* Created, fully copied */
384 static FileProgressStatus
385 real_warn_same_file (enum OperationMode mode, const char *fmt,
386 const char *a, const char *b)
388 char *msg;
389 int result = 0;
390 const char *head_msg;
392 head_msg = mode == Foreground ? MSG_ERROR :
393 _(" Background process error ");
395 msg = g_strdup_printf (fmt, a, b);
396 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
397 g_free(msg);
398 do_refresh ();
399 if ( result ) { /* 1 == Abort */
400 return FILE_ABORT;
401 } else {
402 return FILE_SKIP;
406 #ifdef WITH_BACKGROUND
407 static FileProgressStatus
408 warn_same_file (const char *fmt, const char *a, const char *b)
410 union {
411 void *p;
412 FileProgressStatus (*f) (enum OperationMode, const char *fmt,
413 const char *a, const char *b);
414 } pntr;
415 pntr.f = real_warn_same_file;
417 if (we_are_background)
418 return parent_call (pntr.p, NULL, 3, strlen (fmt),
419 fmt, strlen(a), a, strlen(b), b);
420 else
421 return real_warn_same_file (Foreground, fmt, a, b);
423 #else
424 static FileProgressStatus
425 warn_same_file (const char *fmt, const char *a, const char *b)
427 return real_warn_same_file (Foreground, fmt, a, b);
429 #endif
431 FileProgressStatus
432 copy_file_file (FileOpContext *ctx, const char *src_path, const char *dst_path,
433 int ask_overwrite, off_t *progress_count,
434 double *progress_bytes, int is_toplevel_file)
436 uid_t src_uid = (uid_t) - 1;
437 gid_t src_gid = (gid_t) - 1;
439 char *buf = NULL;
440 int buf_size = BUF_8K;
441 int src_desc, dest_desc = -1;
442 int n_read, n_written;
443 mode_t src_mode = 0; /* The mode of the source file */
444 struct stat sb, sb2;
445 struct utimbuf utb;
446 int dst_exists = 0, appending = 0;
447 off_t n_read_total = 0, file_size = -1;
448 FileProgressStatus return_status, temp_status;
449 struct timeval tv_transfer_start;
450 int dst_status = DEST_NONE; /* 1 if the file is not fully copied */
451 int open_flags;
453 /* FIXME: We should not be using global variables! */
454 ctx->do_reget = 0;
455 return_status = FILE_RETRY;
457 if (file_progress_show_source (ctx, src_path) == FILE_ABORT ||
458 file_progress_show_target (ctx, dst_path) == FILE_ABORT)
459 return FILE_ABORT;
461 mc_refresh ();
463 while (mc_stat (dst_path, &sb2) == 0) {
464 if (S_ISDIR (sb2.st_mode)) {
465 return_status =
466 file_error (_(" Cannot overwrite directory \"%s\" \n %s "),
467 dst_path);
468 if (return_status == FILE_RETRY)
469 continue;
470 return return_status;
472 dst_exists = 1;
473 break;
476 while ((*ctx->stat_func) (src_path, &sb)) {
477 return_status =
478 file_error (_(" Cannot stat source file \"%s\" \n %s "),
479 src_path);
480 if (return_status != FILE_RETRY)
481 return return_status;
484 if (dst_exists) {
485 /* Destination already exists */
486 if (sb.st_dev == sb2.st_dev && sb.st_ino == sb2.st_ino)
487 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "),
488 src_path, dst_path);
489 /* Should we replace destination? */
490 if (ask_overwrite) {
491 ctx->do_reget = 0;
492 return_status = query_replace (ctx, dst_path, &sb, &sb2);
493 if (return_status != FILE_CONT)
494 return return_status;
498 if (!ctx->do_append) {
499 /* Check the hardlinks */
500 if (!ctx->follow_links && sb.st_nlink > 1 &&
501 check_hardlinks (src_path, dst_path, &sb) == 1) {
502 /* We have made a hardlink - no more processing is necessary */
503 return FILE_CONT;
506 if (S_ISLNK (sb.st_mode))
507 return make_symlink (ctx, src_path, dst_path);
509 if (S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode) ||
510 S_ISFIFO (sb.st_mode) || S_ISNAM (sb.st_mode) ||
511 S_ISSOCK (sb.st_mode)) {
512 while (mc_mknod (dst_path, sb.st_mode & ctx->umask_kill,
513 sb.st_rdev) < 0) {
514 return_status = file_error (
515 _(" Cannot create special file \"%s\" \n %s "), dst_path);
516 if (return_status == FILE_RETRY)
517 continue;
518 return return_status;
520 /* Success */
522 while (ctx->preserve_uidgid
523 && mc_chown (dst_path, sb.st_uid, sb.st_gid)) {
524 temp_status = file_error (
525 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
526 if (temp_status == FILE_RETRY)
527 continue;
528 return temp_status;
530 while (ctx->preserve &&
531 mc_chmod (dst_path, sb.st_mode & ctx->umask_kill)) {
532 temp_status = file_error (
533 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
534 if (temp_status == FILE_RETRY)
535 continue;
536 return temp_status;
538 return FILE_CONT;
542 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
544 while ((src_desc = mc_open (src_path, O_RDONLY | O_LINEAR)) < 0) {
545 return_status = file_error (
546 _(" Cannot open source file \"%s\" \n %s "), src_path);
547 if (return_status == FILE_RETRY)
548 continue;
549 ctx->do_append = 0;
550 return return_status;
553 if (ctx->do_reget) {
554 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget) {
555 message (D_ERROR, _("Warning"),
556 _(" Reget failed, about to overwrite file "));
557 ctx->do_reget = ctx->do_append = 0;
561 while (mc_fstat (src_desc, &sb)) {
562 return_status = file_error (
563 _(" Cannot fstat source file \"%s\" \n %s "), src_path);
564 if (return_status == FILE_RETRY)
565 continue;
566 ctx->do_append = 0;
567 goto ret;
569 src_mode = sb.st_mode;
570 src_uid = sb.st_uid;
571 src_gid = sb.st_gid;
572 utb.actime = sb.st_atime;
573 utb.modtime = sb.st_mtime;
574 file_size = sb.st_size;
576 open_flags = O_WRONLY;
577 if (dst_exists != 0) {
578 if (ctx->do_append != 0)
579 open_flags |= O_APPEND;
580 else
581 open_flags |= O_CREAT | O_TRUNC;
582 } else {
583 open_flags |= O_CREAT | O_EXCL;
585 while ((dest_desc = mc_open (dst_path, open_flags, src_mode)) < 0) {
586 if (errno == EEXIST) {
587 goto ret;
589 return_status = file_error (
590 _(" Cannot create target file \"%s\" \n %s "), dst_path);
591 if (return_status == FILE_RETRY)
592 continue;
593 ctx->do_append = 0;
594 goto ret;
596 dst_status = DEST_SHORT; /* file opened, but not fully copied */
598 appending = ctx->do_append;
599 ctx->do_append = 0;
601 /* Find out the optimal buffer size. */
602 while (mc_fstat (dest_desc, &sb)) {
603 return_status = file_error (
604 _(" Cannot fstat target file \"%s\" \n %s "), dst_path);
605 if (return_status == FILE_RETRY)
606 continue;
607 goto ret;
609 buf = g_malloc (buf_size);
611 ctx->eta_secs = 0.0;
612 ctx->bps = 0;
614 return_status = file_progress_show (ctx, 0, file_size);
616 mc_refresh ();
618 if (return_status != FILE_CONT)
619 goto ret;
622 struct timeval tv_current, tv_last_update, tv_last_input;
623 int secs, update_secs;
624 long dt;
625 const char *stalled_msg;
627 tv_last_update = tv_transfer_start;
629 for (;;) {
630 /* src_read */
631 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0))
632 n_read = -1;
633 else
634 while ((n_read = mc_read (src_desc, buf, buf_size)) < 0) {
635 return_status = file_error (
636 _(" Cannot read source file \"%s\" \n %s "), src_path);
637 if (return_status == FILE_RETRY)
638 continue;
639 goto ret;
641 if (n_read == 0)
642 break;
644 gettimeofday (&tv_current, NULL);
646 if (n_read > 0) {
647 char *t = buf;
648 n_read_total += n_read;
650 /* Windows NT ftp servers report that files have no
651 * permissions: -------, so if we happen to have actually
652 * read something, we should fix the permissions.
654 if (!(src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)))
655 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
656 gettimeofday (&tv_last_input, NULL);
658 /* dst_write */
659 while ((n_written =
660 mc_write (dest_desc, t, n_read)) < n_read) {
661 if (n_written > 0) {
662 n_read -= n_written;
663 t += n_written;
664 continue;
666 return_status =
667 file_error (_(" Cannot write target file \"%s\" \n %s "),
668 dst_path);
669 if (return_status != FILE_RETRY)
670 goto ret;
674 /* 1. Update rotating dash after some time (hardcoded to 2 seconds) */
675 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
676 if (secs > 2) {
677 rotate_dash ();
678 tv_last_update = tv_current;
681 /* 2. Check for a stalled condition */
682 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
683 stalled_msg = "";
684 if (update_secs > 4) {
685 stalled_msg = _("(stalled)");
688 /* 3. Compute ETA */
689 if (secs > 2) {
690 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
692 if (n_read_total) {
693 ctx->eta_secs =
694 ((dt / (double) n_read_total) * file_size) - dt;
695 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
696 } else
697 ctx->eta_secs = 0.0;
700 /* 4. Compute BPS rate */
701 if (secs > 2) {
702 ctx->bps_time =
703 (tv_current.tv_sec - tv_transfer_start.tv_sec);
704 if (ctx->bps_time < 1)
705 ctx->bps_time = 1;
706 ctx->bps = n_read_total / ctx->bps_time;
709 file_progress_set_stalled_label (ctx, stalled_msg);
710 return_status = file_progress_show_bytes (ctx, *progress_bytes +
711 n_read_total + ctx->do_reget, ctx->progress_bytes);
712 if (return_status == FILE_CONT) {
713 return_status =
714 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size);
716 mc_refresh ();
717 if (return_status != FILE_CONT)
718 goto ret;
722 dst_status = DEST_FULL; /* copy successful, don't remove target file */
724 ret:
725 g_free (buf);
727 while (src_desc != -1 && mc_close (src_desc) < 0) {
728 temp_status = file_error (
729 _(" Cannot close source file \"%s\" \n %s "), src_path);
730 if (temp_status == FILE_RETRY)
731 continue;
732 if (temp_status == FILE_ABORT)
733 return_status = temp_status;
734 break;
737 while (dest_desc != -1 && mc_close (dest_desc) < 0) {
738 temp_status = file_error (
739 _(" Cannot close target file \"%s\" \n %s "), dst_path);
740 if (temp_status == FILE_RETRY)
741 continue;
742 return_status = temp_status;
743 break;
746 if (dst_status == DEST_SHORT) {
747 /* Remove short file */
748 int result;
749 result = query_dialog (Q_("DialogTitle|Copy"),
750 _("Incomplete file was retrieved. Keep it?"),
751 D_ERROR, 2, _("&Delete"), _("&Keep"));
752 if (!result)
753 mc_unlink (dst_path);
754 } else if (dst_status == DEST_FULL) {
755 /* Copy has succeeded */
756 if (!appending && ctx->preserve_uidgid) {
757 while (mc_chown (dst_path, src_uid, src_gid)) {
758 temp_status = file_error (
759 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
760 if (temp_status == FILE_RETRY)
761 continue;
762 return_status = temp_status;
763 break;
767 if (!appending) {
768 if (ctx->preserve){
769 while (mc_chmod (dst_path, (src_mode & ctx->umask_kill))) {
770 temp_status = file_error (
771 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
772 if (temp_status != FILE_RETRY) {
773 return_status = temp_status;
774 break;
777 } else {
778 src_mode = umask(-1);
779 umask(src_mode);
780 src_mode = 0100666 & ~src_mode;
781 mc_chmod (dst_path, (src_mode & ctx->umask_kill));
783 mc_utime (dst_path, &utb);
787 if (return_status == FILE_CONT)
788 return_status =
789 progress_update_one (ctx, progress_count, progress_bytes,
790 file_size, is_toplevel_file);
792 return return_status;
796 * I think these copy_*_* functions should have a return type.
797 * anyway, this function *must* have two directories as arguments.
799 /* FIXME: This function needs to check the return values of the
800 function calls */
801 FileProgressStatus
802 copy_dir_dir (FileOpContext *ctx, const char *s, const char *_d, int toplevel,
803 int move_over, int delete, struct link *parent_dirs,
804 off_t *progress_count, double *progress_bytes)
806 struct dirent *next;
807 struct stat buf, cbuf;
808 DIR *reading;
809 char *dest_dir = NULL;
810 FileProgressStatus return_status = FILE_CONT;
811 struct utimbuf utb;
812 struct link *lp;
813 char *d;
815 d = strutils_shell_unescape (_d);
817 /* First get the mode of the source dir */
818 retry_src_stat:
819 if ((*ctx->stat_func) (s, &cbuf)) {
820 return_status =
821 file_error (_(" Cannot stat source directory \"%s\" \n %s "), s);
822 if (return_status == FILE_RETRY)
823 goto retry_src_stat;
824 goto ret_fast;
827 if (is_in_linklist (dest_dirs, s, &cbuf)) {
828 /* Don't copy a directory we created before (we don't want to copy
829 infinitely if a directory is copied into itself) */
830 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
831 return_status = FILE_CONT;
832 goto ret_fast;
835 /* Hmm, hardlink to directory??? - Norbert */
836 /* FIXME: In this step we should do something
837 in case the destination already exist */
838 /* Check the hardlinks */
839 if (ctx->preserve && cbuf.st_nlink > 1
840 && check_hardlinks (s, d, &cbuf) == 1) {
841 /* We have made a hardlink - no more processing is necessary */
842 goto ret_fast;
845 if (!S_ISDIR (cbuf.st_mode)) {
846 return_status =
847 file_error (_(" Source \"%s\" is not a directory \n %s "), s);
848 if (return_status == FILE_RETRY)
849 goto retry_src_stat;
850 goto ret_fast;
853 if (is_in_linklist (parent_dirs, s, &cbuf)) {
854 /* we found a cyclic symbolic link */
855 message (D_ERROR, MSG_ERROR,
856 _(" Cannot copy cyclic symbolic link \n `%s' "), s);
857 return_status = FILE_SKIP;
858 goto ret_fast;
861 lp = g_new (struct link, 1);
862 lp->vfs = vfs_get_class (s);
863 lp->ino = cbuf.st_ino;
864 lp->dev = cbuf.st_dev;
865 lp->next = parent_dirs;
866 parent_dirs = lp;
868 retry_dst_stat:
869 /* Now, check if the dest dir exists, if not, create it. */
870 if (mc_stat (d, &buf)) {
871 /* Here the dir doesn't exist : make it ! */
872 if (move_over) {
873 if (mc_rename (s, d) == 0) {
874 return_status = FILE_CONT;
875 goto ret;
878 dest_dir = d;
879 d = NULL;
880 } else {
882 * If the destination directory exists, we want to copy the whole
883 * directory, but we only want this to happen once.
885 * Escape sequences added to the * to compiler warnings.
886 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
887 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
889 if (!S_ISDIR (buf.st_mode)) {
890 return_status = file_error(
891 _(" Destination \"%s\" must be a directory \n %s "), d);
892 if (return_status == FILE_RETRY)
893 goto retry_dst_stat;
894 goto ret;
896 /* Dive into subdir if exists */
897 if (toplevel && ctx->dive_into_subdirs) {
898 dest_dir = concat_dir_and_file (d, x_basename (s));
899 } else {
900 dest_dir = d;
901 d = NULL;
902 goto dont_mkdir;
905 while (my_mkdir (dest_dir, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU)) {
906 return_status = file_error (
907 _(" Cannot create target directory \"%s\" \n %s "), dest_dir);
908 if (return_status != FILE_RETRY)
909 goto ret;
912 lp = g_new (struct link, 1);
913 mc_stat (dest_dir, &buf);
914 lp->vfs = vfs_get_class (dest_dir);
915 lp->ino = buf.st_ino;
916 lp->dev = buf.st_dev;
917 lp->next = dest_dirs;
918 dest_dirs = lp;
920 if (ctx->preserve_uidgid) {
921 while (mc_chown (dest_dir, cbuf.st_uid, cbuf.st_gid)) {
922 return_status = file_error (
923 _(" Cannot chown target directory \"%s\" \n %s "), dest_dir);
924 if (return_status != FILE_RETRY)
925 goto ret;
929 dont_mkdir:
930 /* open the source dir for reading */
931 reading = mc_opendir (s);
932 if (reading == NULL)
933 goto ret;
935 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) {
936 char *path;
938 * Now, we don't want '.' and '..' to be created / copied at any time
940 if (!strcmp (next->d_name, "."))
941 continue;
942 if (!strcmp (next->d_name, ".."))
943 continue;
945 /* get the filename and add it to the src directory */
946 path = concat_dir_and_file (s, next->d_name);
948 (*ctx->stat_func) (path, &buf);
949 if (S_ISDIR (buf.st_mode)) {
950 char *mdpath;
952 mdpath = concat_dir_and_file (dest_dir, next->d_name);
954 * From here, we just intend to recursively copy subdirs, not
955 * the double functionality of copying different when the target
956 * dir already exists. So, we give the recursive call the flag 0
957 * meaning no toplevel.
959 return_status = copy_dir_dir (ctx, path, mdpath, 0, 0, delete,
960 parent_dirs, progress_count, progress_bytes);
961 g_free (mdpath);
962 } else {
963 char *dest_file;
965 dest_file = concat_dir_and_file (dest_dir, x_basename (path));
966 return_status = copy_file_file (ctx, path, dest_file, 1,
967 progress_count, progress_bytes, 0);
968 g_free (dest_file);
970 if (delete && return_status == FILE_CONT) {
971 if (ctx->erase_at_end) {
972 static struct link *tail;
973 size_t len = strlen (path);
974 lp = g_malloc (sizeof (struct link) + len);
975 strncpy (lp->name, path, len + 1);
976 lp->st_mode = buf.st_mode;
977 lp->next = NULL;
978 if (erase_list != NULL) {
979 tail->next = lp;
980 tail = lp;
981 } else
982 erase_list = tail = lp;
983 } else {
984 if (S_ISDIR (buf.st_mode)) {
985 return_status = erase_dir_iff_empty (ctx, path);
986 } else
987 return_status = erase_file (ctx, path, 0, 0, 0);
990 g_free (path);
992 mc_closedir (reading);
994 if (ctx->preserve) {
995 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
996 utb.actime = cbuf.st_atime;
997 utb.modtime = cbuf.st_mtime;
998 mc_utime (dest_dir, &utb);
999 } else {
1000 cbuf.st_mode = umask(-1);
1001 umask(cbuf.st_mode);
1002 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
1003 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
1006 ret:
1007 g_free (dest_dir);
1008 g_free (parent_dirs);
1009 ret_fast:
1010 g_free (d);
1011 return return_status;
1014 /* }}} */
1016 /* {{{ Move routines */
1018 static FileProgressStatus
1019 move_file_file (FileOpContext *ctx, const char *s, const char *d,
1020 off_t *progress_count, double *progress_bytes)
1022 struct stat src_stats, dst_stats;
1023 FileProgressStatus return_status = FILE_CONT;
1024 gboolean copy_done = FALSE;
1026 if (file_progress_show_source (ctx, s) == FILE_ABORT
1027 || file_progress_show_target (ctx, d) == FILE_ABORT)
1028 return FILE_ABORT;
1030 mc_refresh ();
1032 while (mc_lstat (s, &src_stats) != 0) {
1033 /* Source doesn't exist */
1034 return_status =
1035 file_error (_(" Cannot stat file \"%s\" \n %s "), s);
1036 if (return_status != FILE_RETRY)
1037 return return_status;
1040 if (mc_lstat (d, &dst_stats) == 0) {
1041 if (src_stats.st_dev == dst_stats.st_dev
1042 && src_stats.st_ino == dst_stats.st_ino)
1043 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "), s, d);
1045 if (S_ISDIR (dst_stats.st_mode)) {
1046 message (D_ERROR, MSG_ERROR,
1047 _(" Cannot overwrite directory `%s' "), d);
1048 do_refresh ();
1049 return FILE_SKIP;
1052 if (confirm_overwrite) {
1053 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
1054 if (return_status != FILE_CONT)
1055 return return_status;
1057 /* Ok to overwrite */
1060 if (!ctx->do_append) {
1061 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks) {
1062 if ((return_status = make_symlink (ctx, s, d)) == FILE_CONT) {
1063 goto retry_src_remove;
1064 } else
1065 return return_status;
1068 if (mc_rename (s, d) == 0) {
1069 return progress_update_one (ctx, progress_count,
1070 progress_bytes,
1071 src_stats.st_size, 1);
1074 #if 0
1075 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1076 one nfs to the same, but on the server it is on two different
1077 filesystems. Then nfs returns EIO instead of EXDEV.
1078 Hope it will not hurt if we always in case of error try to copy/delete. */
1079 else
1080 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1082 if (errno != EXDEV) {
1083 return_status =
1084 files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s,
1086 if (return_status == FILE_RETRY)
1087 goto retry_rename;
1088 return return_status;
1090 #endif
1092 /* Failed because filesystem boundary -> copy the file instead */
1093 return_status =
1094 copy_file_file (ctx, s, d, 0, progress_count, progress_bytes, 1);
1095 if (return_status != FILE_CONT)
1096 return return_status;
1098 copy_done = TRUE;
1100 if ((return_status =
1101 file_progress_show_source (ctx, NULL)) != FILE_CONT
1102 || (return_status = file_progress_show (ctx, 0, 0)) != FILE_CONT)
1103 return return_status;
1105 mc_refresh ();
1107 retry_src_remove:
1108 if (mc_unlink (s)) {
1109 return_status =
1110 file_error (_(" Cannot remove file \"%s\" \n %s "), s);
1111 if (return_status == FILE_RETRY)
1112 goto retry_src_remove;
1113 return return_status;
1116 if (!copy_done) {
1117 return_status = progress_update_one (ctx,
1118 progress_count,
1119 progress_bytes,
1120 src_stats.st_size, 1);
1123 return return_status;
1126 FileProgressStatus
1127 move_dir_dir (FileOpContext *ctx, const char *s, const char *d,
1128 off_t *progress_count, double *progress_bytes)
1130 struct stat sbuf, dbuf, destbuf;
1131 struct link *lp;
1132 char *destdir;
1133 FileProgressStatus return_status;
1134 gboolean move_over = FALSE;
1135 gboolean dstat_ok;
1137 if (file_progress_show_source (ctx, s) == FILE_ABORT ||
1138 file_progress_show_target (ctx, d) == FILE_ABORT)
1139 return FILE_ABORT;
1141 mc_refresh ();
1143 mc_stat (s, &sbuf);
1144 dstat_ok = (mc_stat (d, &dbuf) == 0);
1146 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
1147 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same directory "), s, d);
1149 if (!dstat_ok)
1150 destdir = g_strdup (d); /* destination doesn't exist */
1151 else if (!ctx->dive_into_subdirs) {
1152 destdir = g_strdup (d);
1153 move_over = TRUE;
1154 } else
1155 destdir = concat_dir_and_file (d, x_basename (s));
1157 /* Check if the user inputted an existing dir */
1158 retry_dst_stat:
1159 if (!mc_stat (destdir, &destbuf)) {
1160 if (move_over) {
1161 return_status = copy_dir_dir (ctx, s, destdir, 0, 1, 1, 0,
1162 progress_count, progress_bytes);
1164 if (return_status != FILE_CONT)
1165 goto ret;
1166 goto oktoret;
1167 } else {
1168 if (S_ISDIR (destbuf.st_mode))
1169 return_status =
1170 file_error (_
1171 (" Cannot overwrite directory \"%s\" %s "),
1172 destdir);
1173 else
1174 return_status =
1175 file_error (_(" Cannot overwrite file \"%s\" %s "),
1176 destdir);
1177 if (return_status == FILE_RETRY)
1178 goto retry_dst_stat;
1180 g_free (destdir);
1181 return return_status;
1184 retry_rename:
1185 if (mc_rename (s, destdir) == 0) {
1186 return_status = FILE_CONT;
1187 goto ret;
1190 if (errno != EXDEV) {
1191 return_status =
1192 files_error (_
1193 (" Cannot move directory \"%s\" to \"%s\" \n %s "),
1194 s, d);
1195 if (return_status == FILE_RETRY)
1196 goto retry_rename;
1197 goto ret;
1199 /* Failed because of filesystem boundary -> copy dir instead */
1200 return_status =
1201 copy_dir_dir (ctx, s, destdir, 0, 0, 1, 0, progress_count,
1202 progress_bytes);
1204 if (return_status != FILE_CONT)
1205 goto ret;
1206 oktoret:
1207 if ((return_status =
1208 file_progress_show_source (ctx, NULL)) != FILE_CONT
1209 || (return_status = file_progress_show (ctx, 0, 0)) != FILE_CONT)
1210 goto ret;
1212 mc_refresh ();
1213 if (ctx->erase_at_end) {
1214 for (; erase_list && return_status != FILE_ABORT;) {
1215 if (S_ISDIR (erase_list->st_mode)) {
1216 return_status =
1217 erase_dir_iff_empty (ctx, erase_list->name);
1218 } else
1219 return_status =
1220 erase_file (ctx, erase_list->name, 0, 0, 0);
1221 lp = erase_list;
1222 erase_list = erase_list->next;
1223 g_free (lp);
1226 erase_dir_iff_empty (ctx, s);
1228 ret:
1229 g_free (destdir);
1230 while (erase_list) {
1231 lp = erase_list;
1232 erase_list = erase_list->next;
1233 g_free (lp);
1235 return return_status;
1238 /* }}} */
1240 /* {{{ Erase routines */
1241 /* Don't update progress status if progress_count==NULL */
1242 static FileProgressStatus
1243 erase_file (FileOpContext *ctx, const char *s, off_t *progress_count,
1244 double *progress_bytes, int is_toplevel_file)
1246 int return_status;
1247 struct stat buf;
1249 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1250 return FILE_ABORT;
1251 mc_refresh ();
1253 if (progress_count && mc_lstat (s, &buf)) {
1254 /* ignore, most likely the mc_unlink fails, too */
1255 buf.st_size = 0;
1258 while (mc_unlink (s)) {
1259 return_status =
1260 file_error (_(" Cannot delete file \"%s\" \n %s "), s);
1261 if (return_status != FILE_RETRY)
1262 return return_status;
1265 if (progress_count)
1266 return progress_update_one (ctx, progress_count, progress_bytes,
1267 buf.st_size, is_toplevel_file);
1268 else
1269 return FILE_CONT;
1272 static FileProgressStatus
1273 recursive_erase (FileOpContext *ctx, const char *s, off_t *progress_count,
1274 double *progress_bytes)
1276 struct dirent *next;
1277 struct stat buf;
1278 DIR *reading;
1279 char *path;
1280 FileProgressStatus return_status = FILE_CONT;
1282 if (!strcmp (s, ".."))
1283 return FILE_RETRY;
1285 reading = mc_opendir (s);
1287 if (!reading)
1288 return FILE_RETRY;
1290 while ((next = mc_readdir (reading)) && return_status == FILE_CONT) {
1291 if (!strcmp (next->d_name, "."))
1292 continue;
1293 if (!strcmp (next->d_name, ".."))
1294 continue;
1295 path = concat_dir_and_file (s, next->d_name);
1296 if (mc_lstat (path, &buf)) {
1297 g_free (path);
1298 mc_closedir (reading);
1299 return FILE_RETRY;
1301 if (S_ISDIR (buf.st_mode))
1302 return_status =
1303 (recursive_erase
1304 (ctx, path, progress_count, progress_bytes)
1305 != FILE_CONT) ? FILE_RETRY : FILE_CONT;
1306 else
1307 return_status =
1308 erase_file (ctx, path, progress_count, progress_bytes, 0);
1309 g_free (path);
1311 mc_closedir (reading);
1312 if (return_status != FILE_CONT)
1313 return return_status;
1314 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1315 return FILE_ABORT;
1316 mc_refresh ();
1318 while (my_rmdir (s)) {
1319 return_status =
1320 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1321 if (return_status != FILE_RETRY)
1322 return return_status;
1325 return FILE_CONT;
1328 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1329 in the directory path points to, 0 else. */
1330 static int
1331 check_dir_is_empty (const char *path)
1333 DIR *dir;
1334 struct dirent *d;
1335 int i;
1337 dir = mc_opendir (path);
1338 if (!dir)
1339 return -1;
1341 for (i = 1, d = mc_readdir (dir); d; d = mc_readdir (dir)) {
1342 if (d->d_name[0] == '.' && (d->d_name[1] == '\0' ||
1343 (d->d_name[1] == '.'
1344 && d->d_name[2] == '\0')))
1345 continue; /* "." or ".." */
1346 i = 0;
1347 break;
1350 mc_closedir (dir);
1351 return i;
1354 FileProgressStatus
1355 erase_dir (FileOpContext *ctx, const char *s, off_t *progress_count,
1356 double *progress_bytes)
1358 FileProgressStatus error;
1360 if (strcmp (s, "..") == 0)
1361 return FILE_SKIP;
1363 if (strcmp (s, ".") == 0)
1364 return FILE_SKIP;
1366 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1367 return FILE_ABORT;
1368 mc_refresh ();
1370 /* The old way to detect a non empty directory was:
1371 error = my_rmdir (s);
1372 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1373 For the linux user space nfs server (nfs-server-2.2beta29-2)
1374 we would have to check also for EIO. I hope the new way is
1375 fool proof. (Norbert)
1377 error = check_dir_is_empty (s);
1378 if (error == 0) { /* not empty */
1379 error = query_recursive (ctx, s);
1380 if (error == FILE_CONT)
1381 return recursive_erase (ctx, s, progress_count,
1382 progress_bytes);
1383 else
1384 return error;
1387 while (my_rmdir (s) == -1) {
1388 error =
1389 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1390 if (error != FILE_RETRY)
1391 return error;
1394 return FILE_CONT;
1397 static FileProgressStatus
1398 erase_dir_iff_empty (FileOpContext *ctx, const char *s)
1400 FileProgressStatus error;
1402 if (strcmp (s, "..") == 0)
1403 return FILE_SKIP;
1405 if (strcmp (s, ".") == 0)
1406 return FILE_SKIP;
1408 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1409 return FILE_ABORT;
1410 mc_refresh ();
1412 if (1 != check_dir_is_empty (s)) /* not empty or error */
1413 return FILE_CONT;
1415 while (my_rmdir (s)) {
1416 error =
1417 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1418 if (error != FILE_RETRY)
1419 return error;
1422 return FILE_CONT;
1425 /* }}} */
1427 /* {{{ Panel operate routines */
1430 * Return currently selected entry name or the name of the first marked
1431 * entry if there is one.
1433 static char *
1434 panel_get_file (WPanel *panel, struct stat *stat_buf)
1436 int i;
1438 if (get_current_type () == view_tree) {
1439 WTree *tree = (WTree *) get_panel_widget (get_current_index ());
1440 char *tree_name = tree_selected_name (tree);
1442 mc_stat (tree_name, stat_buf);
1443 return tree_name;
1446 if (panel->marked) {
1447 for (i = 0; i < panel->count; i++)
1448 if (panel->dir.list[i].f.marked) {
1449 *stat_buf = panel->dir.list[i].st;
1450 return panel->dir.list[i].fname;
1452 } else {
1453 *stat_buf = panel->dir.list[panel->selected].st;
1454 return panel->dir.list[panel->selected].fname;
1456 g_assert_not_reached ();
1457 return NULL;
1461 ComputeDirSizeUI *
1462 compute_dir_size_create_ui (void)
1464 ComputeDirSizeUI *ui;
1466 const char *b_name = N_("&Abort");
1468 #ifdef ENABLE_NLS
1469 b_name = _(b_name);
1470 #endif
1472 ui = g_new (ComputeDirSizeUI, 1);
1474 ui->dlg = create_dlg (0, 0, 8, COLS/2, dialog_colors, NULL,
1475 NULL, _("Directory scanning"), DLG_CENTER);
1476 ui->dirname = label_new (3, 3, "");
1477 add_widget (ui->dlg, ui->dirname);
1479 add_widget (ui->dlg,
1480 button_new (5, (ui->dlg->cols - strlen (b_name))/2,
1481 FILE_ABORT, NORMAL_BUTTON, b_name, NULL));
1483 /* We will manage the dialog without any help,
1484 that's why we have to call init_dlg */
1485 init_dlg (ui->dlg);
1487 return ui;
1490 void
1491 compute_dir_size_destroy_ui (ComputeDirSizeUI *ui)
1493 if (ui != NULL) {
1494 /* schedule to update passive panel */
1495 other_panel->dirty = 1;
1497 /* close and destroy dialog */
1498 dlg_run_done (ui->dlg);
1499 destroy_dlg (ui->dlg);
1500 g_free (ui);
1504 FileProgressStatus
1505 compute_dir_size_update_ui (const void *ui, const char *dirname)
1507 const ComputeDirSizeUI *this = (const ComputeDirSizeUI *) ui;
1508 int c;
1509 Gpm_Event event;
1511 if (ui == NULL)
1512 return FILE_CONT;
1514 label_set_text (this->dirname, name_trunc (dirname, this->dlg->cols - 6));
1516 event.x = -1; /* Don't show the GPM cursor */
1517 c = tty_get_event (&event, FALSE, FALSE);
1518 if (c == EV_NONE)
1519 return FILE_CONT;
1521 /* Reinitialize to avoid old values after events other than
1522 selecting a button */
1523 this->dlg->ret_value = FILE_CONT;
1525 dlg_process_event (this->dlg, c, &event);
1527 switch (this->dlg->ret_value) {
1528 case B_CANCEL:
1529 case FILE_ABORT:
1530 return FILE_ABORT;
1531 default:
1532 return FILE_CONT;
1537 * compute_dir_size:
1539 * Computes the number of bytes used by the files in a directory
1541 FileProgressStatus
1542 compute_dir_size (const char *dirname, const void *ui,
1543 compute_dir_size_callback cback,
1544 off_t *ret_marked, double *ret_total)
1546 DIR *dir;
1547 struct dirent *dirent;
1548 FileProgressStatus ret = FILE_CONT;
1550 dir = mc_opendir (dirname);
1552 if (dir == NULL)
1553 return ret;
1555 while ((dirent = mc_readdir (dir)) != NULL) {
1556 char *fullname;
1557 int res;
1558 struct stat s;
1560 ret = (cback != NULL) ? cback (ui, dirname) : FILE_CONT;
1562 if (ret != FILE_CONT)
1563 break;
1565 if (strcmp (dirent->d_name, ".") == 0)
1566 continue;
1567 if (strcmp (dirent->d_name, "..") == 0)
1568 continue;
1570 fullname = concat_dir_and_file (dirname, dirent->d_name);
1571 res = mc_lstat (fullname, &s);
1573 if (res != 0) {
1574 g_free (fullname);
1575 continue;
1578 if (S_ISDIR (s.st_mode)) {
1579 off_t subdir_count = 0;
1580 double subdir_bytes = 0;
1582 ret = compute_dir_size (fullname, ui, cback, &subdir_count, &subdir_bytes);
1584 if (ret != FILE_CONT) {
1585 g_free (fullname);
1586 break;
1589 *ret_marked += subdir_count;
1590 *ret_total += subdir_bytes;
1591 } else {
1592 (*ret_marked)++;
1593 *ret_total += s.st_size;
1596 g_free (fullname);
1599 mc_closedir (dir);
1601 return ret;
1605 * panel_compute_totals:
1607 * compute the number of files and the number of bytes
1608 * used up by the whole selection, recursing directories
1609 * as required. In addition, it checks to see if it will
1610 * overwrite any files by doing the copy.
1612 static FileProgressStatus
1613 panel_compute_totals (WPanel *panel, const void *ui,
1614 compute_dir_size_callback cback,
1615 off_t *ret_marked, double *ret_total)
1617 int i;
1619 *ret_marked = 0;
1620 *ret_total = 0.0;
1622 for (i = 0; i < panel->count; i++) {
1623 struct stat *s;
1625 if (!panel->dir.list[i].f.marked)
1626 continue;
1628 s = &panel->dir.list[i].st;
1630 if (S_ISDIR (s->st_mode)) {
1631 char *dir_name;
1632 off_t subdir_count = 0;
1633 double subdir_bytes = 0;
1634 FileProgressStatus status;
1636 dir_name =
1637 concat_dir_and_file (panel->cwd, panel->dir.list[i].fname);
1639 status = compute_dir_size (dir_name, ui, cback,
1640 &subdir_count, &subdir_bytes);
1641 g_free (dir_name);
1643 if (status != FILE_CONT)
1644 return FILE_ABORT;
1646 *ret_marked += subdir_count;
1647 *ret_total += subdir_bytes;
1648 } else {
1649 (*ret_marked)++;
1650 *ret_total += s->st_size;
1654 return FILE_CONT;
1658 * This array introduced to avoid translation problems. The former (op_names)
1659 * is assumed to be nouns, suitable in dialog box titles; this one should
1660 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
1661 * (I don't use spaces around the words, because someday they could be
1662 * dropped, when widgets get smarter)
1665 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
1666 static const char *op_names1[] = {
1667 N_("FileOperation|Copy"),
1668 N_("FileOperation|Move"),
1669 N_("FileOperation|Delete")
1673 * These are formats for building a prompt. Parts encoded as follows:
1674 * %o - operation from op_names1
1675 * %f - file/files or files/directories, as appropriate
1676 * %m - "with source mask" or question mark for delete
1677 * %s - source name (truncated)
1678 * %d - number of marked files
1679 * %e - "to:" or question mark for delete
1681 * xgettext:no-c-format */
1682 static const char *one_format = N_("%o %f \"%s\"%m");
1683 /* xgettext:no-c-format */
1684 static const char *many_format = N_("%o %d %f%m");
1686 static const char *prompt_parts[] = {
1687 N_("file"),
1688 N_("files"),
1689 N_("directory"),
1690 N_("directories"),
1691 N_("files/directories"),
1692 N_(" with source mask:"),
1693 N_(" to:")
1696 static const char *question_format = N_("%s?");
1699 * Generate user prompt for panel operation.
1700 * single_source is the name if the source entry or NULL for multiple
1701 * entries.
1702 * src_stat is only used when single_source is not NULL.
1704 static char *
1705 panel_operate_generate_prompt (const WPanel *panel, const int operation,
1706 gboolean single_source,
1707 const struct stat *src_stat)
1709 const char *sp, *cp;
1710 char format_string[BUF_MEDIUM];
1711 char *dp = format_string;
1712 gboolean build_question = FALSE;
1714 #ifdef ENABLE_NLS
1715 static gboolean i18n_flag = FALSE;
1716 if (!i18n_flag) {
1717 size_t i;
1719 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1720 op_names1[i] = Q_(op_names1[i]);
1722 for (i = sizeof (prompt_parts) / sizeof (prompt_parts[0]); i--;)
1723 prompt_parts[i] = _(prompt_parts[i]);
1725 one_format = _(one_format);
1726 many_format = _(many_format);
1727 question_format = _(question_format);
1728 i18n_flag = TRUE;
1730 #endif /* ENABLE_NLS */
1732 sp = single_source ? one_format : many_format;
1734 while (*sp != '\0') {
1735 switch (*sp) {
1736 case '%':
1737 cp = NULL;
1738 switch (sp[1]) {
1739 case 'o':
1740 cp = op_names1[operation];
1741 break;
1742 case 'm':
1743 if (operation == OP_DELETE) {
1744 cp = "";
1745 build_question = TRUE;
1746 } else
1747 cp = prompt_parts[5];
1748 break;
1749 case 'e':
1750 if (operation == OP_DELETE) {
1751 cp = "";
1752 build_question = TRUE;
1753 } else
1754 cp = prompt_parts[6];
1755 break;
1756 case 'f':
1757 if (single_source) {
1758 cp = S_ISDIR (src_stat->
1759 st_mode) ? prompt_parts[2] : prompt_parts[0];
1760 } else {
1761 cp = (panel->marked == panel->dirs_marked)
1762 ? prompt_parts[3]
1763 : (panel->dirs_marked ? prompt_parts[4] : prompt_parts[1]);
1765 break;
1766 default:
1767 *dp++ = *sp++;
1769 if (cp != NULL) {
1770 sp += 2;
1771 while (*cp != '\0')
1772 *dp++ = *cp++;
1774 break;
1775 default:
1776 *dp++ = *sp++;
1779 *dp = '\0';
1781 if (build_question) {
1782 char tmp[BUF_MEDIUM];
1784 memmove (tmp, format_string, sizeof (tmp));
1785 g_snprintf (format_string, sizeof (format_string),
1786 question_format, tmp);
1789 return g_strdup (format_string);
1792 #ifdef WITH_BACKGROUND
1793 static int
1794 end_bg_process (FileOpContext *ctx, enum OperationMode mode) {
1795 int pid = ctx->pid;
1797 (void) mode;
1798 ctx->pid = 0;
1800 unregister_task_with_pid(pid);
1801 // file_op_context_destroy(ctx);
1802 return 1;
1804 #endif
1807 * panel_operate:
1809 * Performs one of the operations on the selection on the source_panel
1810 * (copy, delete, move).
1812 * Returns 1 if did change the directory
1813 * structure, Returns 0 if user aborted
1815 * force_single forces operation on the current entry and affects
1816 * default destination. Current filename is used as default.
1819 panel_operate (void *source_panel, FileOperation operation, 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: