diff options
Diffstat (limited to '')
-rw-r--r-- | src/filemanager/file.c | 3568 |
1 files changed, 3568 insertions, 0 deletions
diff --git a/src/filemanager/file.c b/src/filemanager/file.c new file mode 100644 index 0000000..5303d40 --- /dev/null +++ b/src/filemanager/file.c @@ -0,0 +1,3568 @@ +/* + File management. + + Copyright (C) 1994-2022 + Free Software Foundation, Inc. + + Written by: + Janne Kukonlehto, 1994, 1995 + Fred Leeflang, 1994, 1995 + Miguel de Icaza, 1994, 1995, 1996 + Jakub Jelinek, 1995, 1996 + Norbert Warmuth, 1997 + Pavel Machek, 1998 + Andrew Borodin <aborodin@vmail.ru>, 2011-2022 + + The copy code was based in GNU's cp, and was written by: + Torbjorn Granlund, David MacKenzie, and Jim Meyering. + + The move code was based in GNU's mv, and was written by: + Mike Parker and David MacKenzie. + + Janne Kukonlehto added much error recovery to them for being used + in an interactive program. + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Please note that all dialogs used here must be safe for background + * operations. + */ + +/** \file src/filemanager/file.c + * \brief Source: file management + */ + +/* {{{ Include files */ + +#include <config.h> + +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "lib/global.h" +#include "lib/tty/tty.h" +#include "lib/tty/key.h" +#include "lib/search.h" +#include "lib/strescape.h" +#include "lib/strutil.h" +#include "lib/util.h" +#include "lib/vfs/vfs.h" +#include "lib/widget.h" + +#include "src/setup.h" +#ifdef ENABLE_BACKGROUND +#include "src/background.h" /* do_background() */ +#endif + +/* Needed for other_panel and WTree */ +#include "dir.h" +#include "filegui.h" +#include "filenot.h" +#include "tree.h" +#include "filemanager.h" /* other_panel */ +#include "layout.h" /* rotate_dash() */ +#include "ioblksize.h" /* io_blksize() */ + +#include "file.h" + +/* }}} */ + +/*** global variables ****************************************************************************/ + +/* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix */ +const char *op_names[3] = { + N_("DialogTitle|Copy"), + N_("DialogTitle|Move"), + N_("DialogTitle|Delete") +}; + +/*** file scope macro definitions ****************************************************************/ + +#define FILEOP_UPDATE_INTERVAL 2 +#define FILEOP_STALLING_INTERVAL 4 +#define FILEOP_UPDATE_INTERVAL_US (FILEOP_UPDATE_INTERVAL * G_USEC_PER_SEC) +#define FILEOP_STALLING_INTERVAL_US (FILEOP_STALLING_INTERVAL * G_USEC_PER_SEC) + +/*** file scope type declarations ****************************************************************/ + +/* This is a hard link cache */ +struct link +{ + const struct vfs_class *vfs; + dev_t dev; + ino_t ino; + short linkcount; + mode_t st_mode; + vfs_path_t *src_vpath; + vfs_path_t *dst_vpath; +}; + +/* Status of the destination file */ +typedef enum +{ + DEST_NONE = 0, /**< Not created */ + DEST_SHORT_QUERY, /**< Created, not fully copied, query to do */ + DEST_SHORT_KEEP, /**< Created, not fully copied, keep it */ + DEST_SHORT_DELETE, /**< Created, not fully copied, delete it */ + DEST_FULL /**< Created, fully copied */ +} dest_status_t; + +/* Status of hard link creation */ +typedef enum +{ + HARDLINK_OK = 0, /**< Hardlink was created successfully */ + HARDLINK_CACHED, /**< Hardlink was added to the cache */ + HARDLINK_NOTLINK, /**< This is not a hard link */ + HARDLINK_UNSUPPORTED, /**< VFS doesn't support hard links */ + HARDLINK_ERROR, /**< Hard link creation error */ + HARDLINK_ABORT /**< Stop file operation after hardlink creation error */ +} hardlink_status_t; + +/* + * This array introduced to avoid translation problems. The former (op_names) + * is assumed to be nouns, suitable in dialog box titles; this one should + * contain whatever is used in prompt itself (i.e. in russian, it's verb). + * (I don't use spaces around the words, because someday they could be + * dropped, when widgets get smarter) + */ + +/* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix */ +static const char *op_names1[] = { + N_("FileOperation|Copy"), + N_("FileOperation|Move"), + N_("FileOperation|Delete") +}; + +/* + * These are formats for building a prompt. Parts encoded as follows: + * %o - operation from op_names1 + * %f - file/files or files/directories, as appropriate + * %m - "with source mask" or question mark for delete + * %s - source name (truncated) + * %d - number of marked files + * %n - the '\n' symbol to form two-line prompt for delete or space for other operations + */ +/* xgettext:no-c-format */ +static const char *one_format = N_("%o %f%n\"%s\"%m"); +/* xgettext:no-c-format */ +static const char *many_format = N_("%o %d %f%m"); + +static const char *prompt_parts[] = { + N_("file"), + N_("files"), + N_("directory"), + N_("directories"), + N_("files/directories"), + /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */ + N_(" with source mask:") +}; + +/*** file scope variables ************************************************************************/ + +/* the hard link cache */ +static GSList *linklist = NULL; + +/* the files-to-be-erased list */ +static GQueue *erase_list = NULL; + +/* + * In copy_dir_dir we use two additional single linked lists: The first - + * variable name 'parent_dirs' - holds information about already copied + * directories and is used to detect cyclic symbolic links. + * The second ('dest_dirs' below) holds information about just created + * target directories and is used to detect when an directory is copied + * into itself (we don't want to copy infinitly). + * Both lists don't use the linkcount and name structure members of struct + * link. + */ +static GSList *dest_dirs = NULL; + +/* --------------------------------------------------------------------------------------------- */ +/*** file scope functions ************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +static void +dirsize_status_locate_buttons (dirsize_status_msg_t * dsm) +{ + status_msg_t *sm = STATUS_MSG (dsm); + Widget *wd = WIDGET (sm->dlg); + int y, x; + WRect r; + + y = wd->rect.y + 5; + x = wd->rect.x; + + if (!dsm->allow_skip) + { + /* single button: "Abort" */ + x += (wd->rect.cols - dsm->abort_button->rect.cols) / 2; + r = dsm->abort_button->rect; + r.y = y; + r.x = x; + widget_set_size_rect (dsm->abort_button, &r); + } + else + { + /* two buttons: "Abort" and "Skip" */ + int cols; + + cols = dsm->abort_button->rect.cols + dsm->skip_button->rect.cols + 1; + x += (wd->rect.cols - cols) / 2; + r = dsm->abort_button->rect; + r.y = y; + r.x = x; + widget_set_size_rect (dsm->abort_button, &r); + x += dsm->abort_button->rect.cols + 1; + r = dsm->skip_button->rect; + r.y = y; + r.x = x; + widget_set_size_rect (dsm->skip_button, &r); + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +build_dest (file_op_context_t * ctx, const char *src, const char *dest, FileProgressStatus * status) +{ + char *s, *q; + const char *fnsource; + + *status = FILE_CONT; + + s = g_strdup (src); + + /* We remove \n from the filename since regex routines would use \n as an anchor */ + /* this is just to be allowed to maniupulate file names with \n on it */ + for (q = s; *q != '\0'; q++) + if (*q == '\n') + *q = ' '; + + fnsource = x_basename (s); + + if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL)) + { + q = NULL; + *status = FILE_SKIP; + } + else + { + q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask); + if (ctx->search_handle->error != MC_SEARCH_E_OK) + { + if (ctx->search_handle->error_str != NULL) + message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str); + + *status = FILE_ABORT; + } + } + + MC_PTR_FREE (s); + + if (*status == FILE_CONT) + { + char *repl_dest; + + repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest); + if (ctx->search_handle->error == MC_SEARCH_E_OK) + s = mc_build_filename (repl_dest, q, (char *) NULL); + else + { + if (ctx->search_handle->error_str != NULL) + message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str); + + *status = FILE_ABORT; + } + + g_free (repl_dest); + } + + g_free (q); + + return s; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +free_link (void *data) +{ + struct link *lp = (struct link *) data; + + vfs_path_free (lp->src_vpath, TRUE); + vfs_path_free (lp->dst_vpath, TRUE); + g_free (lp); +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void * +free_erase_list (GQueue * lp) +{ + if (lp != NULL) + g_queue_free_full (lp, free_link); + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static inline void * +free_linklist (GSList * lp) +{ + g_slist_free_full (lp, free_link); + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ + +static const struct link * +is_in_linklist (const GSList * lp, const vfs_path_t * vpath, const struct stat *sb) +{ + const struct vfs_class *class; + ino_t ino = sb->st_ino; + dev_t dev = sb->st_dev; + + class = vfs_path_get_last_path_vfs (vpath); + + for (; lp != NULL; lp = (const GSList *) g_slist_next (lp)) + { + const struct link *lnk = (const struct link *) lp->data; + + if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev) + return lnk; + } + + return NULL; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Check and made hardlink + * + * @return FALSE if the inode wasn't found in the cache and TRUE if it was found + * and a hardlink was successfully made + */ + +static hardlink_status_t +check_hardlinks (const vfs_path_t * src_vpath, const struct stat *src_stat, + const vfs_path_t * dst_vpath, gboolean * skip_all) +{ + struct link *lnk; + ino_t ino = src_stat->st_ino; + dev_t dev = src_stat->st_dev; + + if (src_stat->st_nlink < 2) + return HARDLINK_NOTLINK; + if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0) + return HARDLINK_UNSUPPORTED; + + lnk = (struct link *) is_in_linklist (linklist, src_vpath, src_stat); + if (lnk != NULL) + { + int stat_result; + struct stat link_stat; + + stat_result = mc_stat (lnk->src_vpath, &link_stat); + + if (stat_result == 0 && link_stat.st_ino == ino && link_stat.st_dev == dev) + { + const struct vfs_class *lp_name_class; + const struct vfs_class *my_vfs; + + lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath); + my_vfs = vfs_path_get_last_path_vfs (src_vpath); + + if (lp_name_class == my_vfs) + { + const struct vfs_class *p_class, *dst_name_class; + + dst_name_class = vfs_path_get_last_path_vfs (dst_vpath); + p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath); + + if (dst_name_class == p_class) + { + gboolean ok; + + while (!(ok = (mc_stat (lnk->dst_vpath, &link_stat) == 0)) && !*skip_all) + { + FileProgressStatus status; + + status = + file_error (TRUE, _("Cannot stat hardlink source file \"%s\"\n%s"), + vfs_path_as_str (lnk->dst_vpath)); + if (status == FILE_ABORT) + return HARDLINK_ABORT; + if (status == FILE_RETRY) + continue; + if (status == FILE_SKIPALL) + *skip_all = TRUE; + break; + } + + /* if stat() finished unsuccessfully, don't try to create link */ + if (!ok) + return HARDLINK_ERROR; + + while (!(ok = (mc_link (lnk->dst_vpath, dst_vpath) == 0)) && !*skip_all) + { + FileProgressStatus status; + + status = + file_error (TRUE, _("Cannot create target hardlink \"%s\"\n%s"), + vfs_path_as_str (dst_vpath)); + if (status == FILE_ABORT) + return HARDLINK_ABORT; + if (status == FILE_RETRY) + continue; + if (status == FILE_SKIPALL) + *skip_all = TRUE; + break; + } + + /* Success? */ + return (ok ? HARDLINK_OK : HARDLINK_ERROR); + } + } + } + + if (!*skip_all) + { + FileProgressStatus status; + + /* Message w/o "Retry" action. + * + * FIXME: Can't say what errno is here. Define it and don't display. + * + * file_error() displays a message with text representation of errno + * and the string passed to file_error() should provide the format "%s" + * for that at end (see previous file_error() call for the reference). + * But if format for errno isn't provided, it is safe, because C standard says: + * "If the format is exhausted while arguments remain, the excess arguments + * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999, + * section 7.19.6.1, paragraph 2). + * + */ + errno = 0; + status = + file_error (FALSE, _("Cannot create target hardlink \"%s\""), + vfs_path_as_str (dst_vpath)); + + if (status == FILE_ABORT) + return HARDLINK_ABORT; + + if (status == FILE_SKIPALL) + *skip_all = TRUE; + } + + return HARDLINK_ERROR; + } + + lnk = g_try_new (struct link, 1); + if (lnk != NULL) + { + lnk->vfs = vfs_path_get_last_path_vfs (src_vpath); + lnk->ino = ino; + lnk->dev = dev; + lnk->linkcount = 0; + lnk->st_mode = 0; + lnk->src_vpath = vfs_path_clone (src_vpath); + lnk->dst_vpath = vfs_path_clone (dst_vpath); + + linklist = g_slist_prepend (linklist, lnk); + } + + return HARDLINK_CACHED; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Duplicate the contents of the symbolic link src_vpath in dst_vpath. + * Try to make a stable symlink if the option "stable symlink" was + * set in the file mask dialog. + * If dst_path is an existing symlink it will be deleted silently + * (upper levels take already care of existing files at dst_vpath). + */ + +static FileProgressStatus +make_symlink (file_op_context_t * ctx, const vfs_path_t * src_vpath, const vfs_path_t * dst_vpath) +{ + const char *src_path; + const char *dst_path; + char link_target[MC_MAXPATHLEN]; + int len; + FileProgressStatus return_status; + struct stat dst_stat; + gboolean dst_is_symlink; + vfs_path_t *link_target_vpath = NULL; + + src_path = vfs_path_as_str (src_vpath); + dst_path = vfs_path_as_str (dst_vpath); + + dst_is_symlink = (mc_lstat (dst_vpath, &dst_stat) == 0) && S_ISLNK (dst_stat.st_mode); + + retry_src_readlink: + len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1); + if (len < 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot read source link \"%s\"\n%s"), src_path); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_src_readlink; + } + goto ret; + } + + link_target[len] = '\0'; + + if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath))) + { + message (D_ERROR, MSG_ERROR, + _("Cannot make stable symlinks across " + "non-local filesystems:\n\nOption Stable Symlinks will be disabled")); + ctx->stable_symlinks = FALSE; + } + + if (ctx->stable_symlinks && !g_path_is_absolute (link_target)) + { + const char *r; + + r = strrchr (src_path, PATH_SEP); + if (r != NULL) + { + char *p; + vfs_path_t *q; + + p = g_strndup (src_path, r - src_path + 1); + if (g_path_is_absolute (dst_path)) + q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON); + else + q = vfs_path_build_filename (p, dst_path, (char *) NULL); + + if (vfs_path_tokens_count (q) > 1) + { + char *s; + vfs_path_t *tmp_vpath1, *tmp_vpath2; + + tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1); + s = g_strconcat (p, link_target, (char *) NULL); + g_strlcpy (link_target, s, sizeof (link_target)); + g_free (s); + tmp_vpath2 = vfs_path_from_str (link_target); + s = diff_two_paths (tmp_vpath1, tmp_vpath2); + vfs_path_free (tmp_vpath1, TRUE); + vfs_path_free (tmp_vpath2, TRUE); + if (s != NULL) + { + g_strlcpy (link_target, s, sizeof (link_target)); + g_free (s); + } + } + g_free (p); + vfs_path_free (q, TRUE); + } + } + link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON); + + retry_dst_symlink: + if (mc_symlink (link_target_vpath, dst_vpath) == 0) + { + /* Success */ + return_status = FILE_CONT; + goto ret; + } + /* + * if dst_exists, it is obvious that this had failed. + * We can delete the old symlink and try again... + */ + if (dst_is_symlink && mc_unlink (dst_vpath) == 0 + && mc_symlink (link_target_vpath, dst_vpath) == 0) + { + /* Success */ + return_status = FILE_CONT; + goto ret; + } + + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot create target symlink \"%s\"\n%s"), dst_path); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_dst_symlink; + } + + ret: + vfs_path_free (link_target_vpath, TRUE); + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * do_compute_dir_size: + * + * Computes the number of bytes used by the files in a directory + */ + +static FileProgressStatus +do_compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * dsm, + size_t * dir_count, size_t * ret_marked, uintmax_t * ret_total, + mc_stat_fn stat_func) +{ + static gint64 timestamp = 0; + /* update with 25 FPS rate */ + static const gint64 delay = G_USEC_PER_SEC / 25; + + status_msg_t *sm = STATUS_MSG (dsm); + int res; + struct stat s; + DIR *dir; + struct vfs_dirent *dirent; + FileProgressStatus ret = FILE_CONT; + + (*dir_count)++; + + dir = mc_opendir (dirname_vpath); + if (dir == NULL) + return ret; + + while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL) + { + vfs_path_t *tmp_vpath; + + if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name)) + continue; + + tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL); + + res = stat_func (tmp_vpath, &s); + if (res == 0) + { + if (S_ISDIR (s.st_mode)) + ret = + do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total, + stat_func); + else + { + ret = FILE_CONT; + + (*ret_marked)++; + *ret_total += (uintmax_t) s.st_size; + } + + if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (×tamp, delay)) + { + dsm->dirname_vpath = tmp_vpath; + dsm->dir_count = *dir_count; + dsm->total_size = *ret_total; + ret = sm->update (sm); + } + } + + vfs_path_free (tmp_vpath, TRUE); + } + + mc_closedir (dir); + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * panel_compute_totals: + * + * compute the number of files and the number of bytes + * used up by the whole selection, recursing directories + * as required. In addition, it checks to see if it will + * overwrite any files by doing the copy. + */ + +static FileProgressStatus +panel_compute_totals (const WPanel * panel, dirsize_status_msg_t * sm, size_t * ret_count, + uintmax_t * ret_total, gboolean follow_symlinks) +{ + int i; + size_t dir_count = 0; + mc_stat_fn stat_func = follow_symlinks ? mc_stat : mc_lstat; + + for (i = 0; i < panel->dir.len; i++) + { + const file_entry_t *fe = &panel->dir.list[i]; + const struct stat *s; + + if (fe->f.marked == 0) + continue; + + s = &fe->st; + + if (S_ISDIR (s->st_mode) || (follow_symlinks && link_isdir (fe) && fe->f.stale_link == 0)) + { + vfs_path_t *p; + FileProgressStatus status; + + p = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL); + status = do_compute_dir_size (p, sm, &dir_count, ret_count, ret_total, stat_func); + vfs_path_free (p, TRUE); + + if (status != FILE_CONT) + return status; + } + else + { + (*ret_count)++; + *ret_total += (uintmax_t) s->st_size; + } + } + + return FILE_CONT; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** Initialize variables for progress bars */ +static FileProgressStatus +panel_operate_init_totals (const WPanel * panel, const vfs_path_t * source, + const struct stat *source_stat, file_op_context_t * ctx, + gboolean compute_totals, filegui_dialog_type_t dialog_type) +{ + FileProgressStatus status; + +#ifdef ENABLE_BACKGROUND + if (mc_global.we_are_background) + return FILE_CONT; +#endif + + if (verbose && compute_totals) + { + dirsize_status_msg_t dsm; + gboolean stale_link = FALSE; + + memset (&dsm, 0, sizeof (dsm)); + dsm.allow_skip = TRUE; + status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb, + dirsize_status_update_cb, dirsize_status_deinit_cb); + + ctx->progress_count = 0; + ctx->progress_bytes = 0; + + if (source == NULL) + status = panel_compute_totals (panel, &dsm, &ctx->progress_count, &ctx->progress_bytes, + ctx->follow_links); + else if (S_ISDIR (source_stat->st_mode) + || (ctx->follow_links + && file_is_symlink_to_dir (source, (struct stat *) source_stat, &stale_link) + && !stale_link)) + { + size_t dir_count = 0; + + status = do_compute_dir_size (source, &dsm, &dir_count, &ctx->progress_count, + &ctx->progress_bytes, ctx->stat_func); + } + else + { + ctx->progress_count++; + ctx->progress_bytes += (uintmax_t) source_stat->st_size; + status = FILE_CONT; + } + + status_msg_deinit (STATUS_MSG (&dsm)); + + ctx->progress_totals_computed = (status == FILE_CONT); + + if (status == FILE_SKIP) + status = FILE_CONT; + } + else + { + status = FILE_CONT; + ctx->progress_count = panel->marked; + ctx->progress_bytes = panel->total; + ctx->progress_totals_computed = verbose && dialog_type == FILEGUI_DIALOG_ONE_ITEM; + } + + /* destroy already created UI for single file rename operation */ + file_op_context_destroy_ui (ctx); + + file_op_context_create_ui (ctx, TRUE, dialog_type); + + return status; +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +progress_update_one (file_op_total_context_t * tctx, file_op_context_t * ctx, off_t add) +{ + gint64 tv_current; + static gint64 tv_start = -1; + + tctx->progress_count++; + tctx->progress_bytes += (uintmax_t) add; + + tv_current = g_get_monotonic_time (); + + if (tv_start < 0) + tv_start = tv_current; + + if (tv_current - tv_start > FILEOP_UPDATE_INTERVAL_US) + { + if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM) + { + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + file_progress_show_total (tctx, ctx, tctx->progress_bytes, TRUE); + } + + tv_start = tv_current; + } + + return check_progress_buttons (ctx); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b) +{ + char *msg; + int result = 0; + const char *head_msg; + int width_a, width_b, width; + + head_msg = mode == Foreground ? MSG_ERROR : _("Background process error"); + + width_a = str_term_width1 (a); + width_b = str_term_width1 (b); + width = COLS - 8; + + if (width_a > width) + { + if (width_b > width) + { + char *s; + + s = g_strndup (str_trunc (a, width), width); + b = str_trunc (b, width); + msg = g_strdup_printf (fmt, s, b); + g_free (s); + } + else + { + a = str_trunc (a, width); + msg = g_strdup_printf (fmt, a, b); + } + } + else + { + if (width_b > width) + b = str_trunc (b, width); + + msg = g_strdup_printf (fmt, a, b); + } + + result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort")); + g_free (msg); + do_refresh (); + + return (result == 1) ? FILE_ABORT : FILE_SKIP; +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +warn_same_file (const char *fmt, const char *a, const char *b) +{ +#ifdef ENABLE_BACKGROUND +/* *INDENT-OFF* */ + union + { + void *p; + FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b); + } pntr; +/* *INDENT-ON* */ + + pntr.f = real_warn_same_file; + + if (mc_global.we_are_background) + return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b); +#endif + return real_warn_same_file (Foreground, fmt, a, b); +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +check_same_file (const char *a, const struct stat *ast, const char *b, const struct stat *bst, + FileProgressStatus * status) +{ + if (ast->st_dev != bst->st_dev || ast->st_ino != bst->st_ino) + return FALSE; + + if (S_ISDIR (ast->st_mode)) + *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a, b); + else + *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a, b); + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +static void +get_times (const struct stat *sb, mc_timesbuf_t * times) +{ +#ifdef HAVE_UTIMENSAT + (*times)[0] = sb->st_atim; + (*times)[1] = sb->st_mtim; +#else + times->actime = sb->st_atime; + times->modtime = sb->st_mtime; +#endif +} + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Query/status report routines */ + +static FileProgressStatus +real_do_file_error (enum OperationMode mode, gboolean allow_retry, const char *error) +{ + int result; + const char *msg; + + msg = mode == Foreground ? MSG_ERROR : _("Background process error"); + + if (allow_retry) + result = + query_dialog (msg, error, D_ERROR, 4, _("&Skip"), _("Ski&p all"), _("&Retry"), + _("&Abort")); + else + result = query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("Ski&p all"), _("&Abort")); + + switch (result) + { + case 0: + do_refresh (); + return FILE_SKIP; + + case 1: + do_refresh (); + return FILE_SKIPALL; + + case 2: + if (allow_retry) + { + do_refresh (); + return FILE_RETRY; + } + MC_FALLTHROUGH; + + case 3: + default: + return FILE_ABORT; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +real_query_recursive (file_op_context_t * ctx, enum OperationMode mode, const char *s) +{ + if (ctx->recursive_result < RECURSIVE_ALWAYS) + { + const char *msg; + char *text; + + msg = mode == Foreground + ? _("Directory \"%s\" not empty.\nDelete it recursively?") + : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?"); + text = g_strdup_printf (msg, path_trunc (s, 30)); + + if (safe_delete) + query_set_sel (1); + + ctx->recursive_result = + query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"), + _("Non&e"), _("&Abort")); + g_free (text); + + if (ctx->recursive_result != RECURSIVE_ABORT) + do_refresh (); + } + + switch (ctx->recursive_result) + { + case RECURSIVE_YES: + case RECURSIVE_ALWAYS: + return FILE_CONT; + + case RECURSIVE_NO: + case RECURSIVE_NEVER: + return FILE_SKIP; + + case RECURSIVE_ABORT: + default: + return FILE_ABORT; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_BACKGROUND +static FileProgressStatus +do_file_error (gboolean allow_retry, const char *str) +{ +/* *INDENT-OFF* */ + union + { + void *p; + FileProgressStatus (*f) (enum OperationMode, gboolean, const char *); + } pntr; +/* *INDENT-ON* */ + + pntr.f = real_do_file_error; + + if (mc_global.we_are_background) + return parent_call (pntr.p, NULL, 2, sizeof (allow_retry), allow_retry, strlen (str), str); + else + return real_do_file_error (Foreground, allow_retry, str); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +query_recursive (file_op_context_t * ctx, const char *s) +{ +/* *INDENT-OFF* */ + union + { + void *p; + FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *); + } pntr; +/* *INDENT-ON* */ + + pntr.f = real_query_recursive; + + if (mc_global.we_are_background) + return parent_call (pntr.p, ctx, 1, strlen (s), s); + else + return real_query_recursive (ctx, Foreground, s); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +query_replace (file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dst, + struct stat *dst_stat) +{ +/* *INDENT-OFF* */ + union + { + void *p; + FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *, + struct stat *, const char *, struct stat *); + } pntr; +/* *INDENT-ON* */ + + pntr.f = file_progress_real_query_replace; + + if (mc_global.we_are_background) + return parent_call (pntr.p, ctx, 4, strlen (src), src, sizeof (struct stat), src_stat, + strlen (dst), dst, sizeof (struct stat), dst_stat); + else + return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat); +} + +#else +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +do_file_error (gboolean allow_retry, const char *str) +{ + return real_do_file_error (Foreground, allow_retry, str); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +query_recursive (file_op_context_t * ctx, const char *s) +{ + return real_query_recursive (ctx, Foreground, s); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +query_replace (file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dst, + struct stat *dst_stat) +{ + return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat); +} + +#endif /* !ENABLE_BACKGROUND */ + +/* --------------------------------------------------------------------------------------------- */ +/** Report error with two files */ + +static FileProgressStatus +files_error (const char *format, const char *file1, const char *file2) +{ + char buf[BUF_MEDIUM]; + char *nfile1 = g_strdup (path_trunc (file1, 15)); + char *nfile2 = g_strdup (path_trunc (file2, 15)); + + g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno)); + + g_free (nfile1); + g_free (nfile2); + + return do_file_error (TRUE, buf); +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ + +static void +copy_file_file_display_progress (file_op_total_context_t * tctx, file_op_context_t * ctx, + gint64 tv_current, gint64 tv_transfer_start, off_t file_size, + off_t file_part) +{ + gint64 dt; + + /* Update rotating dash after some time */ + rotate_dash (TRUE); + + /* Compute ETA */ + dt = (tv_current - tv_transfer_start) / G_USEC_PER_SEC; + + if (file_part == 0) + ctx->eta_secs = 0.0; + else + ctx->eta_secs = ((dt / (double) file_part) * file_size) - dt; + + /* Compute BPS rate */ + ctx->bps_time = MAX (1, dt); + ctx->bps = file_part / ctx->bps_time; + + /* Compute total ETA and BPS */ + if (ctx->progress_bytes != 0) + { + uintmax_t remain_bytes; + + remain_bytes = ctx->progress_bytes - tctx->copied_bytes; +#if 1 + { + gint64 total_secs; + + total_secs = (tv_current - tctx->transfer_start) / G_USEC_PER_SEC; + total_secs = MAX (1, total_secs); + + tctx->bps = tctx->copied_bytes / total_secs; + tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0; + } +#else + /* broken on lot of little files */ + tctx->bps_count++; + tctx->bps = (tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count; + tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0; +#endif + } +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +try_remove_file (file_op_context_t * ctx, const vfs_path_t * vpath, FileProgressStatus * status) +{ + while (mc_unlink (vpath) != 0 && !ctx->skip_all) + { + *status = file_error (TRUE, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath)); + if (*status == FILE_RETRY) + continue; + if (*status == FILE_SKIPALL) + ctx->skip_all = TRUE; + return FALSE; + } + + return TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* {{{ Move routines */ + +/** + * Move single file or one of many files from one location to another. + * + * @panel pointer to panel in case of single file, NULL otherwise + * @tctx file operation total context object + * @ctx file operation context object + * @s source file name + * @d destination file name + * + * @return operation result + */ +static FileProgressStatus +move_file_file (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *s, const char *d) +{ + struct stat src_stat, dst_stat; + FileProgressStatus return_status = FILE_CONT; + gboolean copy_done = FALSE; + gboolean old_ask_overwrite; + vfs_path_t *src_vpath, *dst_vpath; + + src_vpath = vfs_path_from_str (s); + dst_vpath = vfs_path_from_str (d); + + file_progress_show_source (ctx, src_vpath); + file_progress_show_target (ctx, dst_vpath); + + /* FIXME: do we really need to check buttons in case of single file? */ + if (check_progress_buttons (ctx) == FILE_ABORT) + { + return_status = FILE_ABORT; + goto ret; + } + + mc_refresh (); + + while (mc_lstat (src_vpath, &src_stat) != 0) + { + /* Source doesn't exist */ + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot stat file \"%s\"\n%s"), s); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + + if (return_status != FILE_RETRY) + goto ret; + } + + if (mc_lstat (dst_vpath, &dst_stat) == 0) + { + if (check_same_file (s, &src_stat, d, &dst_stat, &return_status)) + goto ret; + + if (S_ISDIR (dst_stat.st_mode)) + { + message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d); + do_refresh (); + return_status = FILE_SKIP; + goto ret; + } + + if (confirm_overwrite) + { + return_status = query_replace (ctx, s, &src_stat, d, &dst_stat); + if (return_status != FILE_CONT) + goto ret; + } + /* Ok to overwrite */ + } + + if (!ctx->do_append) + { + if (S_ISLNK (src_stat.st_mode) && ctx->stable_symlinks) + { + return_status = make_symlink (ctx, src_vpath, dst_vpath); + if (return_status == FILE_CONT) + { + if (ctx->preserve) + { + mc_timesbuf_t times; + + get_times (&src_stat, ×); + mc_utime (dst_vpath, ×); + } + goto retry_src_remove; + } + goto ret; + } + + if (mc_rename (src_vpath, dst_vpath) == 0) + { + /* FIXME: do we really need to update progress in case of single file? */ + return_status = progress_update_one (tctx, ctx, src_stat.st_size); + goto ret; + } + } +#if 0 + /* Comparison to EXDEV seems not to work in nfs if you're moving from + one nfs to the same, but on the server it is on two different + filesystems. Then nfs returns EIO instead of EXDEV. + Hope it will not hurt if we always in case of error try to copy/delete. */ + else + errno = EXDEV; /* Hack to copy (append) the file and then delete it */ + + if (errno != EXDEV) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_rename; + } + + goto ret; + } +#endif + + /* Failed rename -> copy the file instead */ + if (panel != NULL) + { + /* In case of single file, calculate totals. In case of many files, + totals are calcuated already. */ + return_status = + panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE, + FILEGUI_DIALOG_ONE_ITEM); + if (return_status != FILE_CONT) + goto ret; + } + + old_ask_overwrite = tctx->ask_overwrite; + tctx->ask_overwrite = FALSE; + return_status = copy_file_file (tctx, ctx, s, d); + tctx->ask_overwrite = old_ask_overwrite; + if (return_status != FILE_CONT) + goto ret; + + copy_done = TRUE; + + /* FIXME: there is no need to update progress and check buttons + at the finish of single file operation. */ + if (panel == NULL) + { + file_progress_show_source (ctx, NULL); + file_progress_show (ctx, 0, 0, "", FALSE); + + return_status = check_progress_buttons (ctx); + if (return_status != FILE_CONT) + goto ret; + } + + mc_refresh (); + + retry_src_remove: + if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL) + goto ret; + + if (!copy_done) + return_status = progress_update_one (tctx, ctx, src_stat.st_size); + + ret: + vfs_path_free (src_vpath, TRUE); + vfs_path_free (dst_vpath, TRUE); + + return return_status; +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Erase routines */ +/** Don't update progress status if progress_count==NULL */ + +static FileProgressStatus +erase_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath) +{ + struct stat buf; + FileProgressStatus return_status; + + /* check buttons if deleting info was changed */ + if (file_progress_show_deleting (ctx, vfs_path_as_str (vpath), &tctx->progress_count)) + { + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + if (check_progress_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + } + + if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0) + { + /* ignore, most likely the mc_unlink fails, too */ + buf.st_size = 0; + } + + if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT) + return FILE_ABORT; + + if (tctx->progress_count == 0) + return FILE_CONT; + + return check_progress_buttons (ctx); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +try_erase_dir (file_op_context_t * ctx, const char *dir) +{ + FileProgressStatus return_status = FILE_CONT; + + while (my_rmdir (dir) != 0 && !ctx->skip_all) + { + return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status != FILE_RETRY) + break; + } + + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ + +/** + Recursive remove of files + abort->cancel stack + skip ->warn every level, gets default + skipall->remove as much as possible +*/ +static FileProgressStatus +recursive_erase (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath) +{ + struct vfs_dirent *next; + DIR *reading; + const char *s; + FileProgressStatus return_status = FILE_CONT; + + reading = mc_opendir (vpath); + if (reading == NULL) + return FILE_RETRY; + + while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) + { + vfs_path_t *tmp_vpath; + struct stat buf; + + if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name)) + continue; + + tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL); + if (mc_lstat (tmp_vpath, &buf) != 0) + { + mc_closedir (reading); + vfs_path_free (tmp_vpath, TRUE); + return FILE_RETRY; + } + if (S_ISDIR (buf.st_mode)) + return_status = recursive_erase (tctx, ctx, tmp_vpath); + else + return_status = erase_file (tctx, ctx, tmp_vpath); + vfs_path_free (tmp_vpath, TRUE); + } + mc_closedir (reading); + + if (return_status == FILE_ABORT) + return FILE_ABORT; + + s = vfs_path_as_str (vpath); + + file_progress_show_deleting (ctx, s, NULL); + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + if (check_progress_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + + return try_erase_dir (ctx, s); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Check if directory is empty or not. + * + * @param vpath directory handler + * + * @returns -1 on error, + * 1 if there are no entries besides "." and ".." in the directory path points to, + * 0 else. + * + * ATTENTION! Be carefull when modifying this function (like commit 25e419ba0886f)! + * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), whuch is uded + * in FISH) don't return "." and ".." entries. + */ +static int +check_dir_is_empty (const vfs_path_t * vpath) +{ + DIR *dir; + struct vfs_dirent *d; + int i = 1; + + dir = mc_opendir (vpath); + if (dir == NULL) + return -1; + + for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir)) + if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name)) + { + i = 0; + break; + } + + mc_closedir (dir); + return i; +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +erase_dir_iff_empty (file_op_context_t * ctx, const vfs_path_t * vpath, size_t count) +{ + const char *s; + + s = vfs_path_as_str (vpath); + + file_progress_show_deleting (ctx, s, NULL); + file_progress_show_count (ctx, count, ctx->progress_count); + if (check_progress_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + + if (check_dir_is_empty (vpath) != 1) + return FILE_CONT; + + /* not empty or error */ + return try_erase_dir (ctx, s); +} + + +/* --------------------------------------------------------------------------------------------- */ + +static void +erase_dir_after_copy (file_op_total_context_t * tctx, file_op_context_t * ctx, + const vfs_path_t * vpath, FileProgressStatus * status) +{ + if (ctx->erase_at_end) + { + /* Reset progress count before delete to avoid counting files twice */ + tctx->progress_count = tctx->prev_progress_count; + + while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT) + { + struct link *lp; + + lp = (struct link *) g_queue_pop_head (erase_list); + + if (S_ISDIR (lp->st_mode)) + *status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count); + else + *status = erase_file (tctx, ctx, lp->src_vpath); + + free_link (lp); + } + + /* Save progress counter before move next directory */ + tctx->prev_progress_count = tctx->progress_count; + } + + erase_dir_iff_empty (ctx, vpath, tctx->progress_count); +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ + +/** + * Move single directory or one of many directories from one location to another. + * + * @panel pointer to panel in case of single directory, NULL otherwise + * @tctx file operation total context object + * @ctx file operation context object + * @s source directory name + * @d destination directory name + * + * @return operation result + */ +static FileProgressStatus +do_move_dir_dir (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *s, const char *d) +{ + struct stat src_stat, dst_stat; + FileProgressStatus return_status = FILE_CONT; + gboolean move_over = FALSE; + gboolean dstat_ok; + vfs_path_t *src_vpath, *dst_vpath; + + src_vpath = vfs_path_from_str (s); + dst_vpath = vfs_path_from_str (d); + + file_progress_show_source (ctx, src_vpath); + file_progress_show_target (ctx, dst_vpath); + + /* FIXME: do we really need to check buttons in case of single directory? */ + if (panel != NULL && check_progress_buttons (ctx) == FILE_ABORT) + { + return_status = FILE_ABORT; + goto ret_fast; + } + + mc_refresh (); + + mc_stat (src_vpath, &src_stat); + + dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0); + + if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status)) + goto ret_fast; + + if (!dstat_ok) + ; /* destination doesn't exist */ + else if (!ctx->dive_into_subdirs) + move_over = TRUE; + else + { + vfs_path_t *tmp; + + tmp = dst_vpath; + dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL); + vfs_path_free (tmp, TRUE); + } + + d = vfs_path_as_str (dst_vpath); + + /* Check if the user inputted an existing dir */ + retry_dst_stat: + if (mc_stat (dst_vpath, &dst_stat) == 0) + { + if (move_over) + { + if (panel != NULL) + { + /* In case of single directory, calculate totals. In case of many directories, + totals are calcuated already. */ + return_status = + panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE, + FILEGUI_DIALOG_MULTI_ITEM); + if (return_status != FILE_CONT) + goto ret; + } + + return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL); + + if (return_status != FILE_CONT) + goto ret; + goto oktoret; + } + else if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + if (S_ISDIR (dst_stat.st_mode)) + return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d); + else + return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_dst_stat; + } + + goto ret_fast; + } + + retry_rename: + if (mc_rename (src_vpath, dst_vpath) == 0) + { + return_status = FILE_CONT; + goto ret; + } + + if (errno != EXDEV) + { + if (!ctx->skip_all) + { + return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_rename; + } + goto ret; + } + + /* Failed because of filesystem boundary -> copy dir instead */ + if (panel != NULL) + { + /* In case of single directory, calculate totals. In case of many directories, + totals are calcuated already. */ + return_status = + panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE, + FILEGUI_DIALOG_MULTI_ITEM); + if (return_status != FILE_CONT) + goto ret; + } + + return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL); + + if (return_status != FILE_CONT) + goto ret; + + oktoret: + /* FIXME: there is no need to update progress and check buttons + at the finish of single directory operation. */ + if (panel == NULL) + { + file_progress_show_source (ctx, NULL); + file_progress_show_target (ctx, NULL); + file_progress_show (ctx, 0, 0, "", FALSE); + + return_status = check_progress_buttons (ctx); + if (return_status != FILE_CONT) + goto ret; + } + + mc_refresh (); + + erase_dir_after_copy (tctx, ctx, src_vpath, &return_status); + + ret: + erase_list = free_erase_list (erase_list); + ret_fast: + vfs_path_free (src_vpath, TRUE); + vfs_path_free (dst_vpath, TRUE); + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ + +/* {{{ Panel operate routines */ + +/** + * Return currently selected entry name or the name of the first marked + * entry if there is one. + */ + +static const char * +panel_get_file (const WPanel * panel) +{ + if (get_current_type () == view_tree) + { + WTree *tree; + const vfs_path_t *selected_name; + + tree = (WTree *) get_panel_widget (get_current_index ()); + selected_name = tree_selected_name (tree); + return vfs_path_as_str (selected_name); + } + + if (panel->marked != 0) + { + int i; + + for (i = 0; i < panel->dir.len; i++) + if (panel->dir.list[i].f.marked) + return panel->dir.list[i].fname->str; + } + + return panel->dir.list[panel->selected].fname->str; +} + +/* --------------------------------------------------------------------------------------------- */ + +static const char * +check_single_entry (const WPanel * panel, gboolean force_single, struct stat *src_stat) +{ + const char *source; + gboolean ok; + + if (force_single) + source = selection (panel)->fname->str; + else + source = panel_get_file (panel); + + ok = !DIR_IS_DOTDOT (source); + + if (!ok) + message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!")); + else + { + vfs_path_t *source_vpath; + + source_vpath = vfs_path_from_str (source); + + /* Update stat to get actual info */ + ok = mc_lstat (source_vpath, src_stat) == 0; + if (!ok) + { + message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"), + path_trunc (source, 30), unix_error_string (errno)); + + /* Directory was changed outside MC. Reload it forced */ + if (!panel->is_panelized) + { + panel_update_flags_t flags = UP_RELOAD; + + /* don't update panelized panel */ + if (get_other_type () == view_listing && other_panel->is_panelized) + flags |= UP_ONLY_CURRENT; + + update_panels (flags, UP_KEEPSEL); + } + } + + vfs_path_free (source_vpath, TRUE); + } + + return ok ? source : NULL; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * Generate user prompt for panel operation. + * src_stat must be not NULL for single source, and NULL for multiple sources + */ + +static char * +panel_operate_generate_prompt (const WPanel * panel, FileOperation operation, + const struct stat *src_stat) +{ + char *sp; + char *format_string; + const char *cp; + + static gboolean i18n_flag = FALSE; + if (!i18n_flag) + { + size_t i; + + for (i = G_N_ELEMENTS (op_names1); i-- != 0;) + op_names1[i] = Q_ (op_names1[i]); + +#ifdef ENABLE_NLS + for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;) + prompt_parts[i] = _(prompt_parts[i]); + + one_format = _(one_format); + many_format = _(many_format); +#endif /* ENABLE_NLS */ + i18n_flag = TRUE; + } + + /* Possible prompts: + * OP_COPY: + * "Copy file \"%s\" with source mask:" + * "Copy %d files with source mask:" + * "Copy directory \"%s\" with source mask:" + * "Copy %d directories with source mask:" + * "Copy %d files/directories with source mask:" + * OP_MOVE: + * "Move file \"%s\" with source mask:" + * "Move %d files with source mask:" + * "Move directory \"%s\" with source mask:" + * "Move %d directories with source mask:" + * "Move %d files/directories with source mask:" + * OP_DELETE: + * "Delete file \"%s\"?" + * "Delete %d files?" + * "Delete directory \"%s\"?" + * "Delete %d directories?" + * "Delete %d files/directories?" + */ + + cp = (src_stat != NULL ? one_format : many_format); + + /* 1. Substitute %o */ + format_string = str_replace_all (cp, "%o", op_names1[(int) operation]); + + /* 2. Substitute %n */ + cp = operation == OP_DELETE ? "\n" : " "; + sp = format_string; + format_string = str_replace_all (sp, "%n", cp); + g_free (sp); + + /* 3. Substitute %f */ + if (src_stat != NULL) + cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0]; + else if (panel->marked == panel->dirs_marked) + cp = prompt_parts[3]; + else + cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1]; + + sp = format_string; + format_string = str_replace_all (sp, "%f", cp); + g_free (sp); + + /* 4. Substitute %m */ + cp = operation == OP_DELETE ? "?" : prompt_parts[5]; + sp = format_string; + format_string = str_replace_all (sp, "%m", cp); + g_free (sp); + + return format_string; +} + +/* --------------------------------------------------------------------------------------------- */ + +static char * +do_confirm_copy_move (const WPanel * panel, FileOperation operation, gboolean force_single, + const char *source, struct stat *src_stat, file_op_context_t * ctx, + gboolean * do_bg) +{ + const char *tmp_dest_dir; + char *dest_dir; + char *format; + char *ret; + + /* Forced single operations default to the original name */ + if (force_single) + tmp_dest_dir = source; + else if (get_other_type () == view_listing) + tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath); + else + tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath); + + /* + * Add trailing backslash only when do non-local ops. + * It saves user from occasional file renames (when destination + * dir is deleted) + */ + if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0' + && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1])) + { + /* add trailing separator */ + dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL); + } + else + { + /* just copy */ + dest_dir = g_strdup (tmp_dest_dir); + } + + if (dest_dir == NULL) + return NULL; + + if (source == NULL) + src_stat = NULL; + + /* Generate confirmation prompt */ + format = panel_operate_generate_prompt (panel, operation, src_stat); + + ret = file_mask_dialog (ctx, operation, source != NULL, format, + source != NULL ? source : (const void *) &panel->marked, dest_dir, + do_bg); + + g_free (format); + g_free (dest_dir); + + return ret; +} + +/* --------------------------------------------------------------------------------------------- */ + +static gboolean +do_confirm_erase (const WPanel * panel, const char *source, struct stat *src_stat) +{ + int i; + char *format; + char fmd_buf[BUF_MEDIUM]; + + if (source == NULL) + src_stat = NULL; + + /* Generate confirmation prompt */ + format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat); + + if (source == NULL) + g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked); + else + { + const int fmd_xlen = 64; + + i = fmd_xlen - str_term_width1 (format) - 4; + g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i)); + } + + g_free (format); + + if (safe_delete) + query_set_sel (1); + + i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No")); + + return (i == 0); +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +operate_single_file (const WPanel * panel, FileOperation operation, file_op_total_context_t * tctx, + file_op_context_t * ctx, const char *src, struct stat *src_stat, + const char *dest, filegui_dialog_type_t dialog_type) +{ + FileProgressStatus value; + vfs_path_t *src_vpath; + gboolean is_file; + + if (g_path_is_absolute (src)) + src_vpath = vfs_path_from_str (src); + else + src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL); + + is_file = !S_ISDIR (src_stat->st_mode); + /* Is link to directory? */ + if (is_file) + { + gboolean is_link; + + is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL); + is_file = !(is_link && ctx->follow_links); + } + + + if (operation == OP_DELETE) + { + value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type); + if (value == FILE_CONT) + { + if (is_file) + value = erase_file (tctx, ctx, src_vpath); + else + value = erase_dir (tctx, ctx, src_vpath); + } + } + else + { + char *temp; + + src = vfs_path_as_str (src_vpath); + + temp = build_dest (ctx, src, dest, &value); + if (temp != NULL) + { + dest = temp; + + switch (operation) + { + case OP_COPY: + /* we use file_mask_op_follow_links only with OP_COPY */ + ctx->stat_func (src_vpath, src_stat); + + value = + panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, + dialog_type); + if (value == FILE_CONT) + { + is_file = !S_ISDIR (src_stat->st_mode); + /* Is link to directory? */ + if (is_file) + { + gboolean is_link; + + is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL); + is_file = !(is_link && ctx->follow_links); + } + + if (is_file) + value = copy_file_file (tctx, ctx, src, dest); + else + value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL); + } + break; + + case OP_MOVE: +#ifdef ENABLE_BACKGROUND + /* create UI to show confirmation dialog */ + if (!mc_global.we_are_background) + file_op_context_create_ui (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM); +#endif + if (is_file) + value = move_file_file (panel, tctx, ctx, src, dest); + else + value = do_move_dir_dir (panel, tctx, ctx, src, dest); + break; + + default: + /* Unknown file operation */ + abort (); + } + + g_free (temp); + } + } + + vfs_path_free (src_vpath, TRUE); + + return value; +} + +/* --------------------------------------------------------------------------------------------- */ + +static FileProgressStatus +operate_one_file (const WPanel * panel, FileOperation operation, file_op_total_context_t * tctx, + file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dest) +{ + FileProgressStatus value = FILE_CONT; + vfs_path_t *src_vpath; + gboolean is_file; + + if (g_path_is_absolute (src)) + src_vpath = vfs_path_from_str (src); + else + src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL); + + is_file = !S_ISDIR (src_stat->st_mode); + + if (operation == OP_DELETE) + { + if (is_file) + value = erase_file (tctx, ctx, src_vpath); + else + value = erase_dir (tctx, ctx, src_vpath); + } + else + { + char *temp; + + src = vfs_path_as_str (src_vpath); + + temp = build_dest (ctx, src, dest, &value); + if (temp != NULL) + { + dest = temp; + + switch (operation) + { + case OP_COPY: + /* we use file_mask_op_follow_links only with OP_COPY */ + ctx->stat_func (src_vpath, src_stat); + is_file = !S_ISDIR (src_stat->st_mode); + + if (is_file) + value = copy_file_file (tctx, ctx, src, dest); + else + value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL); + dest_dirs = free_linklist (dest_dirs); + break; + + case OP_MOVE: + if (is_file) + value = move_file_file (NULL, tctx, ctx, src, dest); + else + value = do_move_dir_dir (NULL, tctx, ctx, src, dest); + break; + + default: + /* Unknown file operation */ + abort (); + } + + g_free (temp); + } + } + + vfs_path_free (src_vpath, TRUE); + + return value; +} + +/* --------------------------------------------------------------------------------------------- */ + +#ifdef ENABLE_BACKGROUND +static int +end_bg_process (file_op_context_t * ctx, enum OperationMode mode) +{ + int pid = ctx->pid; + + (void) mode; + ctx->pid = 0; + + unregister_task_with_pid (pid); + /* file_op_context_destroy(ctx); */ + return 1; +} +#endif +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/*** public functions ****************************************************************************/ +/* --------------------------------------------------------------------------------------------- */ + +/* Is file symlink to directory or not. + * + * @param path file or directory + * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here + * @param stale_link TRUE if file is stale link to directory + * + * @return TRUE if file symlink to directory, ELSE otherwise. + */ +gboolean +file_is_symlink_to_dir (const vfs_path_t * vpath, struct stat * st, gboolean * stale_link) +{ + struct stat st2; + gboolean stale = FALSE; + gboolean res = FALSE; + + if (st == NULL) + { + st = &st2; + + if (mc_lstat (vpath, st) != 0) + goto ret; + } + + if (S_ISLNK (st->st_mode)) + { + struct stat st3; + + stale = (mc_stat (vpath, &st3) != 0); + + if (!stale) + res = (S_ISDIR (st3.st_mode) != 0); + } + + ret: + if (stale_link != NULL) + *stale_link = stale; + + return res; +} + +/* --------------------------------------------------------------------------------------------- */ + +FileProgressStatus +copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx, + const char *src_path, const char *dst_path) +{ + uid_t src_uid = (uid_t) (-1); + gid_t src_gid = (gid_t) (-1); + + int src_desc, dest_desc = -1; + mode_t src_mode = 0; /* The mode of the source file */ + struct stat src_stat, dst_stat; + mc_timesbuf_t times; + gboolean dst_exists = FALSE, appending = FALSE; + off_t file_size = -1; + FileProgressStatus return_status, temp_status; + gint64 tv_transfer_start; + dest_status_t dst_status = DEST_NONE; + int open_flags; + vfs_path_t *src_vpath = NULL, *dst_vpath = NULL; + char *buf = NULL; + + /* FIXME: We should not be using global variables! */ + ctx->do_reget = 0; + return_status = FILE_RETRY; + + dst_vpath = vfs_path_from_str (dst_path); + src_vpath = vfs_path_from_str (src_path); + + file_progress_show_source (ctx, src_vpath); + file_progress_show_target (ctx, dst_vpath); + + if (check_progress_buttons (ctx) == FILE_ABORT) + { + return_status = FILE_ABORT; + goto ret_fast; + } + + mc_refresh (); + + while (mc_stat (dst_vpath, &dst_stat) == 0) + { + if (S_ISDIR (dst_stat.st_mode)) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = + file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + continue; + } + goto ret_fast; + } + + dst_exists = TRUE; + break; + } + + while ((*ctx->stat_func) (src_vpath, &src_stat) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + + if (return_status != FILE_RETRY) + goto ret_fast; + } + + if (dst_exists) + { + /* Destination already exists */ + if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status)) + goto ret_fast; + + /* Should we replace destination? */ + if (tctx->ask_overwrite) + { + ctx->do_reget = 0; + return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat); + if (return_status != FILE_CONT) + goto ret_fast; + } + } + + get_times (&src_stat, ×); + + if (!ctx->do_append) + { + /* Check the hardlinks */ + if (!ctx->follow_links) + { + switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all)) + { + case HARDLINK_OK: + /* We have made a hardlink - no more processing is necessary */ + return_status = FILE_CONT; + goto ret_fast; + + case HARDLINK_ABORT: + return_status = FILE_ABORT; + goto ret_fast; + + default: + break; + } + } + + if (S_ISLNK (src_stat.st_mode)) + { + return_status = make_symlink (ctx, src_vpath, dst_vpath); + if (return_status == FILE_CONT && ctx->preserve) + mc_utime (dst_vpath, ×); + goto ret_fast; + } + + if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode) + || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode)) + { + dev_t rdev = 0; + +#ifdef HAVE_STRUCT_STAT_ST_RDEV + rdev = src_stat.st_rdev; +#endif + + while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0 + && !ctx->skip_all) + { + return_status = + file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + goto ret_fast; + } + /* Success */ + + while (ctx->preserve_uidgid + && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_SKIP) + break; + if (temp_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (temp_status != FILE_RETRY) + { + return_status = temp_status; + goto ret_fast; + } + } + + while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0 + && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_SKIP) + break; + if (temp_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (temp_status != FILE_RETRY) + { + return_status = temp_status; + goto ret_fast; + } + } + + return_status = FILE_CONT; + mc_utime (dst_vpath, ×); + goto ret_fast; + } + } + + tv_transfer_start = g_get_monotonic_time (); + + while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all) + { + return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_SKIP) + break; + ctx->do_append = FALSE; + goto ret_fast; + } + + if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget) + { + message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file")); + ctx->do_reget = 0; + ctx->do_append = FALSE; + } + + while (mc_fstat (src_desc, &src_stat) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + ctx->do_append = FALSE; + } + goto ret; + } + + src_mode = src_stat.st_mode; + src_uid = src_stat.st_uid; + src_gid = src_stat.st_gid; + file_size = src_stat.st_size; + + open_flags = O_WRONLY; + if (!dst_exists) + open_flags |= O_CREAT | O_EXCL; + else if (ctx->do_append) + open_flags |= O_APPEND; + else + open_flags |= O_CREAT | O_TRUNC; + + while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0) + { + if (errno != EEXIST) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = + file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + ctx->do_append = FALSE; + } + } + goto ret; + } + + /* file opened, but not fully copied */ + dst_status = DEST_SHORT_QUERY; + + appending = ctx->do_append; + ctx->do_append = FALSE; + + /* Try clone the file first. */ + if (vfs_clone_file (dest_desc, src_desc) == 0) + { + dst_status = DEST_FULL; + return_status = FILE_CONT; + goto ret; + } + + /* Find out the optimal buffer size. */ + while (mc_fstat (dest_desc, &dst_stat) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + goto ret; + } + + /* try preallocate space; if fail, try copy anyway */ + while (mc_global.vfs.preallocate_space && + vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0) + { + if (ctx->skip_all) + { + /* cannot allocate, start the file copying anyway */ + return_status = FILE_CONT; + break; + } + + return_status = + file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path); + + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + + if (ctx->skip_all || return_status == FILE_SKIP) + { + /* skip the space allocation error, start file copying */ + return_status = FILE_CONT; + break; + } + + if (return_status == FILE_ABORT) + { + mc_close (dest_desc); + dest_desc = -1; + mc_unlink (dst_vpath); + dst_status = DEST_NONE; + goto ret; + } + + /* return_status == FILE_RETRY -- try allocate space again */ + } + + ctx->eta_secs = 0.0; + ctx->bps = 0; + + if (tctx->bps == 0 || (file_size / tctx->bps) > FILEOP_UPDATE_INTERVAL) + file_progress_show (ctx, 0, file_size, "", TRUE); + else + file_progress_show (ctx, 1, 1, "", TRUE); + return_status = check_progress_buttons (ctx); + mc_refresh (); + + if (return_status == FILE_CONT) + { + size_t bufsize; + off_t file_part = 0; + gint64 tv_current, tv_last_update; + gint64 tv_last_input = 0; + gint64 usecs, update_usecs; + const char *stalled_msg = ""; + gboolean is_first_time = TRUE; + + tv_last_update = tv_transfer_start; + + bufsize = io_blksize (dst_stat); + buf = g_malloc (bufsize); + + while (TRUE) + { + ssize_t n_read = -1, n_written; + gboolean force_update; + + /* src_read */ + if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0) + while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all) + { + return_status = + file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path); + if (return_status == FILE_RETRY) + continue; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + goto ret; + } + + if (n_read == 0) + break; + + tv_current = g_get_monotonic_time (); + + if (n_read > 0) + { + char *t = buf; + + file_part += n_read; + + tv_last_input = tv_current; + + /* dst_write */ + while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read) + { + gboolean write_errno_nospace; + + if (n_written > 0) + { + n_read -= n_written; + t += n_written; + continue; + } + + write_errno_nospace = (n_written < 0 && errno == ENOSPC); + + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + return_status = + file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path); + + if (return_status == FILE_SKIP) + { + if (write_errno_nospace) + goto ret; + break; + } + if (return_status == FILE_SKIPALL) + { + ctx->skip_all = TRUE; + if (write_errno_nospace) + goto ret; + } + if (return_status != FILE_RETRY) + goto ret; + } + } + + tctx->copied_bytes = tctx->progress_bytes + file_part + ctx->do_reget; + + usecs = tv_current - tv_last_update; + update_usecs = tv_current - tv_last_input; + + if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US) + { + copy_file_file_display_progress (tctx, ctx, tv_current, tv_transfer_start, + file_size, file_part); + tv_last_update = tv_current; + } + + is_first_time = FALSE; + + if (update_usecs > FILEOP_STALLING_INTERVAL_US) + stalled_msg = _("(stalled)"); + + force_update = (tv_current - tctx->transfer_start) > FILEOP_UPDATE_INTERVAL_US; + + if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM) + { + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update); + } + + file_progress_show (ctx, file_part + ctx->do_reget, file_size, stalled_msg, + force_update); + mc_refresh (); + + return_status = check_progress_buttons (ctx); + if (return_status != FILE_CONT) + { + int query_res; + + query_res = + query_dialog (Q_ ("DialogTitle|Copy"), + _("Incomplete file was retrieved"), D_ERROR, 3, + _("&Delete"), _("&Keep"), _("&Continue copy")); + + switch (query_res) + { + case 0: + /* delete */ + dst_status = DEST_SHORT_DELETE; + goto ret; + + case 1: + /* keep */ + dst_status = DEST_SHORT_KEEP; + goto ret; + + default: + /* continue copy */ + break; + } + } + } + + /* copy successful */ + dst_status = DEST_FULL; + } + + ret: + g_free (buf); + + rotate_dash (FALSE); + while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path); + if (temp_status == FILE_RETRY) + continue; + if (temp_status == FILE_ABORT) + return_status = temp_status; + if (temp_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + break; + } + + while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_RETRY) + continue; + if (temp_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + return_status = temp_status; + break; + } + + if (dst_status == DEST_SHORT_QUERY) + { + /* Query to remove short file */ + if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"), + D_ERROR, 2, _("&Delete"), _("&Keep")) == 0) + mc_unlink (dst_vpath); + } + else if (dst_status == DEST_SHORT_DELETE) + mc_unlink (dst_vpath); + else if (dst_status == DEST_FULL && !appending) + { + /* Copy has succeeded */ + + while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0 + && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_RETRY) + continue; + if (temp_status == FILE_SKIPALL) + { + ctx->skip_all = TRUE; + return_status = FILE_CONT; + } + if (temp_status == FILE_SKIP) + return_status = FILE_CONT; + break; + } + + while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0 + && !ctx->skip_all) + { + temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path); + if (temp_status == FILE_RETRY) + continue; + if (temp_status == FILE_SKIPALL) + { + ctx->skip_all = TRUE; + return_status = FILE_CONT; + } + if (temp_status == FILE_SKIP) + return_status = FILE_CONT; + break; + } + + if (!ctx->preserve && !dst_exists) + { + src_mode = umask (-1); + umask (src_mode); + src_mode = 0100666 & ~src_mode; + mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)); + } + + mc_utime (dst_vpath, ×); + } + + if (return_status == FILE_CONT) + return_status = progress_update_one (tctx, ctx, file_size); + + ret_fast: + vfs_path_free (src_vpath, TRUE); + vfs_path_free (dst_vpath, TRUE); + return return_status; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * I think these copy_*_* functions should have a return type. + * anyway, this function *must* have two directories as arguments. + */ +/* FIXME: This function needs to check the return values of the + function calls */ + +FileProgressStatus +copy_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d, + gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs) +{ + struct vfs_dirent *next; + struct stat dst_stat, src_stat; + DIR *reading; + FileProgressStatus return_status = FILE_CONT; + struct link *lp; + vfs_path_t *src_vpath, *dst_vpath; + gboolean do_mkdir = TRUE; + + src_vpath = vfs_path_from_str (s); + dst_vpath = vfs_path_from_str (d); + + /* First get the mode of the source dir */ + + retry_src_stat: + if ((*ctx->stat_func) (src_vpath, &src_stat) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s); + if (return_status == FILE_RETRY) + goto retry_src_stat; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + goto ret_fast; + } + + if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL) + { + /* Don't copy a directory we created before (we don't want to copy + infinitely if a directory is copied into itself) */ + /* FIXME: should there be an error message and FILE_SKIP? - Norbert */ + return_status = FILE_CONT; + goto ret_fast; + } + + /* Hmm, hardlink to directory??? - Norbert */ + /* FIXME: In this step we should do something in case the destination already exist */ + /* Check the hardlinks */ + if (ctx->preserve) + { + switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all)) + { + case HARDLINK_OK: + /* We have made a hardlink - no more processing is necessary */ + goto ret_fast; + + case HARDLINK_ABORT: + return_status = FILE_ABORT; + goto ret_fast; + + default: + break; + } + } + + if (!S_ISDIR (src_stat.st_mode)) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s); + if (return_status == FILE_RETRY) + goto retry_src_stat; + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + goto ret_fast; + } + + if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL) + { + /* we found a cyclic symbolic link */ + message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s); + return_status = FILE_SKIP; + goto ret_fast; + } + + lp = g_new0 (struct link, 1); + lp->vfs = vfs_path_get_by_index (src_vpath, -1)->class; + lp->ino = src_stat.st_ino; + lp->dev = src_stat.st_dev; + parent_dirs = g_slist_prepend (parent_dirs, lp); + + retry_dst_stat: + /* Now, check if the dest dir exists, if not, create it. */ + if (mc_stat (dst_vpath, &dst_stat) != 0) + { + /* Here the dir doesn't exist : make it ! */ + if (move_over && mc_rename (src_vpath, dst_vpath) == 0) + { + return_status = FILE_CONT; + goto ret; + } + } + else + { + /* + * If the destination directory exists, we want to copy the whole + * directory, but we only want this to happen once. + * + * Escape sequences added to the * to compiler warnings. + * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\* + * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\* + */ + if (!S_ISDIR (dst_stat.st_mode)) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = + file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + if (return_status == FILE_RETRY) + goto retry_dst_stat; + } + goto ret; + } + /* Dive into subdir if exists */ + if (toplevel && ctx->dive_into_subdirs) + { + vfs_path_t *tmp; + + tmp = dst_vpath; + dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL); + vfs_path_free (tmp, TRUE); + + } + else + do_mkdir = FALSE; + } + + d = vfs_path_as_str (dst_vpath); + + if (do_mkdir) + { + while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = + file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + if (return_status != FILE_RETRY) + goto ret; + } + + lp = g_new0 (struct link, 1); + mc_stat (dst_vpath, &dst_stat); + lp->vfs = vfs_path_get_by_index (dst_vpath, -1)->class; + lp->ino = dst_stat.st_ino; + lp->dev = dst_stat.st_dev; + dest_dirs = g_slist_prepend (dest_dirs, lp); + } + + if (ctx->preserve_uidgid) + { + while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0) + { + if (ctx->skip_all) + return_status = FILE_SKIPALL; + else + { + return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d); + if (return_status == FILE_SKIPALL) + ctx->skip_all = TRUE; + } + if (return_status != FILE_RETRY) + goto ret; + } + } + + /* open the source dir for reading */ + reading = mc_opendir (src_vpath); + if (reading == NULL) + goto ret; + + while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) + { + char *path; + vfs_path_t *tmp_vpath; + + /* + * Now, we don't want '.' and '..' to be created / copied at any time + */ + if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name)) + continue; + + /* get the filename and add it to the src directory */ + path = mc_build_filename (s, next->d_name, (char *) NULL); + tmp_vpath = vfs_path_from_str (path); + + (*ctx->stat_func) (tmp_vpath, &dst_stat); + if (S_ISDIR (dst_stat.st_mode)) + { + char *mdpath; + + mdpath = mc_build_filename (d, next->d_name, (char *) NULL); + /* + * From here, we just intend to recursively copy subdirs, not + * the double functionality of copying different when the target + * dir already exists. So, we give the recursive call the flag 0 + * meaning no toplevel. + */ + return_status = + copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs); + g_free (mdpath); + } + else + { + char *dest_file; + + dest_file = mc_build_filename (d, x_basename (path), (char *) NULL); + return_status = copy_file_file (tctx, ctx, path, dest_file); + g_free (dest_file); + } + + g_free (path); + + if (do_delete && return_status == FILE_CONT) + { + if (ctx->erase_at_end) + { + if (erase_list == NULL) + erase_list = g_queue_new (); + + lp = g_new0 (struct link, 1); + lp->src_vpath = tmp_vpath; + lp->st_mode = dst_stat.st_mode; + g_queue_push_tail (erase_list, lp); + tmp_vpath = NULL; + } + else if (S_ISDIR (dst_stat.st_mode)) + return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count); + else + return_status = erase_file (tctx, ctx, tmp_vpath); + } + vfs_path_free (tmp_vpath, TRUE); + } + mc_closedir (reading); + + if (ctx->preserve) + { + mc_timesbuf_t times; + + mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill); + get_times (&src_stat, ×); + mc_utime (dst_vpath, ×); + } + else + { + src_stat.st_mode = umask (-1); + umask (src_stat.st_mode); + src_stat.st_mode = 0100777 & ~src_stat.st_mode; + mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill); + } + + ret: + free_link (parent_dirs->data); + g_slist_free_1 (parent_dirs); + ret_fast: + vfs_path_free (src_vpath, TRUE); + vfs_path_free (dst_vpath, TRUE); + return return_status; +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Move routines */ + +FileProgressStatus +move_dir_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const char *s, const char *d) +{ + return do_move_dir_dir (NULL, tctx, ctx, s, d); +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Erase routines */ + +FileProgressStatus +erase_dir (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath) +{ + file_progress_show_deleting (ctx, vfs_path_as_str (vpath), NULL); + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + if (check_progress_buttons (ctx) == FILE_ABORT) + return FILE_ABORT; + + mc_refresh (); + + /* The old way to detect a non empty directory was: + error = my_rmdir (s); + if (error && (errno == ENOTEMPTY || errno == EEXIST))){ + For the linux user space nfs server (nfs-server-2.2beta29-2) + we would have to check also for EIO. I hope the new way is + fool proof. (Norbert) + */ + if (check_dir_is_empty (vpath) == 0) + { /* not empty */ + FileProgressStatus error; + + error = query_recursive (ctx, vfs_path_as_str (vpath)); + if (error == FILE_CONT) + error = recursive_erase (tctx, ctx, vpath); + return error; + } + + return try_erase_dir (ctx, vfs_path_as_str (vpath)); +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Panel operate routines */ + +void +dirsize_status_init_cb (status_msg_t * sm) +{ + dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm; + WGroup *gd = GROUP (sm->dlg); + Widget *wd = WIDGET (sm->dlg); + WRect r = wd->rect; + + const char *b1_name = N_("&Abort"); + const char *b2_name = N_("&Skip"); + int b_width, ui_width; + +#ifdef ENABLE_NLS + b1_name = _(b1_name); + b2_name = _(b2_name); +#endif + + b_width = str_term_width1 (b1_name) + 4; + if (dsm->allow_skip) + b_width += str_term_width1 (b2_name) + 4 + 1; + + ui_width = MAX (COLS / 2, b_width + 6); + dsm->dirname = label_new (2, 3, ""); + group_add_widget (gd, dsm->dirname); + dsm->count_size = label_new (3, 3, ""); + group_add_widget (gd, dsm->count_size); + group_add_widget (gd, hline_new (4, -1, -1)); + + dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL)); + group_add_widget (gd, dsm->abort_button); + if (dsm->allow_skip) + { + dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL)); + group_add_widget (gd, dsm->skip_button); + widget_select (dsm->skip_button); + } + + r.lines = 8; + r.cols = ui_width; + widget_set_size_rect (wd, &r); + dirsize_status_locate_buttons (dsm); +} + +/* --------------------------------------------------------------------------------------------- */ + +int +dirsize_status_update_cb (status_msg_t * sm) +{ + dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm; + Widget *wd = WIDGET (sm->dlg); + WRect r = wd->rect; + + /* update second (longer label) */ + label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"), + dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si)); + + /* enlarge dialog if required */ + if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols) + { + r.cols = WIDGET (dsm->count_size)->rect.cols + 6; + widget_set_size_rect (wd, &r); + dirsize_status_locate_buttons (dsm); + widget_draw (wd); + /* TODO: ret rid of double redraw */ + } + + /* adjust first label */ + label_set_text (dsm->dirname, + str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6)); + + switch (status_msg_common_update (sm)) + { + case B_CANCEL: + case FILE_ABORT: + return FILE_ABORT; + case FILE_SKIP: + return FILE_SKIP; + default: + return FILE_CONT; + } +} + +/* --------------------------------------------------------------------------------------------- */ + +void +dirsize_status_deinit_cb (status_msg_t * sm) +{ + (void) sm; + + /* schedule to update passive panel */ + if (get_other_type () == view_listing) + other_panel->dirty = TRUE; +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * compute_dir_size: + * + * Computes the number of bytes used by the files in a directory + */ + +FileProgressStatus +compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm, + size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total, + gboolean follow_symlinks) +{ + return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total, + follow_symlinks ? mc_stat : mc_lstat); +} + +/* --------------------------------------------------------------------------------------------- */ +/** + * panel_operate: + * + * Performs one of the operations on the selection on the source_panel + * (copy, delete, move). + * + * Returns TRUE if did change the directory + * structure, Returns FALSE if user aborted + * + * force_single forces operation on the current entry and affects + * default destination. Current filename is used as default. + */ + +gboolean +panel_operate (void *source_panel, FileOperation operation, gboolean force_single) +{ + WPanel *panel = PANEL (source_panel); + const gboolean single_entry = force_single || (panel->marked <= 1) + || (get_current_type () == view_tree); + + const char *source = NULL; + char *dest = NULL; + vfs_path_t *dest_vpath = NULL; + char *save_cwd = NULL, *save_dest = NULL; + struct stat src_stat; + gboolean ret_val = TRUE; + int i; + FileProgressStatus value; + file_op_context_t *ctx; + file_op_total_context_t *tctx; + vfs_path_t *tmp_vpath; + filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM; + + gboolean do_bg = FALSE; /* do background operation? */ + + static gboolean i18n_flag = FALSE; + if (!i18n_flag) + { + for (i = G_N_ELEMENTS (op_names); i-- != 0;) + op_names[i] = Q_ (op_names[i]); + i18n_flag = TRUE; + } + + linklist = free_linklist (linklist); + dest_dirs = free_linklist (dest_dirs); + + save_cwds_stat (); + + if (single_entry) + { + source = check_single_entry (panel, force_single, &src_stat); + + if (source == NULL) + return FALSE; + } + + ctx = file_op_context_new (operation); + + /* Show confirmation dialog */ + if (operation != OP_DELETE) + { + dest = + do_confirm_copy_move (panel, operation, force_single, source, &src_stat, ctx, &do_bg); + + if (dest == NULL) + { + ret_val = FALSE; + goto ret_fast; + } + + dest_vpath = vfs_path_from_str (dest); + } + else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat)) + { + ret_val = FALSE; + goto ret_fast; + } + + tctx = file_op_total_context_new (); + tctx->transfer_start = g_get_monotonic_time (); + +#ifdef ENABLE_BACKGROUND + /* Did the user select to do a background operation? */ + if (do_bg) + { + int v; + + v = do_background (ctx, + g_strconcat (op_names[operation], ": ", + vfs_path_as_str (panel->cwd_vpath), (char *) NULL)); + if (v == -1) + message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background")); + + /* If we are the parent */ + if (v == 1) + { + mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL); + + mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL); + vfs_path_free (dest_vpath, TRUE); + g_free (dest); + /* file_op_context_destroy (ctx); */ + return FALSE; + } + } + else +#endif /* ENABLE_BACKGROUND */ + { + if (operation == OP_DELETE) + dialog_type = FILEGUI_DIALOG_DELETE_ITEM; + else if (single_entry && S_ISDIR (selection (panel)->st.st_mode)) + dialog_type = FILEGUI_DIALOG_MULTI_ITEM; + else if (single_entry || force_single) + dialog_type = FILEGUI_DIALOG_ONE_ITEM; + else + dialog_type = FILEGUI_DIALOG_MULTI_ITEM; + } + + /* Initialize things */ + /* We do not want to trash cache every time file is + created/touched. However, this will make our cache contain + invalid data. */ + if ((dest != NULL) + && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0)) + save_dest = g_strdup (dest); + + if ((vfs_path_tokens_count (panel->cwd_vpath) != 0) + && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0)) + save_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath)); + + /* Now, let's do the job */ + + /* This code is only called by the tree and panel code */ + if (single_entry) + { + /* We now have ETA in all cases */ + + /* One file: FIXME mc_chdir will take user out of any vfs */ + if ((operation != OP_COPY) && (get_current_type () == view_tree)) + { + vfs_path_t *vpath; + int chdir_retcode; + + vpath = vfs_path_from_str (PATH_SEP_STR); + chdir_retcode = mc_chdir (vpath); + vfs_path_free (vpath, TRUE); + if (chdir_retcode < 0) + { + ret_val = FALSE; + goto clean_up; + } + } + + value = + operate_single_file (panel, operation, tctx, ctx, source, &src_stat, dest, dialog_type); + + if ((value == FILE_CONT) && !force_single) + unmark_files (panel); + } + else + { + /* Many files */ + + /* Check destination for copy or move operation */ + while (operation != OP_DELETE) + { + int dst_result; + struct stat dst_stat; + + dst_result = mc_stat (dest_vpath, &dst_stat); + + if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode)) + break; + + if (ctx->skip_all + || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), + dest) != FILE_RETRY) + goto clean_up; + } + + /* TODO: the good way is required to skip directories scanning in case of rename/move + * of several directories. Since reqular expression can be used for destination, + * some directory movements can be a cross-filesystem and directory scanning is useful + * for those directories only. */ + + if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type) + == FILE_CONT) + { + /* Loop for every file, perform the actual copy operation */ + for (i = 0; i < panel->dir.len; i++) + { + const char *source2; + + if (!panel->dir.list[i].f.marked) + continue; /* Skip the unmarked ones */ + + source2 = panel->dir.list[i].fname->str; + src_stat = panel->dir.list[i].st; + + value = operate_one_file (panel, operation, tctx, ctx, source2, &src_stat, dest); + + if (value == FILE_ABORT) + break; + + if (value == FILE_CONT) + do_file_mark (panel, i, 0); + + if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM) + { + file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count); + file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE); + } + + if (operation != OP_DELETE) + file_progress_show (ctx, 0, 0, "", FALSE); + + if (check_progress_buttons (ctx) == FILE_ABORT) + break; + + mc_refresh (); + } /* Loop for every file */ + } + } /* Many entries */ + + clean_up: + /* Clean up */ + if (save_cwd != NULL) + { + tmp_vpath = vfs_path_from_str (save_cwd); + mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL); + vfs_path_free (tmp_vpath, TRUE); + g_free (save_cwd); + } + + if (save_dest != NULL) + { + tmp_vpath = vfs_path_from_str (save_dest); + mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL); + vfs_path_free (tmp_vpath, TRUE); + g_free (save_dest); + } + + linklist = free_linklist (linklist); + dest_dirs = free_linklist (dest_dirs); + g_free (dest); + vfs_path_free (dest_vpath, TRUE); + MC_PTR_FREE (ctx->dest_mask); + +#ifdef ENABLE_BACKGROUND + /* Let our parent know we are saying bye bye */ + if (mc_global.we_are_background) + { + int cur_pid = getpid (); + /* Send pid to parent with child context, it is fork and + don't modify real parent ctx */ + ctx->pid = cur_pid; + parent_call ((void *) end_bg_process, ctx, 0); + + vfs_shut (); + my_exit (EXIT_SUCCESS); + } +#endif /* ENABLE_BACKGROUND */ + + file_op_total_context_destroy (tctx); + ret_fast: + /* update panels before redraw screen in file_op_total_context_destroy() */ + update_panels (UP_OPTIMIZE, UP_KEEPSEL); + file_op_context_destroy (ctx); + + return ret_val; +} + +/* }}} */ + +/* --------------------------------------------------------------------------------------------- */ +/* {{{ Query/status report routines */ +/** Report error with one file */ +FileProgressStatus +file_error (gboolean allow_retry, const char *format, const char *file) +{ + char buf[BUF_MEDIUM]; + + g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno)); + + return do_file_error (allow_retry, buf); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* + Cause emacs to enter folding mode for this file: + Local variables: + end: + */ |