Clarify usage of st_rdev. Use it if HAVE_STRUCT_STAT_ST_RDEV is defined.
[midnight-commander.git] / src / filemanager / file.c
blobd1c8d6567b3967b96ead274931bf95b86dccb6f1
1 /*
2 File management.
4 Copyright (C) 1994-2016
5 Free Software Foundation, Inc.
7 Written by:
8 Janne Kukonlehto, 1994, 1995
9 Fred Leeflang, 1994, 1995
10 Miguel de Icaza, 1994, 1995, 1996
11 Jakub Jelinek, 1995, 1996
12 Norbert Warmuth, 1997
13 Pavel Machek, 1998
14 Andrew Borodin <aborodin@vmail.ru>, 2011-2014
16 The copy code was based in GNU's cp, and was written by:
17 Torbjorn Granlund, David MacKenzie, and Jim Meyering.
19 The move code was based in GNU's mv, and was written by:
20 Mike Parker and David MacKenzie.
22 Janne Kukonlehto added much error recovery to them for being used
23 in an interactive program.
25 This file is part of the Midnight Commander.
27 The Midnight Commander is free software: you can redistribute it
28 and/or modify it under the terms of the GNU General Public License as
29 published by the Free Software Foundation, either version 3 of the License,
30 or (at your option) any later version.
32 The Midnight Commander is distributed in the hope that it will be useful,
33 but WITHOUT ANY WARRANTY; without even the implied warranty of
34 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 GNU General Public License for more details.
37 You should have received a copy of the GNU General Public License
38 along with this program. If not, see <http://www.gnu.org/licenses/>.
42 * Please note that all dialogs used here must be safe for background
43 * operations.
46 /** \file src/filemanager/file.c
47 * \brief Source: file management
50 /* {{{ Include files */
52 #include <config.h>
54 #include <ctype.h>
55 #include <errno.h>
56 #include <stdlib.h>
57 #include <stdio.h>
58 #include <string.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <unistd.h>
63 #include "lib/global.h"
64 #include "lib/tty/tty.h"
65 #include "lib/tty/key.h"
66 #include "lib/search.h"
67 #include "lib/strescape.h"
68 #include "lib/strutil.h"
69 #include "lib/util.h"
70 #include "lib/vfs/vfs.h"
71 #include "lib/widget.h"
73 #include "src/setup.h"
74 #ifdef ENABLE_BACKGROUND
75 #include "src/background.h" /* do_background() */
76 #endif
78 /* Needed for current_panel, other_panel and WTree */
79 #include "dir.h"
80 #include "filegui.h"
81 #include "filenot.h"
82 #include "tree.h"
83 #include "midnight.h" /* current_panel */
84 #include "layout.h" /* rotate_dash() */
85 #include "ioblksize.h" /* io_blksize() */
87 #include "file.h"
89 /* }}} */
91 /*** global variables ****************************************************************************/
93 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */
94 const char *op_names[3] = {
95 N_("DialogTitle|Copy"),
96 N_("DialogTitle|Move"),
97 N_("DialogTitle|Delete")
100 /*** file scope macro definitions ****************************************************************/
102 /* Hack: the vfs code should not rely on this */
103 #define WITH_FULL_PATHS 1
105 #define FILEOP_UPDATE_INTERVAL 2
106 #define FILEOP_STALLING_INTERVAL 4
108 /*** file scope type declarations ****************************************************************/
110 /* This is a hard link cache */
111 struct link
113 const struct vfs_class *vfs;
114 dev_t dev;
115 ino_t ino;
116 short linkcount;
117 mode_t st_mode;
118 vfs_path_t *src_vpath;
119 vfs_path_t *dst_vpath;
122 /* Status of the destination file */
123 typedef enum
125 DEST_NONE = 0, /* Not created */
126 DEST_SHORT = 1, /* Created, not fully copied */
127 DEST_FULL = 2 /* Created, fully copied */
128 } dest_status_t;
131 * This array introduced to avoid translation problems. The former (op_names)
132 * is assumed to be nouns, suitable in dialog box titles; this one should
133 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
134 * (I don't use spaces around the words, because someday they could be
135 * dropped, when widgets get smarter)
138 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
139 static const char *op_names1[] = {
140 N_("FileOperation|Copy"),
141 N_("FileOperation|Move"),
142 N_("FileOperation|Delete")
146 * These are formats for building a prompt. Parts encoded as follows:
147 * %o - operation from op_names1
148 * %f - file/files or files/directories, as appropriate
149 * %m - "with source mask" or question mark for delete
150 * %s - source name (truncated)
151 * %d - number of marked files
152 * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
154 /* xgettext:no-c-format */
155 static const char *one_format = N_("%o %f%n\"%s\"%m");
156 /* xgettext:no-c-format */
157 static const char *many_format = N_("%o %d %f%m");
159 static const char *prompt_parts[] = {
160 N_("file"),
161 N_("files"),
162 N_("directory"),
163 N_("directories"),
164 N_("files/directories"),
165 /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
166 N_(" with source mask:")
169 /*** file scope variables ************************************************************************/
171 /* the hard link cache */
172 static GSList *linklist = NULL;
174 /* the files-to-be-erased list */
175 static GSList *erase_list = NULL;
178 * In copy_dir_dir we use two additional single linked lists: The first -
179 * variable name 'parent_dirs' - holds information about already copied
180 * directories and is used to detect cyclic symbolic links.
181 * The second ('dest_dirs' below) holds information about just created
182 * target directories and is used to detect when an directory is copied
183 * into itself (we don't want to copy infinitly).
184 * Both lists don't use the linkcount and name structure members of struct
185 * link.
187 static GSList *dest_dirs = NULL;
189 static FileProgressStatus transform_error = FILE_CONT;
191 /* --------------------------------------------------------------------------------------------- */
192 /*** file scope functions ************************************************************************/
193 /* --------------------------------------------------------------------------------------------- */
195 static void
196 dirsize_status_locate_buttons (dirsize_status_msg_t * dsm)
198 status_msg_t *sm = STATUS_MSG (dsm);
199 Widget *wd = WIDGET (sm->dlg);
200 int y, x;
202 y = wd->y + 5;
203 x = wd->x;
205 if (!dsm->allow_skip)
207 /* single button: "Abort" */
208 x += (wd->cols - dsm->abort_button->cols) / 2;
209 widget_set_size (dsm->abort_button, y, x,
210 dsm->abort_button->lines, dsm->abort_button->cols);
212 else
214 /* two buttons: "Abort" and "Skip" */
215 int cols;
217 cols = dsm->abort_button->cols + dsm->skip_button->cols + 1;
218 x += (wd->cols - cols) / 2;
219 widget_set_size (dsm->abort_button, y, x, dsm->abort_button->lines,
220 dsm->abort_button->cols);
221 x += dsm->abort_button->cols + 1;
222 widget_set_size (dsm->skip_button, y, x, dsm->skip_button->lines, dsm->skip_button->cols);
226 /* --------------------------------------------------------------------------------------------- */
228 static char *
229 transform_source (file_op_context_t * ctx, const vfs_path_t * source_vpath)
231 char *s, *q;
232 const char *fnsource;
234 s = g_strdup (vfs_path_as_str (source_vpath));
236 /* We remove \n from the filename since regex routines would use \n as an anchor */
237 /* this is just to be allowed to maniupulate file names with \n on it */
238 for (q = s; *q != '\0'; q++)
239 if (*q == '\n')
240 *q = ' ';
242 fnsource = x_basename (s);
244 if (mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
246 q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
247 if (ctx->search_handle->error != MC_SEARCH_E_OK)
249 if (ctx->search_handle->error_str != NULL)
250 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
252 q = NULL;
253 transform_error = FILE_ABORT;
256 else
258 q = NULL;
259 transform_error = FILE_SKIP;
262 g_free (s);
263 return q;
266 /* --------------------------------------------------------------------------------------------- */
268 static void
269 free_link (void *data)
271 struct link *lp = (struct link *) data;
273 vfs_path_free (lp->src_vpath);
274 vfs_path_free (lp->dst_vpath);
275 g_free (lp);
278 /* --------------------------------------------------------------------------------------------- */
280 static inline void *
281 free_linklist (GSList * lp)
283 g_slist_free_full (lp, free_link);
285 return NULL;
288 /* --------------------------------------------------------------------------------------------- */
290 static gboolean
291 is_in_linklist (const GSList * lp, const vfs_path_t * vpath, const struct stat *sb)
293 const struct vfs_class *class;
294 ino_t ino = sb->st_ino;
295 dev_t dev = sb->st_dev;
297 class = vfs_path_get_last_path_vfs (vpath);
299 for (; lp != NULL; lp = (const GSList *) g_slist_next (lp))
301 const struct link *lnk = (const struct link *) lp->data;
303 if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev)
304 return TRUE;
306 return FALSE;
309 /* --------------------------------------------------------------------------------------------- */
311 * Check and made hardlink
313 * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
314 * and a hardlink was successfully made
317 static gboolean
318 check_hardlinks (const vfs_path_t * src_vpath, const vfs_path_t * dst_vpath, struct stat *pstat)
320 GSList *lp;
321 struct link *lnk;
323 const struct vfs_class *my_vfs;
324 ino_t ino = pstat->st_ino;
325 dev_t dev = pstat->st_dev;
326 struct stat link_stat;
328 if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0)
329 return FALSE;
331 my_vfs = vfs_path_get_by_index (src_vpath, -1)->class;
333 for (lp = linklist; lp != NULL; lp = g_slist_next (lp))
335 lnk = (struct link *) lp->data;
337 if (lnk->vfs == my_vfs && lnk->ino == ino && lnk->dev == dev)
339 const struct vfs_class *lp_name_class;
340 int stat_result;
342 lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath);
343 stat_result = mc_stat (lnk->src_vpath, &link_stat);
345 if (stat_result == 0 && link_stat.st_ino == ino
346 && link_stat.st_dev == dev && lp_name_class == my_vfs)
348 const struct vfs_class *p_class, *dst_name_class;
350 dst_name_class = vfs_path_get_last_path_vfs (dst_vpath);
351 p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath);
353 if (dst_name_class == p_class &&
354 mc_stat (lnk->dst_vpath, &link_stat) == 0 &&
355 mc_link (lnk->dst_vpath, dst_vpath) == 0)
356 return TRUE;
359 message (D_ERROR, MSG_ERROR, _("Cannot make the hardlink"));
360 return FALSE;
364 lnk = g_try_new0 (struct link, 1);
365 if (lnk != NULL)
367 lnk->vfs = my_vfs;
368 lnk->ino = ino;
369 lnk->dev = dev;
370 lnk->src_vpath = vfs_path_clone (src_vpath);
371 lnk->dst_vpath = vfs_path_clone (dst_vpath);
372 linklist = g_slist_prepend (linklist, lnk);
375 return FALSE;
378 /* --------------------------------------------------------------------------------------------- */
380 * Duplicate the contents of the symbolic link src_path in dst_path.
381 * Try to make a stable symlink if the option "stable symlink" was
382 * set in the file mask dialog.
383 * If dst_path is an existing symlink it will be deleted silently
384 * (upper levels take already care of existing files at dst_path).
387 static FileProgressStatus
388 make_symlink (file_op_context_t * ctx, const char *src_path, const char *dst_path)
390 char link_target[MC_MAXPATHLEN];
391 int len;
392 FileProgressStatus return_status;
393 struct stat sb;
394 vfs_path_t *src_vpath;
395 vfs_path_t *dst_vpath;
396 gboolean dst_is_symlink;
397 vfs_path_t *link_target_vpath = NULL;
399 src_vpath = vfs_path_from_str (src_path);
400 dst_vpath = vfs_path_from_str (dst_path);
401 dst_is_symlink = (mc_lstat (dst_vpath, &sb) == 0) && S_ISLNK (sb.st_mode);
403 retry_src_readlink:
404 len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1);
405 if (len < 0)
407 if (ctx->skip_all)
408 return_status = FILE_SKIPALL;
409 else
411 return_status = file_error (_("Cannot read source link \"%s\"\n%s"), src_path);
412 if (return_status == FILE_SKIPALL)
413 ctx->skip_all = TRUE;
414 if (return_status == FILE_RETRY)
415 goto retry_src_readlink;
417 goto ret;
420 link_target[len] = '\0';
422 if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath)))
424 message (D_ERROR, MSG_ERROR,
425 _("Cannot make stable symlinks across"
426 "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
427 ctx->stable_symlinks = FALSE;
430 if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
432 const char *r;
434 r = strrchr (src_path, PATH_SEP);
435 if (r != NULL)
437 char *p;
438 vfs_path_t *q;
440 p = g_strndup (src_path, r - src_path + 1);
441 if (g_path_is_absolute (dst_path))
442 q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
443 else
444 q = vfs_path_build_filename (p, dst_path, (char *) NULL);
446 if (vfs_path_tokens_count (q) > 1)
448 char *s;
449 vfs_path_t *tmp_vpath1, *tmp_vpath2;
451 tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
452 s = g_strconcat (p, link_target, (char *) NULL);
453 g_strlcpy (link_target, s, sizeof (link_target));
454 g_free (s);
455 tmp_vpath2 = vfs_path_from_str (link_target);
456 s = diff_two_paths (tmp_vpath1, tmp_vpath2);
457 vfs_path_free (tmp_vpath1);
458 vfs_path_free (tmp_vpath2);
459 if (s != NULL)
461 g_strlcpy (link_target, s, sizeof (link_target));
462 g_free (s);
465 g_free (p);
466 vfs_path_free (q);
469 link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON);
471 retry_dst_symlink:
472 if (mc_symlink (link_target_vpath, dst_vpath) == 0)
474 /* Success */
475 return_status = FILE_CONT;
476 goto ret;
479 * if dst_exists, it is obvious that this had failed.
480 * We can delete the old symlink and try again...
482 if (dst_is_symlink && mc_unlink (dst_vpath) == 0
483 && mc_symlink (link_target_vpath, dst_vpath) == 0)
485 /* Success */
486 return_status = FILE_CONT;
487 goto ret;
490 if (ctx->skip_all)
491 return_status = FILE_SKIPALL;
492 else
494 return_status = file_error (_("Cannot create target symlink \"%s\"\n%s"), dst_path);
495 if (return_status == FILE_SKIPALL)
496 ctx->skip_all = TRUE;
497 if (return_status == FILE_RETRY)
498 goto retry_dst_symlink;
501 ret:
502 vfs_path_free (src_vpath);
503 vfs_path_free (dst_vpath);
504 vfs_path_free (link_target_vpath);
505 return return_status;
508 /* --------------------------------------------------------------------------------------------- */
510 * do_compute_dir_size:
512 * Computes the number of bytes used by the files in a directory
515 static FileProgressStatus
516 do_compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * dsm,
517 size_t * dir_count, size_t * ret_marked, uintmax_t * ret_total,
518 gboolean compute_symlinks)
520 static guint64 timestamp = 0;
521 /* update with 25 FPS rate */
522 static const guint64 delay = G_USEC_PER_SEC / 25;
524 status_msg_t *sm = STATUS_MSG (dsm);
525 int res;
526 struct stat s;
527 DIR *dir;
528 struct dirent *dirent;
529 FileProgressStatus ret = FILE_CONT;
531 if (!compute_symlinks)
533 res = mc_lstat (dirname_vpath, &s);
534 if (res != 0)
535 return ret;
537 /* don't scan symlink to directory */
538 if (S_ISLNK (s.st_mode))
540 (*ret_marked)++;
541 *ret_total += (uintmax_t) s.st_size;
542 return ret;
546 (*dir_count)++;
548 dir = mc_opendir (dirname_vpath);
549 if (dir == NULL)
550 return ret;
552 while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL)
554 vfs_path_t *tmp_vpath;
556 if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name))
557 continue;
559 tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL);
561 res = mc_lstat (tmp_vpath, &s);
562 if (res == 0)
564 if (S_ISDIR (s.st_mode))
565 ret =
566 do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total,
567 compute_symlinks);
568 else
570 ret = FILE_CONT;
572 (*ret_marked)++;
573 *ret_total += (uintmax_t) s.st_size;
576 if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (&timestamp, delay))
578 dsm->dirname_vpath = tmp_vpath;
579 dsm->dir_count = *dir_count;
580 dsm->total_size = *ret_total;
581 ret = sm->update (sm);
585 vfs_path_free (tmp_vpath);
588 mc_closedir (dir);
589 return ret;
592 /* --------------------------------------------------------------------------------------------- */
594 static FileProgressStatus
595 progress_update_one (file_op_total_context_t * tctx, file_op_context_t * ctx, off_t add)
597 struct timeval tv_current;
598 static struct timeval tv_start = { 0, 0 };
600 tctx->progress_count++;
601 tctx->progress_bytes += (uintmax_t) add;
603 if (tv_start.tv_sec == 0)
605 gettimeofday (&tv_start, (struct timezone *) NULL);
607 gettimeofday (&tv_current, (struct timezone *) NULL);
608 if ((tv_current.tv_sec - tv_start.tv_sec) > FILEOP_UPDATE_INTERVAL)
610 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
612 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
613 file_progress_show_total (tctx, ctx, tctx->progress_bytes, TRUE);
615 tv_start.tv_sec = tv_current.tv_sec;
618 return check_progress_buttons (ctx);
621 /* --------------------------------------------------------------------------------------------- */
623 static FileProgressStatus
624 real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b)
626 char *msg;
627 int result = 0;
628 const char *head_msg;
630 head_msg = mode == Foreground ? MSG_ERROR : _("Background process error");
632 msg = g_strdup_printf (fmt, a, b);
633 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
634 g_free (msg);
635 do_refresh ();
637 return (result == 1) ? FILE_ABORT : FILE_SKIP;
640 /* --------------------------------------------------------------------------------------------- */
642 static FileProgressStatus
643 warn_same_file (const char *fmt, const char *a, const char *b)
645 #ifdef ENABLE_BACKGROUND
646 /* *INDENT-OFF* */
647 union
649 void *p;
650 FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b);
651 } pntr;
652 /* *INDENT-ON* */
654 pntr.f = real_warn_same_file;
656 if (mc_global.we_are_background)
657 return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b);
658 #endif
659 return real_warn_same_file (Foreground, fmt, a, b);
662 /* --------------------------------------------------------------------------------------------- */
664 static void
665 get_times (const struct stat *sb, mc_timesbuf_t * times)
667 #ifdef HAVE_UTIMENSAT
668 (*times)[0] = sb->st_atim;
669 (*times)[1] = sb->st_mtim;
670 #else
671 times->actime = sb->st_atime;
672 times->modtime = sb->st_mtime;
673 #endif
676 /* --------------------------------------------------------------------------------------------- */
677 /* {{{ Query/status report routines */
679 static FileProgressStatus
680 real_do_file_error (enum OperationMode mode, const char *error)
682 int result;
683 const char *msg;
685 msg = mode == Foreground ? MSG_ERROR : _("Background process error");
686 result =
687 query_dialog (msg, error, D_ERROR, 4, _("&Skip"), _("Ski&p all"), _("&Retry"), _("&Abort"));
689 switch (result)
691 case 0:
692 do_refresh ();
693 return FILE_SKIP;
695 case 1:
696 do_refresh ();
697 return FILE_SKIPALL;
699 case 2:
700 do_refresh ();
701 return FILE_RETRY;
703 case 3:
704 default:
705 return FILE_ABORT;
709 /* --------------------------------------------------------------------------------------------- */
711 static FileProgressStatus
712 real_query_recursive (file_op_context_t * ctx, enum OperationMode mode, const char *s)
714 if (ctx->recursive_result < RECURSIVE_ALWAYS)
716 const char *msg;
717 char *text;
719 msg = mode == Foreground
720 ? _("Directory \"%s\" not empty.\nDelete it recursively?")
721 : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
722 text = g_strdup_printf (msg, path_trunc (s, 30));
724 if (safe_delete)
725 query_set_sel (1);
727 ctx->recursive_result =
728 query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"),
729 _("Non&e"), _("&Abort"));
730 g_free (text);
732 if (ctx->recursive_result != RECURSIVE_ABORT)
733 do_refresh ();
736 switch (ctx->recursive_result)
738 case RECURSIVE_YES:
739 case RECURSIVE_ALWAYS:
740 return FILE_CONT;
742 case RECURSIVE_NO:
743 case RECURSIVE_NEVER:
744 return FILE_SKIP;
746 case RECURSIVE_ABORT:
747 default:
748 return FILE_ABORT;
752 /* --------------------------------------------------------------------------------------------- */
754 #ifdef ENABLE_BACKGROUND
755 static FileProgressStatus
756 do_file_error (const char *str)
758 /* *INDENT-OFF* */
759 union
761 void *p;
762 FileProgressStatus (*f) (enum OperationMode, const char *);
763 } pntr;
764 /* *INDENT-ON* */
766 pntr.f = real_do_file_error;
768 if (mc_global.we_are_background)
769 return parent_call (pntr.p, NULL, 1, strlen (str), str);
770 else
771 return real_do_file_error (Foreground, str);
774 /* --------------------------------------------------------------------------------------------- */
776 static FileProgressStatus
777 query_recursive (file_op_context_t * ctx, const char *s)
779 /* *INDENT-OFF* */
780 union
782 void *p;
783 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *);
784 } pntr;
785 /* *INDENT-ON* */
787 pntr.f = real_query_recursive;
789 if (mc_global.we_are_background)
790 return parent_call (pntr.p, ctx, 1, strlen (s), s);
791 else
792 return real_query_recursive (ctx, Foreground, s);
795 /* --------------------------------------------------------------------------------------------- */
797 static FileProgressStatus
798 query_replace (file_op_context_t * ctx, const char *destname, struct stat *_s_stat,
799 struct stat *_d_stat)
801 /* *INDENT-OFF* */
802 union
804 void *p;
805 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *,
806 struct stat *, struct stat *);
807 } pntr;
808 /* *INDENT-ON* */
810 pntr.f = file_progress_real_query_replace;
812 if (mc_global.we_are_background)
813 return parent_call (pntr.p, ctx, 3, strlen (destname), destname,
814 sizeof (struct stat), _s_stat, sizeof (struct stat), _d_stat);
815 else
816 return file_progress_real_query_replace (ctx, Foreground, destname, _s_stat, _d_stat);
819 #else
820 /* --------------------------------------------------------------------------------------------- */
822 static FileProgressStatus
823 do_file_error (const char *str)
825 return real_do_file_error (Foreground, str);
828 /* --------------------------------------------------------------------------------------------- */
830 static FileProgressStatus
831 query_recursive (file_op_context_t * ctx, const char *s)
833 return real_query_recursive (ctx, Foreground, s);
836 /* --------------------------------------------------------------------------------------------- */
838 static FileProgressStatus
839 query_replace (file_op_context_t * ctx, const char *destname, struct stat *_s_stat,
840 struct stat *_d_stat)
842 return file_progress_real_query_replace (ctx, Foreground, destname, _s_stat, _d_stat);
845 #endif /* !ENABLE_BACKGROUND */
847 /* --------------------------------------------------------------------------------------------- */
848 /** Report error with two files */
850 static FileProgressStatus
851 files_error (const char *format, const char *file1, const char *file2)
853 char buf[BUF_MEDIUM];
854 char *nfile1 = g_strdup (path_trunc (file1, 15));
855 char *nfile2 = g_strdup (path_trunc (file2, 15));
857 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno));
859 g_free (nfile1);
860 g_free (nfile2);
862 return do_file_error (buf);
865 /* }}} */
867 /* --------------------------------------------------------------------------------------------- */
869 static void
870 copy_file_file_display_progress (file_op_total_context_t * tctx, file_op_context_t * ctx,
871 struct timeval tv_current, struct timeval tv_transfer_start,
872 off_t file_size, off_t n_read_total)
874 long dt;
876 /* 1. Update rotating dash after some time */
877 rotate_dash (TRUE);
879 /* 3. Compute ETA */
880 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
882 if (n_read_total == 0)
883 ctx->eta_secs = 0.0;
884 else
886 ctx->eta_secs = ((dt / (double) n_read_total) * file_size) - dt;
887 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
890 /* 4. Compute BPS rate */
891 ctx->bps_time = (tv_current.tv_sec - tv_transfer_start.tv_sec);
892 if (ctx->bps_time < 1)
893 ctx->bps_time = 1;
894 ctx->bps = n_read_total / ctx->bps_time;
896 /* 5. Compute total ETA and BPS */
897 if (ctx->progress_bytes != 0)
899 uintmax_t remain_bytes;
901 remain_bytes = ctx->progress_bytes - tctx->copied_bytes;
902 #if 1
904 int total_secs = tv_current.tv_sec - tctx->transfer_start.tv_sec;
906 if (total_secs < 1)
907 total_secs = 1;
909 tctx->bps = tctx->copied_bytes / total_secs;
910 tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
912 #else
913 /* broken on lot of little files */
914 tctx->bps_count++;
915 tctx->bps = (tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count;
916 tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
917 #endif
921 /* --------------------------------------------------------------------------------------------- */
923 /* {{{ Move routines */
924 static FileProgressStatus
925 move_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s,
926 const char *d)
928 struct stat src_stats, dst_stats;
929 FileProgressStatus return_status = FILE_CONT;
930 gboolean copy_done = FALSE;
931 gboolean old_ask_overwrite;
932 vfs_path_t *src_vpath, *dst_vpath;
934 src_vpath = vfs_path_from_str (s);
935 dst_vpath = vfs_path_from_str (d);
937 file_progress_show_source (ctx, src_vpath);
938 file_progress_show_target (ctx, dst_vpath);
940 if (check_progress_buttons (ctx) == FILE_ABORT)
942 return_status = FILE_ABORT;
943 goto ret;
946 mc_refresh ();
948 while (mc_lstat (src_vpath, &src_stats) != 0)
950 /* Source doesn't exist */
951 if (ctx->skip_all)
952 return_status = FILE_SKIPALL;
953 else
955 return_status = file_error (_("Cannot stat file \"%s\"\n%s"), s);
956 if (return_status == FILE_SKIPALL)
957 ctx->skip_all = TRUE;
960 if (return_status != FILE_RETRY)
961 goto ret;
964 if (mc_lstat (dst_vpath, &dst_stats) == 0)
966 if (src_stats.st_dev == dst_stats.st_dev && src_stats.st_ino == dst_stats.st_ino)
968 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), s, d);
969 goto ret;
972 if (S_ISDIR (dst_stats.st_mode))
974 message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
975 do_refresh ();
976 return_status = FILE_SKIP;
977 goto ret;
980 if (confirm_overwrite)
982 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
983 if (return_status != FILE_CONT)
984 goto ret;
986 /* Ok to overwrite */
989 if (!ctx->do_append)
991 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks)
993 return_status = make_symlink (ctx, s, d);
994 if (return_status == FILE_CONT)
995 goto retry_src_remove;
996 goto ret;
999 if (mc_rename (src_vpath, dst_vpath) == 0)
1001 return_status = progress_update_one (tctx, ctx, src_stats.st_size);
1002 goto ret;
1005 #if 0
1006 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1007 one nfs to the same, but on the server it is on two different
1008 filesystems. Then nfs returns EIO instead of EXDEV.
1009 Hope it will not hurt if we always in case of error try to copy/delete. */
1010 else
1011 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1013 if (errno != EXDEV)
1015 if (ctx->skip_all)
1016 return_status = FILE_SKIPALL;
1017 else
1019 return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
1020 if (return_status == FILE_SKIPALL)
1021 ctx->skip_all = TRUE;
1022 if (return_status == FILE_RETRY)
1023 goto retry_rename;
1026 goto ret;
1028 #endif
1030 /* Failed because filesystem boundary -> copy the file instead */
1031 old_ask_overwrite = tctx->ask_overwrite;
1032 tctx->ask_overwrite = FALSE;
1033 return_status = copy_file_file (tctx, ctx, s, d);
1034 tctx->ask_overwrite = old_ask_overwrite;
1035 if (return_status != FILE_CONT)
1036 goto ret;
1038 copy_done = TRUE;
1040 file_progress_show_source (ctx, NULL);
1041 file_progress_show (ctx, 0, 0, "", FALSE);
1043 return_status = check_progress_buttons (ctx);
1044 if (return_status != FILE_CONT)
1045 goto ret;
1046 mc_refresh ();
1048 retry_src_remove:
1049 if (mc_unlink (src_vpath) != 0 && !ctx->skip_all)
1051 return_status = file_error (_("Cannot remove file \"%s\"\n%s"), s);
1052 if (return_status == FILE_RETRY)
1053 goto retry_src_remove;
1054 if (return_status == FILE_SKIPALL)
1055 ctx->skip_all = TRUE;
1056 goto ret;
1059 if (!copy_done)
1060 return_status = progress_update_one (tctx, ctx, src_stats.st_size);
1062 ret:
1063 vfs_path_free (src_vpath);
1064 vfs_path_free (dst_vpath);
1066 return return_status;
1069 /* }}} */
1071 /* --------------------------------------------------------------------------------------------- */
1072 /* {{{ Erase routines */
1073 /** Don't update progress status if progress_count==NULL */
1075 static FileProgressStatus
1076 erase_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
1078 struct stat buf;
1080 file_progress_show_deleting (ctx, vfs_path_as_str (vpath), &tctx->progress_count);
1081 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1082 if (check_progress_buttons (ctx) == FILE_ABORT)
1083 return FILE_ABORT;
1085 mc_refresh ();
1087 if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0)
1089 /* ignore, most likely the mc_unlink fails, too */
1090 buf.st_size = 0;
1093 while (mc_unlink (vpath) != 0 && !ctx->skip_all)
1095 int return_status;
1097 return_status = file_error (_("Cannot delete file \"%s\"\n%s"), vfs_path_as_str (vpath));
1098 if (return_status == FILE_ABORT)
1099 return return_status;
1100 if (return_status == FILE_RETRY)
1101 continue;
1102 if (return_status == FILE_SKIPALL)
1103 ctx->skip_all = TRUE;
1104 break;
1107 if (tctx->progress_count == 0)
1108 return FILE_CONT;
1110 return check_progress_buttons (ctx);
1113 /* --------------------------------------------------------------------------------------------- */
1116 Recursive remove of files
1117 abort->cancel stack
1118 skip ->warn every level, gets default
1119 skipall->remove as much as possible
1121 static FileProgressStatus
1122 recursive_erase (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
1124 struct dirent *next;
1125 DIR *reading;
1126 const char *s;
1127 FileProgressStatus return_status = FILE_CONT;
1129 reading = mc_opendir (vpath);
1130 if (reading == NULL)
1131 return FILE_RETRY;
1133 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
1135 vfs_path_t *tmp_vpath;
1136 struct stat buf;
1138 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
1139 continue;
1141 tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
1142 if (mc_lstat (tmp_vpath, &buf) != 0)
1144 mc_closedir (reading);
1145 vfs_path_free (tmp_vpath);
1146 return FILE_RETRY;
1148 if (S_ISDIR (buf.st_mode))
1149 return_status = recursive_erase (tctx, ctx, tmp_vpath);
1150 else
1151 return_status = erase_file (tctx, ctx, tmp_vpath);
1152 vfs_path_free (tmp_vpath);
1154 mc_closedir (reading);
1156 if (return_status == FILE_ABORT)
1157 return FILE_ABORT;
1159 s = vfs_path_as_str (vpath);
1161 file_progress_show_deleting (ctx, s, NULL);
1162 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1163 if (check_progress_buttons (ctx) == FILE_ABORT)
1164 return FILE_ABORT;
1166 mc_refresh ();
1168 while (my_rmdir (s) != 0 && !ctx->skip_all)
1170 return_status = file_error (_("Cannot remove directory \"%s\"\n%s"), s);
1171 if (return_status == FILE_RETRY)
1172 continue;
1173 if (return_status == FILE_ABORT)
1174 break;
1175 if (return_status == FILE_SKIPALL)
1176 ctx->skip_all = TRUE;
1177 break;
1180 return return_status;
1183 /* --------------------------------------------------------------------------------------------- */
1184 /** Return -1 on error, 1 if there are no entries besides "." and ".."
1185 in the directory path points to, 0 else. */
1187 static int
1188 check_dir_is_empty (const vfs_path_t * vpath)
1190 DIR *dir;
1191 struct dirent *d;
1192 int i = 1;
1194 dir = mc_opendir (vpath);
1195 if (dir == NULL)
1196 return -1;
1198 for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1199 if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1201 i = 0;
1202 break;
1205 mc_closedir (dir);
1206 return i;
1209 /* --------------------------------------------------------------------------------------------- */
1211 static FileProgressStatus
1212 erase_dir_iff_empty (file_op_context_t * ctx, const vfs_path_t * vpath, size_t count)
1214 FileProgressStatus error = FILE_CONT;
1215 const char *s;
1217 s = vfs_path_as_str (vpath);
1219 file_progress_show_deleting (ctx, s, NULL);
1220 file_progress_show_count (ctx, count, ctx->progress_count);
1221 if (check_progress_buttons (ctx) == FILE_ABORT)
1222 return FILE_ABORT;
1224 mc_refresh ();
1226 if (check_dir_is_empty (vpath) == 1) /* not empty or error */
1228 while (my_rmdir (s) != 0 && !ctx->skip_all)
1230 error = file_error (_("Cannot remove directory \"%s\"\n%s"), s);
1231 if (error == FILE_SKIPALL)
1232 ctx->skip_all = TRUE;
1233 if (error != FILE_RETRY)
1234 break;
1238 return error;
1241 /* }}} */
1243 /* --------------------------------------------------------------------------------------------- */
1244 /* {{{ Panel operate routines */
1247 * Return currently selected entry name or the name of the first marked
1248 * entry if there is one.
1251 static const char *
1252 panel_get_file (WPanel * panel)
1254 if (get_current_type () == view_tree)
1256 WTree *tree;
1257 const vfs_path_t *selected_name;
1259 tree = (WTree *) get_panel_widget (get_current_index ());
1260 selected_name = tree_selected_name (tree);
1261 return vfs_path_as_str (selected_name);
1264 if (panel->marked != 0)
1266 int i;
1268 for (i = 0; i < panel->dir.len; i++)
1269 if (panel->dir.list[i].f.marked)
1270 return panel->dir.list[i].fname;
1273 return panel->dir.list[panel->selected].fname;
1276 /* --------------------------------------------------------------------------------------------- */
1278 * panel_compute_totals:
1280 * compute the number of files and the number of bytes
1281 * used up by the whole selection, recursing directories
1282 * as required. In addition, it checks to see if it will
1283 * overwrite any files by doing the copy.
1286 static FileProgressStatus
1287 panel_compute_totals (const WPanel * panel, dirsize_status_msg_t * sm, size_t * ret_count,
1288 uintmax_t * ret_total, gboolean compute_symlinks)
1290 int i;
1291 size_t dir_count = 0;
1293 for (i = 0; i < panel->dir.len; i++)
1295 struct stat *s;
1297 if (!panel->dir.list[i].f.marked)
1298 continue;
1300 s = &panel->dir.list[i].st;
1302 if (S_ISDIR (s->st_mode))
1304 vfs_path_t *p;
1305 FileProgressStatus status;
1307 p = vfs_path_append_new (panel->cwd_vpath, panel->dir.list[i].fname, (char *) NULL);
1308 status = compute_dir_size (p, sm, &dir_count, ret_count, ret_total, compute_symlinks);
1309 vfs_path_free (p);
1311 if (status != FILE_CONT)
1312 return status;
1314 else
1316 (*ret_count)++;
1317 *ret_total += (uintmax_t) s->st_size;
1321 return FILE_CONT;
1324 /* --------------------------------------------------------------------------------------------- */
1326 /** Initialize variables for progress bars */
1327 static FileProgressStatus
1328 panel_operate_init_totals (const WPanel * panel, const char *source, file_op_context_t * ctx,
1329 filegui_dialog_type_t dialog_type)
1331 FileProgressStatus status;
1333 #ifdef ENABLE_BACKGROUND
1334 if (mc_global.we_are_background)
1335 return FILE_CONT;
1336 #endif
1338 if (verbose && file_op_compute_totals)
1340 dirsize_status_msg_t dsm;
1342 memset (&dsm, 0, sizeof (dsm));
1343 dsm.allow_skip = TRUE;
1344 status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
1345 dirsize_status_update_cb, dirsize_status_deinit_cb);
1347 ctx->progress_count = 0;
1348 ctx->progress_bytes = 0;
1350 if (source == NULL)
1351 status = panel_compute_totals (panel, &dsm, &ctx->progress_count, &ctx->progress_bytes,
1352 ctx->follow_links);
1353 else
1355 vfs_path_t *p;
1356 size_t dir_count = 0;
1358 p = vfs_path_from_str (source);
1359 status = compute_dir_size (p, &dsm, &dir_count, &ctx->progress_count,
1360 &ctx->progress_bytes, ctx->follow_links);
1361 vfs_path_free (p);
1364 status_msg_deinit (STATUS_MSG (&dsm));
1366 ctx->progress_totals_computed = (status == FILE_CONT);
1368 if (status == FILE_SKIP)
1369 status = FILE_CONT;
1371 else
1373 status = FILE_CONT;
1374 ctx->progress_count = panel->marked;
1375 ctx->progress_bytes = panel->total;
1376 ctx->progress_totals_computed = FALSE;
1379 file_op_context_create_ui (ctx, TRUE, dialog_type);
1381 return status;
1384 /* --------------------------------------------------------------------------------------------- */
1386 * Generate user prompt for panel operation.
1387 * src_stat must be not NULL for single source, and NULL for multiple sources
1390 static char *
1391 panel_operate_generate_prompt (const WPanel * panel, FileOperation operation,
1392 const struct stat *src_stat)
1394 char *sp;
1395 char *format_string;
1396 const char *cp;
1398 static gboolean i18n_flag = FALSE;
1399 if (!i18n_flag)
1401 size_t i;
1403 for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1404 op_names1[i] = Q_ (op_names1[i]);
1406 #ifdef ENABLE_NLS
1407 for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1408 prompt_parts[i] = _(prompt_parts[i]);
1410 one_format = _(one_format);
1411 many_format = _(many_format);
1412 #endif /* ENABLE_NLS */
1413 i18n_flag = TRUE;
1416 /* Possible prompts:
1417 * OP_COPY:
1418 * "Copy file \"%s\" with source mask:"
1419 * "Copy %d files with source mask:"
1420 * "Copy directory \"%s\" with source mask:"
1421 * "Copy %d directories with source mask:"
1422 * "Copy %d files/directories with source mask:"
1423 * OP_MOVE:
1424 * "Move file \"%s\" with source mask:"
1425 * "Move %d files with source mask:"
1426 * "Move directory \"%s\" with source mask:"
1427 * "Move %d directories with source mask:"
1428 * "Move %d files/directories with source mask:"
1429 * OP_DELETE:
1430 * "Delete file \"%s\"?"
1431 * "Delete %d files?"
1432 * "Delete directory \"%s\"?"
1433 * "Delete %d directories?"
1434 * "Delete %d files/directories?"
1437 cp = (src_stat != NULL ? one_format : many_format);
1439 /* 1. Substitute %o */
1440 format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1442 /* 2. Substitute %n */
1443 cp = operation == OP_DELETE ? "\n" : " ";
1444 sp = format_string;
1445 format_string = str_replace_all (sp, "%n", cp);
1446 g_free (sp);
1448 /* 3. Substitute %f */
1449 if (src_stat != NULL)
1450 cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1451 else if (panel->marked == panel->dirs_marked)
1452 cp = prompt_parts[3];
1453 else
1454 cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1456 sp = format_string;
1457 format_string = str_replace_all (sp, "%f", cp);
1458 g_free (sp);
1460 /* 4. Substitute %m */
1461 cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1462 sp = format_string;
1463 format_string = str_replace_all (sp, "%m", cp);
1464 g_free (sp);
1466 return format_string;
1469 /* --------------------------------------------------------------------------------------------- */
1471 #ifdef ENABLE_BACKGROUND
1472 static int
1473 end_bg_process (file_op_context_t * ctx, enum OperationMode mode)
1475 int pid = ctx->pid;
1477 (void) mode;
1478 ctx->pid = 0;
1480 unregister_task_with_pid (pid);
1481 /* file_op_context_destroy(ctx); */
1482 return 1;
1484 #endif
1485 /* }}} */
1487 /* --------------------------------------------------------------------------------------------- */
1488 /*** public functions ****************************************************************************/
1489 /* --------------------------------------------------------------------------------------------- */
1491 FileProgressStatus
1492 copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx,
1493 const char *src_path, const char *dst_path)
1495 uid_t src_uid = (uid_t) (-1);
1496 gid_t src_gid = (gid_t) (-1);
1498 int src_desc, dest_desc = -1;
1499 mode_t src_mode = 0; /* The mode of the source file */
1500 struct stat src_stat, dst_stat;
1501 mc_timesbuf_t times;
1502 gboolean dst_exists = FALSE, appending = FALSE;
1503 off_t file_size = -1;
1504 FileProgressStatus return_status, temp_status;
1505 struct timeval tv_transfer_start;
1506 dest_status_t dst_status = DEST_NONE;
1507 int open_flags;
1508 vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
1509 char *buf = NULL;
1511 /* FIXME: We should not be using global variables! */
1512 ctx->do_reget = 0;
1513 return_status = FILE_RETRY;
1515 dst_vpath = vfs_path_from_str (dst_path);
1516 src_vpath = vfs_path_from_str (src_path);
1518 file_progress_show_source (ctx, src_vpath);
1519 file_progress_show_target (ctx, dst_vpath);
1521 if (check_progress_buttons (ctx) == FILE_ABORT)
1523 return_status = FILE_ABORT;
1524 goto ret_fast;
1527 mc_refresh ();
1529 while (mc_stat (dst_vpath, &dst_stat) == 0)
1531 if (S_ISDIR (dst_stat.st_mode))
1533 if (ctx->skip_all)
1534 return_status = FILE_SKIPALL;
1535 else
1537 return_status = file_error (_("Cannot overwrite directory \"%s\"\n%s"), dst_path);
1538 if (return_status == FILE_SKIPALL)
1539 ctx->skip_all = TRUE;
1540 if (return_status == FILE_RETRY)
1541 continue;
1543 goto ret_fast;
1546 dst_exists = TRUE;
1547 break;
1550 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
1552 if (ctx->skip_all)
1553 return_status = FILE_SKIPALL;
1554 else
1556 return_status = file_error (_("Cannot stat source file \"%s\"\n%s"), src_path);
1557 if (return_status == FILE_SKIPALL)
1558 ctx->skip_all = TRUE;
1561 if (return_status != FILE_RETRY)
1562 goto ret_fast;
1565 if (dst_exists)
1567 /* Destination already exists */
1568 if (src_stat.st_dev == dst_stat.st_dev && src_stat.st_ino == dst_stat.st_ino)
1570 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"),
1571 src_path, dst_path);
1572 goto ret_fast;
1575 /* Should we replace destination? */
1576 if (tctx->ask_overwrite)
1578 ctx->do_reget = 0;
1579 return_status = query_replace (ctx, dst_path, &src_stat, &dst_stat);
1580 if (return_status != FILE_CONT)
1581 goto ret_fast;
1585 if (!ctx->do_append)
1587 /* Check the hardlinks */
1588 if (!ctx->follow_links && src_stat.st_nlink > 1
1589 && check_hardlinks (src_vpath, dst_vpath, &src_stat))
1591 /* We have made a hardlink - no more processing is necessary */
1592 return_status = FILE_CONT;
1593 goto ret_fast;
1596 if (S_ISLNK (src_stat.st_mode))
1598 return_status = make_symlink (ctx, src_path, dst_path);
1599 goto ret_fast;
1602 if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
1603 || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
1605 dev_t rdev = 0;
1607 #ifdef HAVE_STRUCT_STAT_ST_RDEV
1608 rdev = src_stat.st_rdev;
1609 #endif
1611 while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
1612 && !ctx->skip_all)
1614 return_status = file_error (_("Cannot create special file \"%s\"\n%s"), dst_path);
1615 if (return_status == FILE_RETRY)
1616 continue;
1617 if (return_status == FILE_SKIPALL)
1618 ctx->skip_all = TRUE;
1619 goto ret_fast;
1621 /* Success */
1623 while (ctx->preserve_uidgid
1624 && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all)
1626 temp_status = file_error (_("Cannot chown target file \"%s\"\n%s"), dst_path);
1627 if (temp_status == FILE_SKIP)
1628 break;
1629 if (temp_status == FILE_SKIPALL)
1630 ctx->skip_all = TRUE;
1631 if (temp_status != FILE_RETRY)
1633 return_status = temp_status;
1634 goto ret_fast;
1638 while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
1639 && !ctx->skip_all)
1641 temp_status = file_error (_("Cannot chmod target file \"%s\"\n%s"), dst_path);
1642 if (temp_status == FILE_SKIP)
1643 break;
1644 if (temp_status == FILE_SKIPALL)
1645 ctx->skip_all = TRUE;
1646 if (temp_status != FILE_RETRY)
1648 return_status = temp_status;
1649 goto ret_fast;
1653 return_status = FILE_CONT;
1654 goto ret_fast;
1658 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
1660 while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all)
1662 return_status = file_error (_("Cannot open source file \"%s\"\n%s"), src_path);
1663 if (return_status == FILE_RETRY)
1664 continue;
1665 if (return_status == FILE_SKIPALL)
1666 ctx->skip_all = TRUE;
1667 if (return_status == FILE_SKIP)
1668 break;
1669 ctx->do_append = FALSE;
1670 goto ret_fast;
1673 if (ctx->do_reget != 0)
1675 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
1677 message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
1678 ctx->do_reget = 0;
1679 ctx->do_append = FALSE;
1683 while (mc_fstat (src_desc, &src_stat) != 0)
1685 if (ctx->skip_all)
1686 return_status = FILE_SKIPALL;
1687 else
1689 return_status = file_error (_("Cannot fstat source file \"%s\"\n%s"), src_path);
1690 if (return_status == FILE_RETRY)
1691 continue;
1692 if (return_status == FILE_SKIPALL)
1693 ctx->skip_all = TRUE;
1694 ctx->do_append = FALSE;
1696 goto ret;
1699 src_mode = src_stat.st_mode;
1700 src_uid = src_stat.st_uid;
1701 src_gid = src_stat.st_gid;
1702 get_times (&src_stat, &times);
1703 file_size = src_stat.st_size;
1705 open_flags = O_WRONLY;
1706 if (dst_exists)
1708 if (ctx->do_append)
1709 open_flags |= O_APPEND;
1710 else
1711 open_flags |= O_CREAT | O_TRUNC;
1713 else
1715 open_flags |= O_CREAT | O_EXCL;
1718 while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
1720 if (errno != EEXIST)
1722 if (ctx->skip_all)
1723 return_status = FILE_SKIPALL;
1724 else
1726 return_status = file_error (_("Cannot create target file \"%s\"\n%s"), dst_path);
1727 if (return_status == FILE_RETRY)
1728 continue;
1729 if (return_status == FILE_SKIPALL)
1730 ctx->skip_all = TRUE;
1731 ctx->do_append = FALSE;
1734 goto ret;
1736 dst_status = DEST_SHORT; /* file opened, but not fully copied */
1738 appending = ctx->do_append;
1739 ctx->do_append = FALSE;
1741 /* Find out the optimal buffer size. */
1742 while (mc_fstat (dest_desc, &dst_stat) != 0)
1744 if (ctx->skip_all)
1745 return_status = FILE_SKIPALL;
1746 else
1748 return_status = file_error (_("Cannot fstat target file \"%s\"\n%s"), dst_path);
1749 if (return_status == FILE_RETRY)
1750 continue;
1751 if (return_status == FILE_SKIPALL)
1752 ctx->skip_all = TRUE;
1754 goto ret;
1757 /* try preallocate space; if fail, try copy anyway */
1758 while (vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
1760 if (ctx->skip_all)
1762 /* cannot allocate, start the file copying anyway */
1763 return_status = FILE_CONT;
1764 break;
1767 return_status =
1768 file_error (_("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
1770 if (return_status == FILE_SKIPALL)
1771 ctx->skip_all = TRUE;
1773 if (ctx->skip_all || return_status == FILE_SKIP)
1775 /* skip the space allocation error, start file copying */
1776 return_status = FILE_CONT;
1777 break;
1780 if (return_status == FILE_ABORT)
1782 mc_close (dest_desc);
1783 dest_desc = -1;
1784 mc_unlink (dst_vpath);
1785 dst_status = DEST_NONE;
1786 goto ret;
1789 /* return_status == FILE_RETRY -- try allocate space again */
1792 ctx->eta_secs = 0.0;
1793 ctx->bps = 0;
1795 if (tctx->bps == 0 || (file_size / (tctx->bps)) > FILEOP_UPDATE_INTERVAL)
1796 file_progress_show (ctx, 0, file_size, "", TRUE);
1797 else
1798 file_progress_show (ctx, 1, 1, "", TRUE);
1799 return_status = check_progress_buttons (ctx);
1800 mc_refresh ();
1802 if (return_status == FILE_CONT)
1804 size_t bufsize;
1805 off_t n_read_total = 0;
1806 struct timeval tv_current, tv_last_update, tv_last_input;
1807 int secs, update_secs;
1808 const char *stalled_msg = "";
1809 gboolean is_first_time = TRUE;
1811 tv_last_update = tv_transfer_start;
1813 bufsize = io_blksize (dst_stat);
1814 buf = g_malloc (bufsize);
1816 while (TRUE)
1818 ssize_t n_read = -1, n_written;
1820 /* src_read */
1821 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
1822 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all)
1824 return_status = file_error (_("Cannot read source file \"%s\"\n%s"), src_path);
1825 if (return_status == FILE_RETRY)
1826 continue;
1827 if (return_status == FILE_SKIPALL)
1828 ctx->skip_all = TRUE;
1829 goto ret;
1832 if (n_read == 0)
1833 break;
1835 gettimeofday (&tv_current, NULL);
1837 if (n_read > 0)
1839 char *t = buf;
1841 n_read_total += n_read;
1843 /* Windows NT ftp servers report that files have no
1844 * permissions: -------, so if we happen to have actually
1845 * read something, we should fix the permissions.
1847 if ((src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == 0)
1848 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
1849 gettimeofday (&tv_last_input, NULL);
1851 /* dst_write */
1852 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
1854 gboolean write_errno_nospace;
1856 if (n_written > 0)
1858 n_read -= n_written;
1859 t += n_written;
1860 continue;
1863 write_errno_nospace = (n_written < 0 && errno == ENOSPC);
1865 if (ctx->skip_all)
1866 return_status = FILE_SKIPALL;
1867 else
1868 return_status =
1869 file_error (_("Cannot write target file \"%s\"\n%s"), dst_path);
1871 if (return_status == FILE_SKIP)
1873 if (write_errno_nospace)
1874 goto ret;
1875 break;
1877 if (return_status == FILE_SKIPALL)
1879 ctx->skip_all = TRUE;
1880 if (write_errno_nospace)
1881 goto ret;
1883 if (return_status != FILE_RETRY)
1884 goto ret;
1888 tctx->copied_bytes = tctx->progress_bytes + n_read_total + ctx->do_reget;
1890 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
1891 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
1893 if (is_first_time || secs > FILEOP_UPDATE_INTERVAL)
1895 copy_file_file_display_progress (tctx, ctx,
1896 tv_current,
1897 tv_transfer_start, file_size, n_read_total);
1898 tv_last_update = tv_current;
1900 is_first_time = FALSE;
1902 if (update_secs > FILEOP_STALLING_INTERVAL)
1904 stalled_msg = _("(stalled)");
1908 gboolean force_update;
1910 force_update =
1911 (tv_current.tv_sec - tctx->transfer_start.tv_sec) > FILEOP_UPDATE_INTERVAL;
1913 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
1915 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1916 file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
1919 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size, stalled_msg,
1920 force_update);
1922 mc_refresh ();
1924 return_status = check_progress_buttons (ctx);
1926 if (return_status != FILE_CONT)
1928 mc_refresh ();
1929 goto ret;
1933 dst_status = DEST_FULL; /* copy successful, don't remove target file */
1936 ret:
1937 g_free (buf);
1939 rotate_dash (FALSE);
1940 while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all)
1942 temp_status = file_error (_("Cannot close source file \"%s\"\n%s"), src_path);
1943 if (temp_status == FILE_RETRY)
1944 continue;
1945 if (temp_status == FILE_ABORT)
1946 return_status = temp_status;
1947 if (temp_status == FILE_SKIPALL)
1948 ctx->skip_all = TRUE;
1949 break;
1952 while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all)
1954 temp_status = file_error (_("Cannot close target file \"%s\"\n%s"), dst_path);
1955 if (temp_status == FILE_RETRY)
1956 continue;
1957 if (temp_status == FILE_SKIPALL)
1958 ctx->skip_all = TRUE;
1959 return_status = temp_status;
1960 break;
1963 if (dst_status == DEST_SHORT)
1965 /* Query to remove short file */
1966 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved. Keep it?"),
1967 D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
1968 mc_unlink (dst_vpath);
1970 else if (dst_status == DEST_FULL)
1972 /* Copy has succeeded */
1973 if (!appending && ctx->preserve_uidgid)
1975 while (mc_chown (dst_vpath, src_uid, src_gid) != 0 && !ctx->skip_all)
1977 temp_status = file_error (_("Cannot chown target file \"%s\"\n%s"), dst_path);
1978 if (temp_status == FILE_RETRY)
1979 continue;
1980 if (temp_status == FILE_SKIPALL)
1982 ctx->skip_all = TRUE;
1983 return_status = FILE_CONT;
1985 if (temp_status == FILE_SKIP)
1986 return_status = FILE_CONT;
1987 break;
1991 if (!appending)
1993 if (ctx->preserve)
1995 while (mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0 && !ctx->skip_all)
1997 temp_status = file_error (_("Cannot chmod target file \"%s\"\n%s"), dst_path);
1998 if (temp_status == FILE_RETRY)
1999 continue;
2000 if (temp_status == FILE_SKIPALL)
2002 ctx->skip_all = TRUE;
2003 return_status = FILE_CONT;
2005 if (temp_status == FILE_SKIP)
2006 return_status = FILE_CONT;
2007 break;
2010 else if (!dst_exists)
2012 src_mode = umask (-1);
2013 umask (src_mode);
2014 src_mode = 0100666 & ~src_mode;
2015 mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2017 mc_utime (dst_vpath, &times);
2021 if (return_status == FILE_CONT)
2022 return_status = progress_update_one (tctx, ctx, file_size);
2024 ret_fast:
2025 vfs_path_free (src_vpath);
2026 vfs_path_free (dst_vpath);
2027 return return_status;
2030 /* --------------------------------------------------------------------------------------------- */
2032 * I think these copy_*_* functions should have a return type.
2033 * anyway, this function *must* have two directories as arguments.
2035 /* FIXME: This function needs to check the return values of the
2036 function calls */
2038 FileProgressStatus
2039 copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d,
2040 gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs)
2042 struct dirent *next;
2043 struct stat buf, cbuf;
2044 DIR *reading;
2045 FileProgressStatus return_status = FILE_CONT;
2046 struct link *lp;
2047 vfs_path_t *src_vpath, *dst_vpath;
2048 gboolean do_mkdir = TRUE;
2050 src_vpath = vfs_path_from_str (s);
2051 dst_vpath = vfs_path_from_str (d);
2053 /* First get the mode of the source dir */
2055 retry_src_stat:
2056 if ((*ctx->stat_func) (src_vpath, &cbuf) != 0)
2058 if (ctx->skip_all)
2059 return_status = FILE_SKIPALL;
2060 else
2062 return_status = file_error (_("Cannot stat source directory \"%s\"\n%s"), s);
2063 if (return_status == FILE_RETRY)
2064 goto retry_src_stat;
2065 if (return_status == FILE_SKIPALL)
2066 ctx->skip_all = TRUE;
2068 goto ret_fast;
2071 if (is_in_linklist (dest_dirs, src_vpath, &cbuf))
2073 /* Don't copy a directory we created before (we don't want to copy
2074 infinitely if a directory is copied into itself) */
2075 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
2076 return_status = FILE_CONT;
2077 goto ret_fast;
2080 /* Hmm, hardlink to directory??? - Norbert */
2081 /* FIXME: In this step we should do something
2082 in case the destination already exist */
2083 /* Check the hardlinks */
2084 if (ctx->preserve && cbuf.st_nlink > 1 && check_hardlinks (src_vpath, dst_vpath, &cbuf))
2086 /* We have made a hardlink - no more processing is necessary */
2087 goto ret_fast;
2090 if (!S_ISDIR (cbuf.st_mode))
2092 if (ctx->skip_all)
2093 return_status = FILE_SKIPALL;
2094 else
2096 return_status = file_error (_("Source \"%s\" is not a directory\n%s"), s);
2097 if (return_status == FILE_RETRY)
2098 goto retry_src_stat;
2099 if (return_status == FILE_SKIPALL)
2100 ctx->skip_all = TRUE;
2102 goto ret_fast;
2105 if (is_in_linklist (parent_dirs, src_vpath, &cbuf))
2107 /* we found a cyclic symbolic link */
2108 message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
2109 return_status = FILE_SKIP;
2110 goto ret_fast;
2113 lp = g_new0 (struct link, 1);
2114 lp->vfs = vfs_path_get_by_index (src_vpath, -1)->class;
2115 lp->ino = cbuf.st_ino;
2116 lp->dev = cbuf.st_dev;
2117 parent_dirs = g_slist_prepend (parent_dirs, lp);
2119 retry_dst_stat:
2120 /* Now, check if the dest dir exists, if not, create it. */
2121 if (mc_stat (dst_vpath, &buf) != 0)
2123 /* Here the dir doesn't exist : make it ! */
2124 if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
2126 return_status = FILE_CONT;
2127 goto ret;
2130 else
2133 * If the destination directory exists, we want to copy the whole
2134 * directory, but we only want this to happen once.
2136 * Escape sequences added to the * to compiler warnings.
2137 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
2138 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
2140 if (!S_ISDIR (buf.st_mode))
2142 if (ctx->skip_all)
2143 return_status = FILE_SKIPALL;
2144 else
2146 return_status = file_error (_("Destination \"%s\" must be a directory\n%s"), d);
2147 if (return_status == FILE_SKIPALL)
2148 ctx->skip_all = TRUE;
2149 if (return_status == FILE_RETRY)
2150 goto retry_dst_stat;
2152 goto ret;
2154 /* Dive into subdir if exists */
2155 if (toplevel && ctx->dive_into_subdirs)
2157 vfs_path_t *tmp;
2159 tmp = dst_vpath;
2160 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
2161 vfs_path_free (tmp);
2164 else
2165 do_mkdir = FALSE;
2168 d = vfs_path_as_str (dst_vpath);
2170 if (do_mkdir)
2172 while (my_mkdir (dst_vpath, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
2174 if (ctx->skip_all)
2175 return_status = FILE_SKIPALL;
2176 else
2178 return_status = file_error (_("Cannot create target directory \"%s\"\n%s"), d);
2179 if (return_status == FILE_SKIPALL)
2180 ctx->skip_all = TRUE;
2182 if (return_status != FILE_RETRY)
2183 goto ret;
2186 lp = g_new0 (struct link, 1);
2187 mc_stat (dst_vpath, &buf);
2188 lp->vfs = vfs_path_get_by_index (dst_vpath, -1)->class;
2189 lp->ino = buf.st_ino;
2190 lp->dev = buf.st_dev;
2191 dest_dirs = g_slist_prepend (dest_dirs, lp);
2194 if (ctx->preserve_uidgid)
2196 while (mc_chown (dst_vpath, cbuf.st_uid, cbuf.st_gid) != 0)
2198 if (ctx->skip_all)
2199 return_status = FILE_SKIPALL;
2200 else
2202 return_status = file_error (_("Cannot chown target directory \"%s\"\n%s"), d);
2203 if (return_status == FILE_SKIPALL)
2204 ctx->skip_all = TRUE;
2206 if (return_status != FILE_RETRY)
2207 goto ret;
2211 /* open the source dir for reading */
2212 reading = mc_opendir (src_vpath);
2213 if (reading == NULL)
2214 goto ret;
2216 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
2218 char *path;
2219 vfs_path_t *tmp_vpath;
2222 * Now, we don't want '.' and '..' to be created / copied at any time
2224 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
2225 continue;
2227 /* get the filename and add it to the src directory */
2228 path = mc_build_filename (s, next->d_name, (char *) NULL);
2229 tmp_vpath = vfs_path_from_str (path);
2231 (*ctx->stat_func) (tmp_vpath, &buf);
2232 if (S_ISDIR (buf.st_mode))
2234 char *mdpath;
2236 mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
2238 * From here, we just intend to recursively copy subdirs, not
2239 * the double functionality of copying different when the target
2240 * dir already exists. So, we give the recursive call the flag 0
2241 * meaning no toplevel.
2243 return_status =
2244 copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
2245 g_free (mdpath);
2247 else
2249 char *dest_file;
2251 dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
2252 return_status = copy_file_file (tctx, ctx, path, dest_file);
2253 g_free (dest_file);
2256 g_free (path);
2258 if (do_delete && return_status == FILE_CONT)
2260 if (ctx->erase_at_end)
2262 lp = g_new0 (struct link, 1);
2263 lp->src_vpath = tmp_vpath;
2264 lp->st_mode = buf.st_mode;
2265 erase_list = g_slist_append (erase_list, lp);
2266 tmp_vpath = NULL;
2268 else if (S_ISDIR (buf.st_mode))
2269 return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
2270 else
2271 return_status = erase_file (tctx, ctx, tmp_vpath);
2273 vfs_path_free (tmp_vpath);
2275 mc_closedir (reading);
2277 if (ctx->preserve)
2279 mc_timesbuf_t times;
2281 mc_chmod (dst_vpath, cbuf.st_mode & ctx->umask_kill);
2282 get_times (&cbuf, &times);
2283 mc_utime (dst_vpath, &times);
2285 else
2287 cbuf.st_mode = umask (-1);
2288 umask (cbuf.st_mode);
2289 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
2290 mc_chmod (dst_vpath, cbuf.st_mode & ctx->umask_kill);
2293 ret:
2294 free_link (parent_dirs->data);
2295 g_slist_free_1 (parent_dirs);
2296 ret_fast:
2297 vfs_path_free (src_vpath);
2298 vfs_path_free (dst_vpath);
2299 return return_status;
2302 /* }}} */
2304 /* --------------------------------------------------------------------------------------------- */
2305 /* {{{ Move routines */
2307 FileProgressStatus
2308 move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d)
2310 struct stat sbuf, dbuf;
2311 FileProgressStatus return_status = FILE_CONT;
2312 gboolean move_over = FALSE;
2313 gboolean dstat_ok;
2314 vfs_path_t *src_vpath, *dst_vpath;
2316 src_vpath = vfs_path_from_str (s);
2317 dst_vpath = vfs_path_from_str (d);
2319 file_progress_show_source (ctx, src_vpath);
2320 file_progress_show_target (ctx, dst_vpath);
2322 if (check_progress_buttons (ctx) == FILE_ABORT)
2324 return_status = FILE_ABORT;
2325 goto ret_fast;
2328 mc_refresh ();
2330 mc_stat (src_vpath, &sbuf);
2332 dstat_ok = (mc_stat (dst_vpath, &dbuf) == 0);
2333 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
2335 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), s, d);
2336 goto ret_fast;
2339 if (!dstat_ok)
2340 ; /* destination doesn't exist */
2341 else if (!ctx->dive_into_subdirs)
2342 move_over = TRUE;
2343 else
2345 vfs_path_t *tmp;
2347 tmp = dst_vpath;
2348 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
2349 vfs_path_free (tmp);
2352 d = vfs_path_as_str (dst_vpath);
2354 /* Check if the user inputted an existing dir */
2355 retry_dst_stat:
2356 if (mc_stat (dst_vpath, &dbuf) == 0)
2358 if (move_over)
2360 return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL);
2362 if (return_status != FILE_CONT)
2363 goto ret;
2364 goto oktoret;
2366 else if (ctx->skip_all)
2367 return_status = FILE_SKIPALL;
2368 else
2370 if (S_ISDIR (dbuf.st_mode))
2371 return_status = file_error (_("Cannot overwrite directory \"%s\"\n%s"), d);
2372 else
2373 return_status = file_error (_("Cannot overwrite file \"%s\"\n%s"), d);
2374 if (return_status == FILE_SKIPALL)
2375 ctx->skip_all = TRUE;
2376 if (return_status == FILE_RETRY)
2377 goto retry_dst_stat;
2380 goto ret_fast;
2383 retry_rename:
2384 if (mc_rename (src_vpath, dst_vpath) == 0)
2386 return_status = FILE_CONT;
2387 goto ret;
2390 if (errno != EXDEV)
2392 if (!ctx->skip_all)
2394 return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
2395 if (return_status == FILE_SKIPALL)
2396 ctx->skip_all = TRUE;
2397 if (return_status == FILE_RETRY)
2398 goto retry_rename;
2400 goto ret;
2402 /* Failed because of filesystem boundary -> copy dir instead */
2403 return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL);
2405 if (return_status != FILE_CONT)
2406 goto ret;
2407 oktoret:
2408 file_progress_show_source (ctx, NULL);
2409 file_progress_show_target (ctx, NULL);
2410 file_progress_show (ctx, 0, 0, "", FALSE);
2412 return_status = check_progress_buttons (ctx);
2413 if (return_status != FILE_CONT)
2414 goto ret;
2416 mc_refresh ();
2417 if (ctx->erase_at_end)
2419 /* Reset progress count before delete to avoid counting files twice */
2420 tctx->progress_count = tctx->prev_progress_count;
2422 while (erase_list != NULL && return_status != FILE_ABORT)
2424 struct link *lp = (struct link *) erase_list->data;
2426 if (S_ISDIR (lp->st_mode))
2427 return_status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count);
2428 else
2429 return_status = erase_file (tctx, ctx, lp->src_vpath);
2431 erase_list = g_slist_remove (erase_list, lp);
2432 free_link (lp);
2435 /* Save progress counter before move next directory */
2436 tctx->prev_progress_count = tctx->progress_count;
2438 erase_dir_iff_empty (ctx, src_vpath, tctx->progress_count);
2440 ret:
2441 erase_list = free_linklist (erase_list);
2442 ret_fast:
2443 vfs_path_free (src_vpath);
2444 vfs_path_free (dst_vpath);
2445 return return_status;
2448 /* }}} */
2450 /* --------------------------------------------------------------------------------------------- */
2451 /* {{{ Erase routines */
2453 FileProgressStatus
2454 erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * s_vpath)
2456 FileProgressStatus error;
2458 file_progress_show_deleting (ctx, vfs_path_as_str (s_vpath), NULL);
2459 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2460 if (check_progress_buttons (ctx) == FILE_ABORT)
2461 return FILE_ABORT;
2463 mc_refresh ();
2465 /* The old way to detect a non empty directory was:
2466 error = my_rmdir (s);
2467 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
2468 For the linux user space nfs server (nfs-server-2.2beta29-2)
2469 we would have to check also for EIO. I hope the new way is
2470 fool proof. (Norbert)
2472 error = check_dir_is_empty (s_vpath);
2473 if (error == 0)
2474 { /* not empty */
2475 error = query_recursive (ctx, vfs_path_as_str (s_vpath));
2476 if (error == FILE_CONT)
2477 error = recursive_erase (tctx, ctx, s_vpath);
2478 return error;
2481 while (my_rmdir (vfs_path_as_str (s_vpath)) == -1 && !ctx->skip_all)
2483 error = file_error (_("Cannot remove directory \"%s\"\n%s"), vfs_path_as_str (s_vpath));
2484 if (error != FILE_RETRY)
2485 return error;
2488 return FILE_CONT;
2491 /* }}} */
2493 /* --------------------------------------------------------------------------------------------- */
2494 /* {{{ Panel operate routines */
2496 void
2497 dirsize_status_init_cb (status_msg_t * sm)
2499 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
2500 Widget *wd = WIDGET (sm->dlg);
2502 const char *b1_name = N_("&Abort");
2503 const char *b2_name = N_("&Skip");
2504 int b_width, ui_width;
2506 #ifdef ENABLE_NLS
2507 b1_name = _(b1_name);
2508 b2_name = _(b2_name);
2509 #endif
2511 b_width = str_term_width1 (b1_name) + 4;
2512 if (dsm->allow_skip)
2513 b_width += str_term_width1 (b2_name) + 4 + 1;
2515 ui_width = MAX (COLS / 2, b_width + 6);
2516 dsm->dirname = label_new (2, 3, "");
2517 add_widget (sm->dlg, dsm->dirname);
2518 dsm->count_size = label_new (3, 3, "");
2519 add_widget (sm->dlg, dsm->count_size);
2520 add_widget (sm->dlg, hline_new (4, -1, -1));
2522 dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
2523 add_widget (sm->dlg, dsm->abort_button);
2524 if (dsm->allow_skip)
2526 dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
2527 add_widget (sm->dlg, dsm->skip_button);
2528 widget_select (dsm->skip_button);
2531 widget_set_size (wd, wd->y, wd->x, 8, ui_width);
2532 dirsize_status_locate_buttons (dsm);
2535 /* --------------------------------------------------------------------------------------------- */
2538 dirsize_status_update_cb (status_msg_t * sm)
2540 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
2541 Widget *wd = WIDGET (sm->dlg);
2543 /* update second (longer label) */
2544 label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
2545 dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
2547 /* enlarge dialog if required */
2548 if (WIDGET (dsm->count_size)->cols + 6 > wd->cols)
2550 dlg_set_size (sm->dlg, wd->lines, WIDGET (dsm->count_size)->cols + 6);
2551 dirsize_status_locate_buttons (dsm);
2552 dlg_redraw (sm->dlg);
2555 /* adjust first label */
2556 label_set_text (dsm->dirname, str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->cols - 6));
2558 switch (status_msg_common_update (sm))
2560 case B_CANCEL:
2561 case FILE_ABORT:
2562 return FILE_ABORT;
2563 case FILE_SKIP:
2564 return FILE_SKIP;
2565 default:
2566 return FILE_CONT;
2570 /* --------------------------------------------------------------------------------------------- */
2572 void
2573 dirsize_status_deinit_cb (status_msg_t * sm)
2575 (void) sm;
2577 /* schedule to update passive panel */
2578 if (get_other_type () == view_listing)
2579 other_panel->dirty = 1;
2582 /* --------------------------------------------------------------------------------------------- */
2584 * compute_dir_size:
2586 * Computes the number of bytes used by the files in a directory
2589 FileProgressStatus
2590 compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
2591 size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total,
2592 gboolean compute_symlinks)
2594 return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
2595 compute_symlinks);
2598 /* --------------------------------------------------------------------------------------------- */
2600 * panel_operate:
2602 * Performs one of the operations on the selection on the source_panel
2603 * (copy, delete, move).
2605 * Returns TRUE if did change the directory
2606 * structure, Returns FALSE if user aborted
2608 * force_single forces operation on the current entry and affects
2609 * default destination. Current filename is used as default.
2612 gboolean
2613 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
2615 WPanel *panel = PANEL (source_panel);
2616 const gboolean single_entry = force_single || (panel->marked <= 1)
2617 || (get_current_type () == view_tree);
2619 const char *source = NULL;
2620 #ifdef WITH_FULL_PATHS
2621 vfs_path_t *source_with_vpath = NULL;
2622 #endif /* WITH_FULL_PATHS */
2623 char *dest = NULL;
2624 vfs_path_t *dest_vpath = NULL;
2625 char *temp = NULL;
2626 char *save_cwd = NULL, *save_dest = NULL;
2627 struct stat src_stat;
2628 gboolean ret_val = TRUE;
2629 int i;
2630 FileProgressStatus value;
2631 file_op_context_t *ctx;
2632 file_op_total_context_t *tctx;
2633 vfs_path_t *tmp_vpath;
2634 filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
2636 gboolean do_bg = FALSE; /* do background operation? */
2638 static gboolean i18n_flag = FALSE;
2639 if (!i18n_flag)
2641 for (i = G_N_ELEMENTS (op_names); i-- != 0;)
2642 op_names[i] = Q_ (op_names[i]);
2643 i18n_flag = TRUE;
2646 linklist = free_linklist (linklist);
2647 dest_dirs = free_linklist (dest_dirs);
2649 if (single_entry)
2651 gboolean ok;
2653 if (force_single)
2654 source = selection (panel)->fname;
2655 else
2656 source = panel_get_file (panel);
2658 ok = !DIR_IS_DOTDOT (source);
2660 if (!ok)
2661 message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
2662 else
2664 vfs_path_t *source_vpath;
2666 source_vpath = vfs_path_from_str (source);
2668 /* Update stat to get actual info */
2669 ok = mc_lstat (source_vpath, &src_stat) == 0;
2670 if (!ok)
2672 message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
2673 path_trunc (source, 30), unix_error_string (errno));
2675 /* Directory was changed outside MC. Reload it forced */
2676 if (!panel->is_panelized)
2678 panel_update_flags_t flags = UP_RELOAD;
2680 /* don't update panelized panel */
2681 if (get_other_type () == view_listing && other_panel->is_panelized)
2682 flags |= UP_ONLY_CURRENT;
2684 update_panels (flags, UP_KEEPSEL);
2688 vfs_path_free (source_vpath);
2691 if (!ok)
2692 return FALSE;
2695 ctx = file_op_context_new (operation);
2697 /* Show confirmation dialog */
2698 if (operation != OP_DELETE)
2700 const char *tmp_dest_dir;
2701 char *dest_dir;
2702 char *format;
2704 /* Forced single operations default to the original name */
2705 if (force_single)
2706 tmp_dest_dir = source;
2707 else if (get_other_type () == view_listing)
2708 tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
2709 else
2710 tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
2712 * Add trailing backslash only when do non-local ops.
2713 * It saves user from occasional file renames (when destination
2714 * dir is deleted)
2716 if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
2717 && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
2719 /* add trailing separator */
2720 dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
2722 else
2724 /* just copy */
2725 dest_dir = g_strdup (tmp_dest_dir);
2727 if (dest_dir == NULL)
2729 ret_val = FALSE;
2730 goto ret_fast;
2733 /* Generate confirmation prompt */
2734 format =
2735 panel_operate_generate_prompt (panel, operation, source != NULL ? &src_stat : NULL);
2737 dest =
2738 file_mask_dialog (ctx, operation, source != NULL, format,
2739 source != NULL ? source : (const void *) &panel->marked, dest_dir,
2740 &do_bg);
2742 g_free (format);
2743 g_free (dest_dir);
2745 if (dest == NULL || dest[0] == '\0')
2747 g_free (dest);
2748 ret_val = FALSE;
2749 goto ret_fast;
2751 dest_vpath = vfs_path_from_str (dest);
2753 else if (confirm_delete)
2755 char *format;
2756 char fmd_buf[BUF_MEDIUM];
2758 /* Generate confirmation prompt */
2759 format =
2760 panel_operate_generate_prompt (panel, OP_DELETE, source != NULL ? &src_stat : NULL);
2762 if (source == NULL)
2763 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
2764 else
2766 const int fmd_xlen = 64;
2767 i = fmd_xlen - str_term_width1 (format) - 4;
2768 g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
2771 g_free (format);
2773 if (safe_delete)
2774 query_set_sel (1);
2776 i = query_dialog (op_names[operation], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
2778 if (i != 0)
2780 ret_val = FALSE;
2781 goto ret_fast;
2785 tctx = file_op_total_context_new ();
2786 gettimeofday (&tctx->transfer_start, (struct timezone *) NULL);
2788 #ifdef ENABLE_BACKGROUND
2789 /* Did the user select to do a background operation? */
2790 if (do_bg)
2792 int v;
2794 v = do_background (ctx,
2795 g_strconcat (op_names[operation], ": ",
2796 vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
2797 if (v == -1)
2798 message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
2800 /* If we are the parent */
2801 if (v == 1)
2803 mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
2805 mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
2806 vfs_path_free (dest_vpath);
2807 g_free (dest);
2808 /* file_op_context_destroy (ctx); */
2809 return FALSE;
2812 else
2813 #endif /* ENABLE_BACKGROUND */
2815 if (operation == OP_DELETE)
2816 dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
2817 else if (single_entry && S_ISDIR (selection (panel)->st.st_mode))
2818 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
2819 else if (single_entry || force_single)
2820 dialog_type = FILEGUI_DIALOG_ONE_ITEM;
2821 else
2822 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
2825 /* Initialize things */
2826 /* We do not want to trash cache every time file is
2827 created/touched. However, this will make our cache contain
2828 invalid data. */
2829 if ((dest != NULL)
2830 && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
2831 save_dest = g_strdup (dest);
2833 if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
2834 && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
2835 save_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath));
2837 /* Now, let's do the job */
2839 /* This code is only called by the tree and panel code */
2840 if (single_entry)
2842 /* We now have ETA in all cases */
2844 /* One file: FIXME mc_chdir will take user out of any vfs */
2845 if ((operation != OP_COPY) && (get_current_type () == view_tree))
2847 vfs_path_t *vpath;
2848 int chdir_retcode;
2850 vpath = vfs_path_from_str (PATH_SEP_STR);
2851 chdir_retcode = mc_chdir (vpath);
2852 vfs_path_free (vpath);
2853 if (chdir_retcode < 0)
2855 ret_val = FALSE;
2856 goto clean_up;
2860 /* The source and src_stat variables have been initialized before */
2861 #ifdef WITH_FULL_PATHS
2862 if (g_path_is_absolute (source))
2863 source_with_vpath = vfs_path_from_str (source);
2864 else
2865 source_with_vpath = vfs_path_append_new (panel->cwd_vpath, source, (char *) NULL);
2866 #endif /* WITH_FULL_PATHS */
2867 if (panel_operate_init_totals (panel, vfs_path_as_str (source_with_vpath), ctx, dialog_type)
2868 == FILE_CONT)
2870 if (operation == OP_DELETE)
2872 if (S_ISDIR (src_stat.st_mode))
2873 value = erase_dir (tctx, ctx, source_with_vpath);
2874 else
2875 value = erase_file (tctx, ctx, source_with_vpath);
2877 else
2879 temp = transform_source (ctx, source_with_vpath);
2880 if (temp == NULL)
2881 value = transform_error;
2882 else
2884 char *repl_dest, *temp2;
2886 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2887 if (ctx->search_handle->error != MC_SEARCH_E_OK)
2889 if (ctx->search_handle->error_str != NULL)
2890 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
2892 g_free (repl_dest);
2893 goto clean_up;
2896 temp2 = mc_build_filename (repl_dest, temp, (char *) NULL);
2897 g_free (temp);
2898 g_free (repl_dest);
2899 g_free (dest);
2900 vfs_path_free (dest_vpath);
2901 dest = temp2;
2902 dest_vpath = vfs_path_from_str (dest);
2904 switch (operation)
2906 case OP_COPY:
2907 /* we use file_mask_op_follow_links only with OP_COPY */
2908 ctx->stat_func (source_with_vpath, &src_stat);
2910 if (S_ISDIR (src_stat.st_mode))
2911 value =
2912 copy_dir_dir (tctx, ctx, vfs_path_as_str (source_with_vpath),
2913 dest, TRUE, FALSE, FALSE, NULL);
2914 else
2915 value =
2916 copy_file_file (tctx, ctx, vfs_path_as_str (source_with_vpath),
2917 dest);
2918 break;
2920 case OP_MOVE:
2921 if (S_ISDIR (src_stat.st_mode))
2922 value =
2923 move_dir_dir (tctx, ctx, vfs_path_as_str (source_with_vpath), dest);
2924 else
2925 value =
2926 move_file_file (tctx, ctx, vfs_path_as_str (source_with_vpath),
2927 dest);
2928 break;
2930 default:
2931 /* Unknown file operation */
2932 abort ();
2935 } /* Copy or move operation */
2937 if ((value == FILE_CONT) && !force_single)
2938 unmark_files (panel);
2941 else
2943 /* Many files */
2945 /* Check destination for copy or move operation */
2946 while (operation != OP_DELETE)
2948 int dst_result;
2949 struct stat dst_stat;
2951 dst_result = mc_stat (dest_vpath, &dst_stat);
2953 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
2954 break;
2956 if (ctx->skip_all
2957 || file_error (_("Destination \"%s\" must be a directory\n%s"), dest) != FILE_RETRY)
2958 goto clean_up;
2961 if (panel_operate_init_totals (panel, NULL, ctx, dialog_type) == FILE_CONT)
2963 /* Loop for every file, perform the actual copy operation */
2964 for (i = 0; i < panel->dir.len; i++)
2966 const char *source2;
2968 if (!panel->dir.list[i].f.marked)
2969 continue; /* Skip the unmarked ones */
2971 source2 = panel->dir.list[i].fname;
2972 src_stat = panel->dir.list[i].st;
2974 #ifdef WITH_FULL_PATHS
2975 vfs_path_free (source_with_vpath);
2976 if (g_path_is_absolute (source2))
2977 source_with_vpath = vfs_path_from_str (source2);
2978 else
2979 source_with_vpath =
2980 vfs_path_append_new (panel->cwd_vpath, source2, (char *) NULL);
2981 #endif /* WITH_FULL_PATHS */
2983 if (operation == OP_DELETE)
2985 if (S_ISDIR (src_stat.st_mode))
2986 value = erase_dir (tctx, ctx, source_with_vpath);
2987 else
2988 value = erase_file (tctx, ctx, source_with_vpath);
2990 else
2992 temp = transform_source (ctx, source_with_vpath);
2993 if (temp == NULL)
2994 value = transform_error;
2995 else
2997 char *temp2, *repl_dest, *source_with_path_str;
2999 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
3000 if (ctx->search_handle->error != MC_SEARCH_E_OK)
3002 if (ctx->search_handle->error_str != NULL)
3003 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
3005 g_free (repl_dest);
3006 goto clean_up;
3009 temp2 = mc_build_filename (repl_dest, temp, (char *) NULL);
3010 g_free (temp);
3011 g_free (repl_dest);
3012 source_with_path_str =
3013 strutils_shell_unescape (vfs_path_as_str (source_with_vpath));
3014 temp = strutils_shell_unescape (temp2);
3015 g_free (temp2);
3017 switch (operation)
3019 case OP_COPY:
3020 /* we use file_mask_op_follow_links only with OP_COPY */
3022 vfs_path_t *vpath;
3024 vpath = vfs_path_from_str (source_with_path_str);
3025 ctx->stat_func (vpath, &src_stat);
3026 vfs_path_free (vpath);
3028 if (S_ISDIR (src_stat.st_mode))
3029 value = copy_dir_dir (tctx, ctx, source_with_path_str, temp,
3030 TRUE, FALSE, FALSE, NULL);
3031 else
3032 value = copy_file_file (tctx, ctx, source_with_path_str, temp);
3033 dest_dirs = free_linklist (dest_dirs);
3034 break;
3036 case OP_MOVE:
3037 if (S_ISDIR (src_stat.st_mode))
3038 value = move_dir_dir (tctx, ctx, source_with_path_str, temp);
3039 else
3040 value = move_file_file (tctx, ctx, source_with_path_str, temp);
3041 break;
3043 default:
3044 /* Unknown file operation */
3045 abort ();
3048 g_free (source_with_path_str);
3049 g_free (temp);
3051 } /* Copy or move operation */
3053 if (value == FILE_ABORT)
3054 break;
3056 if (value == FILE_CONT)
3057 do_file_mark (panel, i, 0);
3059 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3061 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3062 file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
3065 if (operation != OP_DELETE)
3066 file_progress_show (ctx, 0, 0, "", FALSE);
3068 if (check_progress_buttons (ctx) == FILE_ABORT)
3069 break;
3071 mc_refresh ();
3072 } /* Loop for every file */
3074 } /* Many entries */
3076 clean_up:
3077 /* Clean up */
3078 if (save_cwd != NULL)
3080 tmp_vpath = vfs_path_from_str (save_cwd);
3081 mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3082 vfs_path_free (tmp_vpath);
3083 g_free (save_cwd);
3086 if (save_dest != NULL)
3088 tmp_vpath = vfs_path_from_str (save_dest);
3089 mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3090 vfs_path_free (tmp_vpath);
3091 g_free (save_dest);
3094 linklist = free_linklist (linklist);
3095 dest_dirs = free_linklist (dest_dirs);
3096 #ifdef WITH_FULL_PATHS
3097 vfs_path_free (source_with_vpath);
3098 #endif /* WITH_FULL_PATHS */
3099 g_free (dest);
3100 vfs_path_free (dest_vpath);
3101 MC_PTR_FREE (ctx->dest_mask);
3103 #ifdef ENABLE_BACKGROUND
3104 /* Let our parent know we are saying bye bye */
3105 if (mc_global.we_are_background)
3107 int cur_pid = getpid ();
3108 /* Send pid to parent with child context, it is fork and
3109 don't modify real parent ctx */
3110 ctx->pid = cur_pid;
3111 parent_call ((void *) end_bg_process, ctx, 0);
3113 vfs_shut ();
3114 my_exit (EXIT_SUCCESS);
3116 #endif /* ENABLE_BACKGROUND */
3118 file_op_total_context_destroy (tctx);
3119 ret_fast:
3120 file_op_context_destroy (ctx);
3122 return ret_val;
3125 /* }}} */
3127 /* --------------------------------------------------------------------------------------------- */
3128 /* {{{ Query/status report routines */
3129 /** Report error with one file */
3130 FileProgressStatus
3131 file_error (const char *format, const char *file)
3133 char buf[BUF_MEDIUM];
3135 g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3137 return do_file_error (buf);
3140 /* --------------------------------------------------------------------------------------------- */
3143 Cause emacs to enter folding mode for this file:
3144 Local variables:
3145 end: