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 ? 0 : 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 |= 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     ctx->do_reget = -1;  // reinit for a next file
3037     return return_status;
3038 }
3039 
3040 /* --------------------------------------------------------------------------------------------- */
3041 /**
3042  * I think these copy_*_* functions should have a return type.
3043  * anyway, this function *must* have two directories as arguments.
3044  */
3045 /* FIXME: This function needs to check the return values of the
3046    function calls */
3047 
3048 FileProgressStatus
3049 copy_dir_dir (file_op_context_t *ctx, const char *s, const char *d, gboolean toplevel,
     /* [previous][next][first][last][top][bottom][index][help]  */
3050               gboolean move_over, gboolean do_delete, GSList *parent_dirs)
3051 {
3052     struct vfs_dirent *next;
3053     struct stat dst_stat, src_stat;
3054     unsigned long attrs = 0;
3055     gboolean attrs_ok = copymove_persistent_ext2_attr;
3056     DIR *reading;
3057     FileProgressStatus return_status = FILE_CONT;
3058     link_t *lp;
3059     vfs_path_t *src_vpath, *dst_vpath;
3060     gboolean do_mkdir = TRUE;
3061 
3062     src_vpath = vfs_path_from_str (s);
3063     dst_vpath = vfs_path_from_str (d);
3064 
3065     // First get the mode of the source dir
3066 
3067 retry_src_stat:
3068     while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
3069     {
3070         if (ctx->ignore_all)
3071             return_status = FILE_IGNORE_ALL;
3072         else
3073         {
3074             return_status = file_error (ctx, TRUE, _ ("Cannot stat source directory\n%s"), s);
3075             if (return_status == FILE_IGNORE_ALL)
3076                 ctx->ignore_all = TRUE;
3077         }
3078 
3079         if (return_status != FILE_RETRY)
3080             goto ret_fast;
3081     }
3082 
3083     while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
3084     {
3085         attrs_ok = FALSE;
3086 
3087         // don't show an error message if attributes aren't supported in this FS
3088         if (attrs_ignore_error (errno))
3089             return_status = FILE_CONT;
3090         else if (ctx->ignore_all)
3091             return_status = FILE_IGNORE_ALL;
3092         else
3093         {
3094             return_status =
3095                 file_error (ctx, TRUE, _ ("Cannot get ext2 attributes of source directory\n%s"), s);
3096             if (return_status == FILE_IGNORE_ALL)
3097                 ctx->ignore_all = TRUE;
3098             if (return_status == FILE_ABORT)
3099                 goto ret_fast;
3100         }
3101 
3102         if (return_status != FILE_RETRY)
3103             break;
3104 
3105         // yet another attempt
3106         attrs_ok = TRUE;
3107     }
3108 
3109     if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
3110     {
3111         /* Don't copy a directory we created before (we don't want to copy
3112            infinitely if a directory is copied into itself) */
3113         // FIXME: should there be an error message and FILE_SKIP? - Norbert
3114         return_status = FILE_CONT;
3115         goto ret_fast;
3116     }
3117 
3118     // Hmm, hardlink to directory??? - Norbert
3119     // FIXME: In this step we should do something in case the destination already exist
3120     // Check the hardlinks
3121     if (ctx->preserve)
3122     {
3123         switch (check_hardlinks (ctx, src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
3124         {
3125         case HARDLINK_OK:
3126             // We have made a hardlink - no more processing is necessary
3127             goto ret_fast;
3128 
3129         case HARDLINK_ABORT:
3130             return_status = FILE_ABORT;
3131             goto ret_fast;
3132 
3133         default:
3134             break;
3135         }
3136     }
3137 
3138     if (!S_ISDIR (src_stat.st_mode))
3139     {
3140         if (ctx->ignore_all)
3141             return_status = FILE_IGNORE_ALL;
3142         else
3143         {
3144             return_status = file_error (ctx, TRUE, _ ("Source \"%s\" is not a directory\n%s"), s);
3145             if (return_status == FILE_RETRY)
3146                 goto retry_src_stat;
3147             if (return_status == FILE_IGNORE_ALL)
3148                 ctx->ignore_all = TRUE;
3149         }
3150         goto ret_fast;
3151     }
3152 
3153     if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
3154     {
3155         // we found a cyclic symbolic link
3156         message (D_ERROR, MSG_ERROR, _ ("Cannot copy cyclic symbolic link\n\"%s\""), s);
3157         return_status = FILE_SKIP;
3158         goto ret_fast;
3159     }
3160 
3161     lp = g_new0 (link_t, 1);
3162     lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
3163     lp->ino = src_stat.st_ino;
3164     lp->dev = src_stat.st_dev;
3165     parent_dirs = g_slist_prepend (parent_dirs, lp);
3166 
3167 retry_dst_stat:
3168     // Now, check if the dest dir exists, if not, create it.
3169     if (mc_stat (dst_vpath, &dst_stat) != 0)
3170     {
3171         // Here the dir doesn't exist : make it !
3172         if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
3173         {
3174             return_status = FILE_CONT;
3175             goto ret;
3176         }
3177     }
3178     else
3179     {
3180         /*
3181          * If the destination directory exists, we want to copy the whole
3182          * directory, but we only want this to happen once.
3183          *
3184          * Escape sequences added to the * to compiler warnings.
3185          * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3186          * or ( /bla doesn't exist )       /tmp/\* to /bla     ->  /bla/\*
3187          */
3188         if (!S_ISDIR (dst_stat.st_mode))
3189         {
3190             if (ctx->ignore_all)
3191                 return_status = FILE_IGNORE_ALL;
3192             else
3193             {
3194                 return_status =
3195                     file_error (ctx, TRUE, _ ("Destination\n%s\nmust be a directory\n%s"), d);
3196                 if (return_status == FILE_IGNORE_ALL)
3197                     ctx->ignore_all = TRUE;
3198                 if (return_status == FILE_RETRY)
3199                     goto retry_dst_stat;
3200             }
3201             goto ret;
3202         }
3203         // Dive into subdir if exists
3204         if (toplevel && ctx->dive_into_subdirs)
3205         {
3206             vfs_path_t *tmp;
3207 
3208             tmp = dst_vpath;
3209             dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
3210             vfs_path_free (tmp, TRUE);
3211         }
3212         else
3213             do_mkdir = FALSE;
3214     }
3215 
3216     d = vfs_path_as_str (dst_vpath);
3217 
3218     if (do_mkdir)
3219     {
3220         while (mc_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
3221         {
3222             if (ctx->ignore_all)
3223                 return_status = FILE_IGNORE_ALL;
3224             else
3225             {
3226                 return_status = file_error (ctx, TRUE, _ ("Cannot create target directory\n%s"), d);
3227                 if (return_status == FILE_IGNORE_ALL)
3228                     ctx->ignore_all = TRUE;
3229             }
3230             if (return_status != FILE_RETRY)
3231                 goto ret;
3232         }
3233 
3234         lp = g_new0 (link_t, 1);
3235         mc_stat (dst_vpath, &dst_stat);
3236         lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
3237         lp->ino = dst_stat.st_ino;
3238         lp->dev = dst_stat.st_dev;
3239         dest_dirs = g_slist_prepend (dest_dirs, lp);
3240     }
3241 
3242     if (ctx->preserve_uidgid)
3243     {
3244         while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
3245         {
3246             if (ctx->ignore_all)
3247                 return_status = FILE_IGNORE_ALL;
3248             else
3249             {
3250                 return_status = file_error (ctx, TRUE, _ ("Cannot chown target directory\n%s"), d);
3251                 if (return_status == FILE_IGNORE_ALL)
3252                     ctx->ignore_all = TRUE;
3253             }
3254             if (return_status != FILE_RETRY)
3255                 goto ret;
3256         }
3257     }
3258 
3259     // open the source dir for reading
3260     reading = mc_opendir (src_vpath);
3261     if (reading == NULL)
3262         goto ret;
3263 
3264     while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
3265     {
3266         char *path;
3267         vfs_path_t *tmp_vpath;
3268 
3269         /*
3270          * Now, we don't want '.' and '..' to be created / copied at any time
3271          */
3272         if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
3273             continue;
3274 
3275         // get the filename and add it to the src directory
3276         path = mc_build_filename (s, next->d_name, (char *) NULL);
3277         tmp_vpath = vfs_path_from_str (path);
3278 
3279         (*ctx->stat_func) (tmp_vpath, &dst_stat);
3280         if (S_ISDIR (dst_stat.st_mode))
3281         {
3282             char *mdpath;
3283 
3284             mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
3285             /*
3286              * From here, we just intend to recursively copy subdirs, not
3287              * the double functionality of copying different when the target
3288              * dir already exists. So, we give the recursive call the flag 0
3289              * meaning no toplevel.
3290              */
3291             return_status = copy_dir_dir (ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
3292             g_free (mdpath);
3293         }
3294         else
3295         {
3296             char *dest_file;
3297 
3298             dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
3299             return_status = copy_file_file (ctx, path, dest_file);
3300             g_free (dest_file);
3301         }
3302 
3303         g_free (path);
3304 
3305         if (do_delete && return_status == FILE_CONT)
3306         {
3307             if (ctx->erase_at_end)
3308             {
3309                 if (erase_list == NULL)
3310                     erase_list = g_queue_new ();
3311 
3312                 lp = g_new0 (link_t, 1);
3313                 lp->src_vpath = tmp_vpath;
3314                 lp->st_mode = dst_stat.st_mode;
3315                 g_queue_push_tail (erase_list, lp);
3316                 tmp_vpath = NULL;
3317             }
3318             else if (S_ISDIR (dst_stat.st_mode))
3319                 return_status = erase_dir_iff_empty (ctx, tmp_vpath);
3320             else
3321                 return_status = erase_file (ctx, tmp_vpath);
3322         }
3323         vfs_path_free (tmp_vpath, TRUE);
3324     }
3325     mc_closedir (reading);
3326 
3327     if (ctx->preserve)
3328     {
3329         mc_timesbuf_t times;
3330 
3331         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3332 
3333         if (attrs_ok)
3334             mc_fsetflags (dst_vpath, attrs);
3335 
3336         vfs_get_timesbuf_from_stat (&src_stat, &times);
3337         mc_utime (dst_vpath, &times);
3338     }
3339     else
3340     {
3341         src_stat.st_mode = umask (-1);
3342         umask (src_stat.st_mode);
3343         src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3344         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3345     }
3346 
3347 ret:
3348     free_link (parent_dirs->data);
3349     g_slist_free_1 (parent_dirs);
3350 ret_fast:
3351     vfs_path_free (src_vpath, TRUE);
3352     vfs_path_free (dst_vpath, TRUE);
3353     return return_status;
3354 }
3355 
3356 /* }}} */
3357 
3358 /* --------------------------------------------------------------------------------------------- */
3359 /* {{{ Move routines */
3360 
3361 FileProgressStatus
3362 move_dir_dir (file_op_context_t *ctx, const char *s, const char *d)
     /* [previous][next][first][last][top][bottom][index][help]  */
3363 {
3364     return do_move_dir_dir (NULL, ctx, s, d);
3365 }
3366 
3367 /* }}} */
3368 
3369 /* --------------------------------------------------------------------------------------------- */
3370 /* {{{ Erase routines */
3371 
3372 FileProgressStatus
3373 erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
3374 {
3375     FileProgressStatus error = FILE_CONT;
3376 
3377     file_progress_show_deleting (ctx, vpath, NULL);
3378     file_progress_show_count (ctx);
3379     if (file_progress_check_buttons (ctx) == FILE_ABORT)
3380         return FILE_ABORT;
3381 
3382     mc_refresh ();
3383 
3384     const int res = check_dir_is_empty (ctx, vpath, &error);
3385 
3386     if (res == -1)
3387         return error;
3388 
3389     if (res == 0)
3390     {
3391         // not empty
3392         error = query_recursive (ctx, vfs_path_as_str (vpath));
3393         if (error == FILE_CONT)
3394             error = recursive_erase (ctx, vpath);
3395         return error;
3396     }
3397 
3398     return try_erase_dir (ctx, vpath);
3399 }
3400 
3401 /* }}} */
3402 
3403 /* --------------------------------------------------------------------------------------------- */
3404 /* {{{ Panel operate routines */
3405 
3406 void
3407 dirsize_status_init_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3408 {
3409     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3410     WGroup *gd = GROUP (sm->dlg);
3411     Widget *wd = WIDGET (sm->dlg);
3412     WRect r = wd->rect;
3413 
3414     const char *b1_name = _ ("&Abort");
3415     const char *b2_name = _ ("&Skip");
3416     int b_width, ui_width;
3417 
3418     b_width = str_term_width1 (b1_name) + 4;
3419     if (dsm->allow_skip)
3420         b_width += str_term_width1 (b2_name) + 4 + 1;
3421 
3422     ui_width = MAX (COLS / 2, b_width + 6);
3423     dsm->dirname = label_new (2, 3, NULL);
3424     group_add_widget (gd, dsm->dirname);
3425     dsm->count_size = label_new (3, 3, NULL);
3426     group_add_widget (gd, dsm->count_size);
3427     group_add_widget (gd, hline_new (4, -1, -1));
3428 
3429     dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3430     group_add_widget (gd, dsm->abort_button);
3431     if (dsm->allow_skip)
3432     {
3433         dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3434         group_add_widget (gd, dsm->skip_button);
3435         widget_select (dsm->skip_button);
3436     }
3437 
3438     r.lines = 8;
3439     r.cols = ui_width;
3440     widget_set_size_rect (wd, &r);
3441     dirsize_status_locate_buttons (dsm);
3442 }
3443 
3444 /* --------------------------------------------------------------------------------------------- */
3445 
3446 int
3447 dirsize_status_update_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3448 {
3449     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3450     Widget *wd = WIDGET (sm->dlg);
3451     WRect r = wd->rect;
3452 
3453     // update second (longer label)
3454     label_set_textv (dsm->count_size, _ ("Directories: %zu, total size: %s"), dsm->dir_count,
3455                      size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3456 
3457     // enlarge dialog if required
3458     if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
3459     {
3460         r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
3461         widget_set_size_rect (wd, &r);
3462         dirsize_status_locate_buttons (dsm);
3463         widget_draw (wd);
3464         // TODO: ret rid of double redraw
3465     }
3466 
3467     // adjust first label
3468     label_set_text (dsm->dirname,
3469                     str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
3470 
3471     switch (status_msg_common_update (sm))
3472     {
3473     case B_CANCEL:
3474     case FILE_ABORT:
3475         return FILE_ABORT;
3476     case FILE_SKIP:
3477         return FILE_SKIP;
3478     default:
3479         return FILE_CONT;
3480     }
3481 }
3482 
3483 /* --------------------------------------------------------------------------------------------- */
3484 
3485 void
3486 dirsize_status_deinit_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3487 {
3488     (void) sm;
3489 
3490     // schedule to update passive panel
3491     if (get_other_type () == view_listing)
3492         other_panel->dirty = TRUE;
3493 }
3494 
3495 /* --------------------------------------------------------------------------------------------- */
3496 /**
3497  * compute_dir_size:
3498  *
3499  * Computes the number of bytes used by the files in a directory
3500  */
3501 
3502 FileProgressStatus
3503 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]  */
3504                   size_t *ret_marked_count, uintmax_t *ret_total, gboolean follow_symlinks)
3505 {
3506     return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3507                                 follow_symlinks ? mc_stat : mc_lstat);
3508 }
3509 
3510 /* --------------------------------------------------------------------------------------------- */
3511 /**
3512  * panel_operate:
3513  *
3514  * Performs one of the operations on the current on the source_panel
3515  * (copy, delete, move).
3516  *
3517  * Returns TRUE if did change the directory
3518  * structure, Returns FALSE if user aborted
3519  *
3520  * force_single forces operation on the current entry and affects
3521  * default destination.  Current filename is used as default.
3522  */
3523 
3524 gboolean
3525 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
     /* [previous][next][first][last][top][bottom][index][help]  */
3526 {
3527     WPanel *panel = PANEL (source_panel);
3528     const gboolean single_entry =
3529         force_single || (panel->marked <= 1) || (get_current_type () == view_tree);
3530 
3531     const char *source = NULL;
3532     char *dest = NULL;
3533     vfs_path_t *dest_vpath = NULL;
3534     vfs_path_t *save_cwd = NULL, *save_dest = NULL;
3535     struct stat src_stat;
3536     gboolean ret_val = TRUE;
3537     int i;
3538     FileProgressStatus value;
3539     file_op_context_t *ctx;
3540     filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3541 
3542     gboolean do_bg = FALSE;  // do background operation?
3543 
3544     linklist = free_linklist (linklist);
3545     dest_dirs = free_linklist (dest_dirs);
3546 
3547     save_cwds_stat ();
3548 
3549     if (single_entry)
3550     {
3551         source = check_single_entry (panel, force_single, &src_stat);
3552 
3553         if (source == NULL)
3554             return FALSE;
3555     }
3556 
3557     ctx = file_op_context_new (operation);
3558 
3559     // Show confirmation dialog
3560     if (operation != OP_DELETE)
3561     {
3562         dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
3563         if (dest == NULL)
3564         {
3565             ret_val = FALSE;
3566             goto ret_fast;
3567         }
3568 
3569         dest_vpath = vfs_path_from_str (dest);
3570     }
3571     else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3572     {
3573         ret_val = FALSE;
3574         goto ret_fast;
3575     }
3576 
3577     ctx->total_transfer_start = g_get_monotonic_time ();
3578 
3579 #ifdef ENABLE_BACKGROUND
3580     // Did the user select to do a background operation?
3581     if (do_bg)
3582     {
3583         int v;
3584 
3585         v = do_background (ctx,
3586                            g_strconcat (op_names[operation], ": ",
3587                                         vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3588         if (v == -1)
3589             message (D_ERROR, MSG_ERROR, _ ("Sorry, I could not put the job in background"));
3590 
3591         // If we are the parent
3592         if (v == 1)
3593         {
3594             mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3595 
3596             mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3597             vfs_path_free (dest_vpath, TRUE);
3598             g_free (dest);
3599             //          file_op_context_destroy (ctx);
3600             return FALSE;
3601         }
3602     }
3603     else
3604 #endif
3605     {
3606         const file_entry_t *fe;
3607 
3608         if (operation == OP_DELETE)
3609             dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3610         else if (single_entry
3611                  && ((fe = panel_current_entry (panel)) == NULL ? FALSE : S_ISDIR (fe->st.st_mode)))
3612             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3613         else if (single_entry || force_single)
3614             dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3615         else
3616             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3617     }
3618 
3619     // Initialize things
3620     /* We do not want to trash cache every time file is
3621        created/touched. However, this will make our cache contain
3622        invalid data. */
3623     if ((dest != NULL)
3624         && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3625         save_dest = vfs_path_from_str (dest);
3626 
3627     if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3628         && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3629         save_cwd = vfs_path_clone (panel->cwd_vpath);
3630 
3631     // Now, let's do the job
3632 
3633     // This code is only called by the tree and panel code
3634     if (single_entry)
3635     {
3636         // We now have ETA in all cases
3637 
3638         // One file: FIXME mc_chdir will take user out of any vfs
3639         if ((operation != OP_COPY) && (get_current_type () == view_tree))
3640         {
3641             vfs_path_t *vpath;
3642             int chdir_retcode;
3643 
3644             vpath = vfs_path_from_str (PATH_SEP_STR);
3645             chdir_retcode = mc_chdir (vpath);
3646             vfs_path_free (vpath, TRUE);
3647             if (chdir_retcode < 0)
3648             {
3649                 ret_val = FALSE;
3650                 goto clean_up;
3651             }
3652         }
3653 
3654         value = operate_single_file (panel, ctx, source, &src_stat, dest, dialog_type);
3655         if ((value == FILE_CONT) && !force_single)
3656             unmark_files (panel);
3657     }
3658     else
3659     {
3660         // Many files
3661 
3662         // Check destination for copy or move operation
3663         while (operation != OP_DELETE)
3664         {
3665             int dst_result;
3666             struct stat dst_stat;
3667 
3668             dst_result = mc_stat (dest_vpath, &dst_stat);
3669 
3670             if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3671                 break;
3672 
3673             if (ctx->ignore_all
3674                 || file_error (ctx, TRUE, _ ("Destination\n%s\nmust be a directory\n%s"), dest)
3675                     != FILE_RETRY)
3676                 goto clean_up;
3677         }
3678 
3679         /* TODO: the good way is required to skip directories scanning in case of rename/move
3680          * of several directories. Since reqular expression can be used for destination,
3681          * some directory movements can be a cross-filesystem and directory scanning is useful
3682          * for those directories only. */
3683 
3684         value =
3685             panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type);
3686         if (value == FILE_CONT)
3687             // Loop for every file, perform the actual copy operation
3688             for (i = 0; i < panel->dir.len; i++)
3689             {
3690                 const char *source2;
3691 
3692                 if (panel->dir.list[i].f.marked == 0)
3693                     continue;  // Skip the unmarked ones
3694 
3695                 source2 = panel->dir.list[i].fname->str;
3696                 src_stat = panel->dir.list[i].st;
3697 
3698                 value = operate_one_file (panel, ctx, source2, &src_stat, dest);
3699                 if (value == FILE_ABORT)
3700                     break;
3701 
3702                 if (value == FILE_CONT)
3703                     do_file_mark (panel, i, 0);
3704 
3705                 mc_refresh ();
3706             }  // Loop for every file
3707     }  // Many entries
3708 
3709 clean_up:
3710     // Clean up
3711     if (save_cwd != NULL)
3712     {
3713         mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
3714         vfs_path_free (save_cwd, TRUE);
3715     }
3716 
3717     if (save_dest != NULL)
3718     {
3719         mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
3720         vfs_path_free (save_dest, TRUE);
3721     }
3722 
3723     linklist = free_linklist (linklist);
3724     dest_dirs = free_linklist (dest_dirs);
3725     g_free (dest);
3726     vfs_path_free (dest_vpath, TRUE);
3727     MC_PTR_FREE (ctx->dest_mask);
3728 
3729 #ifdef ENABLE_BACKGROUND
3730     // Let our parent know we are saying bye bye
3731     if (mc_global.we_are_background)
3732     {
3733         /* Send pid to parent with child context, it is fork and
3734            don't modify real parent ctx */
3735         ctx->pid = getpid ();
3736         parent_call ((void *) end_bg_process, ctx, 0);
3737 
3738         vfs_shut ();
3739         my_exit (EXIT_SUCCESS);
3740     }
3741 #endif
3742 
3743 ret_fast:
3744     file_op_context_destroy (ctx);
3745 
3746     update_panels (UP_OPTIMIZE, UP_KEEPSEL);
3747     repaint_screen ();
3748 
3749     return ret_val;
3750 }
3751 
3752 /* }}} */
3753 
3754 /* --------------------------------------------------------------------------------------------- */
3755 
3756 /* {{{ Query/status report routines */
3757 /** Report error with one file */
3758 FileProgressStatus
3759 file_error (file_op_context_t *ctx, gboolean allow_retry, const char *format, const char *file)
     /* [previous][next][first][last][top][bottom][index][help]  */
3760 {
3761     return files_error (ctx, allow_retry, format, file, "");
3762 }
3763 
3764 /* --------------------------------------------------------------------------------------------- */
3765 
3766 /*
3767    Cause emacs to enter folding mode for this file:
3768    Local variables:
3769    end:
3770  */

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