(make_symlink): join if statements.
[midnight-commander.git] / src / filemanager / file.c
blob23cdce7120cba42381289755e6336082ba80cfbd
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 && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath)))
423 message (D_ERROR, MSG_ERROR,
424 _("Cannot make stable symlinks across"
425 "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
426 ctx->stable_symlinks = FALSE;
429 if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
431 const char *r = strrchr (src_path, PATH_SEP);
433 if (r)
435 char *p;
436 vfs_path_t *q;
438 p = g_strndup (src_path, r - src_path + 1);
439 if (g_path_is_absolute (dst_path))
440 q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
441 else
442 q = vfs_path_build_filename (p, dst_path, (char *) NULL);
444 if (vfs_path_tokens_count (q) > 1)
446 char *s;
447 vfs_path_t *tmp_vpath1, *tmp_vpath2;
449 tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
450 s = g_strconcat (p, link_target, (char *) NULL);
451 g_free (p);
452 g_strlcpy (link_target, s, sizeof (link_target));
453 g_free (s);
454 tmp_vpath2 = vfs_path_from_str (link_target);
455 s = diff_two_paths (tmp_vpath1, tmp_vpath2);
456 vfs_path_free (tmp_vpath1);
457 vfs_path_free (tmp_vpath2);
458 if (s)
460 g_strlcpy (link_target, s, sizeof (link_target));
461 g_free (s);
464 else
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 /* --------------------------------------------------------------------------------------------- */
663 /* {{{ Query/status report routines */
665 static FileProgressStatus
666 real_do_file_error (enum OperationMode mode, const char *error)
668 int result;
669 const char *msg;
671 msg = mode == Foreground ? MSG_ERROR : _("Background process error");
672 result =
673 query_dialog (msg, error, D_ERROR, 4, _("&Skip"), _("Ski&p all"), _("&Retry"), _("&Abort"));
675 switch (result)
677 case 0:
678 do_refresh ();
679 return FILE_SKIP;
681 case 1:
682 do_refresh ();
683 return FILE_SKIPALL;
685 case 2:
686 do_refresh ();
687 return FILE_RETRY;
689 case 3:
690 default:
691 return FILE_ABORT;
695 /* --------------------------------------------------------------------------------------------- */
697 static FileProgressStatus
698 real_query_recursive (file_op_context_t * ctx, enum OperationMode mode, const char *s)
700 if (ctx->recursive_result < RECURSIVE_ALWAYS)
702 const char *msg;
703 char *text;
705 msg = mode == Foreground
706 ? _("Directory \"%s\" not empty.\nDelete it recursively?")
707 : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
708 text = g_strdup_printf (msg, path_trunc (s, 30));
710 if (safe_delete)
711 query_set_sel (1);
713 ctx->recursive_result =
714 query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"),
715 _("Non&e"), _("&Abort"));
716 g_free (text);
718 if (ctx->recursive_result != RECURSIVE_ABORT)
719 do_refresh ();
722 switch (ctx->recursive_result)
724 case RECURSIVE_YES:
725 case RECURSIVE_ALWAYS:
726 return FILE_CONT;
728 case RECURSIVE_NO:
729 case RECURSIVE_NEVER:
730 return FILE_SKIP;
732 case RECURSIVE_ABORT:
733 default:
734 return FILE_ABORT;
738 /* --------------------------------------------------------------------------------------------- */
740 #ifdef ENABLE_BACKGROUND
741 static FileProgressStatus
742 do_file_error (const char *str)
744 /* *INDENT-OFF* */
745 union
747 void *p;
748 FileProgressStatus (*f) (enum OperationMode, const char *);
749 } pntr;
750 /* *INDENT-ON* */
752 pntr.f = real_do_file_error;
754 if (mc_global.we_are_background)
755 return parent_call (pntr.p, NULL, 1, strlen (str), str);
756 else
757 return real_do_file_error (Foreground, str);
760 /* --------------------------------------------------------------------------------------------- */
762 static FileProgressStatus
763 query_recursive (file_op_context_t * ctx, const char *s)
765 /* *INDENT-OFF* */
766 union
768 void *p;
769 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *);
770 } pntr;
771 /* *INDENT-ON* */
773 pntr.f = real_query_recursive;
775 if (mc_global.we_are_background)
776 return parent_call (pntr.p, ctx, 1, strlen (s), s);
777 else
778 return real_query_recursive (ctx, Foreground, s);
781 /* --------------------------------------------------------------------------------------------- */
783 static FileProgressStatus
784 query_replace (file_op_context_t * ctx, const char *destname, struct stat *_s_stat,
785 struct stat *_d_stat)
787 /* *INDENT-OFF* */
788 union
790 void *p;
791 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *,
792 struct stat *, struct stat *);
793 } pntr;
794 /* *INDENT-ON* */
796 pntr.f = file_progress_real_query_replace;
798 if (mc_global.we_are_background)
799 return parent_call (pntr.p, ctx, 3, strlen (destname), destname,
800 sizeof (struct stat), _s_stat, sizeof (struct stat), _d_stat);
801 else
802 return file_progress_real_query_replace (ctx, Foreground, destname, _s_stat, _d_stat);
805 #else
806 /* --------------------------------------------------------------------------------------------- */
808 static FileProgressStatus
809 do_file_error (const char *str)
811 return real_do_file_error (Foreground, str);
814 /* --------------------------------------------------------------------------------------------- */
816 static FileProgressStatus
817 query_recursive (file_op_context_t * ctx, const char *s)
819 return real_query_recursive (ctx, Foreground, s);
822 /* --------------------------------------------------------------------------------------------- */
824 static FileProgressStatus
825 query_replace (file_op_context_t * ctx, const char *destname, struct stat *_s_stat,
826 struct stat *_d_stat)
828 return file_progress_real_query_replace (ctx, Foreground, destname, _s_stat, _d_stat);
831 #endif /* !ENABLE_BACKGROUND */
833 /* --------------------------------------------------------------------------------------------- */
834 /** Report error with two files */
836 static FileProgressStatus
837 files_error (const char *format, const char *file1, const char *file2)
839 char buf[BUF_MEDIUM];
840 char *nfile1 = g_strdup (path_trunc (file1, 15));
841 char *nfile2 = g_strdup (path_trunc (file2, 15));
843 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno));
845 g_free (nfile1);
846 g_free (nfile2);
848 return do_file_error (buf);
851 /* }}} */
853 /* --------------------------------------------------------------------------------------------- */
855 static void
856 copy_file_file_display_progress (file_op_total_context_t * tctx, file_op_context_t * ctx,
857 struct timeval tv_current, struct timeval tv_transfer_start,
858 off_t file_size, off_t n_read_total)
860 long dt;
862 /* 1. Update rotating dash after some time */
863 rotate_dash (TRUE);
865 /* 3. Compute ETA */
866 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
868 if (n_read_total == 0)
869 ctx->eta_secs = 0.0;
870 else
872 ctx->eta_secs = ((dt / (double) n_read_total) * file_size) - dt;
873 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
876 /* 4. Compute BPS rate */
877 ctx->bps_time = (tv_current.tv_sec - tv_transfer_start.tv_sec);
878 if (ctx->bps_time < 1)
879 ctx->bps_time = 1;
880 ctx->bps = n_read_total / ctx->bps_time;
882 /* 5. Compute total ETA and BPS */
883 if (ctx->progress_bytes != 0)
885 uintmax_t remain_bytes;
887 remain_bytes = ctx->progress_bytes - tctx->copied_bytes;
888 #if 1
890 int total_secs = tv_current.tv_sec - tctx->transfer_start.tv_sec;
892 if (total_secs < 1)
893 total_secs = 1;
895 tctx->bps = tctx->copied_bytes / total_secs;
896 tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
898 #else
899 /* broken on lot of little files */
900 tctx->bps_count++;
901 tctx->bps = (tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count;
902 tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
903 #endif
907 /* --------------------------------------------------------------------------------------------- */
909 /* {{{ Move routines */
910 static FileProgressStatus
911 move_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s,
912 const char *d)
914 struct stat src_stats, dst_stats;
915 FileProgressStatus return_status = FILE_CONT;
916 gboolean copy_done = FALSE;
917 gboolean old_ask_overwrite;
918 vfs_path_t *src_vpath, *dst_vpath;
920 src_vpath = vfs_path_from_str (s);
921 dst_vpath = vfs_path_from_str (d);
923 file_progress_show_source (ctx, src_vpath);
924 file_progress_show_target (ctx, dst_vpath);
926 if (check_progress_buttons (ctx) == FILE_ABORT)
928 return_status = FILE_ABORT;
929 goto ret;
932 mc_refresh ();
934 while (mc_lstat (src_vpath, &src_stats) != 0)
936 /* Source doesn't exist */
937 if (ctx->skip_all)
938 return_status = FILE_SKIPALL;
939 else
941 return_status = file_error (_("Cannot stat file \"%s\"\n%s"), s);
942 if (return_status == FILE_SKIPALL)
943 ctx->skip_all = TRUE;
946 if (return_status != FILE_RETRY)
947 goto ret;
950 if (mc_lstat (dst_vpath, &dst_stats) == 0)
952 if (src_stats.st_dev == dst_stats.st_dev && src_stats.st_ino == dst_stats.st_ino)
954 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), s, d);
955 goto ret;
958 if (S_ISDIR (dst_stats.st_mode))
960 message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
961 do_refresh ();
962 return_status = FILE_SKIP;
963 goto ret;
966 if (confirm_overwrite)
968 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
969 if (return_status != FILE_CONT)
970 goto ret;
972 /* Ok to overwrite */
975 if (!ctx->do_append)
977 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks)
979 return_status = make_symlink (ctx, s, d);
980 if (return_status == FILE_CONT)
981 goto retry_src_remove;
982 goto ret;
985 if (mc_rename (src_vpath, dst_vpath) == 0)
987 return_status = progress_update_one (tctx, ctx, src_stats.st_size);
988 goto ret;
991 #if 0
992 /* Comparison to EXDEV seems not to work in nfs if you're moving from
993 one nfs to the same, but on the server it is on two different
994 filesystems. Then nfs returns EIO instead of EXDEV.
995 Hope it will not hurt if we always in case of error try to copy/delete. */
996 else
997 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
999 if (errno != EXDEV)
1001 if (ctx->skip_all)
1002 return_status = FILE_SKIPALL;
1003 else
1005 return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
1006 if (return_status == FILE_SKIPALL)
1007 ctx->skip_all = TRUE;
1008 if (return_status == FILE_RETRY)
1009 goto retry_rename;
1012 goto ret;
1014 #endif
1016 /* Failed because filesystem boundary -> copy the file instead */
1017 old_ask_overwrite = tctx->ask_overwrite;
1018 tctx->ask_overwrite = FALSE;
1019 return_status = copy_file_file (tctx, ctx, s, d);
1020 tctx->ask_overwrite = old_ask_overwrite;
1021 if (return_status != FILE_CONT)
1022 goto ret;
1024 copy_done = TRUE;
1026 file_progress_show_source (ctx, NULL);
1027 file_progress_show (ctx, 0, 0, "", FALSE);
1029 return_status = check_progress_buttons (ctx);
1030 if (return_status != FILE_CONT)
1031 goto ret;
1032 mc_refresh ();
1034 retry_src_remove:
1035 if (mc_unlink (src_vpath) != 0 && !ctx->skip_all)
1037 return_status = file_error (_("Cannot remove file \"%s\"\n%s"), s);
1038 if (return_status == FILE_RETRY)
1039 goto retry_src_remove;
1040 if (return_status == FILE_SKIPALL)
1041 ctx->skip_all = TRUE;
1042 goto ret;
1045 if (!copy_done)
1046 return_status = progress_update_one (tctx, ctx, src_stats.st_size);
1048 ret:
1049 vfs_path_free (src_vpath);
1050 vfs_path_free (dst_vpath);
1052 return return_status;
1055 /* }}} */
1057 /* --------------------------------------------------------------------------------------------- */
1058 /* {{{ Erase routines */
1059 /** Don't update progress status if progress_count==NULL */
1061 static FileProgressStatus
1062 erase_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
1064 struct stat buf;
1066 file_progress_show_deleting (ctx, vfs_path_as_str (vpath), &tctx->progress_count);
1067 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1068 if (check_progress_buttons (ctx) == FILE_ABORT)
1069 return FILE_ABORT;
1071 mc_refresh ();
1073 if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0)
1075 /* ignore, most likely the mc_unlink fails, too */
1076 buf.st_size = 0;
1079 while (mc_unlink (vpath) != 0 && !ctx->skip_all)
1081 int return_status;
1083 return_status = file_error (_("Cannot delete file \"%s\"\n%s"), vfs_path_as_str (vpath));
1084 if (return_status == FILE_ABORT)
1085 return return_status;
1086 if (return_status == FILE_RETRY)
1087 continue;
1088 if (return_status == FILE_SKIPALL)
1089 ctx->skip_all = TRUE;
1090 break;
1093 if (tctx->progress_count == 0)
1094 return FILE_CONT;
1096 return check_progress_buttons (ctx);
1099 /* --------------------------------------------------------------------------------------------- */
1102 Recursive remove of files
1103 abort->cancel stack
1104 skip ->warn every level, gets default
1105 skipall->remove as much as possible
1107 static FileProgressStatus
1108 recursive_erase (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
1110 struct dirent *next;
1111 DIR *reading;
1112 const char *s;
1113 FileProgressStatus return_status = FILE_CONT;
1115 reading = mc_opendir (vpath);
1116 if (reading == NULL)
1117 return FILE_RETRY;
1119 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
1121 vfs_path_t *tmp_vpath;
1122 struct stat buf;
1124 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
1125 continue;
1127 tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
1128 if (mc_lstat (tmp_vpath, &buf) != 0)
1130 mc_closedir (reading);
1131 vfs_path_free (tmp_vpath);
1132 return FILE_RETRY;
1134 if (S_ISDIR (buf.st_mode))
1135 return_status = recursive_erase (tctx, ctx, tmp_vpath);
1136 else
1137 return_status = erase_file (tctx, ctx, tmp_vpath);
1138 vfs_path_free (tmp_vpath);
1140 mc_closedir (reading);
1142 if (return_status == FILE_ABORT)
1143 return FILE_ABORT;
1145 s = vfs_path_as_str (vpath);
1147 file_progress_show_deleting (ctx, s, NULL);
1148 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1149 if (check_progress_buttons (ctx) == FILE_ABORT)
1150 return FILE_ABORT;
1152 mc_refresh ();
1154 while (my_rmdir (s) != 0 && !ctx->skip_all)
1156 return_status = file_error (_("Cannot remove directory \"%s\"\n%s"), s);
1157 if (return_status == FILE_RETRY)
1158 continue;
1159 if (return_status == FILE_ABORT)
1160 break;
1161 if (return_status == FILE_SKIPALL)
1162 ctx->skip_all = TRUE;
1163 break;
1166 return return_status;
1169 /* --------------------------------------------------------------------------------------------- */
1170 /** Return -1 on error, 1 if there are no entries besides "." and ".."
1171 in the directory path points to, 0 else. */
1173 static int
1174 check_dir_is_empty (const vfs_path_t * vpath)
1176 DIR *dir;
1177 struct dirent *d;
1178 int i = 1;
1180 dir = mc_opendir (vpath);
1181 if (dir == NULL)
1182 return -1;
1184 for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1185 if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1187 i = 0;
1188 break;
1191 mc_closedir (dir);
1192 return i;
1195 /* --------------------------------------------------------------------------------------------- */
1197 static FileProgressStatus
1198 erase_dir_iff_empty (file_op_context_t * ctx, const vfs_path_t * vpath, size_t count)
1200 FileProgressStatus error = FILE_CONT;
1201 const char *s;
1203 s = vfs_path_as_str (vpath);
1205 file_progress_show_deleting (ctx, s, NULL);
1206 file_progress_show_count (ctx, count, ctx->progress_count);
1207 if (check_progress_buttons (ctx) == FILE_ABORT)
1208 return FILE_ABORT;
1210 mc_refresh ();
1212 if (check_dir_is_empty (vpath) == 1) /* not empty or error */
1214 while (my_rmdir (s) != 0 && !ctx->skip_all)
1216 error = file_error (_("Cannot remove directory \"%s\"\n%s"), s);
1217 if (error == FILE_SKIPALL)
1218 ctx->skip_all = TRUE;
1219 if (error != FILE_RETRY)
1220 break;
1224 return error;
1227 /* }}} */
1229 /* --------------------------------------------------------------------------------------------- */
1230 /* {{{ Panel operate routines */
1233 * Return currently selected entry name or the name of the first marked
1234 * entry if there is one.
1237 static const char *
1238 panel_get_file (WPanel * panel)
1240 if (get_current_type () == view_tree)
1242 WTree *tree;
1243 const vfs_path_t *selected_name;
1245 tree = (WTree *) get_panel_widget (get_current_index ());
1246 selected_name = tree_selected_name (tree);
1247 return vfs_path_as_str (selected_name);
1250 if (panel->marked != 0)
1252 int i;
1254 for (i = 0; i < panel->dir.len; i++)
1255 if (panel->dir.list[i].f.marked)
1256 return panel->dir.list[i].fname;
1259 return panel->dir.list[panel->selected].fname;
1262 /* --------------------------------------------------------------------------------------------- */
1264 * panel_compute_totals:
1266 * compute the number of files and the number of bytes
1267 * used up by the whole selection, recursing directories
1268 * as required. In addition, it checks to see if it will
1269 * overwrite any files by doing the copy.
1272 static FileProgressStatus
1273 panel_compute_totals (const WPanel * panel, dirsize_status_msg_t * sm, size_t * ret_count,
1274 uintmax_t * ret_total, gboolean compute_symlinks)
1276 int i;
1277 size_t dir_count = 0;
1279 for (i = 0; i < panel->dir.len; i++)
1281 struct stat *s;
1283 if (!panel->dir.list[i].f.marked)
1284 continue;
1286 s = &panel->dir.list[i].st;
1288 if (S_ISDIR (s->st_mode))
1290 vfs_path_t *p;
1291 FileProgressStatus status;
1293 p = vfs_path_append_new (panel->cwd_vpath, panel->dir.list[i].fname, (char *) NULL);
1294 status = compute_dir_size (p, sm, &dir_count, ret_count, ret_total, compute_symlinks);
1295 vfs_path_free (p);
1297 if (status != FILE_CONT)
1298 return status;
1300 else
1302 (*ret_count)++;
1303 *ret_total += (uintmax_t) s->st_size;
1307 return FILE_CONT;
1310 /* --------------------------------------------------------------------------------------------- */
1312 /** Initialize variables for progress bars */
1313 static FileProgressStatus
1314 panel_operate_init_totals (const WPanel * panel, const char *source, file_op_context_t * ctx,
1315 filegui_dialog_type_t dialog_type)
1317 FileProgressStatus status;
1319 #ifdef ENABLE_BACKGROUND
1320 if (mc_global.we_are_background)
1321 return FILE_CONT;
1322 #endif
1324 if (verbose && file_op_compute_totals)
1326 dirsize_status_msg_t dsm;
1328 memset (&dsm, 0, sizeof (dsm));
1329 dsm.allow_skip = TRUE;
1330 status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
1331 dirsize_status_update_cb, dirsize_status_deinit_cb);
1333 ctx->progress_count = 0;
1334 ctx->progress_bytes = 0;
1336 if (source == NULL)
1337 status = panel_compute_totals (panel, &dsm, &ctx->progress_count, &ctx->progress_bytes,
1338 ctx->follow_links);
1339 else
1341 vfs_path_t *p;
1342 size_t dir_count = 0;
1344 p = vfs_path_from_str (source);
1345 status = compute_dir_size (p, &dsm, &dir_count, &ctx->progress_count,
1346 &ctx->progress_bytes, ctx->follow_links);
1347 vfs_path_free (p);
1350 status_msg_deinit (STATUS_MSG (&dsm));
1352 ctx->progress_totals_computed = (status == FILE_CONT);
1354 if (status == FILE_SKIP)
1355 status = FILE_CONT;
1357 else
1359 status = FILE_CONT;
1360 ctx->progress_count = panel->marked;
1361 ctx->progress_bytes = panel->total;
1362 ctx->progress_totals_computed = FALSE;
1365 file_op_context_create_ui (ctx, TRUE, dialog_type);
1367 return status;
1370 /* --------------------------------------------------------------------------------------------- */
1372 * Generate user prompt for panel operation.
1373 * src_stat must be not NULL for single source, and NULL for multiple sources
1376 static char *
1377 panel_operate_generate_prompt (const WPanel * panel, FileOperation operation,
1378 const struct stat *src_stat)
1380 char *sp;
1381 char *format_string;
1382 const char *cp;
1384 static gboolean i18n_flag = FALSE;
1385 if (!i18n_flag)
1387 size_t i;
1389 for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1390 op_names1[i] = Q_ (op_names1[i]);
1392 #ifdef ENABLE_NLS
1393 for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1394 prompt_parts[i] = _(prompt_parts[i]);
1396 one_format = _(one_format);
1397 many_format = _(many_format);
1398 #endif /* ENABLE_NLS */
1399 i18n_flag = TRUE;
1402 /* Possible prompts:
1403 * OP_COPY:
1404 * "Copy file \"%s\" with source mask:"
1405 * "Copy %d files with source mask:"
1406 * "Copy directory \"%s\" with source mask:"
1407 * "Copy %d directories with source mask:"
1408 * "Copy %d files/directories with source mask:"
1409 * OP_MOVE:
1410 * "Move file \"%s\" with source mask:"
1411 * "Move %d files with source mask:"
1412 * "Move directory \"%s\" with source mask:"
1413 * "Move %d directories with source mask:"
1414 * "Move %d files/directories with source mask:"
1415 * OP_DELETE:
1416 * "Delete file \"%s\"?"
1417 * "Delete %d files?"
1418 * "Delete directory \"%s\"?"
1419 * "Delete %d directories?"
1420 * "Delete %d files/directories?"
1423 cp = (src_stat != NULL ? one_format : many_format);
1425 /* 1. Substitute %o */
1426 format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1428 /* 2. Substitute %n */
1429 cp = operation == OP_DELETE ? "\n" : " ";
1430 sp = format_string;
1431 format_string = str_replace_all (sp, "%n", cp);
1432 g_free (sp);
1434 /* 3. Substitute %f */
1435 if (src_stat != NULL)
1436 cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1437 else if (panel->marked == panel->dirs_marked)
1438 cp = prompt_parts[3];
1439 else
1440 cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1442 sp = format_string;
1443 format_string = str_replace_all (sp, "%f", cp);
1444 g_free (sp);
1446 /* 4. Substitute %m */
1447 cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1448 sp = format_string;
1449 format_string = str_replace_all (sp, "%m", cp);
1450 g_free (sp);
1452 return format_string;
1455 /* --------------------------------------------------------------------------------------------- */
1457 #ifdef ENABLE_BACKGROUND
1458 static int
1459 end_bg_process (file_op_context_t * ctx, enum OperationMode mode)
1461 int pid = ctx->pid;
1463 (void) mode;
1464 ctx->pid = 0;
1466 unregister_task_with_pid (pid);
1467 /* file_op_context_destroy(ctx); */
1468 return 1;
1470 #endif
1471 /* }}} */
1473 /* --------------------------------------------------------------------------------------------- */
1474 /*** public functions ****************************************************************************/
1475 /* --------------------------------------------------------------------------------------------- */
1477 FileProgressStatus
1478 copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx,
1479 const char *src_path, const char *dst_path)
1481 uid_t src_uid = (uid_t) (-1);
1482 gid_t src_gid = (gid_t) (-1);
1484 int src_desc, dest_desc = -1;
1485 mode_t src_mode = 0; /* The mode of the source file */
1486 struct stat src_stat, dst_stat;
1487 struct utimbuf utb;
1488 gboolean dst_exists = FALSE, appending = FALSE;
1489 off_t file_size = -1;
1490 FileProgressStatus return_status, temp_status;
1491 struct timeval tv_transfer_start;
1492 dest_status_t dst_status = DEST_NONE;
1493 int open_flags;
1494 vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
1495 char *buf = NULL;
1497 /* FIXME: We should not be using global variables! */
1498 ctx->do_reget = 0;
1499 return_status = FILE_RETRY;
1501 dst_vpath = vfs_path_from_str (dst_path);
1502 src_vpath = vfs_path_from_str (src_path);
1504 file_progress_show_source (ctx, src_vpath);
1505 file_progress_show_target (ctx, dst_vpath);
1507 if (check_progress_buttons (ctx) == FILE_ABORT)
1509 return_status = FILE_ABORT;
1510 goto ret_fast;
1513 mc_refresh ();
1515 while (mc_stat (dst_vpath, &dst_stat) == 0)
1517 if (S_ISDIR (dst_stat.st_mode))
1519 if (ctx->skip_all)
1520 return_status = FILE_SKIPALL;
1521 else
1523 return_status = file_error (_("Cannot overwrite directory \"%s\"\n%s"), dst_path);
1524 if (return_status == FILE_SKIPALL)
1525 ctx->skip_all = TRUE;
1526 if (return_status == FILE_RETRY)
1527 continue;
1529 goto ret_fast;
1532 dst_exists = TRUE;
1533 break;
1536 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
1538 if (ctx->skip_all)
1539 return_status = FILE_SKIPALL;
1540 else
1542 return_status = file_error (_("Cannot stat source file \"%s\"\n%s"), src_path);
1543 if (return_status == FILE_SKIPALL)
1544 ctx->skip_all = TRUE;
1547 if (return_status != FILE_RETRY)
1548 goto ret_fast;
1551 if (dst_exists)
1553 /* Destination already exists */
1554 if (src_stat.st_dev == dst_stat.st_dev && src_stat.st_ino == dst_stat.st_ino)
1556 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"),
1557 src_path, dst_path);
1558 goto ret_fast;
1561 /* Should we replace destination? */
1562 if (tctx->ask_overwrite)
1564 ctx->do_reget = 0;
1565 return_status = query_replace (ctx, dst_path, &src_stat, &dst_stat);
1566 if (return_status != FILE_CONT)
1567 goto ret_fast;
1571 if (!ctx->do_append)
1573 /* Check the hardlinks */
1574 if (!ctx->follow_links && src_stat.st_nlink > 1
1575 && check_hardlinks (src_vpath, dst_vpath, &src_stat))
1577 /* We have made a hardlink - no more processing is necessary */
1578 return_status = FILE_CONT;
1579 goto ret_fast;
1582 if (S_ISLNK (src_stat.st_mode))
1584 return_status = make_symlink (ctx, src_path, dst_path);
1585 goto ret_fast;
1588 if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
1589 || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
1591 while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, src_stat.st_rdev) < 0
1592 && !ctx->skip_all)
1594 return_status = file_error (_("Cannot create special file \"%s\"\n%s"), dst_path);
1595 if (return_status == FILE_RETRY)
1596 continue;
1597 if (return_status == FILE_SKIPALL)
1598 ctx->skip_all = TRUE;
1599 goto ret_fast;
1601 /* Success */
1603 while (ctx->preserve_uidgid
1604 && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all)
1606 temp_status = file_error (_("Cannot chown target file \"%s\"\n%s"), dst_path);
1607 if (temp_status == FILE_SKIP)
1608 break;
1609 if (temp_status == FILE_SKIPALL)
1610 ctx->skip_all = TRUE;
1611 if (temp_status != FILE_RETRY)
1613 return_status = temp_status;
1614 goto ret_fast;
1618 while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
1619 && !ctx->skip_all)
1621 temp_status = file_error (_("Cannot chmod target file \"%s\"\n%s"), dst_path);
1622 if (temp_status == FILE_SKIP)
1623 break;
1624 if (temp_status == FILE_SKIPALL)
1625 ctx->skip_all = TRUE;
1626 if (temp_status != FILE_RETRY)
1628 return_status = temp_status;
1629 goto ret_fast;
1633 return_status = FILE_CONT;
1634 goto ret_fast;
1638 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
1640 while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all)
1642 return_status = file_error (_("Cannot open source file \"%s\"\n%s"), src_path);
1643 if (return_status == FILE_RETRY)
1644 continue;
1645 if (return_status == FILE_SKIPALL)
1646 ctx->skip_all = TRUE;
1647 if (return_status == FILE_SKIP)
1648 break;
1649 ctx->do_append = FALSE;
1650 goto ret_fast;
1653 if (ctx->do_reget != 0)
1655 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
1657 message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
1658 ctx->do_reget = 0;
1659 ctx->do_append = FALSE;
1663 while (mc_fstat (src_desc, &src_stat) != 0)
1665 if (ctx->skip_all)
1666 return_status = FILE_SKIPALL;
1667 else
1669 return_status = file_error (_("Cannot fstat source file \"%s\"\n%s"), src_path);
1670 if (return_status == FILE_RETRY)
1671 continue;
1672 if (return_status == FILE_SKIPALL)
1673 ctx->skip_all = TRUE;
1674 ctx->do_append = FALSE;
1676 goto ret;
1679 src_mode = src_stat.st_mode;
1680 src_uid = src_stat.st_uid;
1681 src_gid = src_stat.st_gid;
1682 utb.actime = src_stat.st_atime;
1683 utb.modtime = src_stat.st_mtime;
1684 file_size = src_stat.st_size;
1686 open_flags = O_WRONLY;
1687 if (dst_exists)
1689 if (ctx->do_append)
1690 open_flags |= O_APPEND;
1691 else
1692 open_flags |= O_CREAT | O_TRUNC;
1694 else
1696 open_flags |= O_CREAT | O_EXCL;
1699 while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
1701 if (errno != EEXIST)
1703 if (ctx->skip_all)
1704 return_status = FILE_SKIPALL;
1705 else
1707 return_status = file_error (_("Cannot create target file \"%s\"\n%s"), dst_path);
1708 if (return_status == FILE_RETRY)
1709 continue;
1710 if (return_status == FILE_SKIPALL)
1711 ctx->skip_all = TRUE;
1712 ctx->do_append = FALSE;
1715 goto ret;
1717 dst_status = DEST_SHORT; /* file opened, but not fully copied */
1719 appending = ctx->do_append;
1720 ctx->do_append = FALSE;
1722 /* Find out the optimal buffer size. */
1723 while (mc_fstat (dest_desc, &dst_stat) != 0)
1725 if (ctx->skip_all)
1726 return_status = FILE_SKIPALL;
1727 else
1729 return_status = file_error (_("Cannot fstat target file \"%s\"\n%s"), dst_path);
1730 if (return_status == FILE_RETRY)
1731 continue;
1732 if (return_status == FILE_SKIPALL)
1733 ctx->skip_all = TRUE;
1735 goto ret;
1738 /* try preallocate space; if fail, try copy anyway */
1739 while (vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
1741 if (ctx->skip_all)
1743 /* cannot allocate, start the file copying anyway */
1744 return_status = FILE_CONT;
1745 break;
1748 return_status =
1749 file_error (_("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
1751 if (return_status == FILE_SKIPALL)
1752 ctx->skip_all = TRUE;
1754 if (ctx->skip_all || return_status == FILE_SKIP)
1756 /* skip the space allocation error, start file copying */
1757 return_status = FILE_CONT;
1758 break;
1761 if (return_status == FILE_ABORT)
1763 mc_close (dest_desc);
1764 dest_desc = -1;
1765 mc_unlink (dst_vpath);
1766 dst_status = DEST_NONE;
1767 goto ret;
1770 /* return_status == FILE_RETRY -- try allocate space again */
1773 ctx->eta_secs = 0.0;
1774 ctx->bps = 0;
1776 if (tctx->bps == 0 || (file_size / (tctx->bps)) > FILEOP_UPDATE_INTERVAL)
1777 file_progress_show (ctx, 0, file_size, "", TRUE);
1778 else
1779 file_progress_show (ctx, 1, 1, "", TRUE);
1780 return_status = check_progress_buttons (ctx);
1781 mc_refresh ();
1783 if (return_status == FILE_CONT)
1785 size_t bufsize;
1786 off_t n_read_total = 0;
1787 struct timeval tv_current, tv_last_update, tv_last_input;
1788 int secs, update_secs;
1789 const char *stalled_msg = "";
1790 gboolean is_first_time = TRUE;
1792 tv_last_update = tv_transfer_start;
1794 bufsize = io_blksize (dst_stat);
1795 buf = g_malloc (bufsize);
1797 while (TRUE)
1799 ssize_t n_read = -1, n_written;
1801 /* src_read */
1802 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
1803 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all)
1805 return_status = file_error (_("Cannot read source file \"%s\"\n%s"), src_path);
1806 if (return_status == FILE_RETRY)
1807 continue;
1808 if (return_status == FILE_SKIPALL)
1809 ctx->skip_all = TRUE;
1810 goto ret;
1813 if (n_read == 0)
1814 break;
1816 gettimeofday (&tv_current, NULL);
1818 if (n_read > 0)
1820 char *t = buf;
1822 n_read_total += n_read;
1824 /* Windows NT ftp servers report that files have no
1825 * permissions: -------, so if we happen to have actually
1826 * read something, we should fix the permissions.
1828 if ((src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == 0)
1829 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
1830 gettimeofday (&tv_last_input, NULL);
1832 /* dst_write */
1833 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
1835 gboolean write_errno_nospace;
1837 if (n_written > 0)
1839 n_read -= n_written;
1840 t += n_written;
1841 continue;
1844 write_errno_nospace = (n_written < 0 && errno == ENOSPC);
1846 if (ctx->skip_all)
1847 return_status = FILE_SKIPALL;
1848 else
1849 return_status =
1850 file_error (_("Cannot write target file \"%s\"\n%s"), dst_path);
1852 if (return_status == FILE_SKIP)
1854 if (write_errno_nospace)
1855 goto ret;
1856 break;
1858 if (return_status == FILE_SKIPALL)
1860 ctx->skip_all = TRUE;
1861 if (write_errno_nospace)
1862 goto ret;
1864 if (return_status != FILE_RETRY)
1865 goto ret;
1869 tctx->copied_bytes = tctx->progress_bytes + n_read_total + ctx->do_reget;
1871 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
1872 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
1874 if (is_first_time || secs > FILEOP_UPDATE_INTERVAL)
1876 copy_file_file_display_progress (tctx, ctx,
1877 tv_current,
1878 tv_transfer_start, file_size, n_read_total);
1879 tv_last_update = tv_current;
1881 is_first_time = FALSE;
1883 if (update_secs > FILEOP_STALLING_INTERVAL)
1885 stalled_msg = _("(stalled)");
1889 gboolean force_update;
1891 force_update =
1892 (tv_current.tv_sec - tctx->transfer_start.tv_sec) > FILEOP_UPDATE_INTERVAL;
1894 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
1896 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1897 file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
1900 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size, stalled_msg,
1901 force_update);
1903 mc_refresh ();
1905 return_status = check_progress_buttons (ctx);
1907 if (return_status != FILE_CONT)
1909 mc_refresh ();
1910 goto ret;
1914 dst_status = DEST_FULL; /* copy successful, don't remove target file */
1917 ret:
1918 g_free (buf);
1920 rotate_dash (FALSE);
1921 while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all)
1923 temp_status = file_error (_("Cannot close source file \"%s\"\n%s"), src_path);
1924 if (temp_status == FILE_RETRY)
1925 continue;
1926 if (temp_status == FILE_ABORT)
1927 return_status = temp_status;
1928 if (temp_status == FILE_SKIPALL)
1929 ctx->skip_all = TRUE;
1930 break;
1933 while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all)
1935 temp_status = file_error (_("Cannot close target file \"%s\"\n%s"), dst_path);
1936 if (temp_status == FILE_RETRY)
1937 continue;
1938 if (temp_status == FILE_SKIPALL)
1939 ctx->skip_all = TRUE;
1940 return_status = temp_status;
1941 break;
1944 if (dst_status == DEST_SHORT)
1946 /* Query to remove short file */
1947 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved. Keep it?"),
1948 D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
1949 mc_unlink (dst_vpath);
1951 else if (dst_status == DEST_FULL)
1953 /* Copy has succeeded */
1954 if (!appending && ctx->preserve_uidgid)
1956 while (mc_chown (dst_vpath, src_uid, src_gid) != 0 && !ctx->skip_all)
1958 temp_status = file_error (_("Cannot chown target file \"%s\"\n%s"), dst_path);
1959 if (temp_status == FILE_RETRY)
1960 continue;
1961 if (temp_status == FILE_SKIPALL)
1963 ctx->skip_all = TRUE;
1964 return_status = FILE_CONT;
1966 if (temp_status == FILE_SKIP)
1967 return_status = FILE_CONT;
1968 break;
1972 if (!appending)
1974 if (ctx->preserve)
1976 while (mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0 && !ctx->skip_all)
1978 temp_status = file_error (_("Cannot chmod target file \"%s\"\n%s"), dst_path);
1979 if (temp_status == FILE_RETRY)
1980 continue;
1981 if (temp_status == FILE_SKIPALL)
1983 ctx->skip_all = TRUE;
1984 return_status = FILE_CONT;
1986 if (temp_status == FILE_SKIP)
1987 return_status = FILE_CONT;
1988 break;
1991 else if (!dst_exists)
1993 src_mode = umask (-1);
1994 umask (src_mode);
1995 src_mode = 0100666 & ~src_mode;
1996 mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
1998 mc_utime (dst_vpath, &utb);
2002 if (return_status == FILE_CONT)
2003 return_status = progress_update_one (tctx, ctx, file_size);
2005 ret_fast:
2006 vfs_path_free (src_vpath);
2007 vfs_path_free (dst_vpath);
2008 return return_status;
2011 /* --------------------------------------------------------------------------------------------- */
2013 * I think these copy_*_* functions should have a return type.
2014 * anyway, this function *must* have two directories as arguments.
2016 /* FIXME: This function needs to check the return values of the
2017 function calls */
2019 FileProgressStatus
2020 copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d,
2021 gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs)
2023 struct dirent *next;
2024 struct stat buf, cbuf;
2025 DIR *reading;
2026 FileProgressStatus return_status = FILE_CONT;
2027 struct link *lp;
2028 vfs_path_t *src_vpath, *dst_vpath;
2029 gboolean do_mkdir = TRUE;
2031 src_vpath = vfs_path_from_str (s);
2032 dst_vpath = vfs_path_from_str (d);
2034 /* First get the mode of the source dir */
2036 retry_src_stat:
2037 if ((*ctx->stat_func) (src_vpath, &cbuf) != 0)
2039 if (ctx->skip_all)
2040 return_status = FILE_SKIPALL;
2041 else
2043 return_status = file_error (_("Cannot stat source directory \"%s\"\n%s"), s);
2044 if (return_status == FILE_RETRY)
2045 goto retry_src_stat;
2046 if (return_status == FILE_SKIPALL)
2047 ctx->skip_all = TRUE;
2049 goto ret_fast;
2052 if (is_in_linklist (dest_dirs, src_vpath, &cbuf))
2054 /* Don't copy a directory we created before (we don't want to copy
2055 infinitely if a directory is copied into itself) */
2056 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
2057 return_status = FILE_CONT;
2058 goto ret_fast;
2061 /* Hmm, hardlink to directory??? - Norbert */
2062 /* FIXME: In this step we should do something
2063 in case the destination already exist */
2064 /* Check the hardlinks */
2065 if (ctx->preserve && cbuf.st_nlink > 1 && check_hardlinks (src_vpath, dst_vpath, &cbuf))
2067 /* We have made a hardlink - no more processing is necessary */
2068 goto ret_fast;
2071 if (!S_ISDIR (cbuf.st_mode))
2073 if (ctx->skip_all)
2074 return_status = FILE_SKIPALL;
2075 else
2077 return_status = file_error (_("Source \"%s\" is not a directory\n%s"), s);
2078 if (return_status == FILE_RETRY)
2079 goto retry_src_stat;
2080 if (return_status == FILE_SKIPALL)
2081 ctx->skip_all = TRUE;
2083 goto ret_fast;
2086 if (is_in_linklist (parent_dirs, src_vpath, &cbuf))
2088 /* we found a cyclic symbolic link */
2089 message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
2090 return_status = FILE_SKIP;
2091 goto ret_fast;
2094 lp = g_new0 (struct link, 1);
2095 lp->vfs = vfs_path_get_by_index (src_vpath, -1)->class;
2096 lp->ino = cbuf.st_ino;
2097 lp->dev = cbuf.st_dev;
2098 parent_dirs = g_slist_prepend (parent_dirs, lp);
2100 retry_dst_stat:
2101 /* Now, check if the dest dir exists, if not, create it. */
2102 if (mc_stat (dst_vpath, &buf) != 0)
2104 /* Here the dir doesn't exist : make it ! */
2105 if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
2107 return_status = FILE_CONT;
2108 goto ret;
2111 else
2114 * If the destination directory exists, we want to copy the whole
2115 * directory, but we only want this to happen once.
2117 * Escape sequences added to the * to compiler warnings.
2118 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
2119 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
2121 if (!S_ISDIR (buf.st_mode))
2123 if (ctx->skip_all)
2124 return_status = FILE_SKIPALL;
2125 else
2127 return_status = file_error (_("Destination \"%s\" must be a directory\n%s"), d);
2128 if (return_status == FILE_SKIPALL)
2129 ctx->skip_all = TRUE;
2130 if (return_status == FILE_RETRY)
2131 goto retry_dst_stat;
2133 goto ret;
2135 /* Dive into subdir if exists */
2136 if (toplevel && ctx->dive_into_subdirs)
2138 vfs_path_t *tmp;
2140 tmp = dst_vpath;
2141 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
2142 vfs_path_free (tmp);
2145 else
2146 do_mkdir = FALSE;
2149 d = vfs_path_as_str (dst_vpath);
2151 if (do_mkdir)
2153 while (my_mkdir (dst_vpath, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
2155 if (ctx->skip_all)
2156 return_status = FILE_SKIPALL;
2157 else
2159 return_status = file_error (_("Cannot create target directory \"%s\"\n%s"), d);
2160 if (return_status == FILE_SKIPALL)
2161 ctx->skip_all = TRUE;
2163 if (return_status != FILE_RETRY)
2164 goto ret;
2167 lp = g_new0 (struct link, 1);
2168 mc_stat (dst_vpath, &buf);
2169 lp->vfs = vfs_path_get_by_index (dst_vpath, -1)->class;
2170 lp->ino = buf.st_ino;
2171 lp->dev = buf.st_dev;
2172 dest_dirs = g_slist_prepend (dest_dirs, lp);
2175 if (ctx->preserve_uidgid)
2177 while (mc_chown (dst_vpath, cbuf.st_uid, cbuf.st_gid) != 0)
2179 if (ctx->skip_all)
2180 return_status = FILE_SKIPALL;
2181 else
2183 return_status = file_error (_("Cannot chown target directory \"%s\"\n%s"), d);
2184 if (return_status == FILE_SKIPALL)
2185 ctx->skip_all = TRUE;
2187 if (return_status != FILE_RETRY)
2188 goto ret;
2192 /* open the source dir for reading */
2193 reading = mc_opendir (src_vpath);
2194 if (reading == NULL)
2195 goto ret;
2197 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
2199 char *path;
2200 vfs_path_t *tmp_vpath;
2203 * Now, we don't want '.' and '..' to be created / copied at any time
2205 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
2206 continue;
2208 /* get the filename and add it to the src directory */
2209 path = mc_build_filename (s, next->d_name, (char *) NULL);
2210 tmp_vpath = vfs_path_from_str (path);
2212 (*ctx->stat_func) (tmp_vpath, &buf);
2213 if (S_ISDIR (buf.st_mode))
2215 char *mdpath;
2217 mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
2219 * From here, we just intend to recursively copy subdirs, not
2220 * the double functionality of copying different when the target
2221 * dir already exists. So, we give the recursive call the flag 0
2222 * meaning no toplevel.
2224 return_status =
2225 copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
2226 g_free (mdpath);
2228 else
2230 char *dest_file;
2232 dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
2233 return_status = copy_file_file (tctx, ctx, path, dest_file);
2234 g_free (dest_file);
2237 g_free (path);
2239 if (do_delete && return_status == FILE_CONT)
2241 if (ctx->erase_at_end)
2243 lp = g_new0 (struct link, 1);
2244 lp->src_vpath = tmp_vpath;
2245 lp->st_mode = buf.st_mode;
2246 erase_list = g_slist_append (erase_list, lp);
2247 tmp_vpath = NULL;
2249 else if (S_ISDIR (buf.st_mode))
2250 return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
2251 else
2252 return_status = erase_file (tctx, ctx, tmp_vpath);
2254 vfs_path_free (tmp_vpath);
2256 mc_closedir (reading);
2258 if (ctx->preserve)
2260 struct utimbuf utb;
2262 mc_chmod (dst_vpath, cbuf.st_mode & ctx->umask_kill);
2263 utb.actime = cbuf.st_atime;
2264 utb.modtime = cbuf.st_mtime;
2265 mc_utime (dst_vpath, &utb);
2267 else
2269 cbuf.st_mode = umask (-1);
2270 umask (cbuf.st_mode);
2271 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
2272 mc_chmod (dst_vpath, cbuf.st_mode & ctx->umask_kill);
2275 ret:
2276 free_link (parent_dirs->data);
2277 g_slist_free_1 (parent_dirs);
2278 ret_fast:
2279 vfs_path_free (src_vpath);
2280 vfs_path_free (dst_vpath);
2281 return return_status;
2284 /* }}} */
2286 /* --------------------------------------------------------------------------------------------- */
2287 /* {{{ Move routines */
2289 FileProgressStatus
2290 move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d)
2292 struct stat sbuf, dbuf;
2293 FileProgressStatus return_status = FILE_CONT;
2294 gboolean move_over = FALSE;
2295 gboolean dstat_ok;
2296 vfs_path_t *src_vpath, *dst_vpath;
2298 src_vpath = vfs_path_from_str (s);
2299 dst_vpath = vfs_path_from_str (d);
2301 file_progress_show_source (ctx, src_vpath);
2302 file_progress_show_target (ctx, dst_vpath);
2304 if (check_progress_buttons (ctx) == FILE_ABORT)
2306 return_status = FILE_ABORT;
2307 goto ret_fast;
2310 mc_refresh ();
2312 mc_stat (src_vpath, &sbuf);
2314 dstat_ok = (mc_stat (dst_vpath, &dbuf) == 0);
2315 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
2317 return_status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), s, d);
2318 goto ret_fast;
2321 if (!dstat_ok)
2322 ; /* destination doesn't exist */
2323 else if (!ctx->dive_into_subdirs)
2324 move_over = TRUE;
2325 else
2327 vfs_path_t *tmp;
2329 tmp = dst_vpath;
2330 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
2331 vfs_path_free (tmp);
2334 d = vfs_path_as_str (dst_vpath);
2336 /* Check if the user inputted an existing dir */
2337 retry_dst_stat:
2338 if (mc_stat (dst_vpath, &dbuf) == 0)
2340 if (move_over)
2342 return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL);
2344 if (return_status != FILE_CONT)
2345 goto ret;
2346 goto oktoret;
2348 else if (ctx->skip_all)
2349 return_status = FILE_SKIPALL;
2350 else
2352 if (S_ISDIR (dbuf.st_mode))
2353 return_status = file_error (_("Cannot overwrite directory \"%s\"\n%s"), d);
2354 else
2355 return_status = file_error (_("Cannot overwrite file \"%s\"\n%s"), d);
2356 if (return_status == FILE_SKIPALL)
2357 ctx->skip_all = TRUE;
2358 if (return_status == FILE_RETRY)
2359 goto retry_dst_stat;
2362 goto ret_fast;
2365 retry_rename:
2366 if (mc_rename (src_vpath, dst_vpath) == 0)
2368 return_status = FILE_CONT;
2369 goto ret;
2372 if (errno != EXDEV)
2374 if (!ctx->skip_all)
2376 return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
2377 if (return_status == FILE_SKIPALL)
2378 ctx->skip_all = TRUE;
2379 if (return_status == FILE_RETRY)
2380 goto retry_rename;
2382 goto ret;
2384 /* Failed because of filesystem boundary -> copy dir instead */
2385 return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL);
2387 if (return_status != FILE_CONT)
2388 goto ret;
2389 oktoret:
2390 file_progress_show_source (ctx, NULL);
2391 file_progress_show_target (ctx, NULL);
2392 file_progress_show (ctx, 0, 0, "", FALSE);
2394 return_status = check_progress_buttons (ctx);
2395 if (return_status != FILE_CONT)
2396 goto ret;
2398 mc_refresh ();
2399 if (ctx->erase_at_end)
2401 /* Reset progress count before delete to avoid counting files twice */
2402 tctx->progress_count = tctx->prev_progress_count;
2404 while (erase_list != NULL && return_status != FILE_ABORT)
2406 struct link *lp = (struct link *) erase_list->data;
2408 if (S_ISDIR (lp->st_mode))
2409 return_status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count);
2410 else
2411 return_status = erase_file (tctx, ctx, lp->src_vpath);
2413 erase_list = g_slist_remove (erase_list, lp);
2414 free_link (lp);
2417 /* Save progress counter before move next directory */
2418 tctx->prev_progress_count = tctx->progress_count;
2420 erase_dir_iff_empty (ctx, src_vpath, tctx->progress_count);
2422 ret:
2423 erase_list = free_linklist (erase_list);
2424 ret_fast:
2425 vfs_path_free (src_vpath);
2426 vfs_path_free (dst_vpath);
2427 return return_status;
2430 /* }}} */
2432 /* --------------------------------------------------------------------------------------------- */
2433 /* {{{ Erase routines */
2435 FileProgressStatus
2436 erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * s_vpath)
2438 FileProgressStatus error;
2440 file_progress_show_deleting (ctx, vfs_path_as_str (s_vpath), NULL);
2441 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2442 if (check_progress_buttons (ctx) == FILE_ABORT)
2443 return FILE_ABORT;
2445 mc_refresh ();
2447 /* The old way to detect a non empty directory was:
2448 error = my_rmdir (s);
2449 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
2450 For the linux user space nfs server (nfs-server-2.2beta29-2)
2451 we would have to check also for EIO. I hope the new way is
2452 fool proof. (Norbert)
2454 error = check_dir_is_empty (s_vpath);
2455 if (error == 0)
2456 { /* not empty */
2457 error = query_recursive (ctx, vfs_path_as_str (s_vpath));
2458 if (error == FILE_CONT)
2459 error = recursive_erase (tctx, ctx, s_vpath);
2460 return error;
2463 while (my_rmdir (vfs_path_as_str (s_vpath)) == -1 && !ctx->skip_all)
2465 error = file_error (_("Cannot remove directory \"%s\"\n%s"), vfs_path_as_str (s_vpath));
2466 if (error != FILE_RETRY)
2467 return error;
2470 return FILE_CONT;
2473 /* }}} */
2475 /* --------------------------------------------------------------------------------------------- */
2476 /* {{{ Panel operate routines */
2478 void
2479 dirsize_status_init_cb (status_msg_t * sm)
2481 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
2482 Widget *wd = WIDGET (sm->dlg);
2484 const char *b1_name = N_("&Abort");
2485 const char *b2_name = N_("&Skip");
2486 int b_width, ui_width;
2488 #ifdef ENABLE_NLS
2489 b1_name = _(b1_name);
2490 b2_name = _(b2_name);
2491 #endif
2493 b_width = str_term_width1 (b1_name) + 4;
2494 if (dsm->allow_skip)
2495 b_width += str_term_width1 (b2_name) + 4 + 1;
2497 ui_width = MAX (COLS / 2, b_width + 6);
2498 dsm->dirname = label_new (2, 3, "");
2499 add_widget (sm->dlg, dsm->dirname);
2500 dsm->count_size = label_new (3, 3, "");
2501 add_widget (sm->dlg, dsm->count_size);
2502 add_widget (sm->dlg, hline_new (4, -1, -1));
2504 dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
2505 add_widget (sm->dlg, dsm->abort_button);
2506 if (dsm->allow_skip)
2508 dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
2509 add_widget (sm->dlg, dsm->skip_button);
2510 dlg_select_widget (dsm->skip_button);
2513 widget_set_size (wd, wd->y, wd->x, 8, ui_width);
2514 dirsize_status_locate_buttons (dsm);
2517 /* --------------------------------------------------------------------------------------------- */
2520 dirsize_status_update_cb (status_msg_t * sm)
2522 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
2523 Widget *wd = WIDGET (sm->dlg);
2525 /* update second (longer label) */
2526 label_set_textv (dsm->count_size, _("Directories: %zd, total size: %s"),
2527 dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
2529 /* enlarge dialog if required */
2530 if (WIDGET (dsm->count_size)->cols + 6 > wd->cols)
2532 dlg_set_size (sm->dlg, wd->lines, WIDGET (dsm->count_size)->cols + 6);
2533 dirsize_status_locate_buttons (dsm);
2534 dlg_redraw (sm->dlg);
2537 /* adjust first label */
2538 label_set_text (dsm->dirname, str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->cols - 6));
2540 switch (status_msg_common_update (sm))
2542 case B_CANCEL:
2543 case FILE_ABORT:
2544 return FILE_ABORT;
2545 case FILE_SKIP:
2546 return FILE_SKIP;
2547 default:
2548 return FILE_CONT;
2552 /* --------------------------------------------------------------------------------------------- */
2554 void
2555 dirsize_status_deinit_cb (status_msg_t * sm)
2557 (void) sm;
2559 /* schedule to update passive panel */
2560 if (get_other_type () == view_listing)
2561 other_panel->dirty = 1;
2564 /* --------------------------------------------------------------------------------------------- */
2566 * compute_dir_size:
2568 * Computes the number of bytes used by the files in a directory
2571 FileProgressStatus
2572 compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
2573 size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total,
2574 gboolean compute_symlinks)
2576 return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
2577 compute_symlinks);
2580 /* --------------------------------------------------------------------------------------------- */
2582 * panel_operate:
2584 * Performs one of the operations on the selection on the source_panel
2585 * (copy, delete, move).
2587 * Returns TRUE if did change the directory
2588 * structure, Returns FALSE if user aborted
2590 * force_single forces operation on the current entry and affects
2591 * default destination. Current filename is used as default.
2594 gboolean
2595 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
2597 WPanel *panel = PANEL (source_panel);
2598 const gboolean single_entry = force_single || (panel->marked <= 1)
2599 || (get_current_type () == view_tree);
2601 const char *source = NULL;
2602 #ifdef WITH_FULL_PATHS
2603 vfs_path_t *source_with_vpath = NULL;
2604 #endif /* WITH_FULL_PATHS */
2605 char *dest = NULL;
2606 vfs_path_t *dest_vpath = NULL;
2607 char *temp = NULL;
2608 char *save_cwd = NULL, *save_dest = NULL;
2609 struct stat src_stat;
2610 gboolean ret_val = TRUE;
2611 int i;
2612 FileProgressStatus value;
2613 file_op_context_t *ctx;
2614 file_op_total_context_t *tctx;
2615 vfs_path_t *tmp_vpath;
2616 filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
2618 gboolean do_bg = FALSE; /* do background operation? */
2620 static gboolean i18n_flag = FALSE;
2621 if (!i18n_flag)
2623 for (i = G_N_ELEMENTS (op_names); i-- != 0;)
2624 op_names[i] = Q_ (op_names[i]);
2625 i18n_flag = TRUE;
2628 linklist = free_linklist (linklist);
2629 dest_dirs = free_linklist (dest_dirs);
2631 if (single_entry)
2633 gboolean ok;
2635 if (force_single)
2636 source = selection (panel)->fname;
2637 else
2638 source = panel_get_file (panel);
2640 ok = !DIR_IS_DOTDOT (source);
2642 if (!ok)
2643 message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
2644 else
2646 vfs_path_t *source_vpath;
2648 source_vpath = vfs_path_from_str (source);
2650 /* Update stat to get actual info */
2651 ok = mc_lstat (source_vpath, &src_stat) == 0;
2652 if (!ok)
2654 message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
2655 path_trunc (source, 30), unix_error_string (errno));
2657 /* Directory was changed outside MC. Reload it forced */
2658 if (!panel->is_panelized)
2660 panel_update_flags_t flags = UP_RELOAD;
2662 /* don't update panelized panel */
2663 if (get_other_type () == view_listing && other_panel->is_panelized)
2664 flags |= UP_ONLY_CURRENT;
2666 update_panels (flags, UP_KEEPSEL);
2670 vfs_path_free (source_vpath);
2673 if (!ok)
2674 return FALSE;
2677 ctx = file_op_context_new (operation);
2679 /* Show confirmation dialog */
2680 if (operation != OP_DELETE)
2682 const char *tmp_dest_dir;
2683 char *dest_dir;
2684 char *format;
2686 /* Forced single operations default to the original name */
2687 if (force_single)
2688 tmp_dest_dir = source;
2689 else if (get_other_type () == view_listing)
2690 tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
2691 else
2692 tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
2694 * Add trailing backslash only when do non-local ops.
2695 * It saves user from occasional file renames (when destination
2696 * dir is deleted)
2698 if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
2699 && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
2701 /* add trailing separator */
2702 dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
2704 else
2706 /* just copy */
2707 dest_dir = g_strdup (tmp_dest_dir);
2709 if (dest_dir == NULL)
2711 ret_val = FALSE;
2712 goto ret_fast;
2715 /* Generate confirmation prompt */
2716 format =
2717 panel_operate_generate_prompt (panel, operation, source != NULL ? &src_stat : NULL);
2719 dest =
2720 file_mask_dialog (ctx, operation, source != NULL, format,
2721 source != NULL ? source : (const void *) &panel->marked, dest_dir,
2722 &do_bg);
2724 g_free (format);
2725 g_free (dest_dir);
2727 if (dest == NULL || dest[0] == '\0')
2729 g_free (dest);
2730 ret_val = FALSE;
2731 goto ret_fast;
2733 dest_vpath = vfs_path_from_str (dest);
2735 else if (confirm_delete)
2737 char *format;
2738 char fmd_buf[BUF_MEDIUM];
2740 /* Generate confirmation prompt */
2741 format =
2742 panel_operate_generate_prompt (panel, OP_DELETE, source != NULL ? &src_stat : NULL);
2744 if (source == NULL)
2745 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
2746 else
2748 const int fmd_xlen = 64;
2749 i = fmd_xlen - str_term_width1 (format) - 4;
2750 g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
2753 g_free (format);
2755 if (safe_delete)
2756 query_set_sel (1);
2758 i = query_dialog (op_names[operation], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
2760 if (i != 0)
2762 ret_val = FALSE;
2763 goto ret_fast;
2767 tctx = file_op_total_context_new ();
2768 gettimeofday (&tctx->transfer_start, (struct timezone *) NULL);
2770 #ifdef ENABLE_BACKGROUND
2771 /* Did the user select to do a background operation? */
2772 if (do_bg)
2774 int v;
2776 v = do_background (ctx,
2777 g_strconcat (op_names[operation], ": ",
2778 vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
2779 if (v == -1)
2780 message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
2782 /* If we are the parent */
2783 if (v == 1)
2785 mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
2787 mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
2788 vfs_path_free (dest_vpath);
2789 g_free (dest);
2790 /* file_op_context_destroy (ctx); */
2791 return FALSE;
2794 else
2795 #endif /* ENABLE_BACKGROUND */
2797 if (operation == OP_DELETE)
2798 dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
2799 else if (single_entry && S_ISDIR (selection (panel)->st.st_mode))
2800 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
2801 else if (single_entry || force_single)
2802 dialog_type = FILEGUI_DIALOG_ONE_ITEM;
2803 else
2804 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
2807 /* Initialize things */
2808 /* We do not want to trash cache every time file is
2809 created/touched. However, this will make our cache contain
2810 invalid data. */
2811 if ((dest != NULL)
2812 && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
2813 save_dest = g_strdup (dest);
2815 if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
2816 && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
2817 save_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath));
2819 /* Now, let's do the job */
2821 /* This code is only called by the tree and panel code */
2822 if (single_entry)
2824 /* We now have ETA in all cases */
2826 /* One file: FIXME mc_chdir will take user out of any vfs */
2827 if ((operation != OP_COPY) && (get_current_type () == view_tree))
2829 vfs_path_t *vpath;
2830 int chdir_retcode;
2832 vpath = vfs_path_from_str (PATH_SEP_STR);
2833 chdir_retcode = mc_chdir (vpath);
2834 vfs_path_free (vpath);
2835 if (chdir_retcode < 0)
2837 ret_val = FALSE;
2838 goto clean_up;
2842 /* The source and src_stat variables have been initialized before */
2843 #ifdef WITH_FULL_PATHS
2844 if (g_path_is_absolute (source))
2845 source_with_vpath = vfs_path_from_str (source);
2846 else
2847 source_with_vpath = vfs_path_append_new (panel->cwd_vpath, source, (char *) NULL);
2848 #endif /* WITH_FULL_PATHS */
2849 if (panel_operate_init_totals (panel, vfs_path_as_str (source_with_vpath), ctx, dialog_type)
2850 == FILE_CONT)
2852 if (operation == OP_DELETE)
2854 if (S_ISDIR (src_stat.st_mode))
2855 value = erase_dir (tctx, ctx, source_with_vpath);
2856 else
2857 value = erase_file (tctx, ctx, source_with_vpath);
2859 else
2861 temp = transform_source (ctx, source_with_vpath);
2862 if (temp == NULL)
2863 value = transform_error;
2864 else
2866 char *repl_dest, *temp2;
2868 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2869 if (ctx->search_handle->error != MC_SEARCH_E_OK)
2871 if (ctx->search_handle->error_str != NULL)
2872 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
2874 g_free (repl_dest);
2875 goto clean_up;
2878 temp2 = mc_build_filename (repl_dest, temp, (char *) NULL);
2879 g_free (temp);
2880 g_free (repl_dest);
2881 g_free (dest);
2882 vfs_path_free (dest_vpath);
2883 dest = temp2;
2884 dest_vpath = vfs_path_from_str (dest);
2886 switch (operation)
2888 case OP_COPY:
2889 /* we use file_mask_op_follow_links only with OP_COPY */
2890 ctx->stat_func (source_with_vpath, &src_stat);
2892 if (S_ISDIR (src_stat.st_mode))
2893 value =
2894 copy_dir_dir (tctx, ctx, vfs_path_as_str (source_with_vpath),
2895 dest, TRUE, FALSE, FALSE, NULL);
2896 else
2897 value =
2898 copy_file_file (tctx, ctx, vfs_path_as_str (source_with_vpath),
2899 dest);
2900 break;
2902 case OP_MOVE:
2903 if (S_ISDIR (src_stat.st_mode))
2904 value =
2905 move_dir_dir (tctx, ctx, vfs_path_as_str (source_with_vpath), dest);
2906 else
2907 value =
2908 move_file_file (tctx, ctx, vfs_path_as_str (source_with_vpath),
2909 dest);
2910 break;
2912 default:
2913 /* Unknown file operation */
2914 abort ();
2917 } /* Copy or move operation */
2919 if ((value == FILE_CONT) && !force_single)
2920 unmark_files (panel);
2923 else
2925 /* Many files */
2927 /* Check destination for copy or move operation */
2928 while (operation != OP_DELETE)
2930 int dst_result;
2931 struct stat dst_stat;
2933 dst_result = mc_stat (dest_vpath, &dst_stat);
2935 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
2936 break;
2938 if (ctx->skip_all
2939 || file_error (_("Destination \"%s\" must be a directory\n%s"), dest) != FILE_RETRY)
2940 goto clean_up;
2943 if (panel_operate_init_totals (panel, NULL, ctx, dialog_type) == FILE_CONT)
2945 /* Loop for every file, perform the actual copy operation */
2946 for (i = 0; i < panel->dir.len; i++)
2948 const char *source2;
2950 if (!panel->dir.list[i].f.marked)
2951 continue; /* Skip the unmarked ones */
2953 source2 = panel->dir.list[i].fname;
2954 src_stat = panel->dir.list[i].st;
2956 #ifdef WITH_FULL_PATHS
2957 vfs_path_free (source_with_vpath);
2958 if (g_path_is_absolute (source2))
2959 source_with_vpath = vfs_path_from_str (source2);
2960 else
2961 source_with_vpath =
2962 vfs_path_append_new (panel->cwd_vpath, source2, (char *) NULL);
2963 #endif /* WITH_FULL_PATHS */
2965 if (operation == OP_DELETE)
2967 if (S_ISDIR (src_stat.st_mode))
2968 value = erase_dir (tctx, ctx, source_with_vpath);
2969 else
2970 value = erase_file (tctx, ctx, source_with_vpath);
2972 else
2974 temp = transform_source (ctx, source_with_vpath);
2975 if (temp == NULL)
2976 value = transform_error;
2977 else
2979 char *temp2, *repl_dest, *source_with_path_str;
2981 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2982 if (ctx->search_handle->error != MC_SEARCH_E_OK)
2984 if (ctx->search_handle->error_str != NULL)
2985 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
2987 g_free (repl_dest);
2988 goto clean_up;
2991 temp2 = mc_build_filename (repl_dest, temp, (char *) NULL);
2992 g_free (temp);
2993 g_free (repl_dest);
2994 source_with_path_str =
2995 strutils_shell_unescape (vfs_path_as_str (source_with_vpath));
2996 temp = strutils_shell_unescape (temp2);
2997 g_free (temp2);
2999 switch (operation)
3001 case OP_COPY:
3002 /* we use file_mask_op_follow_links only with OP_COPY */
3004 vfs_path_t *vpath;
3006 vpath = vfs_path_from_str (source_with_path_str);
3007 ctx->stat_func (vpath, &src_stat);
3008 vfs_path_free (vpath);
3010 if (S_ISDIR (src_stat.st_mode))
3011 value = copy_dir_dir (tctx, ctx, source_with_path_str, temp,
3012 TRUE, FALSE, FALSE, NULL);
3013 else
3014 value = copy_file_file (tctx, ctx, source_with_path_str, temp);
3015 dest_dirs = free_linklist (dest_dirs);
3016 break;
3018 case OP_MOVE:
3019 if (S_ISDIR (src_stat.st_mode))
3020 value = move_dir_dir (tctx, ctx, source_with_path_str, temp);
3021 else
3022 value = move_file_file (tctx, ctx, source_with_path_str, temp);
3023 break;
3025 default:
3026 /* Unknown file operation */
3027 abort ();
3030 g_free (source_with_path_str);
3031 g_free (temp);
3033 } /* Copy or move operation */
3035 if (value == FILE_ABORT)
3036 break;
3038 if (value == FILE_CONT)
3039 do_file_mark (panel, i, 0);
3041 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3043 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3044 file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
3047 if (operation != OP_DELETE)
3048 file_progress_show (ctx, 0, 0, "", FALSE);
3050 if (check_progress_buttons (ctx) == FILE_ABORT)
3051 break;
3053 mc_refresh ();
3054 } /* Loop for every file */
3056 } /* Many entries */
3058 clean_up:
3059 /* Clean up */
3060 if (save_cwd != NULL)
3062 tmp_vpath = vfs_path_from_str (save_cwd);
3063 mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3064 vfs_path_free (tmp_vpath);
3065 g_free (save_cwd);
3068 if (save_dest != NULL)
3070 tmp_vpath = vfs_path_from_str (save_dest);
3071 mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3072 vfs_path_free (tmp_vpath);
3073 g_free (save_dest);
3076 linklist = free_linklist (linklist);
3077 dest_dirs = free_linklist (dest_dirs);
3078 #ifdef WITH_FULL_PATHS
3079 vfs_path_free (source_with_vpath);
3080 #endif /* WITH_FULL_PATHS */
3081 g_free (dest);
3082 vfs_path_free (dest_vpath);
3083 MC_PTR_FREE (ctx->dest_mask);
3085 #ifdef ENABLE_BACKGROUND
3086 /* Let our parent know we are saying bye bye */
3087 if (mc_global.we_are_background)
3089 int cur_pid = getpid ();
3090 /* Send pid to parent with child context, it is fork and
3091 don't modify real parent ctx */
3092 ctx->pid = cur_pid;
3093 parent_call ((void *) end_bg_process, ctx, 0);
3095 vfs_shut ();
3096 my_exit (EXIT_SUCCESS);
3098 #endif /* ENABLE_BACKGROUND */
3100 file_op_total_context_destroy (tctx);
3101 ret_fast:
3102 file_op_context_destroy (ctx);
3104 return ret_val;
3107 /* }}} */
3109 /* --------------------------------------------------------------------------------------------- */
3110 /* {{{ Query/status report routines */
3111 /** Report error with one file */
3112 FileProgressStatus
3113 file_error (const char *format, const char *file)
3115 char buf[BUF_MEDIUM];
3117 g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3119 return do_file_error (buf);
3122 /* --------------------------------------------------------------------------------------------- */
3125 Cause emacs to enter folding mode for this file:
3126 Local variables:
3127 end: