Ticket #3641: code cleanup before 4.8.18 release.
[midnight-commander.git] / src / filemanager / file.c
blob7c4d6787fc66c3ef1c8b47b8ce851d7b3b6a0e0f
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, MC_MAXPATHLEN - 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;
419 link_target[len] = 0;
421 if (ctx->stable_symlinks)
424 if (!vfs_file_is_local (src_vpath) || !vfs_file_is_local (dst_vpath))
426 message (D_ERROR, MSG_ERROR,
427 _("Cannot make stable symlinks across"
428 "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
429 ctx->stable_symlinks = FALSE;
433 if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
435 const char *r = strrchr (src_path, PATH_SEP);
437 if (r)
439 char *p;
440 vfs_path_t *q;
442 p = g_strndup (src_path, r - src_path + 1);
443 if (g_path_is_absolute (dst_path))
444 q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
445 else
446 q = vfs_path_build_filename (p, dst_path, (char *) NULL);
448 if (vfs_path_tokens_count (q) > 1)
450 char *s;
451 vfs_path_t *tmp_vpath1, *tmp_vpath2;
453 tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
454 s = g_strconcat (p, link_target, (char *) NULL);
455 g_free (p);
456 g_strlcpy (link_target, s, sizeof (link_target));
457 g_free (s);
458 tmp_vpath2 = vfs_path_from_str (link_target);
459 s = diff_two_paths (tmp_vpath1, tmp_vpath2);
460 vfs_path_free (tmp_vpath1);
461 vfs_path_free (tmp_vpath2);
462 if (s)
464 g_strlcpy (link_target, s, sizeof (link_target));
465 g_free (s);
468 else
469 g_free (p);
470 vfs_path_free (q);
473 link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON);
475 retry_dst_symlink:
476 if (mc_symlink (link_target_vpath, dst_vpath) == 0)
478 /* Success */
479 return_status = FILE_CONT;
480 goto ret;
483 * if dst_exists, it is obvious that this had failed.
484 * We can delete the old symlink and try again...
486 if (dst_is_symlink)
488 if (mc_unlink (dst_vpath) == 0)
489 if (mc_symlink (link_target_vpath, dst_vpath) == 0)
491 /* Success */
492 return_status = FILE_CONT;
493 goto ret;
496 if (ctx->skip_all)
497 return_status = FILE_SKIPALL;
498 else
500 return_status = file_error (_("Cannot create target symlink \"%s\"\n%s"), dst_path);
501 if (return_status == FILE_SKIPALL)
502 ctx->skip_all = TRUE;
503 if (return_status == FILE_RETRY)
504 goto retry_dst_symlink;
507 ret:
508 vfs_path_free (src_vpath);
509 vfs_path_free (dst_vpath);
510 vfs_path_free (link_target_vpath);
511 return return_status;
514 /* --------------------------------------------------------------------------------------------- */
516 * do_compute_dir_size:
518 * Computes the number of bytes used by the files in a directory
521 static FileProgressStatus
522 do_compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * dsm,
523 size_t * dir_count, size_t * ret_marked, uintmax_t * ret_total,
524 gboolean compute_symlinks)
526 static guint64 timestamp = 0;
527 /* update with 25 FPS rate */
528 static const guint64 delay = G_USEC_PER_SEC / 25;
530 status_msg_t *sm = STATUS_MSG (dsm);
531 int res;
532 struct stat s;
533 DIR *dir;
534 struct dirent *dirent;
535 FileProgressStatus ret = FILE_CONT;
537 if (!compute_symlinks)
539 res = mc_lstat (dirname_vpath, &s);
540 if (res != 0)
541 return ret;
543 /* don't scan symlink to directory */
544 if (S_ISLNK (s.st_mode))
546 (*ret_marked)++;
547 *ret_total += (uintmax_t) s.st_size;
548 return ret;
552 (*dir_count)++;
554 dir = mc_opendir (dirname_vpath);
555 if (dir == NULL)
556 return ret;
558 while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL)
560 vfs_path_t *tmp_vpath;
562 if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name))
563 continue;
565 tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL);
567 res = mc_lstat (tmp_vpath, &s);
568 if (res == 0)
570 if (S_ISDIR (s.st_mode))
571 ret =
572 do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total,
573 compute_symlinks);
574 else
576 ret = FILE_CONT;
578 (*ret_marked)++;
579 *ret_total += (uintmax_t) s.st_size;
582 if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (&timestamp, delay))
584 dsm->dirname_vpath = tmp_vpath;
585 dsm->dir_count = *dir_count;
586 dsm->total_size = *ret_total;
587 ret = sm->update (sm);
591 vfs_path_free (tmp_vpath);
594 mc_closedir (dir);
595 return ret;
598 /* --------------------------------------------------------------------------------------------- */
600 static FileProgressStatus
601 progress_update_one (file_op_total_context_t * tctx, file_op_context_t * ctx, off_t add)
603 struct timeval tv_current;
604 static struct timeval tv_start = { 0, 0 };
606 tctx->progress_count++;
607 tctx->progress_bytes += (uintmax_t) add;
609 if (tv_start.tv_sec == 0)
611 gettimeofday (&tv_start, (struct timezone *) NULL);
613 gettimeofday (&tv_current, (struct timezone *) NULL);
614 if ((tv_current.tv_sec - tv_start.tv_sec) > FILEOP_UPDATE_INTERVAL)
616 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
618 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
619 file_progress_show_total (tctx, ctx, tctx->progress_bytes, TRUE);
621 tv_start.tv_sec = tv_current.tv_sec;
624 return check_progress_buttons (ctx);
627 /* --------------------------------------------------------------------------------------------- */
629 static FileProgressStatus
630 real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b)
632 char *msg;
633 int result = 0;
634 const char *head_msg;
636 head_msg = mode == Foreground ? MSG_ERROR : _("Background process error");
638 msg = g_strdup_printf (fmt, a, b);
639 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
640 g_free (msg);
641 do_refresh ();
643 return (result == 1) ? FILE_ABORT : FILE_SKIP;
646 /* --------------------------------------------------------------------------------------------- */
648 static FileProgressStatus
649 warn_same_file (const char *fmt, const char *a, const char *b)
651 #ifdef ENABLE_BACKGROUND
652 /* *INDENT-OFF* */
653 union
655 void *p;
656 FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b);
657 } pntr;
658 /* *INDENT-ON* */
660 pntr.f = real_warn_same_file;
662 if (mc_global.we_are_background)
663 return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b);
664 #endif
665 return real_warn_same_file (Foreground, fmt, a, b);
668 /* --------------------------------------------------------------------------------------------- */
669 /* {{{ Query/status report routines */
671 static FileProgressStatus
672 real_do_file_error (enum OperationMode mode, const char *error)
674 int result;
675 const char *msg;
677 msg = mode == Foreground ? MSG_ERROR : _("Background process error");
678 result =
679 query_dialog (msg, error, D_ERROR, 4, _("&Skip"), _("Ski&p all"), _("&Retry"), _("&Abort"));
681 switch (result)
683 case 0:
684 do_refresh ();
685 return FILE_SKIP;
687 case 1:
688 do_refresh ();
689 return FILE_SKIPALL;
691 case 2:
692 do_refresh ();
693 return FILE_RETRY;
695 case 3:
696 default:
697 return FILE_ABORT;
701 /* --------------------------------------------------------------------------------------------- */
703 static FileProgressStatus
704 real_query_recursive (file_op_context_t * ctx, enum OperationMode mode, const char *s)
706 if (ctx->recursive_result < RECURSIVE_ALWAYS)
708 const char *msg;
709 char *text;
711 msg = mode == Foreground
712 ? _("Directory \"%s\" not empty.\nDelete it recursively?")
713 : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
714 text = g_strdup_printf (msg, path_trunc (s, 30));
716 if (safe_delete)
717 query_set_sel (1);
719 ctx->recursive_result =
720 query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"),
721 _("Non&e"), _("&Abort"));
722 g_free (text);
724 if (ctx->recursive_result != RECURSIVE_ABORT)
725 do_refresh ();
728 switch (ctx->recursive_result)
730 case RECURSIVE_YES:
731 case RECURSIVE_ALWAYS:
732 return FILE_CONT;
734 case RECURSIVE_NO:
735 case RECURSIVE_NEVER:
736 return FILE_SKIP;
738 case RECURSIVE_ABORT:
739 default:
740 return FILE_ABORT;
744 /* --------------------------------------------------------------------------------------------- */
746 #ifdef ENABLE_BACKGROUND
747 static FileProgressStatus
748 do_file_error (const char *str)
750 /* *INDENT-OFF* */
751 union
753 void *p;
754 FileProgressStatus (*f) (enum OperationMode, const char *);
755 } pntr;
756 /* *INDENT-ON* */
758 pntr.f = real_do_file_error;
760 if (mc_global.we_are_background)
761 return parent_call (pntr.p, NULL, 1, strlen (str), str);
762 else
763 return real_do_file_error (Foreground, str);
766 /* --------------------------------------------------------------------------------------------- */
768 static FileProgressStatus
769 query_recursive (file_op_context_t * ctx, const char *s)
771 /* *INDENT-OFF* */
772 union
774 void *p;
775 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *);
776 } pntr;
777 /* *INDENT-ON* */
779 pntr.f = real_query_recursive;
781 if (mc_global.we_are_background)
782 return parent_call (pntr.p, ctx, 1, strlen (s), s);
783 else
784 return real_query_recursive (ctx, Foreground, s);
787 /* --------------------------------------------------------------------------------------------- */
789 static FileProgressStatus
790 query_replace (file_op_context_t * ctx, const char *destname, struct stat *_s_stat,
791 struct stat *_d_stat)
793 /* *INDENT-OFF* */
794 union
796 void *p;
797 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *,
798 struct stat *, struct stat *);
799 } pntr;
800 /* *INDENT-ON* */
802 pntr.f = file_progress_real_query_replace;
804 if (mc_global.we_are_background)
805 return parent_call (pntr.p, ctx, 3, strlen (destname), destname,
806 sizeof (struct stat), _s_stat, sizeof (struct stat), _d_stat);
807 else
808 return file_progress_real_query_replace (ctx, Foreground, destname, _s_stat, _d_stat);
811 #else
812 /* --------------------------------------------------------------------------------------------- */
814 static FileProgressStatus
815 do_file_error (const char *str)
817 return real_do_file_error (Foreground, str);
820 /* --------------------------------------------------------------------------------------------- */
822 static FileProgressStatus
823 query_recursive (file_op_context_t * ctx, const char *s)
825 return real_query_recursive (ctx, Foreground, s);
828 /* --------------------------------------------------------------------------------------------- */
830 static FileProgressStatus
831 query_replace (file_op_context_t * ctx, const char *destname, struct stat *_s_stat,
832 struct stat *_d_stat)
834 return file_progress_real_query_replace (ctx, Foreground, destname, _s_stat, _d_stat);
837 #endif /* !ENABLE_BACKGROUND */
839 /* --------------------------------------------------------------------------------------------- */
840 /** Report error with two files */
842 static FileProgressStatus
843 files_error (const char *format, const char *file1, const char *file2)
845 char buf[BUF_MEDIUM];
846 char *nfile1 = g_strdup (path_trunc (file1, 15));
847 char *nfile2 = g_strdup (path_trunc (file2, 15));
849 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno));
851 g_free (nfile1);
852 g_free (nfile2);
854 return do_file_error (buf);
857 /* }}} */
859 /* --------------------------------------------------------------------------------------------- */
861 static void
862 copy_file_file_display_progress (file_op_total_context_t * tctx, file_op_context_t * ctx,
863 struct timeval tv_current, struct timeval tv_transfer_start,
864 off_t file_size, off_t n_read_total)
866 long dt;
868 /* 1. Update rotating dash after some time */
869 rotate_dash (TRUE);
871 /* 3. Compute ETA */
872 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
874 if (n_read_total == 0)
875 ctx->eta_secs = 0.0;
876 else
878 ctx->eta_secs = ((dt / (double) n_read_total) * file_size) - dt;
879 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
882 /* 4. Compute BPS rate */
883 ctx->bps_time = (tv_current.tv_sec - tv_transfer_start.tv_sec);
884 if (ctx->bps_time < 1)
885 ctx->bps_time = 1;
886 ctx->bps = n_read_total / ctx->bps_time;
888 /* 5. Compute total ETA and BPS */
889 if (ctx->progress_bytes != 0)
891 uintmax_t remain_bytes;
893 remain_bytes = ctx->progress_bytes - tctx->copied_bytes;
894 #if 1
896 int total_secs = tv_current.tv_sec - tctx->transfer_start.tv_sec;
898 if (total_secs < 1)
899 total_secs = 1;
901 tctx->bps = tctx->copied_bytes / total_secs;
902 tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
904 #else
905 /* broken on lot of little files */
906 tctx->bps_count++;
907 tctx->bps = (tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count;
908 tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
909 #endif
913 /* --------------------------------------------------------------------------------------------- */
915 /* {{{ Move routines */
916 static FileProgressStatus
917 move_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s,
918 const char *d)
920 struct stat src_stats, dst_stats;
921 FileProgressStatus return_status = FILE_CONT;
922 gboolean copy_done = FALSE;
923 gboolean old_ask_overwrite;
924 vfs_path_t *src_vpath, *dst_vpath;
926 src_vpath = vfs_path_from_str (s);
927 dst_vpath = vfs_path_from_str (d);
929 file_progress_show_source (ctx, src_vpath);
930 file_progress_show_target (ctx, dst_vpath);
932 if (check_progress_buttons (ctx) == FILE_ABORT)
934 return_status = FILE_ABORT;
935 goto ret;
938 mc_refresh ();
940 while (mc_lstat (src_vpath, &src_stats) != 0)
942 /* Source doesn't exist */
943 if (ctx->skip_all)
944 return_status = FILE_SKIPALL;
945 else
947 return_status = file_error (_("Cannot stat file \"%s\"\n%s"), s);
948 if (return_status == FILE_SKIPALL)
949 ctx->skip_all = TRUE;
952 if (return_status != FILE_RETRY)
953 goto ret;
956 if (mc_lstat (dst_vpath, &dst_stats) == 0)
958 if (src_stats.st_dev == dst_stats.st_dev && src_stats.st_ino == dst_stats.st_ino)
960 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), s, d);
961 goto ret;
964 if (S_ISDIR (dst_stats.st_mode))
966 message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
967 do_refresh ();
968 return_status = FILE_SKIP;
969 goto ret;
972 if (confirm_overwrite)
974 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
975 if (return_status != FILE_CONT)
976 goto ret;
978 /* Ok to overwrite */
981 if (!ctx->do_append)
983 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks)
985 return_status = make_symlink (ctx, s, d);
986 if (return_status == FILE_CONT)
987 goto retry_src_remove;
988 goto ret;
991 if (mc_rename (src_vpath, dst_vpath) == 0)
993 return_status = progress_update_one (tctx, ctx, src_stats.st_size);
994 goto ret;
997 #if 0
998 /* Comparison to EXDEV seems not to work in nfs if you're moving from
999 one nfs to the same, but on the server it is on two different
1000 filesystems. Then nfs returns EIO instead of EXDEV.
1001 Hope it will not hurt if we always in case of error try to copy/delete. */
1002 else
1003 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1005 if (errno != EXDEV)
1007 if (ctx->skip_all)
1008 return_status = FILE_SKIPALL;
1009 else
1011 return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
1012 if (return_status == FILE_SKIPALL)
1013 ctx->skip_all = TRUE;
1014 if (return_status == FILE_RETRY)
1015 goto retry_rename;
1018 goto ret;
1020 #endif
1022 /* Failed because filesystem boundary -> copy the file instead */
1023 old_ask_overwrite = tctx->ask_overwrite;
1024 tctx->ask_overwrite = FALSE;
1025 return_status = copy_file_file (tctx, ctx, s, d);
1026 tctx->ask_overwrite = old_ask_overwrite;
1027 if (return_status != FILE_CONT)
1028 goto ret;
1030 copy_done = TRUE;
1032 file_progress_show_source (ctx, NULL);
1033 file_progress_show (ctx, 0, 0, "", FALSE);
1035 return_status = check_progress_buttons (ctx);
1036 if (return_status != FILE_CONT)
1037 goto ret;
1038 mc_refresh ();
1040 retry_src_remove:
1041 if (mc_unlink (src_vpath) != 0 && !ctx->skip_all)
1043 return_status = file_error (_("Cannot remove file \"%s\"\n%s"), s);
1044 if (return_status == FILE_RETRY)
1045 goto retry_src_remove;
1046 if (return_status == FILE_SKIPALL)
1047 ctx->skip_all = TRUE;
1048 goto ret;
1051 if (!copy_done)
1052 return_status = progress_update_one (tctx, ctx, src_stats.st_size);
1054 ret:
1055 vfs_path_free (src_vpath);
1056 vfs_path_free (dst_vpath);
1058 return return_status;
1061 /* }}} */
1063 /* --------------------------------------------------------------------------------------------- */
1064 /* {{{ Erase routines */
1065 /** Don't update progress status if progress_count==NULL */
1067 static FileProgressStatus
1068 erase_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
1070 struct stat buf;
1072 file_progress_show_deleting (ctx, vfs_path_as_str (vpath), &tctx->progress_count);
1073 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1074 if (check_progress_buttons (ctx) == FILE_ABORT)
1075 return FILE_ABORT;
1077 mc_refresh ();
1079 if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0)
1081 /* ignore, most likely the mc_unlink fails, too */
1082 buf.st_size = 0;
1085 while (mc_unlink (vpath) != 0 && !ctx->skip_all)
1087 int return_status;
1089 return_status = file_error (_("Cannot delete file \"%s\"\n%s"), vfs_path_as_str (vpath));
1090 if (return_status == FILE_ABORT)
1091 return return_status;
1092 if (return_status == FILE_RETRY)
1093 continue;
1094 if (return_status == FILE_SKIPALL)
1095 ctx->skip_all = TRUE;
1096 break;
1099 if (tctx->progress_count == 0)
1100 return FILE_CONT;
1102 return check_progress_buttons (ctx);
1105 /* --------------------------------------------------------------------------------------------- */
1108 Recursive remove of files
1109 abort->cancel stack
1110 skip ->warn every level, gets default
1111 skipall->remove as much as possible
1113 static FileProgressStatus
1114 recursive_erase (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
1116 struct dirent *next;
1117 DIR *reading;
1118 const char *s;
1119 FileProgressStatus return_status = FILE_CONT;
1121 reading = mc_opendir (vpath);
1122 if (reading == NULL)
1123 return FILE_RETRY;
1125 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
1127 vfs_path_t *tmp_vpath;
1128 struct stat buf;
1130 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
1131 continue;
1133 tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
1134 if (mc_lstat (tmp_vpath, &buf) != 0)
1136 mc_closedir (reading);
1137 vfs_path_free (tmp_vpath);
1138 return FILE_RETRY;
1140 if (S_ISDIR (buf.st_mode))
1141 return_status = recursive_erase (tctx, ctx, tmp_vpath);
1142 else
1143 return_status = erase_file (tctx, ctx, tmp_vpath);
1144 vfs_path_free (tmp_vpath);
1146 mc_closedir (reading);
1148 if (return_status == FILE_ABORT)
1149 return FILE_ABORT;
1151 s = vfs_path_as_str (vpath);
1153 file_progress_show_deleting (ctx, s, NULL);
1154 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1155 if (check_progress_buttons (ctx) == FILE_ABORT)
1156 return FILE_ABORT;
1158 mc_refresh ();
1160 while (my_rmdir (s) != 0 && !ctx->skip_all)
1162 return_status = file_error (_("Cannot remove directory \"%s\"\n%s"), s);
1163 if (return_status == FILE_RETRY)
1164 continue;
1165 if (return_status == FILE_ABORT)
1166 break;
1167 if (return_status == FILE_SKIPALL)
1168 ctx->skip_all = TRUE;
1169 break;
1172 return return_status;
1175 /* --------------------------------------------------------------------------------------------- */
1176 /** Return -1 on error, 1 if there are no entries besides "." and ".."
1177 in the directory path points to, 0 else. */
1179 static int
1180 check_dir_is_empty (const vfs_path_t * vpath)
1182 DIR *dir;
1183 struct dirent *d;
1184 int i = 1;
1186 dir = mc_opendir (vpath);
1187 if (dir == NULL)
1188 return -1;
1190 for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1191 if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1193 i = 0;
1194 break;
1197 mc_closedir (dir);
1198 return i;
1201 /* --------------------------------------------------------------------------------------------- */
1203 static FileProgressStatus
1204 erase_dir_iff_empty (file_op_context_t * ctx, const vfs_path_t * vpath, size_t count)
1206 FileProgressStatus error = FILE_CONT;
1207 const char *s;
1209 s = vfs_path_as_str (vpath);
1211 file_progress_show_deleting (ctx, s, NULL);
1212 file_progress_show_count (ctx, count, ctx->progress_count);
1213 if (check_progress_buttons (ctx) == FILE_ABORT)
1214 return FILE_ABORT;
1216 mc_refresh ();
1218 if (check_dir_is_empty (vpath) == 1) /* not empty or error */
1220 while (my_rmdir (s) != 0 && !ctx->skip_all)
1222 error = file_error (_("Cannot remove directory \"%s\"\n%s"), s);
1223 if (error == FILE_SKIPALL)
1224 ctx->skip_all = TRUE;
1225 if (error != FILE_RETRY)
1226 break;
1230 return error;
1233 /* }}} */
1235 /* --------------------------------------------------------------------------------------------- */
1236 /* {{{ Panel operate routines */
1239 * Return currently selected entry name or the name of the first marked
1240 * entry if there is one.
1243 static const char *
1244 panel_get_file (WPanel * panel)
1246 if (get_current_type () == view_tree)
1248 WTree *tree;
1249 const vfs_path_t *selected_name;
1251 tree = (WTree *) get_panel_widget (get_current_index ());
1252 selected_name = tree_selected_name (tree);
1253 return vfs_path_as_str (selected_name);
1256 if (panel->marked != 0)
1258 int i;
1260 for (i = 0; i < panel->dir.len; i++)
1261 if (panel->dir.list[i].f.marked)
1262 return panel->dir.list[i].fname;
1265 return panel->dir.list[panel->selected].fname;
1268 /* --------------------------------------------------------------------------------------------- */
1270 * panel_compute_totals:
1272 * compute the number of files and the number of bytes
1273 * used up by the whole selection, recursing directories
1274 * as required. In addition, it checks to see if it will
1275 * overwrite any files by doing the copy.
1278 static FileProgressStatus
1279 panel_compute_totals (const WPanel * panel, dirsize_status_msg_t * sm, size_t * ret_count,
1280 uintmax_t * ret_total, gboolean compute_symlinks)
1282 int i;
1283 size_t dir_count = 0;
1285 for (i = 0; i < panel->dir.len; i++)
1287 struct stat *s;
1289 if (!panel->dir.list[i].f.marked)
1290 continue;
1292 s = &panel->dir.list[i].st;
1294 if (S_ISDIR (s->st_mode))
1296 vfs_path_t *p;
1297 FileProgressStatus status;
1299 p = vfs_path_append_new (panel->cwd_vpath, panel->dir.list[i].fname, (char *) NULL);
1300 status = compute_dir_size (p, sm, &dir_count, ret_count, ret_total, compute_symlinks);
1301 vfs_path_free (p);
1303 if (status != FILE_CONT)
1304 return status;
1306 else
1308 (*ret_count)++;
1309 *ret_total += (uintmax_t) s->st_size;
1313 return FILE_CONT;
1316 /* --------------------------------------------------------------------------------------------- */
1318 /** Initialize variables for progress bars */
1319 static FileProgressStatus
1320 panel_operate_init_totals (const WPanel * panel, const char *source, file_op_context_t * ctx,
1321 filegui_dialog_type_t dialog_type)
1323 FileProgressStatus status;
1325 #ifdef ENABLE_BACKGROUND
1326 if (mc_global.we_are_background)
1327 return FILE_CONT;
1328 #endif
1330 if (verbose && file_op_compute_totals)
1332 dirsize_status_msg_t dsm;
1334 memset (&dsm, 0, sizeof (dsm));
1335 dsm.allow_skip = TRUE;
1336 status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
1337 dirsize_status_update_cb, dirsize_status_deinit_cb);
1339 ctx->progress_count = 0;
1340 ctx->progress_bytes = 0;
1342 if (source == NULL)
1343 status = panel_compute_totals (panel, &dsm, &ctx->progress_count, &ctx->progress_bytes,
1344 ctx->follow_links);
1345 else
1347 vfs_path_t *p;
1348 size_t dir_count = 0;
1350 p = vfs_path_from_str (source);
1351 status = compute_dir_size (p, &dsm, &dir_count, &ctx->progress_count,
1352 &ctx->progress_bytes, ctx->follow_links);
1353 vfs_path_free (p);
1356 status_msg_deinit (STATUS_MSG (&dsm));
1358 ctx->progress_totals_computed = (status == FILE_CONT);
1360 if (status == FILE_SKIP)
1361 status = FILE_CONT;
1363 else
1365 status = FILE_CONT;
1366 ctx->progress_count = panel->marked;
1367 ctx->progress_bytes = panel->total;
1368 ctx->progress_totals_computed = FALSE;
1371 file_op_context_create_ui (ctx, TRUE, dialog_type);
1373 return status;
1376 /* --------------------------------------------------------------------------------------------- */
1378 * Generate user prompt for panel operation.
1379 * src_stat must be not NULL for single source, and NULL for multiple sources
1382 static char *
1383 panel_operate_generate_prompt (const WPanel * panel, FileOperation operation,
1384 const struct stat *src_stat)
1386 char *sp;
1387 char *format_string;
1388 const char *cp;
1390 static gboolean i18n_flag = FALSE;
1391 if (!i18n_flag)
1393 size_t i;
1395 for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1396 op_names1[i] = Q_ (op_names1[i]);
1398 #ifdef ENABLE_NLS
1399 for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1400 prompt_parts[i] = _(prompt_parts[i]);
1402 one_format = _(one_format);
1403 many_format = _(many_format);
1404 #endif /* ENABLE_NLS */
1405 i18n_flag = TRUE;
1408 /* Possible prompts:
1409 * OP_COPY:
1410 * "Copy file \"%s\" with source mask:"
1411 * "Copy %d files with source mask:"
1412 * "Copy directory \"%s\" with source mask:"
1413 * "Copy %d directories with source mask:"
1414 * "Copy %d files/directories with source mask:"
1415 * OP_MOVE:
1416 * "Move file \"%s\" with source mask:"
1417 * "Move %d files with source mask:"
1418 * "Move directory \"%s\" with source mask:"
1419 * "Move %d directories with source mask:"
1420 * "Move %d files/directories with source mask:"
1421 * OP_DELETE:
1422 * "Delete file \"%s\"?"
1423 * "Delete %d files?"
1424 * "Delete directory \"%s\"?"
1425 * "Delete %d directories?"
1426 * "Delete %d files/directories?"
1429 cp = (src_stat != NULL ? one_format : many_format);
1431 /* 1. Substitute %o */
1432 format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1434 /* 2. Substitute %n */
1435 cp = operation == OP_DELETE ? "\n" : " ";
1436 sp = format_string;
1437 format_string = str_replace_all (sp, "%n", cp);
1438 g_free (sp);
1440 /* 3. Substitute %f */
1441 if (src_stat != NULL)
1442 cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1443 else if (panel->marked == panel->dirs_marked)
1444 cp = prompt_parts[3];
1445 else
1446 cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1448 sp = format_string;
1449 format_string = str_replace_all (sp, "%f", cp);
1450 g_free (sp);
1452 /* 4. Substitute %m */
1453 cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1454 sp = format_string;
1455 format_string = str_replace_all (sp, "%m", cp);
1456 g_free (sp);
1458 return format_string;
1461 /* --------------------------------------------------------------------------------------------- */
1463 #ifdef ENABLE_BACKGROUND
1464 static int
1465 end_bg_process (file_op_context_t * ctx, enum OperationMode mode)
1467 int pid = ctx->pid;
1469 (void) mode;
1470 ctx->pid = 0;
1472 unregister_task_with_pid (pid);
1473 /* file_op_context_destroy(ctx); */
1474 return 1;
1476 #endif
1477 /* }}} */
1479 /* --------------------------------------------------------------------------------------------- */
1480 /*** public functions ****************************************************************************/
1481 /* --------------------------------------------------------------------------------------------- */
1483 FileProgressStatus
1484 copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx,
1485 const char *src_path, const char *dst_path)
1487 uid_t src_uid = (uid_t) (-1);
1488 gid_t src_gid = (gid_t) (-1);
1490 int src_desc, dest_desc = -1;
1491 mode_t src_mode = 0; /* The mode of the source file */
1492 struct stat src_stat, dst_stat;
1493 struct utimbuf utb;
1494 gboolean dst_exists = FALSE, appending = FALSE;
1495 off_t file_size = -1;
1496 FileProgressStatus return_status, temp_status;
1497 struct timeval tv_transfer_start;
1498 dest_status_t dst_status = DEST_NONE;
1499 int open_flags;
1500 vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
1501 char *buf = NULL;
1503 /* FIXME: We should not be using global variables! */
1504 ctx->do_reget = 0;
1505 return_status = FILE_RETRY;
1507 dst_vpath = vfs_path_from_str (dst_path);
1508 src_vpath = vfs_path_from_str (src_path);
1510 file_progress_show_source (ctx, src_vpath);
1511 file_progress_show_target (ctx, dst_vpath);
1513 if (check_progress_buttons (ctx) == FILE_ABORT)
1515 return_status = FILE_ABORT;
1516 goto ret_fast;
1519 mc_refresh ();
1521 while (mc_stat (dst_vpath, &dst_stat) == 0)
1523 if (S_ISDIR (dst_stat.st_mode))
1525 if (ctx->skip_all)
1526 return_status = FILE_SKIPALL;
1527 else
1529 return_status = file_error (_("Cannot overwrite directory \"%s\"\n%s"), dst_path);
1530 if (return_status == FILE_SKIPALL)
1531 ctx->skip_all = TRUE;
1532 if (return_status == FILE_RETRY)
1533 continue;
1535 goto ret_fast;
1538 dst_exists = TRUE;
1539 break;
1542 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
1544 if (ctx->skip_all)
1545 return_status = FILE_SKIPALL;
1546 else
1548 return_status = file_error (_("Cannot stat source file \"%s\"\n%s"), src_path);
1549 if (return_status == FILE_SKIPALL)
1550 ctx->skip_all = TRUE;
1553 if (return_status != FILE_RETRY)
1554 goto ret_fast;
1557 if (dst_exists)
1559 /* Destination already exists */
1560 if (src_stat.st_dev == dst_stat.st_dev && src_stat.st_ino == dst_stat.st_ino)
1562 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"),
1563 src_path, dst_path);
1564 goto ret_fast;
1567 /* Should we replace destination? */
1568 if (tctx->ask_overwrite)
1570 ctx->do_reget = 0;
1571 return_status = query_replace (ctx, dst_path, &src_stat, &dst_stat);
1572 if (return_status != FILE_CONT)
1573 goto ret_fast;
1577 if (!ctx->do_append)
1579 /* Check the hardlinks */
1580 if (!ctx->follow_links && src_stat.st_nlink > 1
1581 && check_hardlinks (src_vpath, dst_vpath, &src_stat))
1583 /* We have made a hardlink - no more processing is necessary */
1584 return_status = FILE_CONT;
1585 goto ret_fast;
1588 if (S_ISLNK (src_stat.st_mode))
1590 return_status = make_symlink (ctx, src_path, dst_path);
1591 goto ret_fast;
1594 if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
1595 || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
1597 while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, src_stat.st_rdev) < 0
1598 && !ctx->skip_all)
1600 return_status = file_error (_("Cannot create special file \"%s\"\n%s"), dst_path);
1601 if (return_status == FILE_RETRY)
1602 continue;
1603 if (return_status == FILE_SKIPALL)
1604 ctx->skip_all = TRUE;
1605 goto ret_fast;
1607 /* Success */
1609 while (ctx->preserve_uidgid
1610 && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all)
1612 temp_status = file_error (_("Cannot chown target file \"%s\"\n%s"), dst_path);
1613 if (temp_status == FILE_SKIP)
1614 break;
1615 if (temp_status == FILE_SKIPALL)
1616 ctx->skip_all = TRUE;
1617 if (temp_status != FILE_RETRY)
1619 return_status = temp_status;
1620 goto ret_fast;
1624 while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
1625 && !ctx->skip_all)
1627 temp_status = file_error (_("Cannot chmod target file \"%s\"\n%s"), dst_path);
1628 if (temp_status == FILE_SKIP)
1629 break;
1630 if (temp_status == FILE_SKIPALL)
1631 ctx->skip_all = TRUE;
1632 if (temp_status != FILE_RETRY)
1634 return_status = temp_status;
1635 goto ret_fast;
1639 return_status = FILE_CONT;
1640 goto ret_fast;
1644 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
1646 while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all)
1648 return_status = file_error (_("Cannot open source file \"%s\"\n%s"), src_path);
1649 if (return_status == FILE_RETRY)
1650 continue;
1651 if (return_status == FILE_SKIPALL)
1652 ctx->skip_all = TRUE;
1653 if (return_status == FILE_SKIP)
1654 break;
1655 ctx->do_append = FALSE;
1656 goto ret_fast;
1659 if (ctx->do_reget != 0)
1661 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
1663 message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
1664 ctx->do_reget = 0;
1665 ctx->do_append = FALSE;
1669 while (mc_fstat (src_desc, &src_stat) != 0)
1671 if (ctx->skip_all)
1672 return_status = FILE_SKIPALL;
1673 else
1675 return_status = file_error (_("Cannot fstat source file \"%s\"\n%s"), src_path);
1676 if (return_status == FILE_RETRY)
1677 continue;
1678 if (return_status == FILE_SKIPALL)
1679 ctx->skip_all = TRUE;
1680 ctx->do_append = FALSE;
1682 goto ret;
1685 src_mode = src_stat.st_mode;
1686 src_uid = src_stat.st_uid;
1687 src_gid = src_stat.st_gid;
1688 utb.actime = src_stat.st_atime;
1689 utb.modtime = src_stat.st_mtime;
1690 file_size = src_stat.st_size;
1692 open_flags = O_WRONLY;
1693 if (dst_exists)
1695 if (ctx->do_append)
1696 open_flags |= O_APPEND;
1697 else
1698 open_flags |= O_CREAT | O_TRUNC;
1700 else
1702 open_flags |= O_CREAT | O_EXCL;
1705 while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
1707 if (errno != EEXIST)
1709 if (ctx->skip_all)
1710 return_status = FILE_SKIPALL;
1711 else
1713 return_status = file_error (_("Cannot create target file \"%s\"\n%s"), dst_path);
1714 if (return_status == FILE_RETRY)
1715 continue;
1716 if (return_status == FILE_SKIPALL)
1717 ctx->skip_all = TRUE;
1718 ctx->do_append = FALSE;
1721 goto ret;
1723 dst_status = DEST_SHORT; /* file opened, but not fully copied */
1725 appending = ctx->do_append;
1726 ctx->do_append = FALSE;
1728 /* Find out the optimal buffer size. */
1729 while (mc_fstat (dest_desc, &dst_stat) != 0)
1731 if (ctx->skip_all)
1732 return_status = FILE_SKIPALL;
1733 else
1735 return_status = file_error (_("Cannot fstat target file \"%s\"\n%s"), dst_path);
1736 if (return_status == FILE_RETRY)
1737 continue;
1738 if (return_status == FILE_SKIPALL)
1739 ctx->skip_all = TRUE;
1741 goto ret;
1744 /* try preallocate space; if fail, try copy anyway */
1745 while (vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
1747 if (ctx->skip_all)
1749 /* cannot allocate, start the file copying anyway */
1750 return_status = FILE_CONT;
1751 break;
1754 return_status =
1755 file_error (_("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
1757 if (return_status == FILE_SKIPALL)
1758 ctx->skip_all = TRUE;
1760 if (ctx->skip_all || return_status == FILE_SKIP)
1762 /* skip the space allocation error, start file copying */
1763 return_status = FILE_CONT;
1764 break;
1767 if (return_status == FILE_ABORT)
1769 mc_close (dest_desc);
1770 dest_desc = -1;
1771 mc_unlink (dst_vpath);
1772 dst_status = DEST_NONE;
1773 goto ret;
1776 /* return_status == FILE_RETRY -- try allocate space again */
1779 ctx->eta_secs = 0.0;
1780 ctx->bps = 0;
1782 if (tctx->bps == 0 || (file_size / (tctx->bps)) > FILEOP_UPDATE_INTERVAL)
1783 file_progress_show (ctx, 0, file_size, "", TRUE);
1784 else
1785 file_progress_show (ctx, 1, 1, "", TRUE);
1786 return_status = check_progress_buttons (ctx);
1787 mc_refresh ();
1789 if (return_status == FILE_CONT)
1791 size_t bufsize;
1792 off_t n_read_total = 0;
1793 struct timeval tv_current, tv_last_update, tv_last_input;
1794 int secs, update_secs;
1795 const char *stalled_msg = "";
1796 gboolean is_first_time = TRUE;
1798 tv_last_update = tv_transfer_start;
1800 bufsize = io_blksize (dst_stat);
1801 buf = g_malloc (bufsize);
1803 while (TRUE)
1805 ssize_t n_read = -1, n_written;
1807 /* src_read */
1808 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
1809 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all)
1811 return_status = file_error (_("Cannot read source file \"%s\"\n%s"), src_path);
1812 if (return_status == FILE_RETRY)
1813 continue;
1814 if (return_status == FILE_SKIPALL)
1815 ctx->skip_all = TRUE;
1816 goto ret;
1819 if (n_read == 0)
1820 break;
1822 gettimeofday (&tv_current, NULL);
1824 if (n_read > 0)
1826 char *t = buf;
1828 n_read_total += n_read;
1830 /* Windows NT ftp servers report that files have no
1831 * permissions: -------, so if we happen to have actually
1832 * read something, we should fix the permissions.
1834 if ((src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == 0)
1835 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
1836 gettimeofday (&tv_last_input, NULL);
1838 /* dst_write */
1839 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
1841 gboolean write_errno_nospace;
1843 if (n_written > 0)
1845 n_read -= n_written;
1846 t += n_written;
1847 continue;
1850 write_errno_nospace = (n_written < 0 && errno == ENOSPC);
1852 if (ctx->skip_all)
1853 return_status = FILE_SKIPALL;
1854 else
1855 return_status =
1856 file_error (_("Cannot write target file \"%s\"\n%s"), dst_path);
1858 if (return_status == FILE_SKIP)
1860 if (write_errno_nospace)
1861 goto ret;
1862 break;
1864 if (return_status == FILE_SKIPALL)
1866 ctx->skip_all = TRUE;
1867 if (write_errno_nospace)
1868 goto ret;
1870 if (return_status != FILE_RETRY)
1871 goto ret;
1875 tctx->copied_bytes = tctx->progress_bytes + n_read_total + ctx->do_reget;
1877 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
1878 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
1880 if (is_first_time || secs > FILEOP_UPDATE_INTERVAL)
1882 copy_file_file_display_progress (tctx, ctx,
1883 tv_current,
1884 tv_transfer_start, file_size, n_read_total);
1885 tv_last_update = tv_current;
1887 is_first_time = FALSE;
1889 if (update_secs > FILEOP_STALLING_INTERVAL)
1891 stalled_msg = _("(stalled)");
1895 gboolean force_update;
1897 force_update =
1898 (tv_current.tv_sec - tctx->transfer_start.tv_sec) > FILEOP_UPDATE_INTERVAL;
1900 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
1902 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1903 file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
1906 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size, stalled_msg,
1907 force_update);
1909 mc_refresh ();
1911 return_status = check_progress_buttons (ctx);
1913 if (return_status != FILE_CONT)
1915 mc_refresh ();
1916 goto ret;
1920 dst_status = DEST_FULL; /* copy successful, don't remove target file */
1923 ret:
1924 g_free (buf);
1926 rotate_dash (FALSE);
1927 while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all)
1929 temp_status = file_error (_("Cannot close source file \"%s\"\n%s"), src_path);
1930 if (temp_status == FILE_RETRY)
1931 continue;
1932 if (temp_status == FILE_ABORT)
1933 return_status = temp_status;
1934 if (temp_status == FILE_SKIPALL)
1935 ctx->skip_all = TRUE;
1936 break;
1939 while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all)
1941 temp_status = file_error (_("Cannot close target file \"%s\"\n%s"), dst_path);
1942 if (temp_status == FILE_RETRY)
1943 continue;
1944 if (temp_status == FILE_SKIPALL)
1945 ctx->skip_all = TRUE;
1946 return_status = temp_status;
1947 break;
1950 if (dst_status == DEST_SHORT)
1952 /* Query to remove short file */
1953 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved. Keep it?"),
1954 D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
1955 mc_unlink (dst_vpath);
1957 else if (dst_status == DEST_FULL)
1959 /* Copy has succeeded */
1960 if (!appending && ctx->preserve_uidgid)
1962 while (mc_chown (dst_vpath, src_uid, src_gid) != 0 && !ctx->skip_all)
1964 temp_status = file_error (_("Cannot chown target file \"%s\"\n%s"), dst_path);
1965 if (temp_status == FILE_RETRY)
1966 continue;
1967 if (temp_status == FILE_SKIPALL)
1969 ctx->skip_all = TRUE;
1970 return_status = FILE_CONT;
1972 if (temp_status == FILE_SKIP)
1973 return_status = FILE_CONT;
1974 break;
1978 if (!appending)
1980 if (ctx->preserve)
1982 while (mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0 && !ctx->skip_all)
1984 temp_status = file_error (_("Cannot chmod target file \"%s\"\n%s"), dst_path);
1985 if (temp_status == FILE_RETRY)
1986 continue;
1987 if (temp_status == FILE_SKIPALL)
1989 ctx->skip_all = TRUE;
1990 return_status = FILE_CONT;
1992 if (temp_status == FILE_SKIP)
1993 return_status = FILE_CONT;
1994 break;
1997 else if (!dst_exists)
1999 src_mode = umask (-1);
2000 umask (src_mode);
2001 src_mode = 0100666 & ~src_mode;
2002 mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2004 mc_utime (dst_vpath, &utb);
2008 if (return_status == FILE_CONT)
2009 return_status = progress_update_one (tctx, ctx, file_size);
2011 ret_fast:
2012 vfs_path_free (src_vpath);
2013 vfs_path_free (dst_vpath);
2014 return return_status;
2017 /* --------------------------------------------------------------------------------------------- */
2019 * I think these copy_*_* functions should have a return type.
2020 * anyway, this function *must* have two directories as arguments.
2022 /* FIXME: This function needs to check the return values of the
2023 function calls */
2025 FileProgressStatus
2026 copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d,
2027 gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs)
2029 struct dirent *next;
2030 struct stat buf, cbuf;
2031 DIR *reading;
2032 FileProgressStatus return_status = FILE_CONT;
2033 struct link *lp;
2034 vfs_path_t *src_vpath, *dst_vpath;
2035 gboolean do_mkdir = TRUE;
2037 src_vpath = vfs_path_from_str (s);
2038 dst_vpath = vfs_path_from_str (d);
2040 /* First get the mode of the source dir */
2042 retry_src_stat:
2043 if ((*ctx->stat_func) (src_vpath, &cbuf) != 0)
2045 if (ctx->skip_all)
2046 return_status = FILE_SKIPALL;
2047 else
2049 return_status = file_error (_("Cannot stat source directory \"%s\"\n%s"), s);
2050 if (return_status == FILE_RETRY)
2051 goto retry_src_stat;
2052 if (return_status == FILE_SKIPALL)
2053 ctx->skip_all = TRUE;
2055 goto ret_fast;
2058 if (is_in_linklist (dest_dirs, src_vpath, &cbuf))
2060 /* Don't copy a directory we created before (we don't want to copy
2061 infinitely if a directory is copied into itself) */
2062 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
2063 return_status = FILE_CONT;
2064 goto ret_fast;
2067 /* Hmm, hardlink to directory??? - Norbert */
2068 /* FIXME: In this step we should do something
2069 in case the destination already exist */
2070 /* Check the hardlinks */
2071 if (ctx->preserve && cbuf.st_nlink > 1 && check_hardlinks (src_vpath, dst_vpath, &cbuf))
2073 /* We have made a hardlink - no more processing is necessary */
2074 goto ret_fast;
2077 if (!S_ISDIR (cbuf.st_mode))
2079 if (ctx->skip_all)
2080 return_status = FILE_SKIPALL;
2081 else
2083 return_status = file_error (_("Source \"%s\" is not a directory\n%s"), s);
2084 if (return_status == FILE_RETRY)
2085 goto retry_src_stat;
2086 if (return_status == FILE_SKIPALL)
2087 ctx->skip_all = TRUE;
2089 goto ret_fast;
2092 if (is_in_linklist (parent_dirs, src_vpath, &cbuf))
2094 /* we found a cyclic symbolic link */
2095 message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
2096 return_status = FILE_SKIP;
2097 goto ret_fast;
2100 lp = g_new0 (struct link, 1);
2101 lp->vfs = vfs_path_get_by_index (src_vpath, -1)->class;
2102 lp->ino = cbuf.st_ino;
2103 lp->dev = cbuf.st_dev;
2104 parent_dirs = g_slist_prepend (parent_dirs, lp);
2106 retry_dst_stat:
2107 /* Now, check if the dest dir exists, if not, create it. */
2108 if (mc_stat (dst_vpath, &buf) != 0)
2110 /* Here the dir doesn't exist : make it ! */
2111 if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
2113 return_status = FILE_CONT;
2114 goto ret;
2117 else
2120 * If the destination directory exists, we want to copy the whole
2121 * directory, but we only want this to happen once.
2123 * Escape sequences added to the * to compiler warnings.
2124 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
2125 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
2127 if (!S_ISDIR (buf.st_mode))
2129 if (ctx->skip_all)
2130 return_status = FILE_SKIPALL;
2131 else
2133 return_status = file_error (_("Destination \"%s\" must be a directory\n%s"), d);
2134 if (return_status == FILE_SKIPALL)
2135 ctx->skip_all = TRUE;
2136 if (return_status == FILE_RETRY)
2137 goto retry_dst_stat;
2139 goto ret;
2141 /* Dive into subdir if exists */
2142 if (toplevel && ctx->dive_into_subdirs)
2144 vfs_path_t *tmp;
2146 tmp = dst_vpath;
2147 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
2148 vfs_path_free (tmp);
2151 else
2152 do_mkdir = FALSE;
2155 d = vfs_path_as_str (dst_vpath);
2157 if (do_mkdir)
2159 while (my_mkdir (dst_vpath, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
2161 if (ctx->skip_all)
2162 return_status = FILE_SKIPALL;
2163 else
2165 return_status = file_error (_("Cannot create target directory \"%s\"\n%s"), d);
2166 if (return_status == FILE_SKIPALL)
2167 ctx->skip_all = TRUE;
2169 if (return_status != FILE_RETRY)
2170 goto ret;
2173 lp = g_new0 (struct link, 1);
2174 mc_stat (dst_vpath, &buf);
2175 lp->vfs = vfs_path_get_by_index (dst_vpath, -1)->class;
2176 lp->ino = buf.st_ino;
2177 lp->dev = buf.st_dev;
2178 dest_dirs = g_slist_prepend (dest_dirs, lp);
2181 if (ctx->preserve_uidgid)
2183 while (mc_chown (dst_vpath, cbuf.st_uid, cbuf.st_gid) != 0)
2185 if (ctx->skip_all)
2186 return_status = FILE_SKIPALL;
2187 else
2189 return_status = file_error (_("Cannot chown target directory \"%s\"\n%s"), d);
2190 if (return_status == FILE_SKIPALL)
2191 ctx->skip_all = TRUE;
2193 if (return_status != FILE_RETRY)
2194 goto ret;
2198 /* open the source dir for reading */
2199 reading = mc_opendir (src_vpath);
2200 if (reading == NULL)
2201 goto ret;
2203 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
2205 char *path;
2206 vfs_path_t *tmp_vpath;
2209 * Now, we don't want '.' and '..' to be created / copied at any time
2211 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
2212 continue;
2214 /* get the filename and add it to the src directory */
2215 path = mc_build_filename (s, next->d_name, (char *) NULL);
2216 tmp_vpath = vfs_path_from_str (path);
2218 (*ctx->stat_func) (tmp_vpath, &buf);
2219 if (S_ISDIR (buf.st_mode))
2221 char *mdpath;
2223 mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
2225 * From here, we just intend to recursively copy subdirs, not
2226 * the double functionality of copying different when the target
2227 * dir already exists. So, we give the recursive call the flag 0
2228 * meaning no toplevel.
2230 return_status =
2231 copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
2232 g_free (mdpath);
2234 else
2236 char *dest_file;
2238 dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
2239 return_status = copy_file_file (tctx, ctx, path, dest_file);
2240 g_free (dest_file);
2243 g_free (path);
2245 if (do_delete && return_status == FILE_CONT)
2247 if (ctx->erase_at_end)
2249 lp = g_new0 (struct link, 1);
2250 lp->src_vpath = tmp_vpath;
2251 lp->st_mode = buf.st_mode;
2252 erase_list = g_slist_append (erase_list, lp);
2253 tmp_vpath = NULL;
2255 else if (S_ISDIR (buf.st_mode))
2256 return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
2257 else
2258 return_status = erase_file (tctx, ctx, tmp_vpath);
2260 vfs_path_free (tmp_vpath);
2262 mc_closedir (reading);
2264 if (ctx->preserve)
2266 struct utimbuf utb;
2268 mc_chmod (dst_vpath, cbuf.st_mode & ctx->umask_kill);
2269 utb.actime = cbuf.st_atime;
2270 utb.modtime = cbuf.st_mtime;
2271 mc_utime (dst_vpath, &utb);
2273 else
2275 cbuf.st_mode = umask (-1);
2276 umask (cbuf.st_mode);
2277 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
2278 mc_chmod (dst_vpath, cbuf.st_mode & ctx->umask_kill);
2281 ret:
2282 free_link (parent_dirs->data);
2283 g_slist_free_1 (parent_dirs);
2284 ret_fast:
2285 vfs_path_free (src_vpath);
2286 vfs_path_free (dst_vpath);
2287 return return_status;
2290 /* }}} */
2292 /* --------------------------------------------------------------------------------------------- */
2293 /* {{{ Move routines */
2295 FileProgressStatus
2296 move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d)
2298 struct stat sbuf, dbuf;
2299 FileProgressStatus return_status = FILE_CONT;
2300 gboolean move_over = FALSE;
2301 gboolean dstat_ok;
2302 vfs_path_t *src_vpath, *dst_vpath;
2304 src_vpath = vfs_path_from_str (s);
2305 dst_vpath = vfs_path_from_str (d);
2307 file_progress_show_source (ctx, src_vpath);
2308 file_progress_show_target (ctx, dst_vpath);
2310 if (check_progress_buttons (ctx) == FILE_ABORT)
2312 return_status = FILE_ABORT;
2313 goto ret_fast;
2316 mc_refresh ();
2318 mc_stat (src_vpath, &sbuf);
2320 dstat_ok = (mc_stat (dst_vpath, &dbuf) == 0);
2321 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
2323 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), s, d);
2324 goto ret_fast;
2327 if (!dstat_ok)
2328 ; /* destination doesn't exist */
2329 else if (!ctx->dive_into_subdirs)
2330 move_over = TRUE;
2331 else
2333 vfs_path_t *tmp;
2335 tmp = dst_vpath;
2336 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
2337 vfs_path_free (tmp);
2340 d = vfs_path_as_str (dst_vpath);
2342 /* Check if the user inputted an existing dir */
2343 retry_dst_stat:
2344 if (mc_stat (dst_vpath, &dbuf) == 0)
2346 if (move_over)
2348 return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL);
2350 if (return_status != FILE_CONT)
2351 goto ret;
2352 goto oktoret;
2354 else if (ctx->skip_all)
2355 return_status = FILE_SKIPALL;
2356 else
2358 if (S_ISDIR (dbuf.st_mode))
2359 return_status = file_error (_("Cannot overwrite directory \"%s\"\n%s"), d);
2360 else
2361 return_status = file_error (_("Cannot overwrite file \"%s\"\n%s"), d);
2362 if (return_status == FILE_SKIPALL)
2363 ctx->skip_all = TRUE;
2364 if (return_status == FILE_RETRY)
2365 goto retry_dst_stat;
2368 goto ret_fast;
2371 retry_rename:
2372 if (mc_rename (src_vpath, dst_vpath) == 0)
2374 return_status = FILE_CONT;
2375 goto ret;
2378 if (errno != EXDEV)
2380 if (!ctx->skip_all)
2382 return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
2383 if (return_status == FILE_SKIPALL)
2384 ctx->skip_all = TRUE;
2385 if (return_status == FILE_RETRY)
2386 goto retry_rename;
2388 goto ret;
2390 /* Failed because of filesystem boundary -> copy dir instead */
2391 return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL);
2393 if (return_status != FILE_CONT)
2394 goto ret;
2395 oktoret:
2396 file_progress_show_source (ctx, NULL);
2397 file_progress_show_target (ctx, NULL);
2398 file_progress_show (ctx, 0, 0, "", FALSE);
2400 return_status = check_progress_buttons (ctx);
2401 if (return_status != FILE_CONT)
2402 goto ret;
2404 mc_refresh ();
2405 if (ctx->erase_at_end)
2407 /* Reset progress count before delete to avoid counting files twice */
2408 tctx->progress_count = tctx->prev_progress_count;
2410 while (erase_list != NULL && return_status != FILE_ABORT)
2412 struct link *lp = (struct link *) erase_list->data;
2414 if (S_ISDIR (lp->st_mode))
2415 return_status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count);
2416 else
2417 return_status = erase_file (tctx, ctx, lp->src_vpath);
2419 erase_list = g_slist_remove (erase_list, lp);
2420 free_link (lp);
2423 /* Save progress counter before move next directory */
2424 tctx->prev_progress_count = tctx->progress_count;
2426 erase_dir_iff_empty (ctx, src_vpath, tctx->progress_count);
2428 ret:
2429 erase_list = free_linklist (erase_list);
2430 ret_fast:
2431 vfs_path_free (src_vpath);
2432 vfs_path_free (dst_vpath);
2433 return return_status;
2436 /* }}} */
2438 /* --------------------------------------------------------------------------------------------- */
2439 /* {{{ Erase routines */
2441 FileProgressStatus
2442 erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * s_vpath)
2444 FileProgressStatus error;
2446 file_progress_show_deleting (ctx, vfs_path_as_str (s_vpath), NULL);
2447 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2448 if (check_progress_buttons (ctx) == FILE_ABORT)
2449 return FILE_ABORT;
2451 mc_refresh ();
2453 /* The old way to detect a non empty directory was:
2454 error = my_rmdir (s);
2455 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
2456 For the linux user space nfs server (nfs-server-2.2beta29-2)
2457 we would have to check also for EIO. I hope the new way is
2458 fool proof. (Norbert)
2460 error = check_dir_is_empty (s_vpath);
2461 if (error == 0)
2462 { /* not empty */
2463 error = query_recursive (ctx, vfs_path_as_str (s_vpath));
2464 if (error == FILE_CONT)
2465 error = recursive_erase (tctx, ctx, s_vpath);
2466 return error;
2469 while (my_rmdir (vfs_path_as_str (s_vpath)) == -1 && !ctx->skip_all)
2471 error = file_error (_("Cannot remove directory \"%s\"\n%s"), vfs_path_as_str (s_vpath));
2472 if (error != FILE_RETRY)
2473 return error;
2476 return FILE_CONT;
2479 /* }}} */
2481 /* --------------------------------------------------------------------------------------------- */
2482 /* {{{ Panel operate routines */
2484 void
2485 dirsize_status_init_cb (status_msg_t * sm)
2487 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
2488 Widget *wd = WIDGET (sm->dlg);
2490 const char *b1_name = N_("&Abort");
2491 const char *b2_name = N_("&Skip");
2492 int b_width, ui_width;
2494 #ifdef ENABLE_NLS
2495 b1_name = _(b1_name);
2496 b2_name = _(b2_name);
2497 #endif
2499 b_width = str_term_width1 (b1_name) + 4;
2500 if (dsm->allow_skip)
2501 b_width += str_term_width1 (b2_name) + 4 + 1;
2503 ui_width = MAX (COLS / 2, b_width + 6);
2504 dsm->dirname = label_new (2, 3, "");
2505 add_widget (sm->dlg, dsm->dirname);
2506 dsm->count_size = label_new (3, 3, "");
2507 add_widget (sm->dlg, dsm->count_size);
2508 add_widget (sm->dlg, hline_new (4, -1, -1));
2510 dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
2511 add_widget (sm->dlg, dsm->abort_button);
2512 if (dsm->allow_skip)
2514 dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
2515 add_widget (sm->dlg, dsm->skip_button);
2516 dlg_select_widget (dsm->skip_button);
2519 widget_set_size (wd, wd->y, wd->x, 8, ui_width);
2520 dirsize_status_locate_buttons (dsm);
2523 /* --------------------------------------------------------------------------------------------- */
2526 dirsize_status_update_cb (status_msg_t * sm)
2528 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
2529 Widget *wd = WIDGET (sm->dlg);
2531 /* update second (longer label) */
2532 label_set_textv (dsm->count_size, _("Directories: %zd, total size: %s"),
2533 dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
2535 /* enlarge dialog if required */
2536 if (WIDGET (dsm->count_size)->cols + 6 > wd->cols)
2538 dlg_set_size (sm->dlg, wd->lines, WIDGET (dsm->count_size)->cols + 6);
2539 dirsize_status_locate_buttons (dsm);
2540 dlg_redraw (sm->dlg);
2543 /* adjust first label */
2544 label_set_text (dsm->dirname, str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->cols - 6));
2546 switch (status_msg_common_update (sm))
2548 case B_CANCEL:
2549 case FILE_ABORT:
2550 return FILE_ABORT;
2551 case FILE_SKIP:
2552 return FILE_SKIP;
2553 default:
2554 return FILE_CONT;
2558 /* --------------------------------------------------------------------------------------------- */
2560 void
2561 dirsize_status_deinit_cb (status_msg_t * sm)
2563 (void) sm;
2565 /* schedule to update passive panel */
2566 if (get_other_type () == view_listing)
2567 other_panel->dirty = 1;
2570 /* --------------------------------------------------------------------------------------------- */
2572 * compute_dir_size:
2574 * Computes the number of bytes used by the files in a directory
2577 FileProgressStatus
2578 compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
2579 size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total,
2580 gboolean compute_symlinks)
2582 return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
2583 compute_symlinks);
2586 /* --------------------------------------------------------------------------------------------- */
2588 * panel_operate:
2590 * Performs one of the operations on the selection on the source_panel
2591 * (copy, delete, move).
2593 * Returns TRUE if did change the directory
2594 * structure, Returns FALSE if user aborted
2596 * force_single forces operation on the current entry and affects
2597 * default destination. Current filename is used as default.
2600 gboolean
2601 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
2603 WPanel *panel = PANEL (source_panel);
2604 const gboolean single_entry = force_single || (panel->marked <= 1)
2605 || (get_current_type () == view_tree);
2607 const char *source = NULL;
2608 #ifdef WITH_FULL_PATHS
2609 vfs_path_t *source_with_vpath = NULL;
2610 #endif /* WITH_FULL_PATHS */
2611 char *dest = NULL;
2612 vfs_path_t *dest_vpath = NULL;
2613 char *temp = NULL;
2614 char *save_cwd = NULL, *save_dest = NULL;
2615 struct stat src_stat;
2616 gboolean ret_val = TRUE;
2617 int i;
2618 FileProgressStatus value;
2619 file_op_context_t *ctx;
2620 file_op_total_context_t *tctx;
2621 vfs_path_t *tmp_vpath;
2622 filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
2624 gboolean do_bg = FALSE; /* do background operation? */
2626 static gboolean i18n_flag = FALSE;
2627 if (!i18n_flag)
2629 for (i = G_N_ELEMENTS (op_names); i-- != 0;)
2630 op_names[i] = Q_ (op_names[i]);
2631 i18n_flag = TRUE;
2634 linklist = free_linklist (linklist);
2635 dest_dirs = free_linklist (dest_dirs);
2637 if (single_entry)
2639 gboolean ok;
2641 if (force_single)
2642 source = selection (panel)->fname;
2643 else
2644 source = panel_get_file (panel);
2646 ok = !DIR_IS_DOTDOT (source);
2648 if (!ok)
2649 message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
2650 else
2652 vfs_path_t *source_vpath;
2654 source_vpath = vfs_path_from_str (source);
2656 /* Update stat to get actual info */
2657 ok = mc_lstat (source_vpath, &src_stat) == 0;
2658 if (!ok)
2660 message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
2661 path_trunc (source, 30), unix_error_string (errno));
2663 /* Directory was changed outside MC. Reload it forced */
2664 if (!panel->is_panelized)
2666 panel_update_flags_t flags = UP_RELOAD;
2668 /* don't update panelized panel */
2669 if (get_other_type () == view_listing && other_panel->is_panelized)
2670 flags |= UP_ONLY_CURRENT;
2672 update_panels (flags, UP_KEEPSEL);
2676 vfs_path_free (source_vpath);
2679 if (!ok)
2680 return FALSE;
2683 ctx = file_op_context_new (operation);
2685 /* Show confirmation dialog */
2686 if (operation != OP_DELETE)
2688 const char *tmp_dest_dir;
2689 char *dest_dir;
2690 char *format;
2692 /* Forced single operations default to the original name */
2693 if (force_single)
2694 tmp_dest_dir = source;
2695 else if (get_other_type () == view_listing)
2696 tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
2697 else
2698 tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
2700 * Add trailing backslash only when do non-local ops.
2701 * It saves user from occasional file renames (when destination
2702 * dir is deleted)
2704 if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
2705 && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
2707 /* add trailing separator */
2708 dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
2710 else
2712 /* just copy */
2713 dest_dir = g_strdup (tmp_dest_dir);
2715 if (dest_dir == NULL)
2717 ret_val = FALSE;
2718 goto ret_fast;
2721 /* Generate confirmation prompt */
2722 format =
2723 panel_operate_generate_prompt (panel, operation, source != NULL ? &src_stat : NULL);
2725 dest =
2726 file_mask_dialog (ctx, operation, source != NULL, format,
2727 source != NULL ? source : (const void *) &panel->marked, dest_dir,
2728 &do_bg);
2730 g_free (format);
2731 g_free (dest_dir);
2733 if (dest == NULL || dest[0] == '\0')
2735 g_free (dest);
2736 ret_val = FALSE;
2737 goto ret_fast;
2739 dest_vpath = vfs_path_from_str (dest);
2741 else if (confirm_delete)
2743 char *format;
2744 char fmd_buf[BUF_MEDIUM];
2746 /* Generate confirmation prompt */
2747 format =
2748 panel_operate_generate_prompt (panel, OP_DELETE, source != NULL ? &src_stat : NULL);
2750 if (source == NULL)
2751 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
2752 else
2754 const int fmd_xlen = 64;
2755 i = fmd_xlen - str_term_width1 (format) - 4;
2756 g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
2759 g_free (format);
2761 if (safe_delete)
2762 query_set_sel (1);
2764 i = query_dialog (op_names[operation], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
2766 if (i != 0)
2768 ret_val = FALSE;
2769 goto ret_fast;
2773 tctx = file_op_total_context_new ();
2774 gettimeofday (&tctx->transfer_start, (struct timezone *) NULL);
2776 #ifdef ENABLE_BACKGROUND
2777 /* Did the user select to do a background operation? */
2778 if (do_bg)
2780 int v;
2782 v = do_background (ctx,
2783 g_strconcat (op_names[operation], ": ",
2784 vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
2785 if (v == -1)
2786 message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
2788 /* If we are the parent */
2789 if (v == 1)
2791 mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
2793 mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
2794 vfs_path_free (dest_vpath);
2795 g_free (dest);
2796 /* file_op_context_destroy (ctx); */
2797 return FALSE;
2800 else
2801 #endif /* ENABLE_BACKGROUND */
2803 if (operation == OP_DELETE)
2804 dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
2805 else if (single_entry && S_ISDIR (selection (panel)->st.st_mode))
2806 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
2807 else if (single_entry || force_single)
2808 dialog_type = FILEGUI_DIALOG_ONE_ITEM;
2809 else
2810 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
2813 /* Initialize things */
2814 /* We do not want to trash cache every time file is
2815 created/touched. However, this will make our cache contain
2816 invalid data. */
2817 if ((dest != NULL)
2818 && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
2819 save_dest = g_strdup (dest);
2821 if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
2822 && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
2823 save_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath));
2825 /* Now, let's do the job */
2827 /* This code is only called by the tree and panel code */
2828 if (single_entry)
2830 /* We now have ETA in all cases */
2832 /* One file: FIXME mc_chdir will take user out of any vfs */
2833 if ((operation != OP_COPY) && (get_current_type () == view_tree))
2835 vfs_path_t *vpath;
2836 int chdir_retcode;
2838 vpath = vfs_path_from_str (PATH_SEP_STR);
2839 chdir_retcode = mc_chdir (vpath);
2840 vfs_path_free (vpath);
2841 if (chdir_retcode < 0)
2843 ret_val = FALSE;
2844 goto clean_up;
2848 /* The source and src_stat variables have been initialized before */
2849 #ifdef WITH_FULL_PATHS
2850 if (g_path_is_absolute (source))
2851 source_with_vpath = vfs_path_from_str (source);
2852 else
2853 source_with_vpath = vfs_path_append_new (panel->cwd_vpath, source, (char *) NULL);
2854 #endif /* WITH_FULL_PATHS */
2855 if (panel_operate_init_totals (panel, vfs_path_as_str (source_with_vpath), ctx, dialog_type)
2856 == FILE_CONT)
2858 if (operation == OP_DELETE)
2860 if (S_ISDIR (src_stat.st_mode))
2861 value = erase_dir (tctx, ctx, source_with_vpath);
2862 else
2863 value = erase_file (tctx, ctx, source_with_vpath);
2865 else
2867 temp = transform_source (ctx, source_with_vpath);
2868 if (temp == NULL)
2869 value = transform_error;
2870 else
2872 char *repl_dest, *temp2;
2874 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2875 if (ctx->search_handle->error != MC_SEARCH_E_OK)
2877 if (ctx->search_handle->error_str != NULL)
2878 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
2880 g_free (repl_dest);
2881 goto clean_up;
2884 temp2 = mc_build_filename (repl_dest, temp, (char *) NULL);
2885 g_free (temp);
2886 g_free (repl_dest);
2887 g_free (dest);
2888 vfs_path_free (dest_vpath);
2889 dest = temp2;
2890 dest_vpath = vfs_path_from_str (dest);
2892 switch (operation)
2894 case OP_COPY:
2895 /* we use file_mask_op_follow_links only with OP_COPY */
2896 ctx->stat_func (source_with_vpath, &src_stat);
2898 if (S_ISDIR (src_stat.st_mode))
2899 value =
2900 copy_dir_dir (tctx, ctx, vfs_path_as_str (source_with_vpath),
2901 dest, TRUE, FALSE, FALSE, NULL);
2902 else
2903 value =
2904 copy_file_file (tctx, ctx, vfs_path_as_str (source_with_vpath),
2905 dest);
2906 break;
2908 case OP_MOVE:
2909 if (S_ISDIR (src_stat.st_mode))
2910 value =
2911 move_dir_dir (tctx, ctx, vfs_path_as_str (source_with_vpath), dest);
2912 else
2913 value =
2914 move_file_file (tctx, ctx, vfs_path_as_str (source_with_vpath),
2915 dest);
2916 break;
2918 default:
2919 /* Unknown file operation */
2920 abort ();
2923 } /* Copy or move operation */
2925 if ((value == FILE_CONT) && !force_single)
2926 unmark_files (panel);
2929 else
2931 /* Many files */
2933 /* Check destination for copy or move operation */
2934 while (operation != OP_DELETE)
2936 int dst_result;
2937 struct stat dst_stat;
2939 dst_result = mc_stat (dest_vpath, &dst_stat);
2941 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
2942 break;
2944 if (ctx->skip_all
2945 || file_error (_("Destination \"%s\" must be a directory\n%s"), dest) != FILE_RETRY)
2946 goto clean_up;
2949 if (panel_operate_init_totals (panel, NULL, ctx, dialog_type) == FILE_CONT)
2951 /* Loop for every file, perform the actual copy operation */
2952 for (i = 0; i < panel->dir.len; i++)
2954 const char *source2;
2956 if (!panel->dir.list[i].f.marked)
2957 continue; /* Skip the unmarked ones */
2959 source2 = panel->dir.list[i].fname;
2960 src_stat = panel->dir.list[i].st;
2962 #ifdef WITH_FULL_PATHS
2963 vfs_path_free (source_with_vpath);
2964 if (g_path_is_absolute (source2))
2965 source_with_vpath = vfs_path_from_str (source2);
2966 else
2967 source_with_vpath =
2968 vfs_path_append_new (panel->cwd_vpath, source2, (char *) NULL);
2969 #endif /* WITH_FULL_PATHS */
2971 if (operation == OP_DELETE)
2973 if (S_ISDIR (src_stat.st_mode))
2974 value = erase_dir (tctx, ctx, source_with_vpath);
2975 else
2976 value = erase_file (tctx, ctx, source_with_vpath);
2978 else
2980 temp = transform_source (ctx, source_with_vpath);
2981 if (temp == NULL)
2982 value = transform_error;
2983 else
2985 char *temp2, *repl_dest, *source_with_path_str;
2987 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2988 if (ctx->search_handle->error != MC_SEARCH_E_OK)
2990 if (ctx->search_handle->error_str != NULL)
2991 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
2993 g_free (repl_dest);
2994 goto clean_up;
2997 temp2 = mc_build_filename (repl_dest, temp, (char *) NULL);
2998 g_free (temp);
2999 g_free (repl_dest);
3000 source_with_path_str =
3001 strutils_shell_unescape (vfs_path_as_str (source_with_vpath));
3002 temp = strutils_shell_unescape (temp2);
3003 g_free (temp2);
3005 switch (operation)
3007 case OP_COPY:
3008 /* we use file_mask_op_follow_links only with OP_COPY */
3010 vfs_path_t *vpath;
3012 vpath = vfs_path_from_str (source_with_path_str);
3013 ctx->stat_func (vpath, &src_stat);
3014 vfs_path_free (vpath);
3016 if (S_ISDIR (src_stat.st_mode))
3017 value = copy_dir_dir (tctx, ctx, source_with_path_str, temp,
3018 TRUE, FALSE, FALSE, NULL);
3019 else
3020 value = copy_file_file (tctx, ctx, source_with_path_str, temp);
3021 dest_dirs = free_linklist (dest_dirs);
3022 break;
3024 case OP_MOVE:
3025 if (S_ISDIR (src_stat.st_mode))
3026 value = move_dir_dir (tctx, ctx, source_with_path_str, temp);
3027 else
3028 value = move_file_file (tctx, ctx, source_with_path_str, temp);
3029 break;
3031 default:
3032 /* Unknown file operation */
3033 abort ();
3036 g_free (source_with_path_str);
3037 g_free (temp);
3039 } /* Copy or move operation */
3041 if (value == FILE_ABORT)
3042 break;
3044 if (value == FILE_CONT)
3045 do_file_mark (panel, i, 0);
3047 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3049 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3050 file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
3053 if (operation != OP_DELETE)
3054 file_progress_show (ctx, 0, 0, "", FALSE);
3056 if (check_progress_buttons (ctx) == FILE_ABORT)
3057 break;
3059 mc_refresh ();
3060 } /* Loop for every file */
3062 } /* Many entries */
3064 clean_up:
3065 /* Clean up */
3066 if (save_cwd != NULL)
3068 tmp_vpath = vfs_path_from_str (save_cwd);
3069 mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3070 vfs_path_free (tmp_vpath);
3071 g_free (save_cwd);
3074 if (save_dest != NULL)
3076 tmp_vpath = vfs_path_from_str (save_dest);
3077 mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3078 vfs_path_free (tmp_vpath);
3079 g_free (save_dest);
3082 linklist = free_linklist (linklist);
3083 dest_dirs = free_linklist (dest_dirs);
3084 #ifdef WITH_FULL_PATHS
3085 vfs_path_free (source_with_vpath);
3086 #endif /* WITH_FULL_PATHS */
3087 g_free (dest);
3088 vfs_path_free (dest_vpath);
3089 MC_PTR_FREE (ctx->dest_mask);
3091 #ifdef ENABLE_BACKGROUND
3092 /* Let our parent know we are saying bye bye */
3093 if (mc_global.we_are_background)
3095 int cur_pid = getpid ();
3096 /* Send pid to parent with child context, it is fork and
3097 don't modify real parent ctx */
3098 ctx->pid = cur_pid;
3099 parent_call ((void *) end_bg_process, ctx, 0);
3101 vfs_shut ();
3102 my_exit (EXIT_SUCCESS);
3104 #endif /* ENABLE_BACKGROUND */
3106 file_op_total_context_destroy (tctx);
3107 ret_fast:
3108 file_op_context_destroy (ctx);
3110 return ret_val;
3113 /* }}} */
3115 /* --------------------------------------------------------------------------------------------- */
3116 /* {{{ Query/status report routines */
3117 /** Report error with one file */
3118 FileProgressStatus
3119 file_error (const char *format, const char *file)
3121 char buf[BUF_MEDIUM];
3123 g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3125 return do_file_error (buf);
3128 /* --------------------------------------------------------------------------------------------- */
3131 Cause emacs to enter folding mode for this file:
3132 Local variables:
3133 end: