Manual pages: mcmcdiffmceditmcview

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

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