root/src/filemanager/file.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. dirsize_status_locate_buttons
  2. build_dest
  3. free_link
  4. free_erase_list
  5. free_linklist
  6. is_in_linklist
  7. check_hardlinks
  8. make_symlink
  9. do_compute_dir_size
  10. panel_compute_totals
  11. panel_operate_init_totals
  12. progress_update_one
  13. real_warn_same_file
  14. warn_same_file
  15. check_same_file
  16. real_do_file_error
  17. real_query_recursive
  18. do_file_error
  19. query_recursive
  20. query_replace
  21. do_file_error
  22. query_recursive
  23. query_replace
  24. files_error
  25. copy_file_file_display_progress
  26. try_remove_file
  27. move_file_file
  28. erase_file
  29. try_erase_dir
  30. recursive_erase
  31. check_dir_is_empty
  32. erase_dir_iff_empty
  33. erase_dir_after_copy
  34. do_move_dir_dir
  35. panel_get_file
  36. check_single_entry
  37. panel_operate_generate_prompt
  38. do_confirm_copy_move
  39. do_confirm_erase
  40. operate_single_file
  41. operate_one_file
  42. end_bg_process
  43. attrs_ignore_error
  44. file_is_symlink_to_dir
  45. copy_file_file
  46. copy_dir_dir
  47. move_dir_dir
  48. erase_dir
  49. dirsize_status_init_cb
  50. dirsize_status_update_cb
  51. dirsize_status_deinit_cb
  52. compute_dir_size
  53. panel_operate
  54. file_error

   1 /*
   2    File management.
   3 
   4    Copyright (C) 1994-2024
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Janne Kukonlehto, 1994, 1995
   9    Fred Leeflang, 1994, 1995
  10    Miguel de Icaza, 1994, 1995, 1996
  11    Jakub Jelinek, 1995, 1996
  12    Norbert Warmuth, 1997
  13    Pavel Machek, 1998
  14    Andrew Borodin <aborodin@vmail.ru>, 2011-2022
  15 
  16    The copy code was based in GNU's cp, and was written by:
  17    Torbjorn Granlund, David MacKenzie, and Jim Meyering.
  18 
  19    The move code was based in GNU's mv, and was written by:
  20    Mike Parker and David MacKenzie.
  21 
  22    Janne Kukonlehto added much error recovery to them for being used
  23    in an interactive program.
  24 
  25    This file is part of the Midnight Commander.
  26 
  27    The Midnight Commander is free software: you can redistribute it
  28    and/or modify it under the terms of the GNU General Public License as
  29    published by the Free Software Foundation, either version 3 of the License,
  30    or (at your option) any later version.
  31 
  32    The Midnight Commander is distributed in the hope that it will be useful,
  33    but WITHOUT ANY WARRANTY; without even the implied warranty of
  34    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  35    GNU General Public License for more details.
  36 
  37    You should have received a copy of the GNU General Public License
  38    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  39  */
  40 
  41 /*
  42  * Please note that all dialogs used here must be safe for background
  43  * operations.
  44  */
  45 
  46 /** \file src/filemanager/file.c
  47  *  \brief Source: file management
  48  */
  49 
  50 /* {{{ Include files */
  51 
  52 #include <config.h>
  53 
  54 #include <ctype.h>
  55 #include <errno.h>
  56 #include <stdlib.h>
  57 #include <stdio.h>
  58 #include <string.h>
  59 #include <sys/types.h>
  60 #include <sys/stat.h>
  61 #include <unistd.h>
  62 
  63 #include "lib/global.h"
  64 #include "lib/tty/tty.h"
  65 #include "lib/tty/key.h"
  66 #include "lib/search.h"
  67 #include "lib/strutil.h"
  68 #include "lib/util.h"
  69 #include "lib/vfs/vfs.h"
  70 #include "lib/vfs/utilvfs.h"
  71 #include "lib/widget.h"
  72 
  73 #include "src/setup.h"
  74 #ifdef ENABLE_BACKGROUND
  75 #include "src/background.h"     /* do_background() */
  76 #endif
  77 
  78 /* Needed for other_panel and WTree */
  79 #include "dir.h"
  80 #include "filegui.h"
  81 #include "filenot.h"
  82 #include "tree.h"
  83 #include "filemanager.h"        /* other_panel */
  84 #include "layout.h"             /* rotate_dash() */
  85 #include "ioblksize.h"          /* io_blksize() */
  86 
  87 #include "file.h"
  88 
  89 /* }}} */
  90 
  91 /*** global variables ****************************************************************************/
  92 
  93 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix  */
  94 const char *op_names[3] = {
  95     N_("DialogTitle|Copy"),
  96     N_("DialogTitle|Move"),
  97     N_("DialogTitle|Delete")
  98 };
  99 
 100 /*** file scope macro definitions ****************************************************************/
 101 
 102 #define FILEOP_UPDATE_INTERVAL 2
 103 #define FILEOP_STALLING_INTERVAL 4
 104 #define FILEOP_UPDATE_INTERVAL_US (FILEOP_UPDATE_INTERVAL * G_USEC_PER_SEC)
 105 #define FILEOP_STALLING_INTERVAL_US (FILEOP_STALLING_INTERVAL * G_USEC_PER_SEC)
 106 
 107 /*** file scope type declarations ****************************************************************/
 108 
 109 /* This is a hard link cache */
 110 typedef struct
 111 {
 112     const struct vfs_class *vfs;
 113     dev_t dev;
 114     ino_t ino;
 115     mode_t st_mode;
 116     vfs_path_t *src_vpath;
 117     vfs_path_t *dst_vpath;
 118 } link_t;
 119 
 120 /* Status of the destination file */
 121 typedef enum
 122 {
 123     DEST_NONE = 0,              /**< Not created */
 124     DEST_SHORT_QUERY,           /**< Created, not fully copied, query to do */
 125     DEST_SHORT_KEEP,            /**< Created, not fully copied, keep it */
 126     DEST_SHORT_DELETE,          /**< Created, not fully copied, delete it */
 127     DEST_FULL                   /**< Created, fully copied */
 128 } dest_status_t;
 129 
 130 /* Status of hard link creation */
 131 typedef enum
 132 {
 133     HARDLINK_OK = 0,            /**< Hardlink was created successfully */
 134     HARDLINK_CACHED,            /**< Hardlink was added to the cache */
 135     HARDLINK_NOTLINK,           /**< This is not a hard link */
 136     HARDLINK_UNSUPPORTED,       /**< VFS doesn't support hard links */
 137     HARDLINK_ERROR,             /**< Hard link creation error */
 138     HARDLINK_ABORT              /**< Stop file operation after hardlink creation error */
 139 } hardlink_status_t;
 140 
 141 /*
 142  * This array introduced to avoid translation problems. The former (op_names)
 143  * is assumed to be nouns, suitable in dialog box titles; this one should
 144  * contain whatever is used in prompt itself (i.e. in russian, it's verb).
 145  * (I don't use spaces around the words, because someday they could be
 146  * dropped, when widgets get smarter)
 147  */
 148 
 149 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix  */
 150 static const char *op_names1[] = {
 151     N_("FileOperation|Copy"),
 152     N_("FileOperation|Move"),
 153     N_("FileOperation|Delete")
 154 };
 155 
 156 /*
 157  * These are formats for building a prompt. Parts encoded as follows:
 158  * %o - operation from op_names1
 159  * %f - file/files or files/directories, as appropriate
 160  * %m - "with source mask" or question mark for delete
 161  * %s - source name (truncated)
 162  * %d - number of marked files
 163  * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
 164  */
 165 /* xgettext:no-c-format */
 166 static const char *one_format = N_("%o %f%n\"%s\"%m");
 167 /* xgettext:no-c-format */
 168 static const char *many_format = N_("%o %d %f%m");
 169 
 170 static const char *prompt_parts[] = {
 171     N_("file"),
 172     N_("files"),
 173     N_("directory"),
 174     N_("directories"),
 175     N_("files/directories"),
 176     /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
 177     N_(" with source mask:")
 178 };
 179 
 180 /*** forward declarations (file scope functions) *************************************************/
 181 
 182 /*** file scope variables ************************************************************************/
 183 
 184 /* the hard link cache */
 185 static GSList *linklist = NULL;
 186 
 187 /* the files-to-be-erased list */
 188 static GQueue *erase_list = NULL;
 189 
 190 /*
 191  * This list holds information about just created target directories and is used to detect
 192  * when an directory is copied into itself (we don't want to copy infinitely).
 193  */
 194 static GSList *dest_dirs = NULL;
 195 
 196 /* --------------------------------------------------------------------------------------------- */
 197 /*** file scope functions ************************************************************************/
 198 /* --------------------------------------------------------------------------------------------- */
 199 
 200 static void
 201 dirsize_status_locate_buttons (dirsize_status_msg_t *dsm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 202 {
 203     status_msg_t *sm = STATUS_MSG (dsm);
 204     Widget *wd = WIDGET (sm->dlg);
 205     int y, x;
 206     WRect r;
 207 
 208     y = wd->rect.y + 5;
 209     x = wd->rect.x;
 210 
 211     if (!dsm->allow_skip)
 212     {
 213         /* single button: "Abort" */
 214         x += (wd->rect.cols - dsm->abort_button->rect.cols) / 2;
 215         r = dsm->abort_button->rect;
 216         r.y = y;
 217         r.x = x;
 218         widget_set_size_rect (dsm->abort_button, &r);
 219     }
 220     else
 221     {
 222         /* two buttons: "Abort" and "Skip" */
 223         int cols;
 224 
 225         cols = dsm->abort_button->rect.cols + dsm->skip_button->rect.cols + 1;
 226         x += (wd->rect.cols - cols) / 2;
 227         r = dsm->abort_button->rect;
 228         r.y = y;
 229         r.x = x;
 230         widget_set_size_rect (dsm->abort_button, &r);
 231         x += dsm->abort_button->rect.cols + 1;
 232         r = dsm->skip_button->rect;
 233         r.y = y;
 234         r.x = x;
 235         widget_set_size_rect (dsm->skip_button, &r);
 236     }
 237 }
 238 
 239 /* --------------------------------------------------------------------------------------------- */
 240 
 241 static char *
 242 build_dest (file_op_context_t *ctx, const char *src, const char *dest, FileProgressStatus *status)
     /* [previous][next][first][last][top][bottom][index][help]  */
 243 {
 244     char *s, *q;
 245     const char *fnsource;
 246 
 247     *status = FILE_CONT;
 248 
 249     s = g_strdup (src);
 250 
 251     /* We remove \n from the filename since regex routines would use \n as an anchor */
 252     /* this is just to be allowed to maniupulate file names with \n on it */
 253     for (q = s; *q != '\0'; q++)
 254         if (*q == '\n')
 255             *q = ' ';
 256 
 257     fnsource = x_basename (s);
 258 
 259     if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
 260     {
 261         q = NULL;
 262         *status = FILE_SKIP;
 263     }
 264     else
 265     {
 266         q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
 267         if (ctx->search_handle->error != MC_SEARCH_E_OK)
 268         {
 269             if (ctx->search_handle->error_str != NULL)
 270                 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
 271 
 272             *status = FILE_ABORT;
 273         }
 274     }
 275 
 276     MC_PTR_FREE (s);
 277 
 278     if (*status == FILE_CONT)
 279     {
 280         char *repl_dest;
 281 
 282         repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
 283         if (ctx->search_handle->error == MC_SEARCH_E_OK)
 284             s = mc_build_filename (repl_dest, q, (char *) NULL);
 285         else
 286         {
 287             if (ctx->search_handle->error_str != NULL)
 288                 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
 289 
 290             *status = FILE_ABORT;
 291         }
 292 
 293         g_free (repl_dest);
 294     }
 295 
 296     g_free (q);
 297 
 298     return s;
 299 }
 300 
 301 /* --------------------------------------------------------------------------------------------- */
 302 
 303 static void
 304 free_link (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 305 {
 306     link_t *lp = (link_t *) data;
 307 
 308     vfs_path_free (lp->src_vpath, TRUE);
 309     vfs_path_free (lp->dst_vpath, TRUE);
 310     g_free (lp);
 311 }
 312 
 313 /* --------------------------------------------------------------------------------------------- */
 314 
 315 static inline void *
 316 free_erase_list (GQueue *lp)
     /* [previous][next][first][last][top][bottom][index][help]  */
 317 {
 318     if (lp != NULL)
 319         g_queue_free_full (lp, free_link);
 320 
 321     return NULL;
 322 }
 323 
 324 /* --------------------------------------------------------------------------------------------- */
 325 
 326 static inline void *
 327 free_linklist (GSList *lp)
     /* [previous][next][first][last][top][bottom][index][help]  */
 328 {
 329     g_slist_free_full (lp, free_link);
 330 
 331     return NULL;
 332 }
 333 
 334 /* --------------------------------------------------------------------------------------------- */
 335 
 336 static const link_t *
 337 is_in_linklist (const GSList *lp, const vfs_path_t *vpath, const struct stat *sb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 338 {
 339     const struct vfs_class *class;
 340     ino_t ino = sb->st_ino;
 341     dev_t dev = sb->st_dev;
 342 
 343     class = vfs_path_get_last_path_vfs (vpath);
 344 
 345     for (; lp != NULL; lp = (const GSList *) g_slist_next (lp))
 346     {
 347         const link_t *lnk = (const link_t *) lp->data;
 348 
 349         if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev)
 350             return lnk;
 351     }
 352 
 353     return NULL;
 354 }
 355 
 356 /* --------------------------------------------------------------------------------------------- */
 357 /**
 358  * Check and made hardlink
 359  *
 360  * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
 361  * and a hardlink was successfully made
 362  */
 363 
 364 static hardlink_status_t
 365 check_hardlinks (const vfs_path_t *src_vpath, const struct stat *src_stat,
     /* [previous][next][first][last][top][bottom][index][help]  */
 366                  const vfs_path_t *dst_vpath, gboolean *ignore_all)
 367 {
 368     link_t *lnk;
 369     ino_t ino = src_stat->st_ino;
 370     dev_t dev = src_stat->st_dev;
 371 
 372     if (src_stat->st_nlink < 2)
 373         return HARDLINK_NOTLINK;
 374     if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0)
 375         return HARDLINK_UNSUPPORTED;
 376 
 377     lnk = (link_t *) is_in_linklist (linklist, src_vpath, src_stat);
 378     if (lnk != NULL)
 379     {
 380         int stat_result;
 381         struct stat link_stat;
 382 
 383         stat_result = mc_stat (lnk->src_vpath, &link_stat);
 384 
 385         if (stat_result == 0 && link_stat.st_ino == ino && link_stat.st_dev == dev)
 386         {
 387             const struct vfs_class *lp_name_class;
 388             const struct vfs_class *my_vfs;
 389 
 390             lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath);
 391             my_vfs = vfs_path_get_last_path_vfs (src_vpath);
 392 
 393             if (lp_name_class == my_vfs)
 394             {
 395                 const struct vfs_class *p_class, *dst_name_class;
 396 
 397                 dst_name_class = vfs_path_get_last_path_vfs (dst_vpath);
 398                 p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath);
 399 
 400                 if (dst_name_class == p_class)
 401                 {
 402                     gboolean ok;
 403 
 404                     while (!(ok = (mc_stat (lnk->dst_vpath, &link_stat) == 0)) && !*ignore_all)
 405                     {
 406                         FileProgressStatus status;
 407 
 408                         status =
 409                             file_error (TRUE, _("Cannot stat hardlink source file \"%s\"\n%s"),
 410                                         vfs_path_as_str (lnk->dst_vpath));
 411                         if (status == FILE_ABORT)
 412                             return HARDLINK_ABORT;
 413                         if (status == FILE_RETRY)
 414                             continue;
 415                         if (status == FILE_IGNORE_ALL)
 416                             *ignore_all = TRUE;
 417                         break;
 418                     }
 419 
 420                     /* if stat() finished unsuccessfully, don't try to create link */
 421                     if (!ok)
 422                         return HARDLINK_ERROR;
 423 
 424                     while (!(ok = (mc_link (lnk->dst_vpath, dst_vpath) == 0)) && !*ignore_all)
 425                     {
 426                         FileProgressStatus status;
 427 
 428                         status =
 429                             file_error (TRUE, _("Cannot create target hardlink \"%s\"\n%s"),
 430                                         vfs_path_as_str (dst_vpath));
 431                         if (status == FILE_ABORT)
 432                             return HARDLINK_ABORT;
 433                         if (status == FILE_RETRY)
 434                             continue;
 435                         if (status == FILE_IGNORE_ALL)
 436                             *ignore_all = TRUE;
 437                         break;
 438                     }
 439 
 440                     /* Success? */
 441                     return (ok ? HARDLINK_OK : HARDLINK_ERROR);
 442                 }
 443             }
 444         }
 445 
 446         if (!*ignore_all)
 447         {
 448             FileProgressStatus status;
 449 
 450             /* Message w/o "Retry" action.
 451              *
 452              * FIXME: Can't say what errno is here. Define it and don't display.
 453              *
 454              * file_error() displays a message with text representation of errno
 455              * and the string passed to file_error() should provide the format "%s"
 456              * for that at end (see previous file_error() call for the reference).
 457              * But if format for errno isn't provided, it is safe, because C standard says:
 458              * "If the format is exhausted while arguments remain, the excess arguments
 459              * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999,
 460              * section 7.19.6.1, paragraph 2).
 461              *
 462              */
 463             errno = 0;
 464             status =
 465                 file_error (FALSE, _("Cannot create target hardlink \"%s\""),
 466                             vfs_path_as_str (dst_vpath));
 467 
 468             if (status == FILE_ABORT)
 469                 return HARDLINK_ABORT;
 470 
 471             if (status == FILE_IGNORE_ALL)
 472                 *ignore_all = TRUE;
 473         }
 474 
 475         return HARDLINK_ERROR;
 476     }
 477 
 478     lnk = g_try_new (link_t, 1);
 479     if (lnk != NULL)
 480     {
 481         lnk->vfs = vfs_path_get_last_path_vfs (src_vpath);
 482         lnk->ino = ino;
 483         lnk->dev = dev;
 484         lnk->st_mode = 0;
 485         lnk->src_vpath = vfs_path_clone (src_vpath);
 486         lnk->dst_vpath = vfs_path_clone (dst_vpath);
 487 
 488         linklist = g_slist_prepend (linklist, lnk);
 489     }
 490 
 491     return HARDLINK_CACHED;
 492 }
 493 
 494 /* --------------------------------------------------------------------------------------------- */
 495 /**
 496  * Duplicate the contents of the symbolic link src_vpath in dst_vpath.
 497  * Try to make a stable symlink if the option "stable symlink" was
 498  * set in the file mask dialog.
 499  * If dst_path is an existing symlink it will be deleted silently
 500  * (upper levels take already care of existing files at dst_vpath).
 501  */
 502 
 503 static FileProgressStatus
 504 make_symlink (file_op_context_t *ctx, const vfs_path_t *src_vpath, const vfs_path_t *dst_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 505 {
 506     const char *src_path;
 507     const char *dst_path;
 508     char link_target[MC_MAXPATHLEN];
 509     int len;
 510     FileProgressStatus return_status;
 511     struct stat dst_stat;
 512     gboolean dst_is_symlink;
 513     vfs_path_t *link_target_vpath = NULL;
 514 
 515     src_path = vfs_path_as_str (src_vpath);
 516     dst_path = vfs_path_as_str (dst_vpath);
 517 
 518     dst_is_symlink = (mc_lstat (dst_vpath, &dst_stat) == 0) && S_ISLNK (dst_stat.st_mode);
 519 
 520   retry_src_readlink:
 521     len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1);
 522     if (len < 0)
 523     {
 524         if (ctx->ignore_all)
 525             return_status = FILE_IGNORE_ALL;
 526         else
 527         {
 528             return_status = file_error (TRUE, _("Cannot read source link \"%s\"\n%s"), src_path);
 529             if (return_status == FILE_IGNORE_ALL)
 530                 ctx->ignore_all = TRUE;
 531             if (return_status == FILE_RETRY)
 532                 goto retry_src_readlink;
 533         }
 534         goto ret;
 535     }
 536 
 537     link_target[len] = '\0';
 538 
 539     if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath)))
 540     {
 541         message (D_ERROR, MSG_ERROR,
 542                  _("Cannot make stable symlinks across "
 543                    "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
 544         ctx->stable_symlinks = FALSE;
 545     }
 546 
 547     if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
 548     {
 549         const char *r;
 550 
 551         r = strrchr (src_path, PATH_SEP);
 552         if (r != NULL)
 553         {
 554             size_t slen;
 555             GString *p;
 556             vfs_path_t *q;
 557 
 558             slen = r - src_path + 1;
 559 
 560             p = g_string_sized_new (slen + len);
 561             g_string_append_len (p, src_path, slen);
 562 
 563             if (g_path_is_absolute (dst_path))
 564                 q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
 565             else
 566                 q = vfs_path_build_filename (p->str, dst_path, (char *) NULL);
 567 
 568             if (vfs_path_tokens_count (q) > 1)
 569             {
 570                 char *s = NULL;
 571                 vfs_path_t *tmp_vpath1, *tmp_vpath2;
 572 
 573                 g_string_append_len (p, link_target, len);
 574                 tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
 575                 tmp_vpath2 = vfs_path_from_str (p->str);
 576                 s = diff_two_paths (tmp_vpath1, tmp_vpath2);
 577                 vfs_path_free (tmp_vpath2, TRUE);
 578                 vfs_path_free (tmp_vpath1, TRUE);
 579                 g_strlcpy (link_target, s != NULL ? s : p->str, sizeof (link_target));
 580                 g_free (s);
 581             }
 582 
 583             g_string_free (p, TRUE);
 584             vfs_path_free (q, TRUE);
 585         }
 586     }
 587     link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON);
 588 
 589   retry_dst_symlink:
 590     if (mc_symlink (link_target_vpath, dst_vpath) == 0)
 591     {
 592         /* Success */
 593         return_status = FILE_CONT;
 594         goto ret;
 595     }
 596     /*
 597      * if dst_exists, it is obvious that this had failed.
 598      * We can delete the old symlink and try again...
 599      */
 600     if (dst_is_symlink && mc_unlink (dst_vpath) == 0
 601         && mc_symlink (link_target_vpath, dst_vpath) == 0)
 602     {
 603         /* Success */
 604         return_status = FILE_CONT;
 605         goto ret;
 606     }
 607 
 608     if (ctx->ignore_all)
 609         return_status = FILE_IGNORE_ALL;
 610     else
 611     {
 612         return_status = file_error (TRUE, _("Cannot create target symlink \"%s\"\n%s"), dst_path);
 613         if (return_status == FILE_IGNORE_ALL)
 614             ctx->ignore_all = TRUE;
 615         if (return_status == FILE_RETRY)
 616             goto retry_dst_symlink;
 617     }
 618 
 619   ret:
 620     vfs_path_free (link_target_vpath, TRUE);
 621     return return_status;
 622 }
 623 
 624 /* --------------------------------------------------------------------------------------------- */
 625 /**
 626  * do_compute_dir_size:
 627  *
 628  * Computes the number of bytes used by the files in a directory
 629  */
 630 
 631 static FileProgressStatus
 632 do_compute_dir_size (const vfs_path_t *dirname_vpath, dirsize_status_msg_t *dsm,
     /* [previous][next][first][last][top][bottom][index][help]  */
 633                      size_t *dir_count, size_t *ret_marked, uintmax_t *ret_total,
 634                      mc_stat_fn stat_func)
 635 {
 636     static gint64 timestamp = 0;
 637     /* update with 25 FPS rate */
 638     static const gint64 delay = G_USEC_PER_SEC / 25;
 639 
 640     status_msg_t *sm = STATUS_MSG (dsm);
 641     int res;
 642     struct stat s;
 643     DIR *dir;
 644     struct vfs_dirent *dirent;
 645     FileProgressStatus ret = FILE_CONT;
 646 
 647     (*dir_count)++;
 648 
 649     dir = mc_opendir (dirname_vpath);
 650     if (dir == NULL)
 651         return ret;
 652 
 653     while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL)
 654     {
 655         vfs_path_t *tmp_vpath;
 656 
 657         if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name))
 658             continue;
 659 
 660         tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL);
 661 
 662         res = stat_func (tmp_vpath, &s);
 663         if (res == 0)
 664         {
 665             if (S_ISDIR (s.st_mode))
 666                 ret =
 667                     do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total,
 668                                          stat_func);
 669             else
 670             {
 671                 ret = FILE_CONT;
 672 
 673                 (*ret_marked)++;
 674                 *ret_total += (uintmax_t) s.st_size;
 675             }
 676 
 677             if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (&timestamp, delay))
 678             {
 679                 dsm->dirname_vpath = tmp_vpath;
 680                 dsm->dir_count = *dir_count;
 681                 dsm->total_size = *ret_total;
 682                 ret = sm->update (sm);
 683             }
 684         }
 685 
 686         vfs_path_free (tmp_vpath, TRUE);
 687     }
 688 
 689     mc_closedir (dir);
 690     return ret;
 691 }
 692 
 693 /* --------------------------------------------------------------------------------------------- */
 694 /**
 695  * panel_compute_totals:
 696  *
 697  * compute the number of files and the number of bytes
 698  * used up by the whole selection, recursing directories
 699  * as required.  In addition, it checks to see if it will
 700  * overwrite any files by doing the copy.
 701  */
 702 
 703 static FileProgressStatus
 704 panel_compute_totals (const WPanel *panel, dirsize_status_msg_t *sm, size_t *ret_count,
     /* [previous][next][first][last][top][bottom][index][help]  */
 705                       uintmax_t *ret_total, gboolean follow_symlinks)
 706 {
 707     int i;
 708     size_t dir_count = 0;
 709     mc_stat_fn stat_func = follow_symlinks ? mc_stat : mc_lstat;
 710 
 711     for (i = 0; i < panel->dir.len; i++)
 712     {
 713         const file_entry_t *fe = &panel->dir.list[i];
 714         const struct stat *s;
 715 
 716         if (fe->f.marked == 0)
 717             continue;
 718 
 719         s = &fe->st;
 720 
 721         if (S_ISDIR (s->st_mode) || (follow_symlinks && link_isdir (fe) && fe->f.stale_link == 0))
 722         {
 723             vfs_path_t *p;
 724             FileProgressStatus status;
 725 
 726             p = vfs_path_append_new (panel->cwd_vpath, fe->fname->str, (char *) NULL);
 727             status = do_compute_dir_size (p, sm, &dir_count, ret_count, ret_total, stat_func);
 728             vfs_path_free (p, TRUE);
 729 
 730             if (status != FILE_CONT)
 731                 return status;
 732         }
 733         else
 734         {
 735             (*ret_count)++;
 736             *ret_total += (uintmax_t) s->st_size;
 737         }
 738     }
 739 
 740     return FILE_CONT;
 741 }
 742 
 743 /* --------------------------------------------------------------------------------------------- */
 744 
 745 /** Initialize variables for progress bars */
 746 static FileProgressStatus
 747 panel_operate_init_totals (const WPanel *panel, const vfs_path_t *source,
     /* [previous][next][first][last][top][bottom][index][help]  */
 748                            const struct stat *source_stat, file_op_context_t *ctx,
 749                            gboolean compute_totals, filegui_dialog_type_t dialog_type)
 750 {
 751     FileProgressStatus status;
 752 
 753 #ifdef ENABLE_BACKGROUND
 754     if (mc_global.we_are_background)
 755         return FILE_CONT;
 756 #endif
 757 
 758     if (verbose && compute_totals)
 759     {
 760         dirsize_status_msg_t dsm;
 761         gboolean stale_link = FALSE;
 762 
 763         memset (&dsm, 0, sizeof (dsm));
 764         dsm.allow_skip = TRUE;
 765         status_msg_init (STATUS_MSG (&dsm), _("Directory scanning"), 0, dirsize_status_init_cb,
 766                          dirsize_status_update_cb, dirsize_status_deinit_cb);
 767 
 768         ctx->progress_count = 0;
 769         ctx->progress_bytes = 0;
 770 
 771         if (source == NULL)
 772             status = panel_compute_totals (panel, &dsm, &ctx->progress_count, &ctx->progress_bytes,
 773                                            ctx->follow_links);
 774         else if (S_ISDIR (source_stat->st_mode)
 775                  || (ctx->follow_links
 776                      && file_is_symlink_to_dir (source, (struct stat *) source_stat, &stale_link)
 777                      && !stale_link))
 778         {
 779             size_t dir_count = 0;
 780 
 781             status = do_compute_dir_size (source, &dsm, &dir_count, &ctx->progress_count,
 782                                           &ctx->progress_bytes, ctx->stat_func);
 783         }
 784         else
 785         {
 786             ctx->progress_count++;
 787             ctx->progress_bytes += (uintmax_t) source_stat->st_size;
 788             status = FILE_CONT;
 789         }
 790 
 791         status_msg_deinit (STATUS_MSG (&dsm));
 792 
 793         ctx->progress_totals_computed = (status == FILE_CONT);
 794 
 795         if (status == FILE_SKIP)
 796             status = FILE_CONT;
 797     }
 798     else
 799     {
 800         status = FILE_CONT;
 801         ctx->progress_count = panel->marked;
 802         ctx->progress_bytes = panel->total;
 803         ctx->progress_totals_computed = verbose && dialog_type == FILEGUI_DIALOG_ONE_ITEM;
 804     }
 805 
 806     /* destroy already created UI for single file rename operation */
 807     file_op_context_destroy_ui (ctx);
 808 
 809     file_op_context_create_ui (ctx, TRUE, dialog_type);
 810 
 811     return status;
 812 }
 813 
 814 /* --------------------------------------------------------------------------------------------- */
 815 
 816 static FileProgressStatus
 817 progress_update_one (file_op_total_context_t *tctx, file_op_context_t *ctx, off_t add)
     /* [previous][next][first][last][top][bottom][index][help]  */
 818 {
 819     gint64 tv_current;
 820     static gint64 tv_start = -1;
 821 
 822     tctx->progress_count++;
 823     tctx->progress_bytes += (uintmax_t) add;
 824 
 825     tv_current = g_get_monotonic_time ();
 826 
 827     if (tv_start < 0)
 828         tv_start = tv_current;
 829 
 830     if (tv_current - tv_start > FILEOP_UPDATE_INTERVAL_US)
 831     {
 832         if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
 833         {
 834             file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
 835             file_progress_show_total (tctx, ctx, tctx->progress_bytes, TRUE);
 836         }
 837 
 838         tv_start = tv_current;
 839     }
 840 
 841     return check_progress_buttons (ctx);
 842 }
 843 
 844 /* --------------------------------------------------------------------------------------------- */
 845 
 846 static FileProgressStatus
 847 real_warn_same_file (enum OperationMode mode, const char *fmt, const char *a, const char *b)
     /* [previous][next][first][last][top][bottom][index][help]  */
 848 {
 849     char *msg;
 850     int result = 0;
 851     const char *head_msg;
 852     int width_a, width_b, width;
 853 
 854     head_msg = mode == Foreground ? MSG_ERROR : _("Background process error");
 855 
 856     width_a = str_term_width1 (a);
 857     width_b = str_term_width1 (b);
 858     width = COLS - 8;
 859 
 860     if (width_a > width)
 861     {
 862         if (width_b > width)
 863         {
 864             char *s;
 865 
 866             s = g_strndup (str_trunc (a, width), width);
 867             b = str_trunc (b, width);
 868             msg = g_strdup_printf (fmt, s, b);
 869             g_free (s);
 870         }
 871         else
 872         {
 873             a = str_trunc (a, width);
 874             msg = g_strdup_printf (fmt, a, b);
 875         }
 876     }
 877     else
 878     {
 879         if (width_b > width)
 880             b = str_trunc (b, width);
 881 
 882         msg = g_strdup_printf (fmt, a, b);
 883     }
 884 
 885     result = query_dialog (head_msg, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
 886     g_free (msg);
 887     do_refresh ();
 888 
 889     return (result == 1) ? FILE_ABORT : FILE_SKIP;
 890 }
 891 
 892 /* --------------------------------------------------------------------------------------------- */
 893 
 894 static FileProgressStatus
 895 warn_same_file (const char *fmt, const char *a, const char *b)
     /* [previous][next][first][last][top][bottom][index][help]  */
 896 {
 897 #ifdef ENABLE_BACKGROUND
 898 /* *INDENT-OFF* */
 899     union
 900     {
 901         void *p;
 902         FileProgressStatus (*f) (enum OperationMode, const char *fmt, const char *a, const char *b);
 903     } pntr;
 904 /* *INDENT-ON* */
 905 
 906     pntr.f = real_warn_same_file;
 907 
 908     if (mc_global.we_are_background)
 909         return parent_call (pntr.p, NULL, 3, strlen (fmt), fmt, strlen (a), a, strlen (b), b);
 910 #endif
 911     return real_warn_same_file (Foreground, fmt, a, b);
 912 }
 913 
 914 /* --------------------------------------------------------------------------------------------- */
 915 
 916 static gboolean
 917 check_same_file (const char *a, const struct stat *ast, const char *b, const struct stat *bst,
     /* [previous][next][first][last][top][bottom][index][help]  */
 918                  FileProgressStatus *status)
 919 {
 920     if (ast->st_dev != bst->st_dev || ast->st_ino != bst->st_ino)
 921         return FALSE;
 922 
 923     if (S_ISDIR (ast->st_mode))
 924         *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same directory"), a, b);
 925     else
 926         *status = warn_same_file (_("\"%s\"\nand\n\"%s\"\nare the same file"), a, b);
 927 
 928     return TRUE;
 929 }
 930 
 931 /* --------------------------------------------------------------------------------------------- */
 932 /* {{{ Query/status report routines */
 933 
 934 static FileProgressStatus
 935 real_do_file_error (enum OperationMode mode, gboolean allow_retry, const char *error)
     /* [previous][next][first][last][top][bottom][index][help]  */
 936 {
 937     int result;
 938     const char *msg;
 939 
 940     msg = mode == Foreground ? MSG_ERROR : _("Background process error");
 941 
 942     if (allow_retry)
 943         result =
 944             query_dialog (msg, error, D_ERROR, 4, _("&Ignore"), _("Ignore a&ll"), _("&Retry"),
 945                           _("&Abort"));
 946     else
 947         result = query_dialog (msg, error, D_ERROR, 3, _("&Ignore"), _("Ignore a&ll"), _("&Abort"));
 948 
 949     switch (result)
 950     {
 951     case 0:
 952         do_refresh ();
 953         return FILE_IGNORE;
 954 
 955     case 1:
 956         do_refresh ();
 957         return FILE_IGNORE_ALL;
 958 
 959     case 2:
 960         if (allow_retry)
 961         {
 962             do_refresh ();
 963             return FILE_RETRY;
 964         }
 965         MC_FALLTHROUGH;
 966 
 967     case 3:
 968     default:
 969         return FILE_ABORT;
 970     }
 971 }
 972 
 973 /* --------------------------------------------------------------------------------------------- */
 974 
 975 static FileProgressStatus
 976 real_query_recursive (file_op_context_t *ctx, enum OperationMode mode, const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
 977 {
 978     if (ctx->recursive_result < RECURSIVE_ALWAYS)
 979     {
 980         const char *msg;
 981         char *text;
 982 
 983         msg = mode == Foreground
 984             ? _("Directory \"%s\" not empty.\nDelete it recursively?")
 985             : _("Background process:\nDirectory \"%s\" not empty.\nDelete it recursively?");
 986         text = g_strdup_printf (msg, path_trunc (s, 30));
 987 
 988         if (safe_delete)
 989             query_set_sel (1);
 990 
 991         ctx->recursive_result =
 992             query_dialog (op_names[OP_DELETE], text, D_ERROR, 5, _("&Yes"), _("&No"), _("A&ll"),
 993                           _("Non&e"), _("&Abort"));
 994         g_free (text);
 995 
 996         if (ctx->recursive_result != RECURSIVE_ABORT)
 997             do_refresh ();
 998     }
 999 
1000     switch (ctx->recursive_result)
1001     {
1002     case RECURSIVE_YES:
1003     case RECURSIVE_ALWAYS:
1004         return FILE_CONT;
1005 
1006     case RECURSIVE_NO:
1007     case RECURSIVE_NEVER:
1008         return FILE_SKIP;
1009 
1010     case RECURSIVE_ABORT:
1011     default:
1012         return FILE_ABORT;
1013     }
1014 }
1015 
1016 /* --------------------------------------------------------------------------------------------- */
1017 
1018 #ifdef ENABLE_BACKGROUND
1019 static FileProgressStatus
1020 do_file_error (gboolean allow_retry, const char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
1021 {
1022 /* *INDENT-OFF* */
1023     union
1024     {
1025         void *p;
1026         FileProgressStatus (*f) (enum OperationMode, gboolean, const char *);
1027     } pntr;
1028 /* *INDENT-ON* */
1029 
1030     pntr.f = real_do_file_error;
1031 
1032     if (mc_global.we_are_background)
1033         return parent_call (pntr.p, NULL, 2, sizeof (allow_retry), allow_retry, strlen (str), str);
1034     else
1035         return real_do_file_error (Foreground, allow_retry, str);
1036 }
1037 
1038 /* --------------------------------------------------------------------------------------------- */
1039 
1040 static FileProgressStatus
1041 query_recursive (file_op_context_t *ctx, const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
1042 {
1043 /* *INDENT-OFF* */
1044     union
1045     {
1046         void *p;
1047         FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *);
1048     } pntr;
1049 /* *INDENT-ON* */
1050 
1051     pntr.f = real_query_recursive;
1052 
1053     if (mc_global.we_are_background)
1054         return parent_call (pntr.p, ctx, 1, strlen (s), s);
1055     else
1056         return real_query_recursive (ctx, Foreground, s);
1057 }
1058 
1059 /* --------------------------------------------------------------------------------------------- */
1060 
1061 static FileProgressStatus
1062 query_replace (file_op_context_t *ctx, const char *src, struct stat *src_stat, const char *dst,
     /* [previous][next][first][last][top][bottom][index][help]  */
1063                struct stat *dst_stat)
1064 {
1065 /* *INDENT-OFF* */
1066     union
1067     {
1068         void *p;
1069         FileProgressStatus (*f) (file_op_context_t *, enum OperationMode, const char *,
1070                                  struct stat *, const char *, struct stat *);
1071     } pntr;
1072 /* *INDENT-ON* */
1073 
1074     pntr.f = file_progress_real_query_replace;
1075 
1076     if (mc_global.we_are_background)
1077         return parent_call (pntr.p, ctx, 4, strlen (src), src, sizeof (struct stat), src_stat,
1078                             strlen (dst), dst, sizeof (struct stat), dst_stat);
1079     else
1080         return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
1081 }
1082 
1083 #else
1084 /* --------------------------------------------------------------------------------------------- */
1085 
1086 static FileProgressStatus
1087 do_file_error (gboolean allow_retry, const char *str)
     /* [previous][next][first][last][top][bottom][index][help]  */
1088 {
1089     return real_do_file_error (Foreground, allow_retry, str);
1090 }
1091 
1092 /* --------------------------------------------------------------------------------------------- */
1093 
1094 static FileProgressStatus
1095 query_recursive (file_op_context_t *ctx, const char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
1096 {
1097     return real_query_recursive (ctx, Foreground, s);
1098 }
1099 
1100 /* --------------------------------------------------------------------------------------------- */
1101 
1102 static FileProgressStatus
1103 query_replace (file_op_context_t *ctx, const char *src, struct stat *src_stat, const char *dst,
     /* [previous][next][first][last][top][bottom][index][help]  */
1104                struct stat *dst_stat)
1105 {
1106     return file_progress_real_query_replace (ctx, Foreground, src, src_stat, dst, dst_stat);
1107 }
1108 
1109 #endif /* !ENABLE_BACKGROUND */
1110 
1111 /* --------------------------------------------------------------------------------------------- */
1112 /** Report error with two files */
1113 
1114 static FileProgressStatus
1115 files_error (const char *format, const char *file1, const char *file2)
     /* [previous][next][first][last][top][bottom][index][help]  */
1116 {
1117     char buf[BUF_MEDIUM];
1118     char *nfile1, *nfile2;
1119 
1120     nfile1 = g_strdup (path_trunc (file1, 15));
1121     nfile2 = g_strdup (path_trunc (file2, 15));
1122     g_snprintf (buf, sizeof (buf), format, nfile1, nfile2, unix_error_string (errno));
1123     g_free (nfile1);
1124     g_free (nfile2);
1125 
1126     return do_file_error (TRUE, buf);
1127 }
1128 
1129 /* }}} */
1130 
1131 /* --------------------------------------------------------------------------------------------- */
1132 
1133 static void
1134 copy_file_file_display_progress (file_op_total_context_t *tctx, file_op_context_t *ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
1135                                  gint64 tv_current, gint64 tv_transfer_start, off_t file_size,
1136                                  off_t file_part)
1137 {
1138     gint64 dt;
1139 
1140     /* Update rotating dash after some time */
1141     rotate_dash (TRUE);
1142 
1143     /* Compute ETA */
1144     dt = (tv_current - tv_transfer_start) / G_USEC_PER_SEC;
1145 
1146     if (file_part == 0)
1147         ctx->eta_secs = 0.0;
1148     else
1149         ctx->eta_secs = ((dt / (double) file_part) * file_size) - dt;
1150 
1151     /* Compute BPS rate */
1152     ctx->bps_time = MAX (1, dt);
1153     ctx->bps = file_part / ctx->bps_time;
1154 
1155     /* Compute total ETA and BPS */
1156     if (ctx->progress_bytes != 0)
1157     {
1158         uintmax_t remain_bytes;
1159 
1160         remain_bytes = ctx->progress_bytes - tctx->copied_bytes;
1161 #if 1
1162         {
1163             gint64 total_secs;
1164 
1165             total_secs = (tv_current - tctx->transfer_start) / G_USEC_PER_SEC;
1166             total_secs = MAX (1, total_secs);
1167 
1168             tctx->bps = tctx->copied_bytes / total_secs;
1169             tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
1170         }
1171 #else
1172         /* broken on lot of little files */
1173         tctx->bps_count++;
1174         tctx->bps = (tctx->bps * (tctx->bps_count - 1) + ctx->bps) / tctx->bps_count;
1175         tctx->eta_secs = (tctx->bps != 0) ? remain_bytes / tctx->bps : 0;
1176 #endif
1177     }
1178 }
1179 
1180 /* --------------------------------------------------------------------------------------------- */
1181 
1182 static gboolean
1183 try_remove_file (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status)
     /* [previous][next][first][last][top][bottom][index][help]  */
1184 {
1185     while (mc_unlink (vpath) != 0 && !ctx->ignore_all)
1186     {
1187         *status = file_error (TRUE, _("Cannot remove file \"%s\"\n%s"), vfs_path_as_str (vpath));
1188         if (*status == FILE_RETRY)
1189             continue;
1190         if (*status == FILE_IGNORE_ALL)
1191             ctx->ignore_all = TRUE;
1192         return FALSE;
1193     }
1194 
1195     return TRUE;
1196 }
1197 
1198 /* --------------------------------------------------------------------------------------------- */
1199 
1200 /* {{{ Move routines */
1201 
1202 /**
1203  * Move single file or one of many files from one location to another.
1204  *
1205  * @panel pointer to panel in case of single file, NULL otherwise
1206  * @tctx file operation total context object
1207  * @ctx file operation context object
1208  * @s source file name
1209  * @d destination file name
1210  *
1211  * @return operation result
1212  */
1213 static FileProgressStatus
1214 move_file_file (const WPanel *panel, file_op_total_context_t *tctx, file_op_context_t *ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
1215                 const char *s, const char *d)
1216 {
1217     struct stat src_stat, dst_stat;
1218     FileProgressStatus return_status = FILE_CONT;
1219     gboolean copy_done = FALSE;
1220     gboolean old_ask_overwrite;
1221     vfs_path_t *src_vpath, *dst_vpath;
1222 
1223     src_vpath = vfs_path_from_str (s);
1224     dst_vpath = vfs_path_from_str (d);
1225 
1226     file_progress_show_source (ctx, src_vpath);
1227     file_progress_show_target (ctx, dst_vpath);
1228 
1229     /* FIXME: do we really need to check buttons in case of single file? */
1230     if (check_progress_buttons (ctx) == FILE_ABORT)
1231     {
1232         return_status = FILE_ABORT;
1233         goto ret;
1234     }
1235 
1236     mc_refresh ();
1237 
1238     while (mc_lstat (src_vpath, &src_stat) != 0)
1239     {
1240         /* Source doesn't exist */
1241         if (ctx->ignore_all)
1242             return_status = FILE_IGNORE_ALL;
1243         else
1244         {
1245             return_status = file_error (TRUE, _("Cannot stat file \"%s\"\n%s"), s);
1246             if (return_status == FILE_IGNORE_ALL)
1247                 ctx->ignore_all = TRUE;
1248         }
1249 
1250         if (return_status != FILE_RETRY)
1251             goto ret;
1252     }
1253 
1254     if (mc_lstat (dst_vpath, &dst_stat) == 0)
1255     {
1256         if (check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1257             goto ret;
1258 
1259         if (S_ISDIR (dst_stat.st_mode))
1260         {
1261             message (D_ERROR, MSG_ERROR, _("Cannot overwrite directory \"%s\""), d);
1262             do_refresh ();
1263             return_status = FILE_SKIP;
1264             goto ret;
1265         }
1266 
1267         if (confirm_overwrite)
1268         {
1269             return_status = query_replace (ctx, s, &src_stat, d, &dst_stat);
1270             if (return_status != FILE_CONT)
1271                 goto ret;
1272         }
1273         /* Ok to overwrite */
1274     }
1275 
1276     if (!ctx->do_append)
1277     {
1278         if (S_ISLNK (src_stat.st_mode) && ctx->stable_symlinks)
1279         {
1280             return_status = make_symlink (ctx, src_vpath, dst_vpath);
1281             if (return_status == FILE_CONT)
1282             {
1283                 if (ctx->preserve)
1284                 {
1285                     mc_timesbuf_t times;
1286 
1287                     vfs_get_timesbuf_from_stat (&src_stat, &times);
1288                     mc_utime (dst_vpath, &times);
1289                 }
1290                 goto retry_src_remove;
1291             }
1292             goto ret;
1293         }
1294 
1295         if (mc_rename (src_vpath, dst_vpath) == 0)
1296         {
1297             /* FIXME: do we really need to update progress in case of single file? */
1298             return_status = progress_update_one (tctx, ctx, src_stat.st_size);
1299             goto ret;
1300         }
1301     }
1302 #if 0
1303     /* Comparison to EXDEV seems not to work in nfs if you're moving from
1304        one nfs to the same, but on the server it is on two different
1305        filesystems. Then nfs returns EIO instead of EXDEV.
1306        Hope it will not hurt if we always in case of error try to copy/delete. */
1307     else
1308         errno = EXDEV;          /* Hack to copy (append) the file and then delete it */
1309 
1310     if (errno != EXDEV)
1311     {
1312         if (ctx->ignore_all)
1313             return_status = FILE_IGNORE_ALL;
1314         else
1315         {
1316             return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
1317             if (return_status == FILE_IGNORE_ALL)
1318                 ctx->ignore_all = TRUE;
1319             if (return_status == FILE_RETRY)
1320                 goto retry_rename;
1321         }
1322 
1323         goto ret;
1324     }
1325 #endif
1326 
1327     /* Failed rename -> copy the file instead */
1328     if (panel != NULL)
1329     {
1330         /* In case of single file, calculate totals. In case of many files,
1331            totals are calculated already. */
1332         return_status =
1333             panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1334                                        FILEGUI_DIALOG_ONE_ITEM);
1335         if (return_status != FILE_CONT)
1336             goto ret;
1337     }
1338 
1339     old_ask_overwrite = tctx->ask_overwrite;
1340     tctx->ask_overwrite = FALSE;
1341     return_status = copy_file_file (tctx, ctx, s, d);
1342     tctx->ask_overwrite = old_ask_overwrite;
1343     if (return_status != FILE_CONT)
1344         goto ret;
1345 
1346     copy_done = TRUE;
1347 
1348     /* FIXME: there is no need to update progress and check buttons
1349        at the finish of single file operation. */
1350     if (panel == NULL)
1351     {
1352         file_progress_show_source (ctx, NULL);
1353         file_progress_show (ctx, 0, 0, "", FALSE);
1354 
1355         return_status = check_progress_buttons (ctx);
1356         if (return_status != FILE_CONT)
1357             goto ret;
1358     }
1359 
1360     mc_refresh ();
1361 
1362   retry_src_remove:
1363     if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL)
1364         goto ret;
1365 
1366     if (!copy_done)
1367         return_status = progress_update_one (tctx, ctx, src_stat.st_size);
1368 
1369   ret:
1370     vfs_path_free (src_vpath, TRUE);
1371     vfs_path_free (dst_vpath, TRUE);
1372 
1373     return return_status;
1374 }
1375 
1376 /* }}} */
1377 
1378 /* --------------------------------------------------------------------------------------------- */
1379 /* {{{ Erase routines */
1380 /** Don't update progress status if progress_count==NULL */
1381 
1382 static FileProgressStatus
1383 erase_file (file_op_total_context_t *tctx, file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1384 {
1385     struct stat buf;
1386     FileProgressStatus return_status;
1387 
1388     /* check buttons if deleting info was changed */
1389     if (file_progress_show_deleting (ctx, vpath, &tctx->progress_count))
1390     {
1391         file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1392         if (check_progress_buttons (ctx) == FILE_ABORT)
1393             return FILE_ABORT;
1394 
1395         mc_refresh ();
1396     }
1397 
1398     if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0)
1399     {
1400         /* ignore, most likely the mc_unlink fails, too */
1401         buf.st_size = 0;
1402     }
1403 
1404     if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT)
1405         return FILE_ABORT;
1406 
1407     if (tctx->progress_count == 0)
1408         return FILE_CONT;
1409 
1410     return check_progress_buttons (ctx);
1411 }
1412 
1413 /* --------------------------------------------------------------------------------------------- */
1414 
1415 static FileProgressStatus
1416 try_erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1417 {
1418     const char *dir;
1419     FileProgressStatus return_status = FILE_CONT;
1420 
1421     dir = vfs_path_as_str (vpath);
1422 
1423     while (my_rmdir (dir) != 0 && !ctx->ignore_all)
1424     {
1425         return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir);
1426         if (return_status == FILE_IGNORE_ALL)
1427             ctx->ignore_all = TRUE;
1428         if (return_status != FILE_RETRY)
1429             break;
1430     }
1431 
1432     return return_status;
1433 }
1434 
1435 /* --------------------------------------------------------------------------------------------- */
1436 
1437 /**
1438   Recursive removal of files
1439   abort -> cancel stack
1440   ignore -> warn every level, gets default
1441   ignore_all -> remove as much as possible
1442 */
1443 static FileProgressStatus
1444 recursive_erase (file_op_total_context_t *tctx, file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1445 {
1446     struct vfs_dirent *next;
1447     DIR *reading;
1448     FileProgressStatus return_status = FILE_CONT;
1449 
1450     reading = mc_opendir (vpath);
1451     if (reading == NULL)
1452         return FILE_RETRY;
1453 
1454     while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
1455     {
1456         vfs_path_t *tmp_vpath;
1457         struct stat buf;
1458 
1459         if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
1460             continue;
1461 
1462         tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
1463         if (mc_lstat (tmp_vpath, &buf) != 0)
1464         {
1465             mc_closedir (reading);
1466             vfs_path_free (tmp_vpath, TRUE);
1467             return FILE_RETRY;
1468         }
1469         if (S_ISDIR (buf.st_mode))
1470             return_status = recursive_erase (tctx, ctx, tmp_vpath);
1471         else
1472             return_status = erase_file (tctx, ctx, tmp_vpath);
1473         vfs_path_free (tmp_vpath, TRUE);
1474     }
1475     mc_closedir (reading);
1476 
1477     if (return_status == FILE_ABORT)
1478         return FILE_ABORT;
1479 
1480     file_progress_show_deleting (ctx, vpath, NULL);
1481     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1482     if (check_progress_buttons (ctx) == FILE_ABORT)
1483         return FILE_ABORT;
1484 
1485     mc_refresh ();
1486 
1487     return try_erase_dir (ctx, vpath);
1488 }
1489 
1490 /* --------------------------------------------------------------------------------------------- */
1491 /**
1492   * Check if directory is empty or not.
1493   *
1494   * @param vpath directory handler
1495   *
1496   * @returns -1 on error,
1497   *          1 if there are no entries besides "." and ".." in the directory path points to,
1498   *          0 else.
1499   *
1500   * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
1501   * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
1502   * in SHELL) don't return "." and ".." entries.
1503   */
1504 static int
1505 check_dir_is_empty (const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1506 {
1507     DIR *dir;
1508     struct vfs_dirent *d;
1509     int i = 1;
1510 
1511     dir = mc_opendir (vpath);
1512     if (dir == NULL)
1513         return -1;
1514 
1515     for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1516         if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1517         {
1518             i = 0;
1519             break;
1520         }
1521 
1522     mc_closedir (dir);
1523     return i;
1524 }
1525 
1526 /* --------------------------------------------------------------------------------------------- */
1527 
1528 static FileProgressStatus
1529 erase_dir_iff_empty (file_op_context_t *ctx, const vfs_path_t *vpath, size_t count)
     /* [previous][next][first][last][top][bottom][index][help]  */
1530 {
1531     file_progress_show_deleting (ctx, vpath, NULL);
1532     file_progress_show_count (ctx, count, ctx->progress_count);
1533     if (check_progress_buttons (ctx) == FILE_ABORT)
1534         return FILE_ABORT;
1535 
1536     mc_refresh ();
1537 
1538     if (check_dir_is_empty (vpath) != 1)
1539         return FILE_CONT;
1540 
1541     /* not empty or error */
1542     return try_erase_dir (ctx, vpath);
1543 }
1544 
1545 /* --------------------------------------------------------------------------------------------- */
1546 
1547 static void
1548 erase_dir_after_copy (file_op_total_context_t *tctx, file_op_context_t *ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
1549                       const vfs_path_t *vpath, FileProgressStatus *status)
1550 {
1551     if (ctx->erase_at_end && erase_list != NULL)
1552     {
1553         /* Reset progress count before delete to avoid counting files twice */
1554         tctx->progress_count = tctx->prev_progress_count;
1555 
1556         while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT)
1557         {
1558             link_t *lp;
1559 
1560             lp = (link_t *) g_queue_pop_head (erase_list);
1561 
1562             if (S_ISDIR (lp->st_mode))
1563                 *status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count);
1564             else
1565                 *status = erase_file (tctx, ctx, lp->src_vpath);
1566 
1567             free_link (lp);
1568         }
1569 
1570         /* Save progress counter before move next directory */
1571         tctx->prev_progress_count = tctx->progress_count;
1572     }
1573 
1574     erase_dir_iff_empty (ctx, vpath, tctx->progress_count);
1575 }
1576 
1577 /* }}} */
1578 
1579 /* --------------------------------------------------------------------------------------------- */
1580 
1581 /**
1582  * Move single directory or one of many directories from one location to another.
1583  *
1584  * @panel pointer to panel in case of single directory, NULL otherwise
1585  * @tctx file operation total context object
1586  * @ctx file operation context object
1587  * @s source directory name
1588  * @d destination directory name
1589  *
1590  * @return operation result
1591  */
1592 static FileProgressStatus
1593 do_move_dir_dir (const WPanel *panel, file_op_total_context_t *tctx, file_op_context_t *ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
1594                  const char *s, const char *d)
1595 {
1596     struct stat src_stat, dst_stat;
1597     FileProgressStatus return_status = FILE_CONT;
1598     gboolean move_over = FALSE;
1599     gboolean dstat_ok;
1600     vfs_path_t *src_vpath, *dst_vpath;
1601 
1602     src_vpath = vfs_path_from_str (s);
1603     dst_vpath = vfs_path_from_str (d);
1604 
1605     file_progress_show_source (ctx, src_vpath);
1606     file_progress_show_target (ctx, dst_vpath);
1607 
1608     /* FIXME: do we really need to check buttons in case of single directory? */
1609     if (panel != NULL && check_progress_buttons (ctx) == FILE_ABORT)
1610     {
1611         return_status = FILE_ABORT;
1612         goto ret_fast;
1613     }
1614 
1615     mc_refresh ();
1616 
1617     mc_stat (src_vpath, &src_stat);
1618 
1619     dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0);
1620 
1621     if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1622         goto ret_fast;
1623 
1624     if (!dstat_ok)
1625         ;                       /* destination doesn't exist */
1626     else if (!ctx->dive_into_subdirs)
1627         move_over = TRUE;
1628     else
1629     {
1630         vfs_path_t *tmp;
1631 
1632         tmp = dst_vpath;
1633         dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
1634         vfs_path_free (tmp, TRUE);
1635     }
1636 
1637     d = vfs_path_as_str (dst_vpath);
1638 
1639     /* Check if the user inputted an existing dir */
1640   retry_dst_stat:
1641     if (mc_stat (dst_vpath, &dst_stat) == 0)
1642     {
1643         if (move_over)
1644         {
1645             if (panel != NULL)
1646             {
1647                 /* In case of single directory, calculate totals. In case of many directories,
1648                    totals are calculated already. */
1649                 return_status =
1650                     panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1651                                                FILEGUI_DIALOG_MULTI_ITEM);
1652                 if (return_status != FILE_CONT)
1653                     goto ret;
1654             }
1655 
1656             return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL);
1657 
1658             if (return_status != FILE_CONT)
1659                 goto ret;
1660             goto oktoret;
1661         }
1662         else if (ctx->ignore_all)
1663             return_status = FILE_IGNORE_ALL;
1664         else
1665         {
1666             if (S_ISDIR (dst_stat.st_mode))
1667                 return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d);
1668             else
1669                 return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d);
1670             if (return_status == FILE_IGNORE_ALL)
1671                 ctx->ignore_all = TRUE;
1672             if (return_status == FILE_RETRY)
1673                 goto retry_dst_stat;
1674         }
1675 
1676         goto ret_fast;
1677     }
1678 
1679   retry_rename:
1680     if (mc_rename (src_vpath, dst_vpath) == 0)
1681     {
1682         return_status = FILE_CONT;
1683         goto ret;
1684     }
1685 
1686     if (errno != EXDEV)
1687     {
1688         if (!ctx->ignore_all)
1689         {
1690             return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
1691             if (return_status == FILE_IGNORE_ALL)
1692                 ctx->ignore_all = TRUE;
1693             if (return_status == FILE_RETRY)
1694                 goto retry_rename;
1695         }
1696         goto ret;
1697     }
1698 
1699     /* Failed because of filesystem boundary -> copy dir instead */
1700     if (panel != NULL)
1701     {
1702         /* In case of single directory, calculate totals. In case of many directories,
1703            totals are calculated already. */
1704         return_status =
1705             panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1706                                        FILEGUI_DIALOG_MULTI_ITEM);
1707         if (return_status != FILE_CONT)
1708             goto ret;
1709     }
1710 
1711     return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL);
1712 
1713     if (return_status != FILE_CONT)
1714         goto ret;
1715 
1716   oktoret:
1717     /* FIXME: there is no need to update progress and check buttons
1718        at the finish of single directory operation. */
1719     if (panel == NULL)
1720     {
1721         file_progress_show_source (ctx, NULL);
1722         file_progress_show_target (ctx, NULL);
1723         file_progress_show (ctx, 0, 0, "", FALSE);
1724 
1725         return_status = check_progress_buttons (ctx);
1726         if (return_status != FILE_CONT)
1727             goto ret;
1728     }
1729 
1730     mc_refresh ();
1731 
1732     erase_dir_after_copy (tctx, ctx, src_vpath, &return_status);
1733 
1734   ret:
1735     erase_list = free_erase_list (erase_list);
1736   ret_fast:
1737     vfs_path_free (src_vpath, TRUE);
1738     vfs_path_free (dst_vpath, TRUE);
1739     return return_status;
1740 }
1741 
1742 /* --------------------------------------------------------------------------------------------- */
1743 
1744 /* {{{ Panel operate routines */
1745 
1746 /**
1747  * Return currently selected entry name or the name of the first marked
1748  * entry if there is one.
1749  */
1750 
1751 static const char *
1752 panel_get_file (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1753 {
1754     const file_entry_t *fe;
1755 
1756     if (get_current_type () == view_tree)
1757     {
1758         WTree *tree;
1759         const vfs_path_t *selected_name;
1760 
1761         tree = (WTree *) get_panel_widget (get_current_index ());
1762         selected_name = tree_selected_name (tree);
1763         return vfs_path_as_str (selected_name);
1764     }
1765 
1766     if (panel->marked != 0)
1767     {
1768         int i;
1769 
1770         for (i = 0; i < panel->dir.len; i++)
1771             if (panel->dir.list[i].f.marked != 0)
1772                 return panel->dir.list[i].fname->str;
1773     }
1774 
1775     fe = panel_current_entry (panel);
1776 
1777     return (fe == NULL ? NULL : fe->fname->str);
1778 }
1779 
1780 /* --------------------------------------------------------------------------------------------- */
1781 
1782 static const char *
1783 check_single_entry (const WPanel *panel, gboolean force_single, struct stat *src_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
1784 {
1785     const char *source;
1786     gboolean ok;
1787 
1788     if (force_single)
1789     {
1790         const file_entry_t *fe;
1791 
1792         fe = panel_current_entry (panel);
1793         source = fe == NULL ? NULL : fe->fname->str;
1794     }
1795     else
1796         source = panel_get_file (panel);
1797 
1798     if (source == NULL)
1799         return NULL;
1800 
1801     ok = !DIR_IS_DOTDOT (source);
1802 
1803     if (!ok)
1804         message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
1805     else
1806     {
1807         vfs_path_t *source_vpath;
1808 
1809         source_vpath = vfs_path_from_str (source);
1810 
1811         /* Update stat to get actual info */
1812         ok = mc_lstat (source_vpath, src_stat) == 0;
1813         if (!ok)
1814         {
1815             message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
1816                      path_trunc (source, 30), unix_error_string (errno));
1817 
1818             /* Directory was changed outside MC. Reload it forced */
1819             if (!panel->is_panelized)
1820             {
1821                 panel_update_flags_t flags = UP_RELOAD;
1822 
1823                 /* don't update panelized panel */
1824                 if (get_other_type () == view_listing && other_panel->is_panelized)
1825                     flags |= UP_ONLY_CURRENT;
1826 
1827                 update_panels (flags, UP_KEEPSEL);
1828             }
1829         }
1830 
1831         vfs_path_free (source_vpath, TRUE);
1832     }
1833 
1834     return ok ? source : NULL;
1835 }
1836 
1837 /* --------------------------------------------------------------------------------------------- */
1838 /**
1839  * Generate user prompt for panel operation.
1840  * src_stat must be not NULL for single source, and NULL for multiple sources
1841  */
1842 
1843 static char *
1844 panel_operate_generate_prompt (const WPanel *panel, FileOperation operation,
     /* [previous][next][first][last][top][bottom][index][help]  */
1845                                const struct stat *src_stat)
1846 {
1847     char *sp;
1848     char *format_string;
1849     const char *cp;
1850 
1851     static gboolean i18n_flag = FALSE;
1852     if (!i18n_flag)
1853     {
1854         size_t i;
1855 
1856         for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1857             op_names1[i] = Q_ (op_names1[i]);
1858 
1859 #ifdef ENABLE_NLS
1860         for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1861             prompt_parts[i] = _(prompt_parts[i]);
1862 
1863         one_format = _(one_format);
1864         many_format = _(many_format);
1865 #endif /* ENABLE_NLS */
1866         i18n_flag = TRUE;
1867     }
1868 
1869     /* Possible prompts:
1870      *   OP_COPY:
1871      *       "Copy file \"%s\" with source mask:"
1872      *       "Copy %d files with source mask:"
1873      *       "Copy directory \"%s\" with source mask:"
1874      *       "Copy %d directories with source mask:"
1875      *       "Copy %d files/directories with source mask:"
1876      *   OP_MOVE:
1877      *       "Move file \"%s\" with source mask:"
1878      *       "Move %d files with source mask:"
1879      *       "Move directory \"%s\" with source mask:"
1880      *       "Move %d directories with source mask:"
1881      *       "Move %d files/directories with source mask:"
1882      *   OP_DELETE:
1883      *       "Delete file \"%s\"?"
1884      *       "Delete %d files?"
1885      *       "Delete directory \"%s\"?"
1886      *       "Delete %d directories?"
1887      *       "Delete %d files/directories?"
1888      */
1889 
1890     cp = (src_stat != NULL ? one_format : many_format);
1891 
1892     /* 1. Substitute %o */
1893     format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1894 
1895     /* 2. Substitute %n */
1896     cp = operation == OP_DELETE ? "\n" : " ";
1897     sp = format_string;
1898     format_string = str_replace_all (sp, "%n", cp);
1899     g_free (sp);
1900 
1901     /* 3. Substitute %f */
1902     if (src_stat != NULL)
1903         cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1904     else if (panel->marked == panel->dirs_marked)
1905         cp = prompt_parts[3];
1906     else
1907         cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1908 
1909     sp = format_string;
1910     format_string = str_replace_all (sp, "%f", cp);
1911     g_free (sp);
1912 
1913     /* 4. Substitute %m */
1914     cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1915     sp = format_string;
1916     format_string = str_replace_all (sp, "%m", cp);
1917     g_free (sp);
1918 
1919     return format_string;
1920 }
1921 
1922 /* --------------------------------------------------------------------------------------------- */
1923 
1924 static char *
1925 do_confirm_copy_move (const WPanel *panel, gboolean force_single, const char *source,
     /* [previous][next][first][last][top][bottom][index][help]  */
1926                       struct stat *src_stat, file_op_context_t *ctx, gboolean *do_bg)
1927 {
1928     const char *tmp_dest_dir;
1929     char *dest_dir;
1930     char *format;
1931     char *ret;
1932 
1933     /* Forced single operations default to the original name */
1934     if (force_single)
1935         tmp_dest_dir = source;
1936     else if (get_other_type () == view_listing)
1937         tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
1938     else
1939         tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
1940 
1941     /*
1942      * Add trailing backslash only when do non-local ops.
1943      * It saves user from occasional file renames (when destination
1944      * dir is deleted)
1945      */
1946     if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
1947         && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
1948     {
1949         /* add trailing separator */
1950         dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
1951     }
1952     else
1953     {
1954         /* just copy */
1955         dest_dir = g_strdup (tmp_dest_dir);
1956     }
1957 
1958     if (dest_dir == NULL)
1959         return NULL;
1960 
1961     if (source == NULL)
1962         src_stat = NULL;
1963 
1964     /* Generate confirmation prompt */
1965     format = panel_operate_generate_prompt (panel, ctx->operation, src_stat);
1966 
1967     ret = file_mask_dialog (ctx, source != NULL, format,
1968                             source != NULL ? source : (const void *) &panel->marked, dest_dir,
1969                             do_bg);
1970 
1971     g_free (format);
1972     g_free (dest_dir);
1973 
1974     return ret;
1975 }
1976 
1977 /* --------------------------------------------------------------------------------------------- */
1978 
1979 static gboolean
1980 do_confirm_erase (const WPanel *panel, const char *source, struct stat *src_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
1981 {
1982     int i;
1983     char *format;
1984     char fmd_buf[BUF_MEDIUM];
1985 
1986     if (source == NULL)
1987         src_stat = NULL;
1988 
1989     /* Generate confirmation prompt */
1990     format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat);
1991 
1992     if (source == NULL)
1993         g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1994     else
1995     {
1996         const int fmd_xlen = 64;
1997 
1998         i = fmd_xlen - str_term_width1 (format) - 4;
1999         g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
2000     }
2001 
2002     g_free (format);
2003 
2004     if (safe_delete)
2005         query_set_sel (1);
2006 
2007     i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
2008 
2009     return (i == 0);
2010 }
2011 
2012 /* --------------------------------------------------------------------------------------------- */
2013 
2014 static FileProgressStatus
2015 operate_single_file (const WPanel *panel, file_op_total_context_t *tctx, file_op_context_t *ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
2016                      const char *src, struct stat *src_stat, const char *dest,
2017                      filegui_dialog_type_t dialog_type)
2018 {
2019     FileProgressStatus value;
2020     vfs_path_t *src_vpath;
2021     gboolean is_file;
2022 
2023     if (g_path_is_absolute (src))
2024         src_vpath = vfs_path_from_str (src);
2025     else
2026         src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2027 
2028     is_file = !S_ISDIR (src_stat->st_mode);
2029     /* Is link to directory? */
2030     if (is_file)
2031     {
2032         gboolean is_link;
2033 
2034         is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2035         is_file = !(is_link && ctx->follow_links);
2036     }
2037 
2038     if (ctx->operation == OP_DELETE)
2039     {
2040         value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type);
2041         if (value == FILE_CONT)
2042         {
2043             if (is_file)
2044                 value = erase_file (tctx, ctx, src_vpath);
2045             else
2046                 value = erase_dir (tctx, ctx, src_vpath);
2047         }
2048     }
2049     else
2050     {
2051         char *temp;
2052 
2053         src = vfs_path_as_str (src_vpath);
2054 
2055         temp = build_dest (ctx, src, dest, &value);
2056         if (temp != NULL)
2057         {
2058             dest = temp;
2059 
2060             switch (ctx->operation)
2061             {
2062             case OP_COPY:
2063                 /* we use file_mask_op_follow_links only with OP_COPY */
2064                 ctx->stat_func (src_vpath, src_stat);
2065 
2066                 value =
2067                     panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file,
2068                                                dialog_type);
2069                 if (value == FILE_CONT)
2070                 {
2071                     is_file = !S_ISDIR (src_stat->st_mode);
2072                     /* Is link to directory? */
2073                     if (is_file)
2074                     {
2075                         gboolean is_link;
2076 
2077                         is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2078                         is_file = !(is_link && ctx->follow_links);
2079                     }
2080 
2081                     if (is_file)
2082                         value = copy_file_file (tctx, ctx, src, dest);
2083                     else
2084                         value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2085                 }
2086                 break;
2087 
2088             case OP_MOVE:
2089 #ifdef ENABLE_BACKGROUND
2090                 if (!mc_global.we_are_background)
2091 #endif
2092                     /* create UI to show confirmation dialog */
2093                     file_op_context_create_ui (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM);
2094 
2095                 if (is_file)
2096                     value = move_file_file (panel, tctx, ctx, src, dest);
2097                 else
2098                     value = do_move_dir_dir (panel, tctx, ctx, src, dest);
2099                 break;
2100 
2101             default:
2102                 /* Unknown file operation */
2103                 abort ();
2104             }
2105 
2106             g_free (temp);
2107         }
2108     }
2109 
2110     vfs_path_free (src_vpath, TRUE);
2111 
2112     return value;
2113 }
2114 
2115 /* --------------------------------------------------------------------------------------------- */
2116 
2117 static FileProgressStatus
2118 operate_one_file (const WPanel *panel, file_op_total_context_t *tctx, file_op_context_t *ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
2119                   const char *src, struct stat *src_stat, const char *dest)
2120 {
2121     FileProgressStatus value = FILE_CONT;
2122     vfs_path_t *src_vpath;
2123     gboolean is_file;
2124 
2125     if (g_path_is_absolute (src))
2126         src_vpath = vfs_path_from_str (src);
2127     else
2128         src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2129 
2130     is_file = !S_ISDIR (src_stat->st_mode);
2131 
2132     if (ctx->operation == OP_DELETE)
2133     {
2134         if (is_file)
2135             value = erase_file (tctx, ctx, src_vpath);
2136         else
2137             value = erase_dir (tctx, ctx, src_vpath);
2138     }
2139     else
2140     {
2141         char *temp;
2142 
2143         src = vfs_path_as_str (src_vpath);
2144 
2145         temp = build_dest (ctx, src, dest, &value);
2146         if (temp != NULL)
2147         {
2148             dest = temp;
2149 
2150             switch (ctx->operation)
2151             {
2152             case OP_COPY:
2153                 /* we use file_mask_op_follow_links only with OP_COPY */
2154                 ctx->stat_func (src_vpath, src_stat);
2155                 is_file = !S_ISDIR (src_stat->st_mode);
2156 
2157                 if (is_file)
2158                     value = copy_file_file (tctx, ctx, src, dest);
2159                 else
2160                     value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2161                 dest_dirs = free_linklist (dest_dirs);
2162                 break;
2163 
2164             case OP_MOVE:
2165                 if (is_file)
2166                     value = move_file_file (NULL, tctx, ctx, src, dest);
2167                 else
2168                     value = do_move_dir_dir (NULL, tctx, ctx, src, dest);
2169                 break;
2170 
2171             default:
2172                 /* Unknown file operation */
2173                 abort ();
2174             }
2175 
2176             g_free (temp);
2177         }
2178     }
2179 
2180     vfs_path_free (src_vpath, TRUE);
2181 
2182     return value;
2183 }
2184 
2185 /* --------------------------------------------------------------------------------------------- */
2186 
2187 #ifdef ENABLE_BACKGROUND
2188 static int
2189 end_bg_process (file_op_context_t *ctx, enum OperationMode mode)
     /* [previous][next][first][last][top][bottom][index][help]  */
2190 {
2191     int pid = ctx->pid;
2192 
2193     (void) mode;
2194     ctx->pid = 0;
2195 
2196     unregister_task_with_pid (pid);
2197     /*     file_op_context_destroy(ctx); */
2198     return 1;
2199 }
2200 #endif
2201 /* }}} */
2202 
2203 /* --------------------------------------------------------------------------------------------- */
2204 
2205 /**
2206  * On Solaris, ENOTSUP != EOPNOTSUPP. Some FS also return ENOSYS or EINVAL as "not implemented".
2207  * On some Linux kernels (tested on 4.9, 5.4) there is ENOTTY on tmpfs.
2208  */
2209 static inline gboolean
2210 attrs_ignore_error (const int e)
     /* [previous][next][first][last][top][bottom][index][help]  */
2211 {
2212     return (e == ENOTSUP || e == EOPNOTSUPP || e == ENOSYS || e == EINVAL || e == ENOTTY
2213             || e == ELOOP || e == ENXIO);
2214 }
2215 
2216 /* --------------------------------------------------------------------------------------------- */
2217 /*** public functions ****************************************************************************/
2218 /* --------------------------------------------------------------------------------------------- */
2219 
2220 /* Is file symlink to directory or not.
2221  *
2222  * @param path file or directory
2223  * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2224  * @param stale_link TRUE if file is stale link to directory
2225  *
2226  * @return TRUE if file symlink to directory, ELSE otherwise.
2227  */
2228 gboolean
2229 file_is_symlink_to_dir (const vfs_path_t *vpath, struct stat *st, gboolean *stale_link)
     /* [previous][next][first][last][top][bottom][index][help]  */
2230 {
2231     struct stat st2;
2232     gboolean stale = FALSE;
2233     gboolean res = FALSE;
2234 
2235     if (st == NULL)
2236     {
2237         st = &st2;
2238 
2239         if (mc_lstat (vpath, st) != 0)
2240             goto ret;
2241     }
2242 
2243     if (S_ISLNK (st->st_mode))
2244     {
2245         struct stat st3;
2246 
2247         stale = (mc_stat (vpath, &st3) != 0);
2248 
2249         if (!stale)
2250             res = (S_ISDIR (st3.st_mode) != 0);
2251     }
2252 
2253   ret:
2254     if (stale_link != NULL)
2255         *stale_link = stale;
2256 
2257     return res;
2258 }
2259 
2260 /* --------------------------------------------------------------------------------------------- */
2261 
2262 FileProgressStatus
2263 copy_file_file (file_op_total_context_t *tctx, file_op_context_t *ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
2264                 const char *src_path, const char *dst_path)
2265 {
2266     uid_t src_uid = (uid_t) (-1);
2267     gid_t src_gid = (gid_t) (-1);
2268 
2269     int src_desc, dest_desc = -1;
2270     mode_t src_mode = 0;        /* The mode of the source file */
2271     struct stat src_stat, dst_stat;
2272     mc_timesbuf_t times;
2273     unsigned long attrs = 0;
2274     gboolean attrs_ok = ctx->preserve;
2275     gboolean dst_exists = FALSE, appending = FALSE;
2276     off_t file_size = -1;
2277     FileProgressStatus return_status, temp_status;
2278     gint64 tv_transfer_start;
2279     dest_status_t dst_status = DEST_NONE;
2280     int open_flags;
2281     vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
2282     char *buf = NULL;
2283 
2284     /* Keep the non-default value applied in chain of calls:
2285        move_file_file() -> file_progress_real_query_replace()
2286        move_file_file() -> copy_file_file() */
2287     if (ctx->do_reget < 0)
2288         ctx->do_reget = 0;
2289 
2290     return_status = FILE_RETRY;
2291 
2292     dst_vpath = vfs_path_from_str (dst_path);
2293     src_vpath = vfs_path_from_str (src_path);
2294 
2295     file_progress_show_source (ctx, src_vpath);
2296     file_progress_show_target (ctx, dst_vpath);
2297 
2298     if (check_progress_buttons (ctx) == FILE_ABORT)
2299     {
2300         return_status = FILE_ABORT;
2301         goto ret_fast;
2302     }
2303 
2304     mc_refresh ();
2305 
2306     while (mc_stat (dst_vpath, &dst_stat) == 0)
2307     {
2308         if (S_ISDIR (dst_stat.st_mode))
2309         {
2310             if (ctx->ignore_all)
2311                 return_status = FILE_IGNORE_ALL;
2312             else
2313             {
2314                 return_status =
2315                     file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path);
2316                 if (return_status == FILE_IGNORE_ALL)
2317                     ctx->ignore_all = TRUE;
2318                 if (return_status == FILE_RETRY)
2319                     continue;
2320             }
2321             goto ret_fast;
2322         }
2323 
2324         dst_exists = TRUE;
2325         break;
2326     }
2327 
2328     while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2329     {
2330         if (ctx->ignore_all)
2331             return_status = FILE_IGNORE_ALL;
2332         else
2333         {
2334             return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path);
2335             if (return_status == FILE_IGNORE_ALL)
2336                 ctx->ignore_all = TRUE;
2337         }
2338 
2339         if (return_status != FILE_RETRY)
2340             goto ret_fast;
2341     }
2342 
2343     while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2344     {
2345         attrs_ok = FALSE;
2346 
2347         /* don't show an error message if attributes aren't supported in this FS */
2348         if (attrs_ignore_error (errno))
2349             return_status = FILE_CONT;
2350         else if (ctx->ignore_all)
2351             return_status = FILE_IGNORE_ALL;
2352         else
2353         {
2354             return_status =
2355                 file_error (TRUE, _("Cannot get ext2 attributes of source file \"%s\"\n%s"),
2356                             src_path);
2357             if (return_status == FILE_IGNORE_ALL)
2358                 ctx->ignore_all = TRUE;
2359             if (return_status == FILE_ABORT)
2360                 goto ret_fast;
2361         }
2362 
2363         if (return_status != FILE_RETRY)
2364             break;
2365 
2366         /* yet another attempt */
2367         attrs_ok = TRUE;
2368     }
2369 
2370     if (dst_exists)
2371     {
2372         /* Destination already exists */
2373         if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status))
2374             goto ret_fast;
2375 
2376         /* Should we replace destination? */
2377         if (tctx->ask_overwrite)
2378         {
2379             ctx->do_reget = 0;
2380             return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
2381             if (return_status != FILE_CONT)
2382                 goto ret_fast;
2383         }
2384     }
2385 
2386     vfs_get_timesbuf_from_stat (&src_stat, &times);
2387 
2388     if (!ctx->do_append)
2389     {
2390         /* Check the hardlinks */
2391         if (!ctx->follow_links)
2392         {
2393             switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
2394             {
2395             case HARDLINK_OK:
2396                 /* We have made a hardlink - no more processing is necessary */
2397                 return_status = FILE_CONT;
2398                 goto ret_fast;
2399 
2400             case HARDLINK_ABORT:
2401                 return_status = FILE_ABORT;
2402                 goto ret_fast;
2403 
2404             default:
2405                 break;
2406             }
2407         }
2408 
2409         if (S_ISLNK (src_stat.st_mode))
2410         {
2411             return_status = make_symlink (ctx, src_vpath, dst_vpath);
2412             if (return_status == FILE_CONT && ctx->preserve)
2413             {
2414                 mc_utime (dst_vpath, &times);
2415 
2416                 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2417                 {
2418                     attrs_ok = FALSE;
2419 
2420                     /* don't show an error message if attributes aren't supported in this FS */
2421                     if (attrs_ignore_error (errno))
2422                         return_status = FILE_CONT;
2423                     else if (return_status == FILE_IGNORE_ALL)
2424                         ctx->ignore_all = TRUE;
2425                     else
2426                         return_status =
2427                             file_error (TRUE,
2428                                         _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2429                                         dst_path);
2430 
2431                     if (return_status != FILE_RETRY)
2432                         break;
2433 
2434                     /* yet another attempt */
2435                     attrs_ok = TRUE;
2436                 }
2437             }
2438             goto ret_fast;
2439         }
2440 
2441         if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
2442             || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
2443         {
2444             dev_t rdev = 0;
2445 
2446 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2447             rdev = src_stat.st_rdev;
2448 #endif
2449 
2450             while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
2451                    && !ctx->ignore_all)
2452             {
2453                 return_status =
2454                     file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path);
2455                 if (return_status == FILE_RETRY)
2456                     continue;
2457                 if (return_status == FILE_IGNORE_ALL)
2458                     ctx->ignore_all = TRUE;
2459                 goto ret_fast;
2460             }
2461             /* Success */
2462 
2463             while (ctx->preserve_uidgid
2464                    && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0
2465                    && !ctx->ignore_all)
2466             {
2467                 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2468                 if (temp_status == FILE_IGNORE)
2469                     break;
2470                 if (temp_status == FILE_IGNORE_ALL)
2471                     ctx->ignore_all = TRUE;
2472                 if (temp_status != FILE_RETRY)
2473                 {
2474                     return_status = temp_status;
2475                     goto ret_fast;
2476                 }
2477             }
2478 
2479             while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
2480                    && !ctx->ignore_all)
2481             {
2482                 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2483                 if (temp_status == FILE_IGNORE)
2484                     break;
2485                 if (temp_status == FILE_IGNORE_ALL)
2486                     ctx->ignore_all = TRUE;
2487                 if (temp_status != FILE_RETRY)
2488                 {
2489                     return_status = temp_status;
2490                     goto ret_fast;
2491                 }
2492             }
2493 
2494             while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2495             {
2496                 attrs_ok = FALSE;
2497 
2498                 /* don't show an error message if attributes aren't supported in this FS */
2499                 if (attrs_ignore_error (errno))
2500                     break;
2501 
2502                 temp_status =
2503                     file_error (TRUE, _("Cannot set ext2 attributes of target file \"%s\"\n%s"),
2504                                 dst_path);
2505                 if (temp_status == FILE_IGNORE)
2506                     break;
2507                 if (temp_status == FILE_IGNORE_ALL)
2508                     ctx->ignore_all = TRUE;
2509                 if (temp_status != FILE_RETRY)
2510                 {
2511                     return_status = temp_status;
2512                     goto ret_fast;
2513                 }
2514 
2515                 /* yet another attempt */
2516                 attrs_ok = TRUE;
2517             }
2518 
2519             return_status = FILE_CONT;
2520             mc_utime (dst_vpath, &times);
2521             goto ret_fast;
2522         }
2523     }
2524 
2525     tv_transfer_start = g_get_monotonic_time ();
2526 
2527     while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->ignore_all)
2528     {
2529         return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path);
2530         if (return_status == FILE_RETRY)
2531             continue;
2532         if (return_status == FILE_IGNORE_ALL)
2533             ctx->ignore_all = TRUE;
2534         if (return_status == FILE_IGNORE)
2535             break;
2536         ctx->do_append = FALSE;
2537         goto ret_fast;
2538     }
2539 
2540     if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
2541     {
2542         message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
2543         ctx->do_reget = 0;
2544         ctx->do_append = FALSE;
2545     }
2546 
2547     while (mc_fstat (src_desc, &src_stat) != 0)
2548     {
2549         if (ctx->ignore_all)
2550             return_status = FILE_IGNORE_ALL;
2551         else
2552         {
2553             return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
2554             if (return_status == FILE_RETRY)
2555                 continue;
2556             if (return_status == FILE_IGNORE_ALL)
2557                 ctx->ignore_all = TRUE;
2558             ctx->do_append = FALSE;
2559         }
2560         goto ret;
2561     }
2562 
2563     src_mode = src_stat.st_mode;
2564     src_uid = src_stat.st_uid;
2565     src_gid = src_stat.st_gid;
2566     file_size = src_stat.st_size;
2567 
2568     open_flags = O_WRONLY;
2569     if (!dst_exists)
2570         open_flags |= O_CREAT | O_EXCL;
2571     else if (ctx->do_append)
2572         open_flags |= O_APPEND;
2573     else
2574         open_flags |= O_CREAT | O_TRUNC;
2575 
2576     while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
2577     {
2578         if (errno != EEXIST)
2579         {
2580             if (ctx->ignore_all)
2581                 return_status = FILE_IGNORE_ALL;
2582             else
2583             {
2584                 return_status =
2585                     file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
2586                 if (return_status == FILE_RETRY)
2587                     continue;
2588                 if (return_status == FILE_IGNORE_ALL)
2589                     ctx->ignore_all = TRUE;
2590                 ctx->do_append = FALSE;
2591             }
2592         }
2593         goto ret;
2594     }
2595 
2596     /* file opened, but not fully copied */
2597     dst_status = DEST_SHORT_QUERY;
2598 
2599     appending = ctx->do_append;
2600     ctx->do_append = FALSE;
2601 
2602     /* Try clone the file first. */
2603     if (vfs_clone_file (dest_desc, src_desc) == 0)
2604     {
2605         dst_status = DEST_FULL;
2606         return_status = FILE_CONT;
2607         goto ret;
2608     }
2609 
2610     /* Find out the optimal buffer size.  */
2611     while (mc_fstat (dest_desc, &dst_stat) != 0)
2612     {
2613         if (ctx->ignore_all)
2614             return_status = FILE_IGNORE_ALL;
2615         else
2616         {
2617             return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
2618             if (return_status == FILE_RETRY)
2619                 continue;
2620             if (return_status == FILE_IGNORE_ALL)
2621                 ctx->ignore_all = TRUE;
2622         }
2623         goto ret;
2624     }
2625 
2626     /* try preallocate space; if fail, try copy anyway */
2627     while (mc_global.vfs.preallocate_space &&
2628            vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
2629     {
2630         if (ctx->ignore_all)
2631         {
2632             /* cannot allocate, start the file copying anyway */
2633             return_status = FILE_CONT;
2634             break;
2635         }
2636 
2637         return_status =
2638             file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
2639 
2640         if (return_status == FILE_IGNORE_ALL)
2641             ctx->ignore_all = TRUE;
2642 
2643         if (ctx->ignore_all || return_status == FILE_IGNORE)
2644         {
2645             /* skip the space allocation error, start file copying */
2646             return_status = FILE_CONT;
2647             break;
2648         }
2649 
2650         if (return_status == FILE_ABORT)
2651         {
2652             mc_close (dest_desc);
2653             dest_desc = -1;
2654             mc_unlink (dst_vpath);
2655             dst_status = DEST_NONE;
2656             goto ret;
2657         }
2658 
2659         /* return_status == FILE_RETRY -- try allocate space again */
2660     }
2661 
2662     ctx->eta_secs = 0.0;
2663     ctx->bps = 0;
2664 
2665     if (tctx->bps == 0 || (file_size / tctx->bps) > FILEOP_UPDATE_INTERVAL)
2666         file_progress_show (ctx, 0, file_size, "", TRUE);
2667     else
2668         file_progress_show (ctx, 1, 1, "", TRUE);
2669     return_status = check_progress_buttons (ctx);
2670     mc_refresh ();
2671 
2672     if (return_status == FILE_CONT)
2673     {
2674         size_t bufsize;
2675         off_t file_part = 0;
2676         gint64 tv_current, tv_last_update;
2677         gint64 tv_last_input = 0;
2678         gint64 usecs, update_usecs;
2679         const char *stalled_msg = "";
2680         gboolean is_first_time = TRUE;
2681 
2682         tv_last_update = tv_transfer_start;
2683 
2684         bufsize = io_blksize (dst_stat);
2685         buf = g_malloc (bufsize);
2686 
2687         while (TRUE)
2688         {
2689             ssize_t n_read = -1, n_written;
2690             gboolean force_update;
2691 
2692             /* src_read */
2693             if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
2694                 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->ignore_all)
2695                 {
2696                     return_status =
2697                         file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
2698                     if (return_status == FILE_RETRY)
2699                         continue;
2700                     if (return_status == FILE_IGNORE_ALL)
2701                         ctx->ignore_all = TRUE;
2702                     goto ret;
2703                 }
2704 
2705             if (n_read == 0)
2706                 break;
2707 
2708             tv_current = g_get_monotonic_time ();
2709 
2710             if (n_read > 0)
2711             {
2712                 char *t = buf;
2713 
2714                 file_part += n_read;
2715 
2716                 tv_last_input = tv_current;
2717 
2718                 /* dst_write */
2719                 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
2720                 {
2721                     gboolean write_errno_nospace;
2722 
2723                     if (n_written > 0)
2724                     {
2725                         n_read -= n_written;
2726                         t += n_written;
2727                         continue;
2728                     }
2729 
2730                     write_errno_nospace = (n_written < 0 && errno == ENOSPC);
2731 
2732                     if (ctx->ignore_all)
2733                         return_status = FILE_IGNORE_ALL;
2734                     else
2735                         return_status =
2736                             file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
2737 
2738                     if (return_status == FILE_IGNORE)
2739                     {
2740                         if (write_errno_nospace)
2741                             goto ret;
2742                         break;
2743                     }
2744                     if (return_status == FILE_IGNORE_ALL)
2745                     {
2746                         ctx->ignore_all = TRUE;
2747                         if (write_errno_nospace)
2748                             goto ret;
2749                     }
2750                     if (return_status != FILE_RETRY)
2751                         goto ret;
2752                 }
2753             }
2754 
2755             tctx->copied_bytes = tctx->progress_bytes + file_part + ctx->do_reget;
2756 
2757             usecs = tv_current - tv_last_update;
2758             update_usecs = tv_current - tv_last_input;
2759 
2760             if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US)
2761             {
2762                 copy_file_file_display_progress (tctx, ctx, tv_current, tv_transfer_start,
2763                                                  file_size, file_part);
2764                 tv_last_update = tv_current;
2765             }
2766 
2767             is_first_time = FALSE;
2768 
2769             if (update_usecs > FILEOP_STALLING_INTERVAL_US)
2770                 stalled_msg = _("(stalled)");
2771 
2772             force_update = (tv_current - tctx->transfer_start) > FILEOP_UPDATE_INTERVAL_US;
2773 
2774             if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
2775             {
2776                 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2777                 file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
2778             }
2779 
2780             file_progress_show (ctx, file_part + ctx->do_reget, file_size, stalled_msg,
2781                                 force_update);
2782             mc_refresh ();
2783 
2784             return_status = check_progress_buttons (ctx);
2785             if (return_status != FILE_CONT)
2786             {
2787                 int query_res;
2788 
2789                 query_res =
2790                     query_dialog (Q_ ("DialogTitle|Copy"),
2791                                   _("Incomplete file was retrieved"), D_ERROR, 3,
2792                                   _("&Delete"), _("&Keep"), _("&Continue copy"));
2793 
2794                 switch (query_res)
2795                 {
2796                 case 0:
2797                     /* delete */
2798                     dst_status = DEST_SHORT_DELETE;
2799                     goto ret;
2800 
2801                 case 1:
2802                     /* keep */
2803                     dst_status = DEST_SHORT_KEEP;
2804                     goto ret;
2805 
2806                 default:
2807                     /* continue copy */
2808                     break;
2809                 }
2810             }
2811         }
2812 
2813         /* copy successful */
2814         dst_status = DEST_FULL;
2815     }
2816 
2817   ret:
2818     g_free (buf);
2819 
2820     rotate_dash (FALSE);
2821     while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->ignore_all)
2822     {
2823         temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
2824         if (temp_status == FILE_RETRY)
2825             continue;
2826         if (temp_status == FILE_ABORT)
2827             return_status = temp_status;
2828         if (temp_status == FILE_IGNORE_ALL)
2829             ctx->ignore_all = TRUE;
2830         break;
2831     }
2832 
2833     while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->ignore_all)
2834     {
2835         temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
2836         if (temp_status == FILE_RETRY)
2837             continue;
2838         if (temp_status == FILE_IGNORE_ALL)
2839             ctx->ignore_all = TRUE;
2840         return_status = temp_status;
2841         break;
2842     }
2843 
2844     if (dst_status == DEST_SHORT_QUERY)
2845     {
2846         /* Query to remove short file */
2847         if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
2848                           D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
2849             dst_status = DEST_SHORT_DELETE;
2850         else
2851             dst_status = DEST_SHORT_KEEP;
2852     }
2853 
2854     if (dst_status == DEST_SHORT_DELETE)
2855         mc_unlink (dst_vpath);
2856     else if (dst_status == DEST_FULL && !appending)
2857     {
2858         /* Copy has succeeded */
2859 
2860         while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
2861                && !ctx->ignore_all)
2862         {
2863             temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2864             if (temp_status == FILE_ABORT)
2865             {
2866                 return_status = FILE_ABORT;
2867                 goto ret_fast;
2868             }
2869             if (temp_status == FILE_RETRY)
2870                 continue;
2871             if (temp_status == FILE_IGNORE_ALL)
2872             {
2873                 ctx->ignore_all = TRUE;
2874                 return_status = FILE_CONT;
2875             }
2876             if (temp_status == FILE_IGNORE)
2877                 return_status = FILE_CONT;
2878             break;
2879         }
2880 
2881         while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
2882                && !ctx->ignore_all)
2883         {
2884             temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2885             if (temp_status == FILE_ABORT)
2886             {
2887                 return_status = FILE_ABORT;
2888                 goto ret_fast;
2889             }
2890             if (temp_status == FILE_RETRY)
2891                 continue;
2892             if (temp_status == FILE_IGNORE_ALL)
2893             {
2894                 ctx->ignore_all = TRUE;
2895                 return_status = FILE_CONT;
2896             }
2897             if (temp_status == FILE_IGNORE)
2898                 return_status = FILE_CONT;
2899             break;
2900         }
2901 
2902         if (!ctx->preserve && !dst_exists)
2903         {
2904             src_mode = umask (-1);
2905             umask (src_mode);
2906             src_mode = 0100666 & ~src_mode;
2907             mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2908         }
2909     }
2910 
2911     if (dst_status == DEST_FULL || dst_status == DEST_SHORT_KEEP)
2912     {
2913         /* Always sync timestamps */
2914         mc_utime (dst_vpath, &times);
2915 
2916         while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2917         {
2918             attrs_ok = FALSE;
2919 
2920             /* don't show an error message if attributes aren't supported in this FS */
2921             if (attrs_ignore_error (errno))
2922             {
2923                 return_status = FILE_CONT;
2924                 break;
2925             }
2926 
2927             temp_status =
2928                 file_error (TRUE, _("Cannot set ext2 attributes for target file \"%s\"\n%s"),
2929                             dst_path);
2930             if (temp_status == FILE_ABORT)
2931                 return_status = FILE_ABORT;
2932             if (temp_status == FILE_RETRY)
2933             {
2934                 attrs_ok = TRUE;
2935                 continue;
2936             }
2937             if (temp_status == FILE_IGNORE_ALL)
2938             {
2939                 ctx->ignore_all = TRUE;
2940                 return_status = FILE_CONT;
2941             }
2942             if (temp_status == FILE_IGNORE)
2943                 return_status = FILE_CONT;
2944             break;
2945         }
2946     }
2947 
2948     if (return_status == FILE_CONT)
2949         return_status = progress_update_one (tctx, ctx, file_size);
2950 
2951   ret_fast:
2952     vfs_path_free (src_vpath, TRUE);
2953     vfs_path_free (dst_vpath, TRUE);
2954     return return_status;
2955 }
2956 
2957 /* --------------------------------------------------------------------------------------------- */
2958 /**
2959  * I think these copy_*_* functions should have a return type.
2960  * anyway, this function *must* have two directories as arguments.
2961  */
2962 /* FIXME: This function needs to check the return values of the
2963    function calls */
2964 
2965 FileProgressStatus
2966 copy_dir_dir (file_op_total_context_t *tctx, file_op_context_t *ctx, const char *s, const char *d,
     /* [previous][next][first][last][top][bottom][index][help]  */
2967               gboolean toplevel, gboolean move_over, gboolean do_delete, GSList *parent_dirs)
2968 {
2969     struct vfs_dirent *next;
2970     struct stat dst_stat, src_stat;
2971     unsigned long attrs = 0;
2972     gboolean attrs_ok = ctx->preserve;
2973     DIR *reading;
2974     FileProgressStatus return_status = FILE_CONT;
2975     link_t *lp;
2976     vfs_path_t *src_vpath, *dst_vpath;
2977     gboolean do_mkdir = TRUE;
2978 
2979     src_vpath = vfs_path_from_str (s);
2980     dst_vpath = vfs_path_from_str (d);
2981 
2982     /* First get the mode of the source dir */
2983 
2984   retry_src_stat:
2985     while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2986     {
2987         if (ctx->ignore_all)
2988             return_status = FILE_IGNORE_ALL;
2989         else
2990         {
2991             return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
2992             if (return_status == FILE_IGNORE_ALL)
2993                 ctx->ignore_all = TRUE;
2994         }
2995 
2996         if (return_status != FILE_RETRY)
2997             goto ret_fast;
2998     }
2999 
3000     while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
3001     {
3002         attrs_ok = FALSE;
3003 
3004         /* don't show an error message if attributes aren't supported in this FS */
3005         if (attrs_ignore_error (errno))
3006             return_status = FILE_CONT;
3007         else if (ctx->ignore_all)
3008             return_status = FILE_IGNORE_ALL;
3009         else
3010         {
3011             return_status =
3012                 file_error (TRUE, _("Cannot get ext2 attributes of source directory \"%s\"\n%s"),
3013                             s);
3014             if (return_status == FILE_IGNORE_ALL)
3015                 ctx->ignore_all = TRUE;
3016             if (return_status == FILE_ABORT)
3017                 goto ret_fast;
3018         }
3019 
3020         if (return_status != FILE_RETRY)
3021             break;
3022 
3023         /* yet another attempt */
3024         attrs_ok = TRUE;
3025     }
3026 
3027     if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
3028     {
3029         /* Don't copy a directory we created before (we don't want to copy
3030            infinitely if a directory is copied into itself) */
3031         /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
3032         return_status = FILE_CONT;
3033         goto ret_fast;
3034     }
3035 
3036     /* Hmm, hardlink to directory??? - Norbert */
3037     /* FIXME: In this step we should do something in case the destination already exist */
3038     /* Check the hardlinks */
3039     if (ctx->preserve)
3040     {
3041         switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
3042         {
3043         case HARDLINK_OK:
3044             /* We have made a hardlink - no more processing is necessary */
3045             goto ret_fast;
3046 
3047         case HARDLINK_ABORT:
3048             return_status = FILE_ABORT;
3049             goto ret_fast;
3050 
3051         default:
3052             break;
3053         }
3054     }
3055 
3056     if (!S_ISDIR (src_stat.st_mode))
3057     {
3058         if (ctx->ignore_all)
3059             return_status = FILE_IGNORE_ALL;
3060         else
3061         {
3062             return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
3063             if (return_status == FILE_RETRY)
3064                 goto retry_src_stat;
3065             if (return_status == FILE_IGNORE_ALL)
3066                 ctx->ignore_all = TRUE;
3067         }
3068         goto ret_fast;
3069     }
3070 
3071     if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
3072     {
3073         /* we found a cyclic symbolic link */
3074         message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
3075         return_status = FILE_SKIP;
3076         goto ret_fast;
3077     }
3078 
3079     lp = g_new0 (link_t, 1);
3080     lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
3081     lp->ino = src_stat.st_ino;
3082     lp->dev = src_stat.st_dev;
3083     parent_dirs = g_slist_prepend (parent_dirs, lp);
3084 
3085   retry_dst_stat:
3086     /* Now, check if the dest dir exists, if not, create it. */
3087     if (mc_stat (dst_vpath, &dst_stat) != 0)
3088     {
3089         /* Here the dir doesn't exist : make it ! */
3090         if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
3091         {
3092             return_status = FILE_CONT;
3093             goto ret;
3094         }
3095     }
3096     else
3097     {
3098         /*
3099          * If the destination directory exists, we want to copy the whole
3100          * directory, but we only want this to happen once.
3101          *
3102          * Escape sequences added to the * to compiler warnings.
3103          * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3104          * or ( /bla doesn't exist )       /tmp/\* to /bla     ->  /bla/\*
3105          */
3106         if (!S_ISDIR (dst_stat.st_mode))
3107         {
3108             if (ctx->ignore_all)
3109                 return_status = FILE_IGNORE_ALL;
3110             else
3111             {
3112                 return_status =
3113                     file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
3114                 if (return_status == FILE_IGNORE_ALL)
3115                     ctx->ignore_all = TRUE;
3116                 if (return_status == FILE_RETRY)
3117                     goto retry_dst_stat;
3118             }
3119             goto ret;
3120         }
3121         /* Dive into subdir if exists */
3122         if (toplevel && ctx->dive_into_subdirs)
3123         {
3124             vfs_path_t *tmp;
3125 
3126             tmp = dst_vpath;
3127             dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
3128             vfs_path_free (tmp, TRUE);
3129 
3130         }
3131         else
3132             do_mkdir = FALSE;
3133     }
3134 
3135     d = vfs_path_as_str (dst_vpath);
3136 
3137     if (do_mkdir)
3138     {
3139         while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
3140         {
3141             if (ctx->ignore_all)
3142                 return_status = FILE_IGNORE_ALL;
3143             else
3144             {
3145                 return_status =
3146                     file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
3147                 if (return_status == FILE_IGNORE_ALL)
3148                     ctx->ignore_all = TRUE;
3149             }
3150             if (return_status != FILE_RETRY)
3151                 goto ret;
3152         }
3153 
3154         lp = g_new0 (link_t, 1);
3155         mc_stat (dst_vpath, &dst_stat);
3156         lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
3157         lp->ino = dst_stat.st_ino;
3158         lp->dev = dst_stat.st_dev;
3159         dest_dirs = g_slist_prepend (dest_dirs, lp);
3160     }
3161 
3162     if (ctx->preserve_uidgid)
3163     {
3164         while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
3165         {
3166             if (ctx->ignore_all)
3167                 return_status = FILE_IGNORE_ALL;
3168             else
3169             {
3170                 return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
3171                 if (return_status == FILE_IGNORE_ALL)
3172                     ctx->ignore_all = TRUE;
3173             }
3174             if (return_status != FILE_RETRY)
3175                 goto ret;
3176         }
3177     }
3178 
3179     /* open the source dir for reading */
3180     reading = mc_opendir (src_vpath);
3181     if (reading == NULL)
3182         goto ret;
3183 
3184     while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
3185     {
3186         char *path;
3187         vfs_path_t *tmp_vpath;
3188 
3189         /*
3190          * Now, we don't want '.' and '..' to be created / copied at any time
3191          */
3192         if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
3193             continue;
3194 
3195         /* get the filename and add it to the src directory */
3196         path = mc_build_filename (s, next->d_name, (char *) NULL);
3197         tmp_vpath = vfs_path_from_str (path);
3198 
3199         (*ctx->stat_func) (tmp_vpath, &dst_stat);
3200         if (S_ISDIR (dst_stat.st_mode))
3201         {
3202             char *mdpath;
3203 
3204             mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
3205             /*
3206              * From here, we just intend to recursively copy subdirs, not
3207              * the double functionality of copying different when the target
3208              * dir already exists. So, we give the recursive call the flag 0
3209              * meaning no toplevel.
3210              */
3211             return_status =
3212                 copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
3213             g_free (mdpath);
3214         }
3215         else
3216         {
3217             char *dest_file;
3218 
3219             dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
3220             return_status = copy_file_file (tctx, ctx, path, dest_file);
3221             g_free (dest_file);
3222         }
3223 
3224         g_free (path);
3225 
3226         if (do_delete && return_status == FILE_CONT)
3227         {
3228             if (ctx->erase_at_end)
3229             {
3230                 if (erase_list == NULL)
3231                     erase_list = g_queue_new ();
3232 
3233                 lp = g_new0 (link_t, 1);
3234                 lp->src_vpath = tmp_vpath;
3235                 lp->st_mode = dst_stat.st_mode;
3236                 g_queue_push_tail (erase_list, lp);
3237                 tmp_vpath = NULL;
3238             }
3239             else if (S_ISDIR (dst_stat.st_mode))
3240                 return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
3241             else
3242                 return_status = erase_file (tctx, ctx, tmp_vpath);
3243         }
3244         vfs_path_free (tmp_vpath, TRUE);
3245     }
3246     mc_closedir (reading);
3247 
3248     if (ctx->preserve)
3249     {
3250         mc_timesbuf_t times;
3251 
3252         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3253 
3254         if (attrs_ok)
3255             mc_fsetflags (dst_vpath, attrs);
3256 
3257         vfs_get_timesbuf_from_stat (&src_stat, &times);
3258         mc_utime (dst_vpath, &times);
3259     }
3260     else
3261     {
3262         src_stat.st_mode = umask (-1);
3263         umask (src_stat.st_mode);
3264         src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3265         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3266     }
3267 
3268   ret:
3269     free_link (parent_dirs->data);
3270     g_slist_free_1 (parent_dirs);
3271   ret_fast:
3272     vfs_path_free (src_vpath, TRUE);
3273     vfs_path_free (dst_vpath, TRUE);
3274     return return_status;
3275 }
3276 
3277 /* }}} */
3278 
3279 /* --------------------------------------------------------------------------------------------- */
3280 /* {{{ Move routines */
3281 
3282 FileProgressStatus
3283 move_dir_dir (file_op_total_context_t *tctx, file_op_context_t *ctx, const char *s, const char *d)
     /* [previous][next][first][last][top][bottom][index][help]  */
3284 {
3285     return do_move_dir_dir (NULL, tctx, ctx, s, d);
3286 }
3287 
3288 /* }}} */
3289 
3290 /* --------------------------------------------------------------------------------------------- */
3291 /* {{{ Erase routines */
3292 
3293 FileProgressStatus
3294 erase_dir (file_op_total_context_t *tctx, file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
3295 {
3296     file_progress_show_deleting (ctx, vpath, NULL);
3297     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3298     if (check_progress_buttons (ctx) == FILE_ABORT)
3299         return FILE_ABORT;
3300 
3301     mc_refresh ();
3302 
3303     /* The old way to detect a non empty directory was:
3304        error = my_rmdir (s);
3305        if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3306        For the linux user space nfs server (nfs-server-2.2beta29-2)
3307        we would have to check also for EIO. I hope the new way is
3308        fool proof. (Norbert)
3309      */
3310     if (check_dir_is_empty (vpath) == 0)
3311     {                           /* not empty */
3312         FileProgressStatus error;
3313 
3314         error = query_recursive (ctx, vfs_path_as_str (vpath));
3315         if (error == FILE_CONT)
3316             error = recursive_erase (tctx, ctx, vpath);
3317         return error;
3318     }
3319 
3320     return try_erase_dir (ctx, vpath);
3321 }
3322 
3323 /* }}} */
3324 
3325 /* --------------------------------------------------------------------------------------------- */
3326 /* {{{ Panel operate routines */
3327 
3328 void
3329 dirsize_status_init_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3330 {
3331     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3332     WGroup *gd = GROUP (sm->dlg);
3333     Widget *wd = WIDGET (sm->dlg);
3334     WRect r = wd->rect;
3335 
3336     const char *b1_name = N_("&Abort");
3337     const char *b2_name = N_("&Skip");
3338     int b_width, ui_width;
3339 
3340 #ifdef ENABLE_NLS
3341     b1_name = _(b1_name);
3342     b2_name = _(b2_name);
3343 #endif
3344 
3345     b_width = str_term_width1 (b1_name) + 4;
3346     if (dsm->allow_skip)
3347         b_width += str_term_width1 (b2_name) + 4 + 1;
3348 
3349     ui_width = MAX (COLS / 2, b_width + 6);
3350     dsm->dirname = label_new (2, 3, NULL);
3351     group_add_widget (gd, dsm->dirname);
3352     dsm->count_size = label_new (3, 3, NULL);
3353     group_add_widget (gd, dsm->count_size);
3354     group_add_widget (gd, hline_new (4, -1, -1));
3355 
3356     dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3357     group_add_widget (gd, dsm->abort_button);
3358     if (dsm->allow_skip)
3359     {
3360         dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3361         group_add_widget (gd, dsm->skip_button);
3362         widget_select (dsm->skip_button);
3363     }
3364 
3365     r.lines = 8;
3366     r.cols = ui_width;
3367     widget_set_size_rect (wd, &r);
3368     dirsize_status_locate_buttons (dsm);
3369 }
3370 
3371 /* --------------------------------------------------------------------------------------------- */
3372 
3373 int
3374 dirsize_status_update_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3375 {
3376     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3377     Widget *wd = WIDGET (sm->dlg);
3378     WRect r = wd->rect;
3379 
3380     /* update second (longer label) */
3381     label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
3382                      dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3383 
3384     /* enlarge dialog if required */
3385     if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
3386     {
3387         r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
3388         widget_set_size_rect (wd, &r);
3389         dirsize_status_locate_buttons (dsm);
3390         widget_draw (wd);
3391         /* TODO: ret rid of double redraw */
3392     }
3393 
3394     /* adjust first label */
3395     label_set_text (dsm->dirname,
3396                     str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
3397 
3398     switch (status_msg_common_update (sm))
3399     {
3400     case B_CANCEL:
3401     case FILE_ABORT:
3402         return FILE_ABORT;
3403     case FILE_SKIP:
3404         return FILE_SKIP;
3405     default:
3406         return FILE_CONT;
3407     }
3408 }
3409 
3410 /* --------------------------------------------------------------------------------------------- */
3411 
3412 void
3413 dirsize_status_deinit_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3414 {
3415     (void) sm;
3416 
3417     /* schedule to update passive panel */
3418     if (get_other_type () == view_listing)
3419         other_panel->dirty = TRUE;
3420 }
3421 
3422 /* --------------------------------------------------------------------------------------------- */
3423 /**
3424  * compute_dir_size:
3425  *
3426  * Computes the number of bytes used by the files in a directory
3427  */
3428 
3429 FileProgressStatus
3430 compute_dir_size (const vfs_path_t *dirname_vpath, dirsize_status_msg_t *sm,
     /* [previous][next][first][last][top][bottom][index][help]  */
3431                   size_t *ret_dir_count, size_t *ret_marked_count, uintmax_t *ret_total,
3432                   gboolean follow_symlinks)
3433 {
3434     return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3435                                 follow_symlinks ? mc_stat : mc_lstat);
3436 }
3437 
3438 /* --------------------------------------------------------------------------------------------- */
3439 /**
3440  * panel_operate:
3441  *
3442  * Performs one of the operations on the current on the source_panel
3443  * (copy, delete, move).
3444  *
3445  * Returns TRUE if did change the directory
3446  * structure, Returns FALSE if user aborted
3447  *
3448  * force_single forces operation on the current entry and affects
3449  * default destination.  Current filename is used as default.
3450  */
3451 
3452 gboolean
3453 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
     /* [previous][next][first][last][top][bottom][index][help]  */
3454 {
3455     WPanel *panel = PANEL (source_panel);
3456     const gboolean single_entry = force_single || (panel->marked <= 1)
3457         || (get_current_type () == view_tree);
3458 
3459     const char *source = NULL;
3460     char *dest = NULL;
3461     vfs_path_t *dest_vpath = NULL;
3462     vfs_path_t *save_cwd = NULL, *save_dest = NULL;
3463     struct stat src_stat;
3464     gboolean ret_val = TRUE;
3465     int i;
3466     FileProgressStatus value;
3467     file_op_context_t *ctx;
3468     file_op_total_context_t *tctx;
3469     filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3470 
3471     gboolean do_bg = FALSE;     /* do background operation? */
3472 
3473     static gboolean i18n_flag = FALSE;
3474     if (!i18n_flag)
3475     {
3476         for (i = G_N_ELEMENTS (op_names); i-- != 0;)
3477             op_names[i] = Q_ (op_names[i]);
3478         i18n_flag = TRUE;
3479     }
3480 
3481     linklist = free_linklist (linklist);
3482     dest_dirs = free_linklist (dest_dirs);
3483 
3484     save_cwds_stat ();
3485 
3486     if (single_entry)
3487     {
3488         source = check_single_entry (panel, force_single, &src_stat);
3489 
3490         if (source == NULL)
3491             return FALSE;
3492     }
3493 
3494     ctx = file_op_context_new (operation);
3495 
3496     /* Show confirmation dialog */
3497     if (operation != OP_DELETE)
3498     {
3499         dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
3500         if (dest == NULL)
3501         {
3502             ret_val = FALSE;
3503             goto ret_fast;
3504         }
3505 
3506         dest_vpath = vfs_path_from_str (dest);
3507     }
3508     else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3509     {
3510         ret_val = FALSE;
3511         goto ret_fast;
3512     }
3513 
3514     tctx = file_op_total_context_new ();
3515     tctx->transfer_start = g_get_monotonic_time ();
3516 
3517 #ifdef ENABLE_BACKGROUND
3518     /* Did the user select to do a background operation? */
3519     if (do_bg)
3520     {
3521         int v;
3522 
3523         v = do_background (ctx,
3524                            g_strconcat (op_names[operation], ": ",
3525                                         vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3526         if (v == -1)
3527             message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
3528 
3529         /* If we are the parent */
3530         if (v == 1)
3531         {
3532             mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3533 
3534             mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3535             vfs_path_free (dest_vpath, TRUE);
3536             g_free (dest);
3537             /*          file_op_context_destroy (ctx); */
3538             return FALSE;
3539         }
3540     }
3541     else
3542 #endif /* ENABLE_BACKGROUND */
3543     {
3544         const file_entry_t *fe;
3545 
3546         if (operation == OP_DELETE)
3547             dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3548         else if (single_entry
3549                  && ((fe = panel_current_entry (panel)) == NULL ? FALSE : S_ISDIR (fe->st.st_mode)))
3550             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3551         else if (single_entry || force_single)
3552             dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3553         else
3554             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3555     }
3556 
3557     /* Initialize things */
3558     /* We do not want to trash cache every time file is
3559        created/touched. However, this will make our cache contain
3560        invalid data. */
3561     if ((dest != NULL)
3562         && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3563         save_dest = vfs_path_from_str (dest);
3564 
3565     if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3566         && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3567         save_cwd = vfs_path_clone (panel->cwd_vpath);
3568 
3569     /* Now, let's do the job */
3570 
3571     /* This code is only called by the tree and panel code */
3572     if (single_entry)
3573     {
3574         /* We now have ETA in all cases */
3575 
3576         /* One file: FIXME mc_chdir will take user out of any vfs */
3577         if ((operation != OP_COPY) && (get_current_type () == view_tree))
3578         {
3579             vfs_path_t *vpath;
3580             int chdir_retcode;
3581 
3582             vpath = vfs_path_from_str (PATH_SEP_STR);
3583             chdir_retcode = mc_chdir (vpath);
3584             vfs_path_free (vpath, TRUE);
3585             if (chdir_retcode < 0)
3586             {
3587                 ret_val = FALSE;
3588                 goto clean_up;
3589             }
3590         }
3591 
3592         value = operate_single_file (panel, tctx, ctx, source, &src_stat, dest, dialog_type);
3593         if ((value == FILE_CONT) && !force_single)
3594             unmark_files (panel);
3595     }
3596     else
3597     {
3598         /* Many files */
3599 
3600         /* Check destination for copy or move operation */
3601         while (operation != OP_DELETE)
3602         {
3603             int dst_result;
3604             struct stat dst_stat;
3605 
3606             dst_result = mc_stat (dest_vpath, &dst_stat);
3607 
3608             if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3609                 break;
3610 
3611             if (ctx->ignore_all
3612                 || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
3613                                dest) != FILE_RETRY)
3614                 goto clean_up;
3615         }
3616 
3617         /* TODO: the good way is required to skip directories scanning in case of rename/move
3618          * of several directories. Since reqular expression can be used for destination,
3619          * some directory movements can be a cross-filesystem and directory scanning is useful
3620          * for those directories only. */
3621 
3622         if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type)
3623             == FILE_CONT)
3624         {
3625             /* Loop for every file, perform the actual copy operation */
3626             for (i = 0; i < panel->dir.len; i++)
3627             {
3628                 const char *source2;
3629 
3630                 if (panel->dir.list[i].f.marked == 0)
3631                     continue;   /* Skip the unmarked ones */
3632 
3633                 source2 = panel->dir.list[i].fname->str;
3634                 src_stat = panel->dir.list[i].st;
3635 
3636                 value = operate_one_file (panel, tctx, ctx, source2, &src_stat, dest);
3637 
3638                 if (value == FILE_ABORT)
3639                     break;
3640 
3641                 if (value == FILE_CONT)
3642                     do_file_mark (panel, i, 0);
3643 
3644                 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3645                 {
3646                     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3647                     file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
3648                 }
3649 
3650                 if (operation != OP_DELETE)
3651                     file_progress_show (ctx, 0, 0, "", FALSE);
3652 
3653                 if (check_progress_buttons (ctx) == FILE_ABORT)
3654                     break;
3655 
3656                 mc_refresh ();
3657             }                   /* Loop for every file */
3658         }
3659     }                           /* Many entries */
3660 
3661   clean_up:
3662     /* Clean up */
3663     if (save_cwd != NULL)
3664     {
3665         mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
3666         vfs_path_free (save_cwd, TRUE);
3667     }
3668 
3669     if (save_dest != NULL)
3670     {
3671         mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
3672         vfs_path_free (save_dest, TRUE);
3673     }
3674 
3675     linklist = free_linklist (linklist);
3676     dest_dirs = free_linklist (dest_dirs);
3677     g_free (dest);
3678     vfs_path_free (dest_vpath, TRUE);
3679     MC_PTR_FREE (ctx->dest_mask);
3680 
3681 #ifdef ENABLE_BACKGROUND
3682     /* Let our parent know we are saying bye bye */
3683     if (mc_global.we_are_background)
3684     {
3685         /* Send pid to parent with child context, it is fork and
3686            don't modify real parent ctx */
3687         ctx->pid = getpid ();
3688         parent_call ((void *) end_bg_process, ctx, 0);
3689 
3690         vfs_shut ();
3691         my_exit (EXIT_SUCCESS);
3692     }
3693 #endif /* ENABLE_BACKGROUND */
3694 
3695     file_op_total_context_destroy (tctx);
3696   ret_fast:
3697     file_op_context_destroy (ctx);
3698 
3699     update_panels (UP_OPTIMIZE, UP_KEEPSEL);
3700     repaint_screen ();
3701 
3702     return ret_val;
3703 }
3704 
3705 /* }}} */
3706 
3707 /* --------------------------------------------------------------------------------------------- */
3708 /* {{{ Query/status report routines */
3709 /** Report error with one file */
3710 FileProgressStatus
3711 file_error (gboolean allow_retry, const char *format, const char *file)
     /* [previous][next][first][last][top][bottom][index][help]  */
3712 {
3713     char buf[BUF_MEDIUM];
3714 
3715     g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3716 
3717     return do_file_error (allow_retry, buf);
3718 }
3719 
3720 /* --------------------------------------------------------------------------------------------- */
3721 
3722 /*
3723    Cause emacs to enter folding mode for this file:
3724    Local variables:
3725    end:
3726  */

/* [previous][next][first][last][top][bottom][index][help]  */