(file_op_total_context_t): remove. All members were moved to file_op_context_t.
[midnight-commander.git] / src / filemanager / file.c
blob91ea587ea69ecbbe53596fdea777f01f4aaea516
1 /*
2 File management.
4 Copyright (C) 1994-2024
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-2022
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/strutil.h"
68 #include "lib/util.h"
69 #include "lib/vfs/vfs.h"
70 #include "lib/vfs/utilvfs.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 other_panel and WTree */
79 #include "dir.h"
80 #include "filegui.h"
81 #include "filenot.h"
82 #include "tree.h"
83 #include "filemanager.h" /* other_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 #define FILEOP_UPDATE_INTERVAL 2
103 #define FILEOP_STALLING_INTERVAL 4
104 #define FILEOP_UPDATE_INTERVAL_US (FILEOP_UPDATE_INTERVAL * G_USEC_PER_SEC)
105 #define FILEOP_STALLING_INTERVAL_US (FILEOP_STALLING_INTERVAL * G_USEC_PER_SEC)
107 /*** file scope type declarations ****************************************************************/
109 /* This is a hard link cache */
110 typedef struct
112 const struct vfs_class *vfs;
113 dev_t dev;
114 ino_t ino;
115 mode_t st_mode;
116 vfs_path_t *src_vpath;
117 vfs_path_t *dst_vpath;
118 } link_t;
120 /* Status of the destination file */
121 typedef enum
123 DEST_NONE = 0, /**< Not created */
124 DEST_SHORT_QUERY, /**< Created, not fully copied, query to do */
125 DEST_SHORT_KEEP, /**< Created, not fully copied, keep it */
126 DEST_SHORT_DELETE, /**< Created, not fully copied, delete it */
127 DEST_FULL /**< Created, fully copied */
128 } dest_status_t;
130 /* Status of hard link creation */
131 typedef enum
133 HARDLINK_OK = 0, /**< Hardlink was created successfully */
134 HARDLINK_CACHED, /**< Hardlink was added to the cache */
135 HARDLINK_NOTLINK, /**< This is not a hard link */
136 HARDLINK_UNSUPPORTED, /**< VFS doesn't support hard links */
137 HARDLINK_ERROR, /**< Hard link creation error */
138 HARDLINK_ABORT /**< Stop file operation after hardlink creation error */
139 } hardlink_status_t;
142 * This array introduced to avoid translation problems. The former (op_names)
143 * is assumed to be nouns, suitable in dialog box titles; this one should
144 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
145 * (I don't use spaces around the words, because someday they could be
146 * dropped, when widgets get smarter)
149 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */
150 static const char *op_names1[] = {
151 N_("FileOperation|Copy"),
152 N_("FileOperation|Move"),
153 N_("FileOperation|Delete")
157 * These are formats for building a prompt. Parts encoded as follows:
158 * %o - operation from op_names1
159 * %f - file/files or files/directories, as appropriate
160 * %m - "with source mask" or question mark for delete
161 * %s - source name (truncated)
162 * %d - number of marked files
163 * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
165 /* xgettext:no-c-format */
166 static const char *one_format = N_("%o %f%n\"%s\"%m");
167 /* xgettext:no-c-format */
168 static const char *many_format = N_("%o %d %f%m");
170 static const char *prompt_parts[] = {
171 N_("file"),
172 N_("files"),
173 N_("directory"),
174 N_("directories"),
175 N_("files/directories"),
176 /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
177 N_(" with source mask:")
180 /*** forward declarations (file scope functions) *************************************************/
182 /*** file scope variables ************************************************************************/
184 /* the hard link cache */
185 static GSList *linklist = NULL;
187 /* the files-to-be-erased list */
188 static GQueue *erase_list = NULL;
191 * This list holds information about just created target directories and is used to detect
192 * when an directory is copied into itself (we don't want to copy infinitely).
194 static GSList *dest_dirs = NULL;
196 /* --------------------------------------------------------------------------------------------- */
197 /*** file scope functions ************************************************************************/
198 /* --------------------------------------------------------------------------------------------- */
200 static void
201 dirsize_status_locate_buttons (dirsize_status_msg_t *dsm)
203 status_msg_t *sm = STATUS_MSG (dsm);
204 Widget *wd = WIDGET (sm->dlg);
205 int y, x;
206 WRect r;
208 y = wd->rect.y + 5;
209 x = wd->rect.x;
211 if (!dsm->allow_skip)
213 /* single button: "Abort" */
214 x += (wd->rect.cols - dsm->abort_button->rect.cols) / 2;
215 r = dsm->abort_button->rect;
216 r.y = y;
217 r.x = x;
218 widget_set_size_rect (dsm->abort_button, &r);
220 else
222 /* two buttons: "Abort" and "Skip" */
223 int cols;
225 cols = dsm->abort_button->rect.cols + dsm->skip_button->rect.cols + 1;
226 x += (wd->rect.cols - cols) / 2;
227 r = dsm->abort_button->rect;
228 r.y = y;
229 r.x = x;
230 widget_set_size_rect (dsm->abort_button, &r);
231 x += dsm->abort_button->rect.cols + 1;
232 r = dsm->skip_button->rect;
233 r.y = y;
234 r.x = x;
235 widget_set_size_rect (dsm->skip_button, &r);
239 /* --------------------------------------------------------------------------------------------- */
241 static char *
242 build_dest (file_op_context_t *ctx, const char *src, const char *dest, FileProgressStatus *status)
244 char *s, *q;
245 const char *fnsource;
247 *status = FILE_CONT;
249 s = g_strdup (src);
251 /* We remove \n from the filename since regex routines would use \n as an anchor */
252 /* this is just to be allowed to maniupulate file names with \n on it */
253 for (q = s; *q != '\0'; q++)
254 if (*q == '\n')
255 *q = ' ';
257 fnsource = x_basename (s);
259 if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
261 q = NULL;
262 *status = FILE_SKIP;
264 else
266 q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
267 if (ctx->search_handle->error != MC_SEARCH_E_OK)
269 if (ctx->search_handle->error_str != NULL)
270 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
272 *status = FILE_ABORT;
276 MC_PTR_FREE (s);
278 if (*status == FILE_CONT)
280 char *repl_dest;
282 repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
283 if (ctx->search_handle->error == MC_SEARCH_E_OK)
284 s = mc_build_filename (repl_dest, q, (char *) NULL);
285 else
287 if (ctx->search_handle->error_str != NULL)
288 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
290 *status = FILE_ABORT;
293 g_free (repl_dest);
296 g_free (q);
298 return s;
301 /* --------------------------------------------------------------------------------------------- */
303 static void
304 free_link (void *data)
306 link_t *lp = (link_t *) data;
308 vfs_path_free (lp->src_vpath, TRUE);
309 vfs_path_free (lp->dst_vpath, TRUE);
310 g_free (lp);
313 /* --------------------------------------------------------------------------------------------- */
315 static inline void *
316 free_erase_list (GQueue *lp)
318 if (lp != NULL)
319 g_queue_free_full (lp, free_link);
321 return NULL;
324 /* --------------------------------------------------------------------------------------------- */
326 static inline void *
327 free_linklist (GSList *lp)
329 g_slist_free_full (lp, free_link);
331 return NULL;
334 /* --------------------------------------------------------------------------------------------- */
336 static const link_t *
337 is_in_linklist (const GSList *lp, const vfs_path_t *vpath, const struct stat *sb)
339 const struct vfs_class *class;
340 ino_t ino = sb->st_ino;
341 dev_t dev = sb->st_dev;
343 class = vfs_path_get_last_path_vfs (vpath);
345 for (; lp != NULL; lp = (const GSList *) g_slist_next (lp))
347 const link_t *lnk = (const link_t *) lp->data;
349 if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev)
350 return lnk;
353 return NULL;
356 /* --------------------------------------------------------------------------------------------- */
358 * Check and made hardlink
360 * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
361 * and a hardlink was successfully made
364 static hardlink_status_t
365 check_hardlinks (const vfs_path_t *src_vpath, const struct stat *src_stat,
366 const vfs_path_t *dst_vpath, gboolean *ignore_all)
368 link_t *lnk;
369 ino_t ino = src_stat->st_ino;
370 dev_t dev = src_stat->st_dev;
372 if (src_stat->st_nlink < 2)
373 return HARDLINK_NOTLINK;
374 if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0)
375 return HARDLINK_UNSUPPORTED;
377 lnk = (link_t *) is_in_linklist (linklist, src_vpath, src_stat);
378 if (lnk != NULL)
380 int stat_result;
381 struct stat link_stat;
383 stat_result = mc_stat (lnk->src_vpath, &link_stat);
385 if (stat_result == 0 && link_stat.st_ino == ino && link_stat.st_dev == dev)
387 const struct vfs_class *lp_name_class;
388 const struct vfs_class *my_vfs;
390 lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath);
391 my_vfs = vfs_path_get_last_path_vfs (src_vpath);
393 if (lp_name_class == my_vfs)
395 const struct vfs_class *p_class, *dst_name_class;
397 dst_name_class = vfs_path_get_last_path_vfs (dst_vpath);
398 p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath);
400 if (dst_name_class == p_class)
402 gboolean ok;
404 while (!(ok = (mc_stat (lnk->dst_vpath, &link_stat) == 0)) && !*ignore_all)
406 FileProgressStatus status;
408 status =
409 file_error (TRUE, _("Cannot stat hardlink source file \"%s\"\n%s"),
410 vfs_path_as_str (lnk->dst_vpath));
411 if (status == FILE_ABORT)
412 return HARDLINK_ABORT;
413 if (status == FILE_RETRY)
414 continue;
415 if (status == FILE_IGNORE_ALL)
416 *ignore_all = TRUE;
417 break;
420 /* if stat() finished unsuccessfully, don't try to create link */
421 if (!ok)
422 return HARDLINK_ERROR;
424 while (!(ok = (mc_link (lnk->dst_vpath, dst_vpath) == 0)) && !*ignore_all)
426 FileProgressStatus status;
428 status =
429 file_error (TRUE, _("Cannot create target hardlink \"%s\"\n%s"),
430 vfs_path_as_str (dst_vpath));
431 if (status == FILE_ABORT)
432 return HARDLINK_ABORT;
433 if (status == FILE_RETRY)
434 continue;
435 if (status == FILE_IGNORE_ALL)
436 *ignore_all = TRUE;
437 break;
440 /* Success? */
441 return (ok ? HARDLINK_OK : HARDLINK_ERROR);
446 if (!*ignore_all)
448 FileProgressStatus status;
450 /* Message w/o "Retry" action.
452 * FIXME: Can't say what errno is here. Define it and don't display.
454 * file_error() displays a message with text representation of errno
455 * and the string passed to file_error() should provide the format "%s"
456 * for that at end (see previous file_error() call for the reference).
457 * But if format for errno isn't provided, it is safe, because C standard says:
458 * "If the format is exhausted while arguments remain, the excess arguments
459 * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999,
460 * section 7.19.6.1, paragraph 2).
463 errno = 0;
464 status =
465 file_error (FALSE, _("Cannot create target hardlink \"%s\""),
466 vfs_path_as_str (dst_vpath));
468 if (status == FILE_ABORT)
469 return HARDLINK_ABORT;
471 if (status == FILE_IGNORE_ALL)
472 *ignore_all = TRUE;
475 return HARDLINK_ERROR;
478 lnk = g_try_new (link_t, 1);
479 if (lnk != NULL)
481 lnk->vfs = vfs_path_get_last_path_vfs (src_vpath);
482 lnk->ino = ino;
483 lnk->dev = dev;
484 lnk->st_mode = 0;
485 lnk->src_vpath = vfs_path_clone (src_vpath);
486 lnk->dst_vpath = vfs_path_clone (dst_vpath);
488 linklist = g_slist_prepend (linklist, lnk);
491 return HARDLINK_CACHED;
494 /* --------------------------------------------------------------------------------------------- */
496 * Duplicate the contents of the symbolic link src_vpath in dst_vpath.
497 * Try to make a stable symlink if the option "stable symlink" was
498 * set in the file mask dialog.
499 * If dst_path is an existing symlink it will be deleted silently
500 * (upper levels take already care of existing files at dst_vpath).
503 static FileProgressStatus
504 make_symlink (file_op_context_t *ctx, const vfs_path_t *src_vpath, const vfs_path_t *dst_vpath)
506 const char *src_path;
507 const char *dst_path;
508 char link_target[MC_MAXPATHLEN];
509 int len;
510 FileProgressStatus return_status;
511 struct stat dst_stat;
512 gboolean dst_is_symlink;
513 vfs_path_t *link_target_vpath = NULL;
515 src_path = vfs_path_as_str (src_vpath);
516 dst_path = vfs_path_as_str (dst_vpath);
518 dst_is_symlink = (mc_lstat (dst_vpath, &dst_stat) == 0) && S_ISLNK (dst_stat.st_mode);
520 retry_src_readlink:
521 len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1);
522 if (len < 0)
524 if (ctx->ignore_all)
525 return_status = FILE_IGNORE_ALL;
526 else
528 return_status = file_error (TRUE, _("Cannot read source link \"%s\"\n%s"), src_path);
529 if (return_status == FILE_IGNORE_ALL)
530 ctx->ignore_all = TRUE;
531 if (return_status == FILE_RETRY)
532 goto retry_src_readlink;
534 goto ret;
537 link_target[len] = '\0';
539 if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath)))
541 message (D_ERROR, MSG_ERROR,
542 _("Cannot make stable symlinks across "
543 "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
544 ctx->stable_symlinks = FALSE;
547 if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
549 const char *r;
551 r = strrchr (src_path, PATH_SEP);
552 if (r != NULL)
554 size_t slen;
555 GString *p;
556 vfs_path_t *q;
558 slen = r - src_path + 1;
560 p = g_string_sized_new (slen + len);
561 g_string_append_len (p, src_path, slen);
563 if (g_path_is_absolute (dst_path))
564 q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
565 else
566 q = vfs_path_build_filename (p->str, dst_path, (char *) NULL);
568 if (vfs_path_tokens_count (q) > 1)
570 char *s = NULL;
571 vfs_path_t *tmp_vpath1, *tmp_vpath2;
573 g_string_append_len (p, link_target, len);
574 tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
575 tmp_vpath2 = vfs_path_from_str (p->str);
576 s = diff_two_paths (tmp_vpath1, tmp_vpath2);
577 vfs_path_free (tmp_vpath2, TRUE);
578 vfs_path_free (tmp_vpath1, TRUE);
579 g_strlcpy (link_target, s != NULL ? s : p->str, sizeof (link_target));
580 g_free (s);
583 g_string_free (p, TRUE);
584 vfs_path_free (q, TRUE);
587 link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON);
589 retry_dst_symlink:
590 if (mc_symlink (link_target_vpath, dst_vpath) == 0)
592 /* Success */
593 return_status = FILE_CONT;
594 goto ret;
597 * if dst_exists, it is obvious that this had failed.
598 * We can delete the old symlink and try again...
600 if (dst_is_symlink && mc_unlink (dst_vpath) == 0
601 && mc_symlink (link_target_vpath, dst_vpath) == 0)
603 /* Success */
604 return_status = FILE_CONT;
605 goto ret;
608 if (ctx->ignore_all)
609 return_status = FILE_IGNORE_ALL;
610 else
612 return_status = file_error (TRUE, _("Cannot create target symlink \"%s\"\n%s"), dst_path);
613 if (return_status == FILE_IGNORE_ALL)
614 ctx->ignore_all = TRUE;
615 if (return_status == FILE_RETRY)
616 goto retry_dst_symlink;
619 ret:
620 vfs_path_free (link_target_vpath, TRUE);
621 return return_status;
624 /* --------------------------------------------------------------------------------------------- */
626 * do_compute_dir_size:
628 * Computes the number of bytes used by the files in a directory
631 static FileProgressStatus
632 do_compute_dir_size (const vfs_path_t *dirname_vpath, dirsize_status_msg_t *dsm,
633 size_t *dir_count, size_t *ret_marked, uintmax_t *ret_total,
634 mc_stat_fn stat_func)
636 static gint64 timestamp = 0;
637 /* update with 25 FPS rate */
638 static const gint64 delay = G_USEC_PER_SEC / 25;
640 status_msg_t *sm = STATUS_MSG (dsm);
641 int res;
642 struct stat s;
643 DIR *dir;
644 struct vfs_dirent *dirent;
645 FileProgressStatus ret = FILE_CONT;
647 (*dir_count)++;
649 dir = mc_opendir (dirname_vpath);
650 if (dir == NULL)
651 return ret;
653 while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL)
655 vfs_path_t *tmp_vpath;
657 if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name))
658 continue;
660 tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL);
662 res = stat_func (tmp_vpath, &s);
663 if (res == 0)
665 if (S_ISDIR (s.st_mode))
666 ret =
667 do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total,
668 stat_func);
669 else
671 ret = FILE_CONT;
673 (*ret_marked)++;
674 *ret_total += (uintmax_t) s.st_size;
677 if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (&timestamp, delay))
679 dsm->dirname_vpath = tmp_vpath;
680 dsm->dir_count = *dir_count;
681 dsm->total_size = *ret_total;
682 ret = sm->update (sm);
686 vfs_path_free (tmp_vpath, TRUE);
689 mc_closedir (dir);
690 return ret;
693 /* --------------------------------------------------------------------------------------------- */
695 * panel_compute_totals:
697 * compute the number of files and the number of bytes
698 * used up by the whole selection, recursing directories
699 * as required. In addition, it checks to see if it will
700 * overwrite any files by doing the copy.
703 static FileProgressStatus
704 panel_compute_totals (const WPanel *panel, dirsize_status_msg_t *sm, size_t *ret_count,
705 uintmax_t *ret_total, gboolean follow_symlinks)
707 int i;
708 size_t dir_count = 0;
709 mc_stat_fn stat_func = follow_symlinks ? mc_stat : mc_lstat;
711 for (i = 0; i < panel->dir.len; i++)
713 const file_entry_t *fe = &panel->dir.list[i];
714 const struct stat *s;
716 if (fe->f.marked == 0)
717 continue;
719 s = &fe->st;
721 if (S_ISDIR (s->st_mode) || (follow_symlinks && link_isdir (fe) && fe->f.stale_link == 0))
723 vfs_path_t *p;
724 FileProgressStatus status;
726 p = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
727 status = do_compute_dir_size (p, sm, &dir_count, ret_count, ret_total, stat_func);
728 vfs_path_free (p, TRUE);
730 if (status != FILE_CONT)
731 return status;
733 else
735 (*ret_count)++;
736 *ret_total += (uintmax_t) s->st_size;
740 return FILE_CONT;
743 /* --------------------------------------------------------------------------------------------- */
745 /** Initialize variables for progress bars */
746 static FileProgressStatus
747 panel_operate_init_totals (const WPanel *panel, const vfs_path_t *source,
748 const struct stat *source_stat, file_op_context_t *ctx,
749 gboolean compute_totals, filegui_dialog_type_t dialog_type)
751 FileProgressStatus status;
753 #ifdef ENABLE_BACKGROUND
754 if (mc_global.we_are_background)
755 return FILE_CONT;
756 #endif
758 if (verbose && compute_totals)
760 dirsize_status_msg_t dsm;
761 gboolean stale_link = FALSE;
763 memset (&dsm, 0, sizeof (dsm));
764 dsm.allow_skip = TRUE;
765 status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
766 dirsize_status_update_cb, dirsize_status_deinit_cb);
768 ctx->total_count = 0;
769 ctx->total_bytes = 0;
771 if (source == NULL)
772 status = panel_compute_totals (panel, &dsm, &ctx->total_count, &ctx->total_bytes,
773 ctx->follow_links);
774 else if (S_ISDIR (source_stat->st_mode)
775 || (ctx->follow_links
776 && file_is_symlink_to_dir (source, (struct stat *) source_stat, &stale_link)
777 && !stale_link))
779 size_t dir_count = 0;
781 status = do_compute_dir_size (source, &dsm, &dir_count, &ctx->total_count,
782 &ctx->total_bytes, ctx->stat_func);
784 else
786 ctx->total_count++;
787 ctx->total_bytes += (uintmax_t) source_stat->st_size;
788 status = FILE_CONT;
791 status_msg_deinit (STATUS_MSG (&dsm));
793 ctx->totals_computed = (status == FILE_CONT);
795 if (status == FILE_SKIP)
796 status = FILE_CONT;
798 else
800 status = FILE_CONT;
801 ctx->total_count = panel->marked;
802 ctx->total_bytes = panel->total;
803 ctx->totals_computed = verbose && dialog_type == FILEGUI_DIALOG_ONE_ITEM;
806 /* destroy already created UI for single file rename operation */
807 file_progress_ui_destroy (ctx);
809 file_progress_ui_create (ctx, TRUE, dialog_type);
811 return status;
814 /* --------------------------------------------------------------------------------------------- */
816 static FileProgressStatus
817 progress_update_one (file_op_context_t *ctx, off_t add)
819 gint64 tv_current;
820 static gint64 tv_start = -1;
822 ctx->progress_count++;
823 ctx->progress_bytes += (uintmax_t) add;
825 tv_current = g_get_monotonic_time ();
827 if (tv_start < 0)
828 tv_start = tv_current;
830 if (tv_current - tv_start > FILEOP_UPDATE_INTERVAL_US)
832 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
834 file_progress_show_count (ctx, ctx->progress_count, ctx->total_count);
835 file_progress_show_total (ctx, ctx->progress_bytes, TRUE);
838 tv_start = tv_current;
841 return file_progress_check_buttons (ctx);
844 /* --------------------------------------------------------------------------------------------- */
846 static FileProgressStatus
847 real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b)
849 char *msg;
850 int result = 0;
851 const char *head_msg;
852 int width_a, width_b, width;
854 head_msg = mode == Foreground ? MSG_ERROR : _("Background process error");
856 width_a = str_term_width1 (a);
857 width_b = str_term_width1 (b);
858 width = COLS - 8;
860 if (width_a > width)
862 if (width_b > width)
864 char *s;
866 s = g_strndup (str_trunc (a, width), width);
867 b = str_trunc (b, width);
868 msg = g_strdup_printf (fmt, s, b);
869 g_free (s);
871 else
873 a = str_trunc (a, width);
874 msg = g_strdup_printf (fmt, a, b);
877 else
879 if (width_b > width)
880 b = str_trunc (b, width);
882 msg = g_strdup_printf (fmt, a, b);
885 result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
886 g_free (msg);
887 do_refresh ();
889 return (result == 1) ? FILE_ABORT : FILE_SKIP;
892 /* --------------------------------------------------------------------------------------------- */
894 static FileProgressStatus
895 warn_same_file (const char *fmt, const char *a, const char *b)
897 #ifdef ENABLE_BACKGROUND
898 /* *INDENT-OFF* */
899 union
901 void *p;
902 FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b);
903 } pntr;
904 /* *INDENT-ON* */
906 pntr.f = real_warn_same_file;
908 if (mc_global.we_are_background)
909 return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b);
910 #endif
911 return real_warn_same_file (Foreground, fmt, a, b);
914 /* --------------------------------------------------------------------------------------------- */
916 static gboolean
917 check_same_file (const char *a, const struct stat *ast, const char *b, const struct stat *bst,
918 FileProgressStatus *status)
920 if (ast->st_dev != bst->st_dev || ast->st_ino != bst->st_ino)
921 return FALSE;
923 if (S_ISDIR (ast->st_mode))
924 *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a, b);
925 else
926 *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a, b);
928 return TRUE;
931 /* --------------------------------------------------------------------------------------------- */
932 /* {{{ Query/status report routines */
934 static FileProgressStatus
935 real_do_file_error (enum OperationMode mode, gboolean allow_retry, const char *error)
937 int result;
938 const char *msg;
940 msg = mode == Foreground ? MSG_ERROR : _("Background process error");
942 if (allow_retry)
943 result =
944 query_dialog (msg, error, D_ERROR, 4, _("&Ignore"), _("Ignore a&ll"), _("&Retry"),
945 _("&Abort"));
946 else
947 result = query_dialog (msg, error, D_ERROR, 3, _("&Ignore"), _("Ignore a&ll"), _("&Abort"));
949 switch (result)
951 case 0:
952 do_refresh ();
953 return FILE_IGNORE;
955 case 1:
956 do_refresh ();
957 return FILE_IGNORE_ALL;
959 case 2:
960 if (allow_retry)
962 do_refresh ();
963 return FILE_RETRY;
965 MC_FALLTHROUGH;
967 case 3:
968 default:
969 return FILE_ABORT;
973 /* --------------------------------------------------------------------------------------------- */
975 static FileProgressStatus
976 real_query_recursive (file_op_context_t *ctx, enum OperationMode mode, const char *s)
978 if (ctx->recursive_result < RECURSIVE_ALWAYS)
980 const char *msg;
981 char *text;
983 msg = mode == Foreground
984 ? _("Directory \"%s\" not empty.\nDelete it recursively?")
985 : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
986 text = g_strdup_printf (msg, path_trunc (s, 30));
988 if (safe_delete)
989 query_set_sel (1);
991 ctx->recursive_result =
992 query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"),
993 _("Non&e"), _("&Abort"));
994 g_free (text);
996 if (ctx->recursive_result != RECURSIVE_ABORT)
997 do_refresh ();
1000 switch (ctx->recursive_result)
1002 case RECURSIVE_YES:
1003 case RECURSIVE_ALWAYS:
1004 return FILE_CONT;
1006 case RECURSIVE_NO:
1007 case RECURSIVE_NEVER:
1008 return FILE_SKIP;
1010 case RECURSIVE_ABORT:
1011 default:
1012 return FILE_ABORT;
1016 /* --------------------------------------------------------------------------------------------- */
1018 #ifdef ENABLE_BACKGROUND
1019 static FileProgressStatus
1020 do_file_error (gboolean allow_retry, const char *str)
1022 /* *INDENT-OFF* */
1023 union
1025 void *p;
1026 FileProgressStatus (*f) (enum OperationMode, gboolean, const char *);
1027 } pntr;
1028 /* *INDENT-ON* */
1030 pntr.f = real_do_file_error;
1032 if (mc_global.we_are_background)
1033 return parent_call (pntr.p, NULL, 2, sizeof (allow_retry), allow_retry, strlen (str), str);
1034 else
1035 return real_do_file_error (Foreground, allow_retry, str);
1038 /* --------------------------------------------------------------------------------------------- */
1040 static FileProgressStatus
1041 query_recursive (file_op_context_t *ctx, const char *s)
1043 /* *INDENT-OFF* */
1044 union
1046 void *p;
1047 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *);
1048 } pntr;
1049 /* *INDENT-ON* */
1051 pntr.f = real_query_recursive;
1053 if (mc_global.we_are_background)
1054 return parent_call (pntr.p, ctx, 1, strlen (s), s);
1055 else
1056 return real_query_recursive (ctx, Foreground, s);
1059 /* --------------------------------------------------------------------------------------------- */
1061 static FileProgressStatus
1062 query_replace (file_op_context_t *ctx, const char *src, struct stat *src_stat, const char *dst,
1063 struct stat *dst_stat)
1065 /* *INDENT-OFF* */
1066 union
1068 void *p;
1069 FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *,
1070 struct stat *, const char *, struct stat *);
1071 } pntr;
1072 /* *INDENT-ON* */
1074 pntr.f = file_progress_real_query_replace;
1076 if (mc_global.we_are_background)
1077 return parent_call (pntr.p, ctx, 4, strlen (src), src, sizeof (struct stat), src_stat,
1078 strlen (dst), dst, sizeof (struct stat), dst_stat);
1079 else
1080 return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
1083 #else
1084 /* --------------------------------------------------------------------------------------------- */
1086 static FileProgressStatus
1087 do_file_error (gboolean allow_retry, const char *str)
1089 return real_do_file_error (Foreground, allow_retry, str);
1092 /* --------------------------------------------------------------------------------------------- */
1094 static FileProgressStatus
1095 query_recursive (file_op_context_t *ctx, const char *s)
1097 return real_query_recursive (ctx, Foreground, s);
1100 /* --------------------------------------------------------------------------------------------- */
1102 static FileProgressStatus
1103 query_replace (file_op_context_t *ctx, const char *src, struct stat *src_stat, const char *dst,
1104 struct stat *dst_stat)
1106 return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
1109 #endif /* !ENABLE_BACKGROUND */
1111 /* --------------------------------------------------------------------------------------------- */
1112 /** Report error with two files */
1114 static FileProgressStatus
1115 files_error (const char *format, const char *file1, const char *file2)
1117 char buf[BUF_MEDIUM];
1118 char *nfile1, *nfile2;
1120 nfile1 = g_strdup (path_trunc (file1, 15));
1121 nfile2 = g_strdup (path_trunc (file2, 15));
1122 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno));
1123 g_free (nfile1);
1124 g_free (nfile2);
1126 return do_file_error (TRUE, buf);
1129 /* }}} */
1131 /* --------------------------------------------------------------------------------------------- */
1133 static void
1134 copy_file_file_display_progress (file_op_context_t *ctx, gint64 tv_current, off_t file_part,
1135 off_t file_size)
1137 gint64 dt;
1139 /* Update rotating dash after some time */
1140 rotate_dash (TRUE);
1142 /* Compute ETA */
1143 dt = (tv_current - ctx->transfer_start) / G_USEC_PER_SEC;
1145 if (file_part == 0)
1146 ctx->eta_secs = 0.0;
1147 else
1148 ctx->eta_secs = ((dt / (double) file_part) * file_size) - dt;
1150 /* Compute BPS rate */
1151 ctx->bps_time = MAX (1, dt);
1152 ctx->bps = file_part / ctx->bps_time;
1154 /* Compute total ETA and BPS */
1155 if (ctx->total_bytes != 0)
1157 gint64 total_secs;
1159 total_secs = (tv_current - ctx->total_transfer_start) / G_USEC_PER_SEC;
1160 total_secs = MAX (1, total_secs);
1162 ctx->total_bps = ctx->copied_bytes / total_secs;
1163 const uintmax_t remain_bytes = ctx->total_bytes - ctx->copied_bytes;
1164 ctx->total_eta_secs = ctx->total_bps != 0 ? remain_bytes / ctx->total_bps : 0;
1168 /* --------------------------------------------------------------------------------------------- */
1170 static gboolean
1171 try_remove_file (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status)
1173 while (mc_unlink (vpath) != 0 && !ctx->ignore_all)
1175 *status = file_error (TRUE, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath));
1176 if (*status == FILE_RETRY)
1177 continue;
1178 if (*status == FILE_IGNORE_ALL)
1179 ctx->ignore_all = TRUE;
1180 return FALSE;
1183 return TRUE;
1186 /* --------------------------------------------------------------------------------------------- */
1188 /* {{{ Move routines */
1191 * Move single file or one of many files from one location to another.
1193 * @panel pointer to panel in case of single file, NULL otherwise
1194 * @ctx file operation context object
1195 * @s source file name
1196 * @d destination file name
1198 * @return operation result
1200 static FileProgressStatus
1201 move_file_file (const WPanel *panel, file_op_context_t *ctx, const char *s, const char *d)
1203 struct stat src_stat, dst_stat;
1204 FileProgressStatus return_status = FILE_CONT;
1205 gboolean copy_done = FALSE;
1206 gboolean old_ask_overwrite;
1207 vfs_path_t *src_vpath, *dst_vpath;
1209 src_vpath = vfs_path_from_str (s);
1210 dst_vpath = vfs_path_from_str (d);
1212 file_progress_show_source (ctx, src_vpath);
1213 file_progress_show_target (ctx, dst_vpath);
1215 /* FIXME: do we really need to check buttons in case of single file? */
1216 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1218 return_status = FILE_ABORT;
1219 goto ret;
1222 mc_refresh ();
1224 while (mc_lstat (src_vpath, &src_stat) != 0)
1226 /* Source doesn't exist */
1227 if (ctx->ignore_all)
1228 return_status = FILE_IGNORE_ALL;
1229 else
1231 return_status = file_error (TRUE, _("Cannot stat file \"%s\"\n%s"), s);
1232 if (return_status == FILE_IGNORE_ALL)
1233 ctx->ignore_all = TRUE;
1236 if (return_status != FILE_RETRY)
1237 goto ret;
1240 if (mc_lstat (dst_vpath, &dst_stat) == 0)
1242 if (check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1243 goto ret;
1245 if (S_ISDIR (dst_stat.st_mode))
1247 message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
1248 do_refresh ();
1249 return_status = FILE_SKIP;
1250 goto ret;
1253 if (confirm_overwrite)
1255 return_status = query_replace (ctx, s, &src_stat, d, &dst_stat);
1256 if (return_status != FILE_CONT)
1257 goto ret;
1259 /* Ok to overwrite */
1262 if (!ctx->do_append)
1264 if (S_ISLNK (src_stat.st_mode) && ctx->stable_symlinks)
1266 return_status = make_symlink (ctx, src_vpath, dst_vpath);
1267 if (return_status == FILE_CONT)
1269 if (ctx->preserve)
1271 mc_timesbuf_t times;
1273 vfs_get_timesbuf_from_stat (&src_stat, &times);
1274 mc_utime (dst_vpath, &times);
1276 goto retry_src_remove;
1278 goto ret;
1281 if (mc_rename (src_vpath, dst_vpath) == 0)
1283 /* FIXME: do we really need to update progress in case of single file? */
1284 return_status = progress_update_one (ctx, src_stat.st_size);
1285 goto ret;
1288 #if 0
1289 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1290 one nfs to the same, but on the server it is on two different
1291 filesystems. Then nfs returns EIO instead of EXDEV.
1292 Hope it will not hurt if we always in case of error try to copy/delete. */
1293 else
1294 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1296 if (errno != EXDEV)
1298 if (ctx->ignore_all)
1299 return_status = FILE_IGNORE_ALL;
1300 else
1302 return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
1303 if (return_status == FILE_IGNORE_ALL)
1304 ctx->ignore_all = TRUE;
1305 if (return_status == FILE_RETRY)
1306 goto retry_rename;
1309 goto ret;
1311 #endif
1313 /* Failed rename -> copy the file instead */
1314 if (panel != NULL)
1316 /* In case of single file, calculate totals. In case of many files,
1317 totals are calculated already. */
1318 return_status =
1319 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1320 FILEGUI_DIALOG_ONE_ITEM);
1321 if (return_status != FILE_CONT)
1322 goto ret;
1325 old_ask_overwrite = ctx->ask_overwrite;
1326 ctx->ask_overwrite = FALSE;
1327 return_status = copy_file_file (ctx, s, d);
1328 ctx->ask_overwrite = old_ask_overwrite;
1329 if (return_status != FILE_CONT)
1330 goto ret;
1332 copy_done = TRUE;
1334 /* FIXME: there is no need to update progress and check buttons
1335 at the finish of single file operation. */
1336 if (panel == NULL)
1338 file_progress_show_source (ctx, NULL);
1339 if (verbose)
1340 file_progress_show (ctx, 0, 0, "", FALSE);
1342 return_status = file_progress_check_buttons (ctx);
1343 if (return_status != FILE_CONT)
1344 goto ret;
1347 mc_refresh ();
1349 retry_src_remove:
1350 if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL)
1351 goto ret;
1353 if (!copy_done)
1354 return_status = progress_update_one (ctx, src_stat.st_size);
1356 ret:
1357 vfs_path_free (src_vpath, TRUE);
1358 vfs_path_free (dst_vpath, TRUE);
1360 return return_status;
1363 /* }}} */
1365 /* --------------------------------------------------------------------------------------------- */
1366 /* {{{ Erase routines */
1367 /** Don't update progress status if progress_count==NULL */
1369 static FileProgressStatus
1370 erase_file (file_op_context_t *ctx, const vfs_path_t *vpath)
1372 struct stat buf;
1373 FileProgressStatus return_status;
1375 /* check buttons if deleting info was changed */
1376 if (file_progress_show_deleting (ctx, vpath, &ctx->progress_count))
1378 file_progress_show_count (ctx, ctx->progress_count, ctx->total_count);
1379 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1380 return FILE_ABORT;
1382 mc_refresh ();
1385 if (ctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0)
1387 /* ignore, most likely the mc_unlink fails, too */
1388 buf.st_size = 0;
1391 if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT)
1392 return FILE_ABORT;
1394 if (ctx->progress_count == 0)
1395 return FILE_CONT;
1397 return file_progress_check_buttons (ctx);
1400 /* --------------------------------------------------------------------------------------------- */
1402 static FileProgressStatus
1403 try_erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
1405 const char *dir;
1406 FileProgressStatus return_status = FILE_CONT;
1408 dir = vfs_path_as_str (vpath);
1410 while (my_rmdir (dir) != 0 && !ctx->ignore_all)
1412 return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir);
1413 if (return_status == FILE_IGNORE_ALL)
1414 ctx->ignore_all = TRUE;
1415 if (return_status != FILE_RETRY)
1416 break;
1419 return return_status;
1422 /* --------------------------------------------------------------------------------------------- */
1425 Recursive removal of files
1426 abort -> cancel stack
1427 ignore -> warn every level, gets default
1428 ignore_all -> remove as much as possible
1430 static FileProgressStatus
1431 recursive_erase (file_op_context_t *ctx, const vfs_path_t *vpath)
1433 struct vfs_dirent *next;
1434 DIR *reading;
1435 FileProgressStatus return_status = FILE_CONT;
1437 reading = mc_opendir (vpath);
1438 if (reading == NULL)
1439 return FILE_RETRY;
1441 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
1443 vfs_path_t *tmp_vpath;
1444 struct stat buf;
1446 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
1447 continue;
1449 tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
1450 if (mc_lstat (tmp_vpath, &buf) != 0)
1452 mc_closedir (reading);
1453 vfs_path_free (tmp_vpath, TRUE);
1454 return FILE_RETRY;
1456 if (S_ISDIR (buf.st_mode))
1457 return_status = recursive_erase (ctx, tmp_vpath);
1458 else
1459 return_status = erase_file (ctx, tmp_vpath);
1460 vfs_path_free (tmp_vpath, TRUE);
1462 mc_closedir (reading);
1464 if (return_status == FILE_ABORT)
1465 return FILE_ABORT;
1467 file_progress_show_deleting (ctx, vpath, NULL);
1468 file_progress_show_count (ctx, ctx->progress_count, ctx->total_count);
1469 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1470 return FILE_ABORT;
1472 mc_refresh ();
1474 return try_erase_dir (ctx, vpath);
1477 /* --------------------------------------------------------------------------------------------- */
1479 * Check if directory is empty or not.
1481 * @param vpath directory handler
1483 * @returns -1 on error,
1484 * 1 if there are no entries besides "." and ".." in the directory path points to,
1485 * 0 else.
1487 * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
1488 * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
1489 * in SHELL) don't return "." and ".." entries.
1491 static int
1492 check_dir_is_empty (const vfs_path_t *vpath)
1494 DIR *dir;
1495 struct vfs_dirent *d;
1496 int i = 1;
1498 dir = mc_opendir (vpath);
1499 if (dir == NULL)
1500 return -1;
1502 for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1503 if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1505 i = 0;
1506 break;
1509 mc_closedir (dir);
1510 return i;
1513 /* --------------------------------------------------------------------------------------------- */
1515 static FileProgressStatus
1516 erase_dir_iff_empty (file_op_context_t *ctx, const vfs_path_t *vpath, size_t count)
1518 file_progress_show_deleting (ctx, vpath, NULL);
1519 file_progress_show_count (ctx, count, ctx->total_count);
1520 if (file_progress_check_buttons (ctx) == FILE_ABORT)
1521 return FILE_ABORT;
1523 mc_refresh ();
1525 if (check_dir_is_empty (vpath) != 1)
1526 return FILE_CONT;
1528 /* not empty or error */
1529 return try_erase_dir (ctx, vpath);
1532 /* --------------------------------------------------------------------------------------------- */
1534 static void
1535 erase_dir_after_copy (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status)
1537 if (ctx->erase_at_end && erase_list != NULL)
1539 /* Reset progress count before delete to avoid counting files twice */
1540 ctx->progress_count = ctx->prev_progress_count;
1542 while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT)
1544 link_t *lp;
1546 lp = (link_t *) g_queue_pop_head (erase_list);
1548 if (S_ISDIR (lp->st_mode))
1549 *status = erase_dir_iff_empty (ctx, lp->src_vpath, ctx->progress_count);
1550 else
1551 *status = erase_file (ctx, lp->src_vpath);
1553 free_link (lp);
1556 /* Save progress counter before move next directory */
1557 ctx->prev_progress_count = ctx->progress_count;
1560 erase_dir_iff_empty (ctx, vpath, ctx->progress_count);
1563 /* }}} */
1565 /* --------------------------------------------------------------------------------------------- */
1568 * Move single directory or one of many directories from one location to another.
1570 * @panel pointer to panel in case of single directory, NULL otherwise
1571 * @ctx file operation context object
1572 * @s source directory name
1573 * @d destination directory name
1575 * @return operation result
1577 static FileProgressStatus
1578 do_move_dir_dir (const WPanel *panel, file_op_context_t *ctx, const char *s, const char *d)
1580 struct stat src_stat, dst_stat;
1581 FileProgressStatus return_status = FILE_CONT;
1582 gboolean move_over = FALSE;
1583 gboolean dstat_ok;
1584 vfs_path_t *src_vpath, *dst_vpath;
1586 src_vpath = vfs_path_from_str (s);
1587 dst_vpath = vfs_path_from_str (d);
1589 file_progress_show_source (ctx, src_vpath);
1590 file_progress_show_target (ctx, dst_vpath);
1592 /* FIXME: do we really need to check buttons in case of single directory? */
1593 if (panel != NULL && file_progress_check_buttons (ctx) == FILE_ABORT)
1595 return_status = FILE_ABORT;
1596 goto ret_fast;
1599 mc_refresh ();
1601 mc_stat (src_vpath, &src_stat);
1603 dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0);
1605 if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1606 goto ret_fast;
1608 if (!dstat_ok)
1609 ; /* destination doesn't exist */
1610 else if (!ctx->dive_into_subdirs)
1611 move_over = TRUE;
1612 else
1614 vfs_path_t *tmp;
1616 tmp = dst_vpath;
1617 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
1618 vfs_path_free (tmp, TRUE);
1621 d = vfs_path_as_str (dst_vpath);
1623 /* Check if the user inputted an existing dir */
1624 retry_dst_stat:
1625 if (mc_stat (dst_vpath, &dst_stat) == 0)
1627 if (move_over)
1629 if (panel != NULL)
1631 /* In case of single directory, calculate totals. In case of many directories,
1632 totals are calculated already. */
1633 return_status =
1634 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1635 FILEGUI_DIALOG_MULTI_ITEM);
1636 if (return_status != FILE_CONT)
1637 goto ret;
1640 return_status = copy_dir_dir (ctx, s, d, FALSE, TRUE, TRUE, NULL);
1642 if (return_status != FILE_CONT)
1643 goto ret;
1644 goto oktoret;
1646 else if (ctx->ignore_all)
1647 return_status = FILE_IGNORE_ALL;
1648 else
1650 if (S_ISDIR (dst_stat.st_mode))
1651 return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d);
1652 else
1653 return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d);
1654 if (return_status == FILE_IGNORE_ALL)
1655 ctx->ignore_all = TRUE;
1656 if (return_status == FILE_RETRY)
1657 goto retry_dst_stat;
1660 goto ret_fast;
1663 retry_rename:
1664 if (mc_rename (src_vpath, dst_vpath) == 0)
1666 return_status = FILE_CONT;
1667 goto ret;
1670 if (errno != EXDEV)
1672 if (!ctx->ignore_all)
1674 return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
1675 if (return_status == FILE_IGNORE_ALL)
1676 ctx->ignore_all = TRUE;
1677 if (return_status == FILE_RETRY)
1678 goto retry_rename;
1680 goto ret;
1683 /* Failed because of filesystem boundary -> copy dir instead */
1684 if (panel != NULL)
1686 /* In case of single directory, calculate totals. In case of many directories,
1687 totals are calculated already. */
1688 return_status =
1689 panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1690 FILEGUI_DIALOG_MULTI_ITEM);
1691 if (return_status != FILE_CONT)
1692 goto ret;
1695 return_status = copy_dir_dir (ctx, s, d, FALSE, FALSE, TRUE, NULL);
1697 if (return_status != FILE_CONT)
1698 goto ret;
1700 oktoret:
1701 /* FIXME: there is no need to update progress and check buttons
1702 at the finish of single directory operation. */
1703 if (panel == NULL)
1705 file_progress_show_source (ctx, NULL);
1706 file_progress_show_target (ctx, NULL);
1707 if (verbose)
1708 file_progress_show (ctx, 0, 0, "", FALSE);
1710 return_status = file_progress_check_buttons (ctx);
1711 if (return_status != FILE_CONT)
1712 goto ret;
1715 mc_refresh ();
1717 erase_dir_after_copy (ctx, src_vpath, &return_status);
1719 ret:
1720 erase_list = free_erase_list (erase_list);
1721 ret_fast:
1722 vfs_path_free (src_vpath, TRUE);
1723 vfs_path_free (dst_vpath, TRUE);
1724 return return_status;
1727 /* --------------------------------------------------------------------------------------------- */
1729 /* {{{ Panel operate routines */
1732 * Return currently selected entry name or the name of the first marked
1733 * entry if there is one.
1736 static const char *
1737 panel_get_file (const WPanel *panel)
1739 const file_entry_t *fe;
1741 if (get_current_type () == view_tree)
1743 WTree *tree;
1744 const vfs_path_t *selected_name;
1746 tree = (WTree *) get_panel_widget (get_current_index ());
1747 selected_name = tree_selected_name (tree);
1748 return vfs_path_as_str (selected_name);
1751 if (panel->marked != 0)
1753 int i;
1755 for (i = 0; i < panel->dir.len; i++)
1756 if (panel->dir.list[i].f.marked != 0)
1757 return panel->dir.list[i].fname->str;
1760 fe = panel_current_entry (panel);
1762 return (fe == NULL ? NULL : fe->fname->str);
1765 /* --------------------------------------------------------------------------------------------- */
1767 static const char *
1768 check_single_entry (const WPanel *panel, gboolean force_single, struct stat *src_stat)
1770 const char *source;
1771 gboolean ok;
1773 if (force_single)
1775 const file_entry_t *fe;
1777 fe = panel_current_entry (panel);
1778 source = fe == NULL ? NULL : fe->fname->str;
1780 else
1781 source = panel_get_file (panel);
1783 if (source == NULL)
1784 return NULL;
1786 ok = !DIR_IS_DOTDOT (source);
1788 if (!ok)
1789 message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
1790 else
1792 vfs_path_t *source_vpath;
1794 source_vpath = vfs_path_from_str (source);
1796 /* Update stat to get actual info */
1797 ok = mc_lstat (source_vpath, src_stat) == 0;
1798 if (!ok)
1800 message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
1801 path_trunc (source, 30), unix_error_string (errno));
1803 /* Directory was changed outside MC. Reload it forced */
1804 if (!panel->is_panelized)
1806 panel_update_flags_t flags = UP_RELOAD;
1808 /* don't update panelized panel */
1809 if (get_other_type () == view_listing && other_panel->is_panelized)
1810 flags |= UP_ONLY_CURRENT;
1812 update_panels (flags, UP_KEEPSEL);
1816 vfs_path_free (source_vpath, TRUE);
1819 return ok ? source : NULL;
1822 /* --------------------------------------------------------------------------------------------- */
1824 * Generate user prompt for panel operation.
1825 * src_stat must be not NULL for single source, and NULL for multiple sources
1828 static char *
1829 panel_operate_generate_prompt (const WPanel *panel, FileOperation operation,
1830 const struct stat *src_stat)
1832 char *sp;
1833 char *format_string;
1834 const char *cp;
1836 static gboolean i18n_flag = FALSE;
1837 if (!i18n_flag)
1839 size_t i;
1841 for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1842 op_names1[i] = Q_ (op_names1[i]);
1844 #ifdef ENABLE_NLS
1845 for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1846 prompt_parts[i] = _(prompt_parts[i]);
1848 one_format = _(one_format);
1849 many_format = _(many_format);
1850 #endif /* ENABLE_NLS */
1851 i18n_flag = TRUE;
1854 /* Possible prompts:
1855 * OP_COPY:
1856 * "Copy file \"%s\" with source mask:"
1857 * "Copy %d files with source mask:"
1858 * "Copy directory \"%s\" with source mask:"
1859 * "Copy %d directories with source mask:"
1860 * "Copy %d files/directories with source mask:"
1861 * OP_MOVE:
1862 * "Move file \"%s\" with source mask:"
1863 * "Move %d files with source mask:"
1864 * "Move directory \"%s\" with source mask:"
1865 * "Move %d directories with source mask:"
1866 * "Move %d files/directories with source mask:"
1867 * OP_DELETE:
1868 * "Delete file \"%s\"?"
1869 * "Delete %d files?"
1870 * "Delete directory \"%s\"?"
1871 * "Delete %d directories?"
1872 * "Delete %d files/directories?"
1875 cp = (src_stat != NULL ? one_format : many_format);
1877 /* 1. Substitute %o */
1878 format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1880 /* 2. Substitute %n */
1881 cp = operation == OP_DELETE ? "\n" : " ";
1882 sp = format_string;
1883 format_string = str_replace_all (sp, "%n", cp);
1884 g_free (sp);
1886 /* 3. Substitute %f */
1887 if (src_stat != NULL)
1888 cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1889 else if (panel->marked == panel->dirs_marked)
1890 cp = prompt_parts[3];
1891 else
1892 cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1894 sp = format_string;
1895 format_string = str_replace_all (sp, "%f", cp);
1896 g_free (sp);
1898 /* 4. Substitute %m */
1899 cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1900 sp = format_string;
1901 format_string = str_replace_all (sp, "%m", cp);
1902 g_free (sp);
1904 return format_string;
1907 /* --------------------------------------------------------------------------------------------- */
1909 static char *
1910 do_confirm_copy_move (const WPanel *panel, gboolean force_single, const char *source,
1911 struct stat *src_stat, file_op_context_t *ctx, gboolean *do_bg)
1913 const char *tmp_dest_dir;
1914 char *dest_dir;
1915 char *format;
1916 char *ret;
1918 /* Forced single operations default to the original name */
1919 if (force_single)
1920 tmp_dest_dir = source;
1921 else if (get_other_type () == view_listing)
1922 tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
1923 else
1924 tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
1927 * Add trailing backslash only when do non-local ops.
1928 * It saves user from occasional file renames (when destination
1929 * dir is deleted)
1931 if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
1932 && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
1934 /* add trailing separator */
1935 dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
1937 else
1939 /* just copy */
1940 dest_dir = g_strdup (tmp_dest_dir);
1943 if (dest_dir == NULL)
1944 return NULL;
1946 if (source == NULL)
1947 src_stat = NULL;
1949 /* Generate confirmation prompt */
1950 format = panel_operate_generate_prompt (panel, ctx->operation, src_stat);
1952 ret = file_mask_dialog (ctx, source != NULL, format,
1953 source != NULL ? source : (const void *) &panel->marked, dest_dir,
1954 do_bg);
1956 g_free (format);
1957 g_free (dest_dir);
1959 return ret;
1962 /* --------------------------------------------------------------------------------------------- */
1964 static gboolean
1965 do_confirm_erase (const WPanel *panel, const char *source, struct stat *src_stat)
1967 int i;
1968 char *format;
1969 char fmd_buf[BUF_MEDIUM];
1971 if (source == NULL)
1972 src_stat = NULL;
1974 /* Generate confirmation prompt */
1975 format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat);
1977 if (source == NULL)
1978 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1979 else
1981 const int fmd_xlen = 64;
1983 i = fmd_xlen - str_term_width1 (format) - 4;
1984 g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
1987 g_free (format);
1989 if (safe_delete)
1990 query_set_sel (1);
1992 i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
1994 return (i == 0);
1997 /* --------------------------------------------------------------------------------------------- */
1999 static FileProgressStatus
2000 operate_single_file (const WPanel *panel, file_op_context_t *ctx, const char *src,
2001 struct stat *src_stat, const char *dest, filegui_dialog_type_t dialog_type)
2003 FileProgressStatus value;
2004 vfs_path_t *src_vpath;
2005 gboolean is_file;
2007 if (g_path_is_absolute (src))
2008 src_vpath = vfs_path_from_str (src);
2009 else
2010 src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2012 is_file = !S_ISDIR (src_stat->st_mode);
2013 /* Is link to directory? */
2014 if (is_file)
2016 gboolean is_link;
2018 is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2019 is_file = !(is_link && ctx->follow_links);
2022 if (ctx->operation == OP_DELETE)
2024 value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type);
2025 if (value == FILE_CONT)
2027 if (is_file)
2028 value = erase_file (ctx, src_vpath);
2029 else
2030 value = erase_dir (ctx, src_vpath);
2033 else
2035 char *temp;
2037 src = vfs_path_as_str (src_vpath);
2039 temp = build_dest (ctx, src, dest, &value);
2040 if (temp != NULL)
2042 dest = temp;
2044 switch (ctx->operation)
2046 case OP_COPY:
2047 /* we use file_mask_op_follow_links only with OP_COPY */
2048 ctx->stat_func (src_vpath, src_stat);
2050 value =
2051 panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file,
2052 dialog_type);
2053 if (value == FILE_CONT)
2055 is_file = !S_ISDIR (src_stat->st_mode);
2056 /* Is link to directory? */
2057 if (is_file)
2059 gboolean is_link;
2061 is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2062 is_file = !(is_link && ctx->follow_links);
2065 if (is_file)
2066 value = copy_file_file (ctx, src, dest);
2067 else
2068 value = copy_dir_dir (ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2070 break;
2072 case OP_MOVE:
2073 #ifdef ENABLE_BACKGROUND
2074 if (!mc_global.we_are_background)
2075 #endif
2076 /* create UI to show confirmation dialog */
2077 file_progress_ui_create (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM);
2079 if (is_file)
2080 value = move_file_file (panel, ctx, src, dest);
2081 else
2082 value = do_move_dir_dir (panel, ctx, src, dest);
2083 break;
2085 default:
2086 /* Unknown file operation */
2087 abort ();
2090 g_free (temp);
2094 vfs_path_free (src_vpath, TRUE);
2096 return value;
2099 /* --------------------------------------------------------------------------------------------- */
2101 static FileProgressStatus
2102 operate_one_file (const WPanel *panel, file_op_context_t *ctx, const char *src,
2103 struct stat *src_stat, const char *dest)
2105 FileProgressStatus value = FILE_CONT;
2106 vfs_path_t *src_vpath;
2107 gboolean is_file;
2109 if (g_path_is_absolute (src))
2110 src_vpath = vfs_path_from_str (src);
2111 else
2112 src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2114 is_file = !S_ISDIR (src_stat->st_mode);
2116 if (ctx->operation == OP_DELETE)
2118 if (is_file)
2119 value = erase_file (ctx, src_vpath);
2120 else
2121 value = erase_dir (ctx, src_vpath);
2123 else
2125 char *temp;
2127 src = vfs_path_as_str (src_vpath);
2129 temp = build_dest (ctx, src, dest, &value);
2130 if (temp != NULL)
2132 dest = temp;
2134 switch (ctx->operation)
2136 case OP_COPY:
2137 /* we use file_mask_op_follow_links only with OP_COPY */
2138 ctx->stat_func (src_vpath, src_stat);
2139 is_file = !S_ISDIR (src_stat->st_mode);
2141 if (is_file)
2142 value = copy_file_file (ctx, src, dest);
2143 else
2144 value = copy_dir_dir (ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2145 dest_dirs = free_linklist (dest_dirs);
2146 break;
2148 case OP_MOVE:
2149 if (is_file)
2150 value = move_file_file (NULL, ctx, src, dest);
2151 else
2152 value = do_move_dir_dir (NULL, ctx, src, dest);
2153 break;
2155 default:
2156 /* Unknown file operation */
2157 abort ();
2160 g_free (temp);
2164 vfs_path_free (src_vpath, TRUE);
2166 return value;
2169 /* --------------------------------------------------------------------------------------------- */
2171 #ifdef ENABLE_BACKGROUND
2172 static int
2173 end_bg_process (file_op_context_t *ctx, enum OperationMode mode)
2175 int pid = ctx->pid;
2177 (void) mode;
2178 ctx->pid = 0;
2180 unregister_task_with_pid (pid);
2181 /* file_op_context_destroy(ctx); */
2182 return 1;
2184 #endif
2185 /* }}} */
2187 /* --------------------------------------------------------------------------------------------- */
2190 * On Solaris, ENOTSUP != EOPNOTSUPP. Some FS also return ENOSYS or EINVAL as "not implemented".
2191 * On some Linux kernels (tested on 4.9, 5.4) there is ENOTTY on tmpfs.
2193 static inline gboolean
2194 attrs_ignore_error (const int e)
2196 return (e == ENOTSUP || e == EOPNOTSUPP || e == ENOSYS || e == EINVAL || e == ENOTTY
2197 || e == ELOOP || e == ENXIO);
2200 /* --------------------------------------------------------------------------------------------- */
2201 /*** public functions ****************************************************************************/
2202 /* --------------------------------------------------------------------------------------------- */
2204 /* Is file symlink to directory or not.
2206 * @param path file or directory
2207 * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2208 * @param stale_link TRUE if file is stale link to directory
2210 * @return TRUE if file symlink to directory, ELSE otherwise.
2212 gboolean
2213 file_is_symlink_to_dir (const vfs_path_t *vpath, struct stat *st, gboolean *stale_link)
2215 struct stat st2;
2216 gboolean stale = FALSE;
2217 gboolean res = FALSE;
2219 if (st == NULL)
2221 st = &st2;
2223 if (mc_lstat (vpath, st) != 0)
2224 goto ret;
2227 if (S_ISLNK (st->st_mode))
2229 struct stat st3;
2231 stale = (mc_stat (vpath, &st3) != 0);
2233 if (!stale)
2234 res = (S_ISDIR (st3.st_mode) != 0);
2237 ret:
2238 if (stale_link != NULL)
2239 *stale_link = stale;
2241 return res;
2244 /* --------------------------------------------------------------------------------------------- */
2246 FileProgressStatus
2247 copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_path)
2249 uid_t src_uid = (uid_t) (-1);
2250 gid_t src_gid = (gid_t) (-1);
2252 int src_desc, dest_desc = -1;
2253 mode_t src_mode = 0; /* The mode of the source file */
2254 struct stat src_stat, dst_stat;
2255 mc_timesbuf_t times;
2256 unsigned long attrs = 0;
2257 gboolean attrs_ok = ctx->preserve;
2258 gboolean dst_exists = FALSE, appending = FALSE;
2259 off_t file_size = -1;
2260 FileProgressStatus return_status, temp_status;
2261 dest_status_t dst_status = DEST_NONE;
2262 int open_flags;
2263 vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
2264 char *buf = NULL;
2266 /* Keep the non-default value applied in chain of calls:
2267 move_file_file() -> file_progress_real_query_replace()
2268 move_file_file() -> copy_file_file() */
2269 if (ctx->do_reget < 0)
2270 ctx->do_reget = 0;
2272 return_status = FILE_RETRY;
2274 dst_vpath = vfs_path_from_str (dst_path);
2275 src_vpath = vfs_path_from_str (src_path);
2277 file_progress_show_source (ctx, src_vpath);
2278 file_progress_show_target (ctx, dst_vpath);
2280 if (file_progress_check_buttons (ctx) == FILE_ABORT)
2282 return_status = FILE_ABORT;
2283 goto ret_fast;
2286 mc_refresh ();
2288 while (mc_stat (dst_vpath, &dst_stat) == 0)
2290 if (S_ISDIR (dst_stat.st_mode))
2292 if (ctx->ignore_all)
2293 return_status = FILE_IGNORE_ALL;
2294 else
2296 return_status =
2297 file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path);
2298 if (return_status == FILE_IGNORE_ALL)
2299 ctx->ignore_all = TRUE;
2300 if (return_status == FILE_RETRY)
2301 continue;
2303 goto ret_fast;
2306 dst_exists = TRUE;
2307 break;
2310 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2312 if (ctx->ignore_all)
2313 return_status = FILE_IGNORE_ALL;
2314 else
2316 return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path);
2317 if (return_status == FILE_IGNORE_ALL)
2318 ctx->ignore_all = TRUE;
2321 if (return_status != FILE_RETRY)
2322 goto ret_fast;
2325 while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2327 attrs_ok = FALSE;
2329 /* don't show an error message if attributes aren't supported in this FS */
2330 if (attrs_ignore_error (errno))
2331 return_status = FILE_CONT;
2332 else if (ctx->ignore_all)
2333 return_status = FILE_IGNORE_ALL;
2334 else
2336 return_status =
2337 file_error (TRUE, _("Cannot get ext2 attributes of source file \"%s\"\n%s"),
2338 src_path);
2339 if (return_status == FILE_IGNORE_ALL)
2340 ctx->ignore_all = TRUE;
2341 if (return_status == FILE_ABORT)
2342 goto ret_fast;
2345 if (return_status != FILE_RETRY)
2346 break;
2348 /* yet another attempt */
2349 attrs_ok = TRUE;
2352 if (dst_exists)
2354 /* Destination already exists */
2355 if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status))
2356 goto ret_fast;
2358 /* Should we replace destination? */
2359 if (ctx->ask_overwrite)
2361 ctx->do_reget = 0;
2362 return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
2363 if (return_status != FILE_CONT)
2364 goto ret_fast;
2368 vfs_get_timesbuf_from_stat (&src_stat, &times);
2370 if (!ctx->do_append)
2372 /* Check the hardlinks */
2373 if (!ctx->follow_links)
2375 switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
2377 case HARDLINK_OK:
2378 /* We have made a hardlink - no more processing is necessary */
2379 return_status = FILE_CONT;
2380 goto ret_fast;
2382 case HARDLINK_ABORT:
2383 return_status = FILE_ABORT;
2384 goto ret_fast;
2386 default:
2387 break;
2391 if (S_ISLNK (src_stat.st_mode))
2393 return_status = make_symlink (ctx, src_vpath, dst_vpath);
2394 if (return_status == FILE_CONT && ctx->preserve)
2396 mc_utime (dst_vpath, &times);
2398 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2400 attrs_ok = FALSE;
2402 /* don't show an error message if attributes aren't supported in this FS */
2403 if (attrs_ignore_error (errno))
2404 return_status = FILE_CONT;
2405 else if (return_status == FILE_IGNORE_ALL)
2406 ctx->ignore_all = TRUE;
2407 else
2408 return_status =
2409 file_error (TRUE,
2410 _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2411 dst_path);
2413 if (return_status != FILE_RETRY)
2414 break;
2416 /* yet another attempt */
2417 attrs_ok = TRUE;
2420 goto ret_fast;
2423 if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
2424 || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
2426 dev_t rdev = 0;
2428 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2429 rdev = src_stat.st_rdev;
2430 #endif
2432 while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
2433 && !ctx->ignore_all)
2435 return_status =
2436 file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path);
2437 if (return_status == FILE_RETRY)
2438 continue;
2439 if (return_status == FILE_IGNORE_ALL)
2440 ctx->ignore_all = TRUE;
2441 goto ret_fast;
2443 /* Success */
2445 while (ctx->preserve_uidgid
2446 && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0
2447 && !ctx->ignore_all)
2449 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2450 if (temp_status == FILE_IGNORE)
2451 break;
2452 if (temp_status == FILE_IGNORE_ALL)
2453 ctx->ignore_all = TRUE;
2454 if (temp_status != FILE_RETRY)
2456 return_status = temp_status;
2457 goto ret_fast;
2461 while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
2462 && !ctx->ignore_all)
2464 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2465 if (temp_status == FILE_IGNORE)
2466 break;
2467 if (temp_status == FILE_IGNORE_ALL)
2468 ctx->ignore_all = TRUE;
2469 if (temp_status != FILE_RETRY)
2471 return_status = temp_status;
2472 goto ret_fast;
2476 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2478 attrs_ok = FALSE;
2480 /* don't show an error message if attributes aren't supported in this FS */
2481 if (attrs_ignore_error (errno))
2482 break;
2484 temp_status =
2485 file_error (TRUE, _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2486 dst_path);
2487 if (temp_status == FILE_IGNORE)
2488 break;
2489 if (temp_status == FILE_IGNORE_ALL)
2490 ctx->ignore_all = TRUE;
2491 if (temp_status != FILE_RETRY)
2493 return_status = temp_status;
2494 goto ret_fast;
2497 /* yet another attempt */
2498 attrs_ok = TRUE;
2501 return_status = FILE_CONT;
2502 mc_utime (dst_vpath, &times);
2503 goto ret_fast;
2507 ctx->transfer_start = g_get_monotonic_time ();
2509 while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->ignore_all)
2511 return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path);
2512 if (return_status == FILE_RETRY)
2513 continue;
2514 if (return_status == FILE_IGNORE_ALL)
2515 ctx->ignore_all = TRUE;
2516 if (return_status == FILE_IGNORE)
2517 break;
2518 ctx->do_append = FALSE;
2519 goto ret_fast;
2522 if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
2524 message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
2525 ctx->do_reget = 0;
2526 ctx->do_append = FALSE;
2529 while (mc_fstat (src_desc, &src_stat) != 0)
2531 if (ctx->ignore_all)
2532 return_status = FILE_IGNORE_ALL;
2533 else
2535 return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
2536 if (return_status == FILE_RETRY)
2537 continue;
2538 if (return_status == FILE_IGNORE_ALL)
2539 ctx->ignore_all = TRUE;
2540 ctx->do_append = FALSE;
2542 goto ret;
2545 src_mode = src_stat.st_mode;
2546 src_uid = src_stat.st_uid;
2547 src_gid = src_stat.st_gid;
2548 file_size = src_stat.st_size;
2550 open_flags = O_WRONLY;
2551 if (!dst_exists)
2552 open_flags |= O_CREAT | O_EXCL;
2553 else if (ctx->do_append)
2554 open_flags |= O_APPEND;
2555 else
2556 open_flags |= O_CREAT | O_TRUNC;
2558 while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
2560 if (errno != EEXIST)
2562 if (ctx->ignore_all)
2563 return_status = FILE_IGNORE_ALL;
2564 else
2566 return_status =
2567 file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
2568 if (return_status == FILE_RETRY)
2569 continue;
2570 if (return_status == FILE_IGNORE_ALL)
2571 ctx->ignore_all = TRUE;
2572 ctx->do_append = FALSE;
2575 goto ret;
2578 /* file opened, but not fully copied */
2579 dst_status = DEST_SHORT_QUERY;
2581 appending = ctx->do_append;
2582 ctx->do_append = FALSE;
2584 /* Try clone the file first. */
2585 if (vfs_clone_file (dest_desc, src_desc) == 0)
2587 dst_status = DEST_FULL;
2588 return_status = FILE_CONT;
2589 goto ret;
2592 /* Find out the optimal buffer size. */
2593 while (mc_fstat (dest_desc, &dst_stat) != 0)
2595 if (ctx->ignore_all)
2596 return_status = FILE_IGNORE_ALL;
2597 else
2599 return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
2600 if (return_status == FILE_RETRY)
2601 continue;
2602 if (return_status == FILE_IGNORE_ALL)
2603 ctx->ignore_all = TRUE;
2605 goto ret;
2608 /* try preallocate space; if fail, try copy anyway */
2609 while (mc_global.vfs.preallocate_space &&
2610 vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
2612 if (ctx->ignore_all)
2614 /* cannot allocate, start the file copying anyway */
2615 return_status = FILE_CONT;
2616 break;
2619 return_status =
2620 file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
2622 if (return_status == FILE_IGNORE_ALL)
2623 ctx->ignore_all = TRUE;
2625 if (ctx->ignore_all || return_status == FILE_IGNORE)
2627 /* skip the space allocation error, start file copying */
2628 return_status = FILE_CONT;
2629 break;
2632 if (return_status == FILE_ABORT)
2634 mc_close (dest_desc);
2635 dest_desc = -1;
2636 mc_unlink (dst_vpath);
2637 dst_status = DEST_NONE;
2638 goto ret;
2641 /* return_status == FILE_RETRY -- try allocate space again */
2644 ctx->eta_secs = 0.0;
2645 ctx->bps = 0;
2647 if (verbose)
2649 if (ctx->total_bps == 0 || (file_size / ctx->total_bps) > FILEOP_UPDATE_INTERVAL)
2650 file_progress_show (ctx, 0, file_size, "", TRUE);
2651 else
2652 file_progress_show (ctx, 1, 1, "", TRUE);
2655 return_status = file_progress_check_buttons (ctx);
2656 mc_refresh ();
2658 if (return_status == FILE_CONT)
2660 size_t bufsize;
2661 off_t file_part = 0;
2662 gint64 tv_current, tv_last_update;
2663 gint64 tv_last_input = 0;
2664 gint64 usecs, update_usecs;
2665 const char *stalled_msg = "";
2666 gboolean is_first_time = TRUE;
2668 tv_last_update = ctx->transfer_start;
2670 bufsize = io_blksize (dst_stat);
2671 buf = g_malloc (bufsize);
2673 while (TRUE)
2675 ssize_t n_read = -1, n_written;
2676 gboolean force_update;
2678 /* src_read */
2679 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
2680 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->ignore_all)
2682 return_status =
2683 file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
2684 if (return_status == FILE_RETRY)
2685 continue;
2686 if (return_status == FILE_IGNORE_ALL)
2687 ctx->ignore_all = TRUE;
2688 goto ret;
2691 if (n_read == 0)
2692 break;
2694 tv_current = g_get_monotonic_time ();
2696 if (n_read > 0)
2698 char *t = buf;
2700 file_part += n_read;
2702 tv_last_input = tv_current;
2704 /* dst_write */
2705 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
2707 gboolean write_errno_nospace;
2709 if (n_written > 0)
2711 n_read -= n_written;
2712 t += n_written;
2713 continue;
2716 write_errno_nospace = (n_written < 0 && errno == ENOSPC);
2718 if (ctx->ignore_all)
2719 return_status = FILE_IGNORE_ALL;
2720 else
2721 return_status =
2722 file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
2724 if (return_status == FILE_IGNORE)
2726 if (write_errno_nospace)
2727 goto ret;
2728 break;
2730 if (return_status == FILE_IGNORE_ALL)
2732 ctx->ignore_all = TRUE;
2733 if (write_errno_nospace)
2734 goto ret;
2736 if (return_status != FILE_RETRY)
2737 goto ret;
2741 ctx->copied_bytes = ctx->progress_bytes + file_part + ctx->do_reget;
2743 usecs = tv_current - tv_last_update;
2744 update_usecs = tv_current - tv_last_input;
2746 if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US)
2748 copy_file_file_display_progress (ctx, tv_current, file_part,
2749 file_size - ctx->do_reget);
2750 tv_last_update = tv_current;
2753 is_first_time = FALSE;
2755 if (update_usecs > FILEOP_STALLING_INTERVAL_US)
2756 stalled_msg = _("(stalled)");
2758 force_update = (tv_current - ctx->total_transfer_start) > FILEOP_UPDATE_INTERVAL_US;
2760 if (verbose)
2762 if (ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
2764 file_progress_show_count (ctx, ctx->progress_count, ctx->total_count);
2765 file_progress_show_total (ctx, ctx->copied_bytes, force_update);
2768 file_progress_show (ctx, file_part + ctx->do_reget, file_size, stalled_msg,
2769 force_update);
2771 mc_refresh ();
2774 return_status = file_progress_check_buttons (ctx);
2775 if (return_status != FILE_CONT)
2777 int query_res;
2779 query_res =
2780 query_dialog (Q_ ("DialogTitle|Copy"),
2781 _("Incomplete file was retrieved"), D_ERROR, 3,
2782 _("&Delete"), _("&Keep"), _("&Continue copy"));
2784 switch (query_res)
2786 case 0:
2787 /* delete */
2788 dst_status = DEST_SHORT_DELETE;
2789 goto ret;
2791 case 1:
2792 /* keep */
2793 dst_status = DEST_SHORT_KEEP;
2794 goto ret;
2796 default:
2797 /* continue copy */
2798 break;
2803 /* copy successful */
2804 dst_status = DEST_FULL;
2807 ret:
2808 g_free (buf);
2810 rotate_dash (FALSE);
2811 while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->ignore_all)
2813 temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
2814 if (temp_status == FILE_RETRY)
2815 continue;
2816 if (temp_status == FILE_ABORT)
2817 return_status = temp_status;
2818 if (temp_status == FILE_IGNORE_ALL)
2819 ctx->ignore_all = TRUE;
2820 break;
2823 while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->ignore_all)
2825 temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
2826 if (temp_status == FILE_RETRY)
2827 continue;
2828 if (temp_status == FILE_IGNORE_ALL)
2829 ctx->ignore_all = TRUE;
2830 return_status = temp_status;
2831 break;
2834 if (dst_status == DEST_SHORT_QUERY)
2836 /* Query to remove short file */
2837 if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
2838 D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
2839 dst_status = DEST_SHORT_DELETE;
2840 else
2841 dst_status = DEST_SHORT_KEEP;
2844 if (dst_status == DEST_SHORT_DELETE)
2845 mc_unlink (dst_vpath);
2846 else if (dst_status == DEST_FULL && !appending)
2848 /* Copy has succeeded */
2850 while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
2851 && !ctx->ignore_all)
2853 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2854 if (temp_status == FILE_ABORT)
2856 return_status = FILE_ABORT;
2857 goto ret_fast;
2859 if (temp_status == FILE_RETRY)
2860 continue;
2861 if (temp_status == FILE_IGNORE_ALL)
2863 ctx->ignore_all = TRUE;
2864 return_status = FILE_CONT;
2866 if (temp_status == FILE_IGNORE)
2867 return_status = FILE_CONT;
2868 break;
2871 while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
2872 && !ctx->ignore_all)
2874 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2875 if (temp_status == FILE_ABORT)
2877 return_status = FILE_ABORT;
2878 goto ret_fast;
2880 if (temp_status == FILE_RETRY)
2881 continue;
2882 if (temp_status == FILE_IGNORE_ALL)
2884 ctx->ignore_all = TRUE;
2885 return_status = FILE_CONT;
2887 if (temp_status == FILE_IGNORE)
2888 return_status = FILE_CONT;
2889 break;
2892 if (!ctx->preserve && !dst_exists)
2894 src_mode = umask (-1);
2895 umask (src_mode);
2896 src_mode = 0100666 & ~src_mode;
2897 mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2901 if (dst_status == DEST_FULL || dst_status == DEST_SHORT_KEEP)
2903 /* Always sync timestamps */
2904 mc_utime (dst_vpath, &times);
2906 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2908 attrs_ok = FALSE;
2910 /* don't show an error message if attributes aren't supported in this FS */
2911 if (attrs_ignore_error (errno))
2913 return_status = FILE_CONT;
2914 break;
2917 temp_status =
2918 file_error (TRUE, _("Cannot set ext2 attributes for target file \"%s\"\n%s"),
2919 dst_path);
2920 if (temp_status == FILE_ABORT)
2921 return_status = FILE_ABORT;
2922 if (temp_status == FILE_RETRY)
2924 attrs_ok = TRUE;
2925 continue;
2927 if (temp_status == FILE_IGNORE_ALL)
2929 ctx->ignore_all = TRUE;
2930 return_status = FILE_CONT;
2932 if (temp_status == FILE_IGNORE)
2933 return_status = FILE_CONT;
2934 break;
2938 if (return_status == FILE_CONT)
2939 return_status = progress_update_one (ctx, file_size);
2941 ret_fast:
2942 vfs_path_free (src_vpath, TRUE);
2943 vfs_path_free (dst_vpath, TRUE);
2944 return return_status;
2947 /* --------------------------------------------------------------------------------------------- */
2949 * I think these copy_*_* functions should have a return type.
2950 * anyway, this function *must* have two directories as arguments.
2952 /* FIXME: This function needs to check the return values of the
2953 function calls */
2955 FileProgressStatus
2956 copy_dir_dir (file_op_context_t *ctx, const char *s, const char *d, gboolean toplevel,
2957 gboolean move_over, gboolean do_delete, GSList *parent_dirs)
2959 struct vfs_dirent *next;
2960 struct stat dst_stat, src_stat;
2961 unsigned long attrs = 0;
2962 gboolean attrs_ok = ctx->preserve;
2963 DIR *reading;
2964 FileProgressStatus return_status = FILE_CONT;
2965 link_t *lp;
2966 vfs_path_t *src_vpath, *dst_vpath;
2967 gboolean do_mkdir = TRUE;
2969 src_vpath = vfs_path_from_str (s);
2970 dst_vpath = vfs_path_from_str (d);
2972 /* First get the mode of the source dir */
2974 retry_src_stat:
2975 while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2977 if (ctx->ignore_all)
2978 return_status = FILE_IGNORE_ALL;
2979 else
2981 return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
2982 if (return_status == FILE_IGNORE_ALL)
2983 ctx->ignore_all = TRUE;
2986 if (return_status != FILE_RETRY)
2987 goto ret_fast;
2990 while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2992 attrs_ok = FALSE;
2994 /* don't show an error message if attributes aren't supported in this FS */
2995 if (attrs_ignore_error (errno))
2996 return_status = FILE_CONT;
2997 else if (ctx->ignore_all)
2998 return_status = FILE_IGNORE_ALL;
2999 else
3001 return_status =
3002 file_error (TRUE, _("Cannot get ext2 attributes of source directory \"%s\"\n%s"),
3004 if (return_status == FILE_IGNORE_ALL)
3005 ctx->ignore_all = TRUE;
3006 if (return_status == FILE_ABORT)
3007 goto ret_fast;
3010 if (return_status != FILE_RETRY)
3011 break;
3013 /* yet another attempt */
3014 attrs_ok = TRUE;
3017 if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
3019 /* Don't copy a directory we created before (we don't want to copy
3020 infinitely if a directory is copied into itself) */
3021 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
3022 return_status = FILE_CONT;
3023 goto ret_fast;
3026 /* Hmm, hardlink to directory??? - Norbert */
3027 /* FIXME: In this step we should do something in case the destination already exist */
3028 /* Check the hardlinks */
3029 if (ctx->preserve)
3031 switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
3033 case HARDLINK_OK:
3034 /* We have made a hardlink - no more processing is necessary */
3035 goto ret_fast;
3037 case HARDLINK_ABORT:
3038 return_status = FILE_ABORT;
3039 goto ret_fast;
3041 default:
3042 break;
3046 if (!S_ISDIR (src_stat.st_mode))
3048 if (ctx->ignore_all)
3049 return_status = FILE_IGNORE_ALL;
3050 else
3052 return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
3053 if (return_status == FILE_RETRY)
3054 goto retry_src_stat;
3055 if (return_status == FILE_IGNORE_ALL)
3056 ctx->ignore_all = TRUE;
3058 goto ret_fast;
3061 if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
3063 /* we found a cyclic symbolic link */
3064 message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
3065 return_status = FILE_SKIP;
3066 goto ret_fast;
3069 lp = g_new0 (link_t, 1);
3070 lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
3071 lp->ino = src_stat.st_ino;
3072 lp->dev = src_stat.st_dev;
3073 parent_dirs = g_slist_prepend (parent_dirs, lp);
3075 retry_dst_stat:
3076 /* Now, check if the dest dir exists, if not, create it. */
3077 if (mc_stat (dst_vpath, &dst_stat) != 0)
3079 /* Here the dir doesn't exist : make it ! */
3080 if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
3082 return_status = FILE_CONT;
3083 goto ret;
3086 else
3089 * If the destination directory exists, we want to copy the whole
3090 * directory, but we only want this to happen once.
3092 * Escape sequences added to the * to compiler warnings.
3093 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3094 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
3096 if (!S_ISDIR (dst_stat.st_mode))
3098 if (ctx->ignore_all)
3099 return_status = FILE_IGNORE_ALL;
3100 else
3102 return_status =
3103 file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
3104 if (return_status == FILE_IGNORE_ALL)
3105 ctx->ignore_all = TRUE;
3106 if (return_status == FILE_RETRY)
3107 goto retry_dst_stat;
3109 goto ret;
3111 /* Dive into subdir if exists */
3112 if (toplevel && ctx->dive_into_subdirs)
3114 vfs_path_t *tmp;
3116 tmp = dst_vpath;
3117 dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
3118 vfs_path_free (tmp, TRUE);
3121 else
3122 do_mkdir = FALSE;
3125 d = vfs_path_as_str (dst_vpath);
3127 if (do_mkdir)
3129 while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
3131 if (ctx->ignore_all)
3132 return_status = FILE_IGNORE_ALL;
3133 else
3135 return_status =
3136 file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
3137 if (return_status == FILE_IGNORE_ALL)
3138 ctx->ignore_all = TRUE;
3140 if (return_status != FILE_RETRY)
3141 goto ret;
3144 lp = g_new0 (link_t, 1);
3145 mc_stat (dst_vpath, &dst_stat);
3146 lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
3147 lp->ino = dst_stat.st_ino;
3148 lp->dev = dst_stat.st_dev;
3149 dest_dirs = g_slist_prepend (dest_dirs, lp);
3152 if (ctx->preserve_uidgid)
3154 while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
3156 if (ctx->ignore_all)
3157 return_status = FILE_IGNORE_ALL;
3158 else
3160 return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
3161 if (return_status == FILE_IGNORE_ALL)
3162 ctx->ignore_all = TRUE;
3164 if (return_status != FILE_RETRY)
3165 goto ret;
3169 /* open the source dir for reading */
3170 reading = mc_opendir (src_vpath);
3171 if (reading == NULL)
3172 goto ret;
3174 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
3176 char *path;
3177 vfs_path_t *tmp_vpath;
3180 * Now, we don't want '.' and '..' to be created / copied at any time
3182 if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
3183 continue;
3185 /* get the filename and add it to the src directory */
3186 path = mc_build_filename (s, next->d_name, (char *) NULL);
3187 tmp_vpath = vfs_path_from_str (path);
3189 (*ctx->stat_func) (tmp_vpath, &dst_stat);
3190 if (S_ISDIR (dst_stat.st_mode))
3192 char *mdpath;
3194 mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
3196 * From here, we just intend to recursively copy subdirs, not
3197 * the double functionality of copying different when the target
3198 * dir already exists. So, we give the recursive call the flag 0
3199 * meaning no toplevel.
3201 return_status = copy_dir_dir (ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
3202 g_free (mdpath);
3204 else
3206 char *dest_file;
3208 dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
3209 return_status = copy_file_file (ctx, path, dest_file);
3210 g_free (dest_file);
3213 g_free (path);
3215 if (do_delete && return_status == FILE_CONT)
3217 if (ctx->erase_at_end)
3219 if (erase_list == NULL)
3220 erase_list = g_queue_new ();
3222 lp = g_new0 (link_t, 1);
3223 lp->src_vpath = tmp_vpath;
3224 lp->st_mode = dst_stat.st_mode;
3225 g_queue_push_tail (erase_list, lp);
3226 tmp_vpath = NULL;
3228 else if (S_ISDIR (dst_stat.st_mode))
3229 return_status = erase_dir_iff_empty (ctx, tmp_vpath, ctx->progress_count);
3230 else
3231 return_status = erase_file (ctx, tmp_vpath);
3233 vfs_path_free (tmp_vpath, TRUE);
3235 mc_closedir (reading);
3237 if (ctx->preserve)
3239 mc_timesbuf_t times;
3241 mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3243 if (attrs_ok)
3244 mc_fsetflags (dst_vpath, attrs);
3246 vfs_get_timesbuf_from_stat (&src_stat, &times);
3247 mc_utime (dst_vpath, &times);
3249 else
3251 src_stat.st_mode = umask (-1);
3252 umask (src_stat.st_mode);
3253 src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3254 mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3257 ret:
3258 free_link (parent_dirs->data);
3259 g_slist_free_1 (parent_dirs);
3260 ret_fast:
3261 vfs_path_free (src_vpath, TRUE);
3262 vfs_path_free (dst_vpath, TRUE);
3263 return return_status;
3266 /* }}} */
3268 /* --------------------------------------------------------------------------------------------- */
3269 /* {{{ Move routines */
3271 FileProgressStatus
3272 move_dir_dir (file_op_context_t *ctx, const char *s, const char *d)
3274 return do_move_dir_dir (NULL, ctx, s, d);
3277 /* }}} */
3279 /* --------------------------------------------------------------------------------------------- */
3280 /* {{{ Erase routines */
3282 FileProgressStatus
3283 erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
3285 file_progress_show_deleting (ctx, vpath, NULL);
3286 file_progress_show_count (ctx, ctx->progress_count, ctx->total_count);
3287 if (file_progress_check_buttons (ctx) == FILE_ABORT)
3288 return FILE_ABORT;
3290 mc_refresh ();
3292 /* The old way to detect a non empty directory was:
3293 error = my_rmdir (s);
3294 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3295 For the linux user space nfs server (nfs-server-2.2beta29-2)
3296 we would have to check also for EIO. I hope the new way is
3297 fool proof. (Norbert)
3299 if (check_dir_is_empty (vpath) == 0)
3300 { /* not empty */
3301 FileProgressStatus error;
3303 error = query_recursive (ctx, vfs_path_as_str (vpath));
3304 if (error == FILE_CONT)
3305 error = recursive_erase (ctx, vpath);
3306 return error;
3309 return try_erase_dir (ctx, vpath);
3312 /* }}} */
3314 /* --------------------------------------------------------------------------------------------- */
3315 /* {{{ Panel operate routines */
3317 void
3318 dirsize_status_init_cb (status_msg_t *sm)
3320 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3321 WGroup *gd = GROUP (sm->dlg);
3322 Widget *wd = WIDGET (sm->dlg);
3323 WRect r = wd->rect;
3325 const char *b1_name = N_("&Abort");
3326 const char *b2_name = N_("&Skip");
3327 int b_width, ui_width;
3329 #ifdef ENABLE_NLS
3330 b1_name = _(b1_name);
3331 b2_name = _(b2_name);
3332 #endif
3334 b_width = str_term_width1 (b1_name) + 4;
3335 if (dsm->allow_skip)
3336 b_width += str_term_width1 (b2_name) + 4 + 1;
3338 ui_width = MAX (COLS / 2, b_width + 6);
3339 dsm->dirname = label_new (2, 3, NULL);
3340 group_add_widget (gd, dsm->dirname);
3341 dsm->count_size = label_new (3, 3, NULL);
3342 group_add_widget (gd, dsm->count_size);
3343 group_add_widget (gd, hline_new (4, -1, -1));
3345 dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3346 group_add_widget (gd, dsm->abort_button);
3347 if (dsm->allow_skip)
3349 dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3350 group_add_widget (gd, dsm->skip_button);
3351 widget_select (dsm->skip_button);
3354 r.lines = 8;
3355 r.cols = ui_width;
3356 widget_set_size_rect (wd, &r);
3357 dirsize_status_locate_buttons (dsm);
3360 /* --------------------------------------------------------------------------------------------- */
3363 dirsize_status_update_cb (status_msg_t *sm)
3365 dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3366 Widget *wd = WIDGET (sm->dlg);
3367 WRect r = wd->rect;
3369 /* update second (longer label) */
3370 label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
3371 dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3373 /* enlarge dialog if required */
3374 if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
3376 r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
3377 widget_set_size_rect (wd, &r);
3378 dirsize_status_locate_buttons (dsm);
3379 widget_draw (wd);
3380 /* TODO: ret rid of double redraw */
3383 /* adjust first label */
3384 label_set_text (dsm->dirname,
3385 str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
3387 switch (status_msg_common_update (sm))
3389 case B_CANCEL:
3390 case FILE_ABORT:
3391 return FILE_ABORT;
3392 case FILE_SKIP:
3393 return FILE_SKIP;
3394 default:
3395 return FILE_CONT;
3399 /* --------------------------------------------------------------------------------------------- */
3401 void
3402 dirsize_status_deinit_cb (status_msg_t *sm)
3404 (void) sm;
3406 /* schedule to update passive panel */
3407 if (get_other_type () == view_listing)
3408 other_panel->dirty = TRUE;
3411 /* --------------------------------------------------------------------------------------------- */
3413 * compute_dir_size:
3415 * Computes the number of bytes used by the files in a directory
3418 FileProgressStatus
3419 compute_dir_size (const vfs_path_t *dirname_vpath, dirsize_status_msg_t *sm,
3420 size_t *ret_dir_count, size_t *ret_marked_count, uintmax_t *ret_total,
3421 gboolean follow_symlinks)
3423 return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3424 follow_symlinks ? mc_stat : mc_lstat);
3427 /* --------------------------------------------------------------------------------------------- */
3429 * panel_operate:
3431 * Performs one of the operations on the current on the source_panel
3432 * (copy, delete, move).
3434 * Returns TRUE if did change the directory
3435 * structure, Returns FALSE if user aborted
3437 * force_single forces operation on the current entry and affects
3438 * default destination. Current filename is used as default.
3441 gboolean
3442 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
3444 WPanel *panel = PANEL (source_panel);
3445 const gboolean single_entry = force_single || (panel->marked <= 1)
3446 || (get_current_type () == view_tree);
3448 const char *source = NULL;
3449 char *dest = NULL;
3450 vfs_path_t *dest_vpath = NULL;
3451 vfs_path_t *save_cwd = NULL, *save_dest = NULL;
3452 struct stat src_stat;
3453 gboolean ret_val = TRUE;
3454 int i;
3455 FileProgressStatus value;
3456 file_op_context_t *ctx;
3457 filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3459 gboolean do_bg = FALSE; /* do background operation? */
3461 static gboolean i18n_flag = FALSE;
3462 if (!i18n_flag)
3464 for (i = G_N_ELEMENTS (op_names); i-- != 0;)
3465 op_names[i] = Q_ (op_names[i]);
3466 i18n_flag = TRUE;
3469 linklist = free_linklist (linklist);
3470 dest_dirs = free_linklist (dest_dirs);
3472 save_cwds_stat ();
3474 if (single_entry)
3476 source = check_single_entry (panel, force_single, &src_stat);
3478 if (source == NULL)
3479 return FALSE;
3482 ctx = file_op_context_new (operation);
3484 /* Show confirmation dialog */
3485 if (operation != OP_DELETE)
3487 dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
3488 if (dest == NULL)
3490 ret_val = FALSE;
3491 goto ret_fast;
3494 dest_vpath = vfs_path_from_str (dest);
3496 else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3498 ret_val = FALSE;
3499 goto ret_fast;
3502 ctx->total_transfer_start = g_get_monotonic_time ();
3504 #ifdef ENABLE_BACKGROUND
3505 /* Did the user select to do a background operation? */
3506 if (do_bg)
3508 int v;
3510 v = do_background (ctx,
3511 g_strconcat (op_names[operation], ": ",
3512 vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3513 if (v == -1)
3514 message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
3516 /* If we are the parent */
3517 if (v == 1)
3519 mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3521 mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3522 vfs_path_free (dest_vpath, TRUE);
3523 g_free (dest);
3524 /* file_op_context_destroy (ctx); */
3525 return FALSE;
3528 else
3529 #endif /* ENABLE_BACKGROUND */
3531 const file_entry_t *fe;
3533 if (operation == OP_DELETE)
3534 dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3535 else if (single_entry
3536 && ((fe = panel_current_entry (panel)) == NULL ? FALSE : S_ISDIR (fe->st.st_mode)))
3537 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3538 else if (single_entry || force_single)
3539 dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3540 else
3541 dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3544 /* Initialize things */
3545 /* We do not want to trash cache every time file is
3546 created/touched. However, this will make our cache contain
3547 invalid data. */
3548 if ((dest != NULL)
3549 && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3550 save_dest = vfs_path_from_str (dest);
3552 if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3553 && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3554 save_cwd = vfs_path_clone (panel->cwd_vpath);
3556 /* Now, let's do the job */
3558 /* This code is only called by the tree and panel code */
3559 if (single_entry)
3561 /* We now have ETA in all cases */
3563 /* One file: FIXME mc_chdir will take user out of any vfs */
3564 if ((operation != OP_COPY) && (get_current_type () == view_tree))
3566 vfs_path_t *vpath;
3567 int chdir_retcode;
3569 vpath = vfs_path_from_str (PATH_SEP_STR);
3570 chdir_retcode = mc_chdir (vpath);
3571 vfs_path_free (vpath, TRUE);
3572 if (chdir_retcode < 0)
3574 ret_val = FALSE;
3575 goto clean_up;
3579 value = operate_single_file (panel, ctx, source, &src_stat, dest, dialog_type);
3580 if ((value == FILE_CONT) && !force_single)
3581 unmark_files (panel);
3583 else
3585 /* Many files */
3587 /* Check destination for copy or move operation */
3588 while (operation != OP_DELETE)
3590 int dst_result;
3591 struct stat dst_stat;
3593 dst_result = mc_stat (dest_vpath, &dst_stat);
3595 if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3596 break;
3598 if (ctx->ignore_all
3599 || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
3600 dest) != FILE_RETRY)
3601 goto clean_up;
3604 /* TODO: the good way is required to skip directories scanning in case of rename/move
3605 * of several directories. Since reqular expression can be used for destination,
3606 * some directory movements can be a cross-filesystem and directory scanning is useful
3607 * for those directories only. */
3609 if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type)
3610 == FILE_CONT)
3612 /* Loop for every file, perform the actual copy operation */
3613 for (i = 0; i < panel->dir.len; i++)
3615 const char *source2;
3617 if (panel->dir.list[i].f.marked == 0)
3618 continue; /* Skip the unmarked ones */
3620 source2 = panel->dir.list[i].fname->str;
3621 src_stat = panel->dir.list[i].st;
3623 value = operate_one_file (panel, ctx, source2, &src_stat, dest);
3625 if (value == FILE_ABORT)
3626 break;
3628 if (value == FILE_CONT)
3629 do_file_mark (panel, i, 0);
3631 if (verbose)
3633 if (ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3635 file_progress_show_count (ctx, ctx->progress_count, ctx->total_count);
3636 file_progress_show_total (ctx, ctx->progress_bytes, FALSE);
3639 if (operation != OP_DELETE)
3640 file_progress_show (ctx, 0, 0, "", FALSE);
3643 if (file_progress_check_buttons (ctx) == FILE_ABORT)
3644 break;
3646 mc_refresh ();
3647 } /* Loop for every file */
3649 } /* Many entries */
3651 clean_up:
3652 /* Clean up */
3653 if (save_cwd != NULL)
3655 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
3656 vfs_path_free (save_cwd, TRUE);
3659 if (save_dest != NULL)
3661 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
3662 vfs_path_free (save_dest, TRUE);
3665 linklist = free_linklist (linklist);
3666 dest_dirs = free_linklist (dest_dirs);
3667 g_free (dest);
3668 vfs_path_free (dest_vpath, TRUE);
3669 MC_PTR_FREE (ctx->dest_mask);
3671 #ifdef ENABLE_BACKGROUND
3672 /* Let our parent know we are saying bye bye */
3673 if (mc_global.we_are_background)
3675 /* Send pid to parent with child context, it is fork and
3676 don't modify real parent ctx */
3677 ctx->pid = getpid ();
3678 parent_call ((void *) end_bg_process, ctx, 0);
3680 vfs_shut ();
3681 my_exit (EXIT_SUCCESS);
3683 #endif /* ENABLE_BACKGROUND */
3685 ret_fast:
3686 file_op_context_destroy (ctx);
3688 update_panels (UP_OPTIMIZE, UP_KEEPSEL);
3689 repaint_screen ();
3691 return ret_val;
3694 /* }}} */
3696 /* --------------------------------------------------------------------------------------------- */
3697 /* {{{ Query/status report routines */
3698 /** Report error with one file */
3699 FileProgressStatus
3700 file_error (gboolean allow_retry, const char *format, const char *file)
3702 char buf[BUF_MEDIUM];
3704 g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3706 return do_file_error (allow_retry, buf);
3709 /* --------------------------------------------------------------------------------------------- */
3712 Cause emacs to enter folding mode for this file:
3713 Local variables:
3714 end: