Manual pages: mc • mcdiff • mcedit • mcview

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 && errno != ENOENT && !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             const gboolean vanished = (errno == ENOENT);
1463 
1464             vfs_path_free (tmp_vpath, TRUE);
1465 
1466             // The entry has been removed since the directory was read, e.g. "._" (#4720)
1467             if (vanished)
1468                 continue;
1469 
1470             mc_closedir (reading);
1471             return FILE_RETRY;
1472         }
1473         if (S_ISDIR (buf.st_mode))
1474             return_status = recursive_erase (ctx, tmp_vpath);
1475         else
1476             return_status = erase_file (ctx, tmp_vpath);
1477         vfs_path_free (tmp_vpath, TRUE);
1478     }
1479     mc_closedir (reading);
1480 
1481     if (return_status == FILE_ABORT)
1482         return FILE_ABORT;
1483 
1484     file_progress_show_deleting (ctx, vpath, NULL);
1485     file_progress_show_count (ctx);
1486     if (file_progress_check_buttons (ctx) == FILE_ABORT)
1487         return FILE_ABORT;
1488 
1489     mc_refresh ();
1490 
1491     return try_erase_dir (ctx, vpath);
1492 }
1493 
1494 /* --------------------------------------------------------------------------------------------- */
1495 /**
1496  * Check if directory is empty or not.
1497  *
1498  * @param ctx file operation context descriptor
1499  * @param vpath directory handler
1500  * @param error status of directory reading
1501  *
1502  * @return -1 on error,
1503  *          1 if there are no entries besides "." and ".." in the directory path points to,
1504  *          0 else.
1505  *
1506  * ATTENTION! Be careful when modifying this function (like commit 25e419ba0886f)!
1507  * Some implementations of readdir() in MC VFS (for example, vfs_s_readdir(), which is used
1508  * in SHELL) don't return "." and ".." entries.
1509  */
1510 static int
1511 check_dir_is_empty (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *error)
     /* [previous][next][first][last][top][bottom][index][help]  */
1512 {
1513     DIR *dir;
1514     struct vfs_dirent *d;
1515     int i = 1;
1516 
1517     while ((dir = mc_opendir (vpath)) == NULL)
1518     {
1519         if (ctx->ignore_all)
1520             *error = FILE_IGNORE_ALL;
1521         else
1522         {
1523             const FileProgressStatus status = file_error (
1524                 ctx, TRUE, _ ("Cannot enter into directory\n%s"), vfs_path_as_str (vpath));
1525 
1526             if (status == FILE_RETRY)
1527                 continue;
1528             if (status == FILE_IGNORE_ALL)
1529                 ctx->ignore_all = TRUE;
1530 
1531             *error = status;  // FILE_IGNORE, FILE_IGNORE_ALL, FILE_ABORT
1532         }
1533 
1534         return (-1);
1535     }
1536 
1537     for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1538         if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1539         {
1540             i = 0;
1541             break;
1542         }
1543 
1544     mc_closedir (dir);
1545     *error = FILE_CONT;
1546     return i;
1547 }
1548 
1549 /* --------------------------------------------------------------------------------------------- */
1550 
1551 static FileProgressStatus
1552 erase_dir_iff_empty (file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1553 {
1554     FileProgressStatus error = FILE_CONT;
1555 
1556     file_progress_show_deleting (ctx, vpath, NULL);
1557     file_progress_show_count (ctx);
1558     if (file_progress_check_buttons (ctx) == FILE_ABORT)
1559         return FILE_ABORT;
1560 
1561     mc_refresh ();
1562 
1563     const int res = check_dir_is_empty (ctx, vpath, &error);
1564 
1565     if (res == -1)
1566         return error;
1567 
1568     if (res != 1)
1569         return FILE_CONT;
1570 
1571     // not empty or error
1572     return try_erase_dir (ctx, vpath);
1573 }
1574 
1575 /* --------------------------------------------------------------------------------------------- */
1576 
1577 static void
1578 erase_dir_after_copy (file_op_context_t *ctx, const vfs_path_t *vpath, FileProgressStatus *status)
     /* [previous][next][first][last][top][bottom][index][help]  */
1579 {
1580     if (ctx->erase_at_end && erase_list != NULL)
1581     {
1582         // Reset progress count before delete to avoid counting files twice
1583         ctx->total_progress_count = ctx->prev_total_progress_count;
1584 
1585         while (!g_queue_is_empty (erase_list) && *status != FILE_ABORT)
1586         {
1587             link_t *lp;
1588 
1589             lp = (link_t *) g_queue_pop_head (erase_list);
1590 
1591             if (S_ISDIR (lp->st_mode))
1592                 *status = erase_dir_iff_empty (ctx, lp->src_vpath);
1593             else
1594                 *status = erase_file (ctx, lp->src_vpath);
1595 
1596             free_link (lp);
1597         }
1598 
1599         // Save progress counter before move next directory
1600         ctx->prev_total_progress_count = ctx->total_progress_count;
1601     }
1602 
1603     erase_dir_iff_empty (ctx, vpath);
1604 }
1605 
1606 /* }}} */
1607 
1608 /* --------------------------------------------------------------------------------------------- */
1609 
1610 /**
1611  * Move single directory or one of many directories from one location to another.
1612  *
1613  * @panel pointer to panel in case of single directory, NULL otherwise
1614  * @ctx file operation context object
1615  * @s source directory name
1616  * @d destination directory name
1617  *
1618  * @return operation result
1619  */
1620 static FileProgressStatus
1621 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]  */
1622 {
1623     struct stat src_stat, dst_stat;
1624     FileProgressStatus return_status = FILE_CONT;
1625     gboolean move_over = FALSE;
1626     gboolean dstat_ok;
1627     vfs_path_t *src_vpath, *dst_vpath;
1628 
1629     src_vpath = vfs_path_from_str (s);
1630     dst_vpath = vfs_path_from_str (d);
1631 
1632     file_progress_show_source (ctx, src_vpath);
1633     file_progress_show_target (ctx, dst_vpath);
1634 
1635     // FIXME: do we really need to check buttons in case of single directory?
1636     if (panel != NULL && file_progress_check_buttons (ctx) == FILE_ABORT)
1637     {
1638         return_status = FILE_ABORT;
1639         goto ret_fast;
1640     }
1641 
1642     mc_refresh ();
1643 
1644     mc_stat (src_vpath, &src_stat);
1645 
1646     dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0);
1647 
1648     if (dstat_ok && check_same_file (ctx, s, &src_stat, d, &dst_stat, &return_status))
1649         goto ret_fast;
1650 
1651     if (!dstat_ok)
1652         ;  // destination doesn't exist
1653     else if (!ctx->dive_into_subdirs)
1654         move_over = TRUE;
1655     else
1656     {
1657         vfs_path_t *tmp;
1658 
1659         tmp = dst_vpath;
1660         dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
1661         vfs_path_free (tmp, TRUE);
1662     }
1663 
1664     d = vfs_path_as_str (dst_vpath);
1665 
1666     // Check if the user inputted an existing dir
1667 retry_dst_stat:
1668     if (mc_stat (dst_vpath, &dst_stat) == 0)
1669     {
1670         if (move_over)
1671         {
1672             if (panel != NULL)
1673             {
1674                 /* In case of single directory, calculate totals. In case of many directories,
1675                    totals are calculated already. */
1676                 return_status = panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1677                                                            FILEGUI_DIALOG_MULTI_ITEM);
1678                 if (return_status != FILE_CONT)
1679                     goto ret;
1680             }
1681 
1682             return_status = copy_dir_dir (ctx, s, d, FALSE, TRUE, TRUE, NULL);
1683 
1684             if (return_status != FILE_CONT)
1685                 goto ret;
1686             goto oktoret;
1687         }
1688         else if (ctx->ignore_all)
1689             return_status = FILE_IGNORE_ALL;
1690         else
1691         {
1692             if (S_ISDIR (dst_stat.st_mode))
1693                 return_status = file_error (ctx, TRUE, _ ("Cannot overwrite directory\n%s"), d);
1694             else
1695                 return_status = file_error (ctx, TRUE, _ ("Cannot overwrite file\n%s"), d);
1696             if (return_status == FILE_IGNORE_ALL)
1697                 ctx->ignore_all = TRUE;
1698             if (return_status == FILE_RETRY)
1699                 goto retry_dst_stat;
1700         }
1701 
1702         goto ret_fast;
1703     }
1704 
1705 retry_rename:
1706     if (mc_rename (src_vpath, dst_vpath) == 0)
1707     {
1708         return_status = FILE_CONT;
1709         goto ret;
1710     }
1711 
1712     if (errno != EXDEV)
1713     {
1714         if (!ctx->ignore_all)
1715         {
1716             return_status = files_error (ctx, TRUE, _ ("Cannot move directory\n%s\nto\n%s"), s, d);
1717             if (return_status == FILE_IGNORE_ALL)
1718                 ctx->ignore_all = TRUE;
1719             if (return_status == FILE_RETRY)
1720                 goto retry_rename;
1721         }
1722         goto ret;
1723     }
1724 
1725     // Failed because of filesystem boundary -> copy dir instead
1726     if (panel != NULL)
1727     {
1728         /* In case of single directory, calculate totals. In case of many directories,
1729            totals are calculated already. */
1730         return_status = panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1731                                                    FILEGUI_DIALOG_MULTI_ITEM);
1732         if (return_status != FILE_CONT)
1733             goto ret;
1734     }
1735 
1736     return_status = copy_dir_dir (ctx, s, d, FALSE, FALSE, TRUE, NULL);
1737 
1738     if (return_status != FILE_CONT)
1739         goto ret;
1740 
1741 oktoret:
1742     /* FIXME: there is no need to update progress and check buttons
1743        at the finish of single directory operation. */
1744     if (panel == NULL)
1745     {
1746         file_progress_show_source (ctx, NULL);
1747         file_progress_show_target (ctx, NULL);
1748         if (verbose)
1749             file_progress_show (ctx, 0, 0, "", FALSE);
1750 
1751         return_status = file_progress_check_buttons (ctx);
1752         if (return_status != FILE_CONT)
1753             goto ret;
1754     }
1755 
1756     mc_refresh ();
1757 
1758     erase_dir_after_copy (ctx, src_vpath, &return_status);
1759 
1760 ret:
1761     erase_list = free_erase_list (erase_list);
1762 ret_fast:
1763     vfs_path_free (src_vpath, TRUE);
1764     vfs_path_free (dst_vpath, TRUE);
1765     return return_status;
1766 }
1767 
1768 /* --------------------------------------------------------------------------------------------- */
1769 
1770 /* {{{ Panel operate routines */
1771 
1772 /**
1773  * Return currently selected entry name or the name of the first marked
1774  * entry if there is one.
1775  */
1776 
1777 static const char *
1778 panel_get_file (const WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1779 {
1780     const file_entry_t *fe;
1781 
1782     if (get_current_type () == view_tree)
1783     {
1784         WTree *tree;
1785         const vfs_path_t *selected_name;
1786 
1787         tree = (WTree *) get_panel_widget (get_current_index ());
1788         selected_name = tree_selected_name (tree);
1789         return vfs_path_as_str (selected_name);
1790     }
1791 
1792     if (panel->marked != 0)
1793     {
1794         int i;
1795 
1796         for (i = 0; i < panel->dir.len; i++)
1797             if (panel->dir.list[i].f.marked != 0)
1798                 return panel->dir.list[i].fname->str;
1799     }
1800 
1801     fe = panel_current_entry (panel);
1802 
1803     return (fe == NULL ? NULL : fe->fname->str);
1804 }
1805 
1806 /* --------------------------------------------------------------------------------------------- */
1807 
1808 static const char *
1809 check_single_entry (const WPanel *panel, gboolean force_single, struct stat *src_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
1810 {
1811     const char *source;
1812     gboolean ok;
1813 
1814     if (force_single)
1815     {
1816         const file_entry_t *fe;
1817 
1818         fe = panel_current_entry (panel);
1819         source = fe == NULL ? NULL : fe->fname->str;
1820     }
1821     else
1822         source = panel_get_file (panel);
1823 
1824     if (source == NULL)
1825         return NULL;
1826 
1827     ok = !DIR_IS_DOTDOT (source);
1828 
1829     if (!ok)
1830         message (D_ERROR, MSG_ERROR, _ ("Cannot operate on \"..\"!"));
1831     else
1832     {
1833         vfs_path_t *source_vpath;
1834 
1835         source_vpath = vfs_path_from_str (source);
1836 
1837         // Update stat to get actual info
1838         ok = mc_lstat (source_vpath, src_stat) == 0;
1839         if (!ok)
1840         {
1841             file_error_message (_ ("Cannot stat\n%s"), source);
1842 
1843             // Directory was changed outside MC. Reload it forced
1844             if (!panel->is_panelized)
1845             {
1846                 panel_update_flags_t flags = UP_RELOAD;
1847 
1848                 // don't update panelized panel
1849                 if (get_other_type () == view_listing && other_panel->is_panelized)
1850                     flags |= UP_ONLY_CURRENT;
1851 
1852                 update_panels (flags, UP_KEEPSEL);
1853             }
1854         }
1855 
1856         vfs_path_free (source_vpath, TRUE);
1857     }
1858 
1859     return ok ? source : NULL;
1860 }
1861 
1862 /* --------------------------------------------------------------------------------------------- */
1863 /**
1864  * Generate user prompt for panel operation.
1865  * src_stat must be not NULL for single source, and NULL for multiple sources
1866  */
1867 
1868 static char *
1869 panel_operate_generate_prompt (const WPanel *panel, FileOperation operation,
     /* [previous][next][first][last][top][bottom][index][help]  */
1870                                const struct stat *src_stat)
1871 {
1872     char *sp;
1873     char *format_string;
1874     const char *cp;
1875 
1876     static gboolean i18n_flag = FALSE;
1877     if (!i18n_flag)
1878     {
1879         size_t i;
1880 
1881         for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1882             op_names1[i] = Q_ (op_names1[i]);
1883 
1884 #ifdef ENABLE_NLS
1885         for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1886             prompt_parts[i] = _ (prompt_parts[i]);
1887 
1888         one_format = _ (one_format);
1889         many_format = _ (many_format);
1890 #endif
1891         i18n_flag = TRUE;
1892     }
1893 
1894     /* Possible prompts:
1895      *   OP_COPY:
1896      *       "Copy file \"%s\" with source mask:"
1897      *       "Copy %d files with source mask:"
1898      *       "Copy directory \"%s\" with source mask:"
1899      *       "Copy %d directories with source mask:"
1900      *       "Copy %d files/directories with source mask:"
1901      *   OP_MOVE:
1902      *       "Move file \"%s\" with source mask:"
1903      *       "Move %d files with source mask:"
1904      *       "Move directory \"%s\" with source mask:"
1905      *       "Move %d directories with source mask:"
1906      *       "Move %d files/directories with source mask:"
1907      *   OP_DELETE:
1908      *       "Delete file \"%s\"?"
1909      *       "Delete %d files?"
1910      *       "Delete directory \"%s\"?"
1911      *       "Delete %d directories?"
1912      *       "Delete %d files/directories?"
1913      */
1914 
1915     cp = (src_stat != NULL ? one_format : many_format);
1916 
1917     // 1. Substitute %o
1918     format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1919 
1920     // 2. Substitute %n
1921     cp = operation == OP_DELETE ? "\n" : " ";
1922     sp = format_string;
1923     format_string = str_replace_all (sp, "%n", cp);
1924     g_free (sp);
1925 
1926     // 3. Substitute %f
1927     if (src_stat != NULL)
1928         cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1929     else if (panel->marked == panel->dirs_marked)
1930         cp = prompt_parts[3];
1931     else
1932         cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1933 
1934     sp = format_string;
1935     format_string = str_replace_all (sp, "%f", cp);
1936     g_free (sp);
1937 
1938     // 4. Substitute %m
1939     cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1940     sp = format_string;
1941     format_string = str_replace_all (sp, "%m", cp);
1942     g_free (sp);
1943 
1944     return format_string;
1945 }
1946 
1947 /* --------------------------------------------------------------------------------------------- */
1948 
1949 static char *
1950 do_confirm_copy_move (const WPanel *panel, gboolean force_single, const char *source,
     /* [previous][next][first][last][top][bottom][index][help]  */
1951                       struct stat *src_stat, file_op_context_t *ctx, gboolean *do_bg)
1952 {
1953     const char *tmp_dest_dir;
1954     char *dest_dir;
1955     char *format;
1956     char *ret;
1957 
1958     // Forced single operations default to the original name
1959     if (force_single)
1960         tmp_dest_dir = source;
1961     else if (get_other_type () == view_listing)
1962         tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
1963     else
1964         tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
1965 
1966     /*
1967      * Add trailing backslash only when do non-local ops.
1968      * It saves user from occasional file renames (when destination
1969      * dir is deleted)
1970      */
1971     if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
1972         && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
1973     {
1974         // add trailing separator
1975         dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
1976     }
1977     else
1978     {
1979         // just copy
1980         dest_dir = g_strdup (tmp_dest_dir);
1981     }
1982 
1983     if (dest_dir == NULL)
1984         return NULL;
1985 
1986     if (source == NULL)
1987         src_stat = NULL;
1988 
1989     // Generate confirmation prompt
1990     format = panel_operate_generate_prompt (panel, ctx->operation, src_stat);
1991 
1992     ret =
1993         file_mask_dialog (ctx, source != NULL, format,
1994                           source != NULL ? source : (const void *) &panel->marked, dest_dir, do_bg);
1995 
1996     g_free (format);
1997     g_free (dest_dir);
1998 
1999     return ret;
2000 }
2001 
2002 /* --------------------------------------------------------------------------------------------- */
2003 
2004 static gboolean
2005 do_confirm_erase (const WPanel *panel, const char *source, struct stat *src_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
2006 {
2007     int i;
2008     char *format;
2009     char fmd_buf[BUF_MEDIUM];
2010 
2011     if (source == NULL)
2012         src_stat = NULL;
2013 
2014     // Generate confirmation prompt
2015     format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat);
2016 
2017     if (source == NULL)
2018         g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
2019     else
2020     {
2021         const int fmd_xlen = 64;
2022 
2023         i = fmd_xlen - str_term_width1 (format) - 4;
2024         g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
2025     }
2026 
2027     g_free (format);
2028 
2029     if (safe_delete)
2030         query_set_sel (1);
2031 
2032     i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _ ("&Yes"), _ ("&No"));
2033 
2034     return (i == 0);
2035 }
2036 
2037 /* --------------------------------------------------------------------------------------------- */
2038 
2039 static FileProgressStatus
2040 operate_single_file (const WPanel *panel, file_op_context_t *ctx, const char *src,
     /* [previous][next][first][last][top][bottom][index][help]  */
2041                      struct stat *src_stat, const char *dest, filegui_dialog_type_t dialog_type)
2042 {
2043     FileProgressStatus value;
2044     vfs_path_t *src_vpath;
2045     gboolean is_file;
2046 
2047     if (g_path_is_absolute (src))
2048         src_vpath = vfs_path_from_str (src);
2049     else
2050         src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2051 
2052     is_file = !S_ISDIR (src_stat->st_mode);
2053     // Is link to directory?
2054     if (is_file)
2055     {
2056         gboolean is_link;
2057 
2058         is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2059         is_file = !(is_link && ctx->follow_links);
2060     }
2061 
2062     if (ctx->operation == OP_DELETE)
2063     {
2064         value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type);
2065         if (value == FILE_CONT)
2066         {
2067             if (is_file)
2068                 value = erase_file (ctx, src_vpath);
2069             else
2070                 value = erase_dir (ctx, src_vpath);
2071         }
2072     }
2073     else
2074     {
2075         char *temp;
2076 
2077         src = vfs_path_as_str (src_vpath);
2078 
2079         temp = build_dest (ctx, src, dest, &value);
2080         if (temp != NULL)
2081         {
2082             dest = temp;
2083 
2084             switch (ctx->operation)
2085             {
2086             case OP_COPY:
2087                 // we use file_mask_op_follow_links only with OP_COPY
2088                 ctx->stat_func (src_vpath, src_stat);
2089 
2090                 value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file,
2091                                                    dialog_type);
2092                 if (value == FILE_CONT)
2093                 {
2094                     is_file = !S_ISDIR (src_stat->st_mode);
2095                     // Is link to directory?
2096                     if (is_file)
2097                     {
2098                         gboolean is_link;
2099 
2100                         is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2101                         is_file = !(is_link && ctx->follow_links);
2102                     }
2103 
2104                     if (is_file)
2105                         value = copy_file_file (ctx, src, dest);
2106                     else
2107                         value = copy_dir_dir (ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2108                 }
2109                 break;
2110 
2111             case OP_MOVE:
2112 #ifdef ENABLE_BACKGROUND
2113                 if (!mc_global.we_are_background)
2114 #endif
2115                     // create UI to show confirmation dialog
2116                     file_progress_ui_create (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM);
2117 
2118                 if (is_file)
2119                     value = move_file_file (panel, ctx, src, dest);
2120                 else
2121                     value = do_move_dir_dir (panel, ctx, src, dest);
2122                 break;
2123 
2124             default:
2125                 // Unknown file operation
2126                 abort ();
2127             }
2128 
2129             g_free (temp);
2130         }
2131     }
2132 
2133     vfs_path_free (src_vpath, TRUE);
2134 
2135     return value;
2136 }
2137 
2138 /* --------------------------------------------------------------------------------------------- */
2139 
2140 static FileProgressStatus
2141 operate_one_file (const WPanel *panel, file_op_context_t *ctx, const char *src,
     /* [previous][next][first][last][top][bottom][index][help]  */
2142                   struct stat *src_stat, const char *dest)
2143 {
2144     FileProgressStatus value = FILE_CONT;
2145     vfs_path_t *src_vpath;
2146     gboolean is_file;
2147 
2148     if (g_path_is_absolute (src))
2149         src_vpath = vfs_path_from_str (src);
2150     else
2151         src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2152 
2153     is_file = !S_ISDIR (src_stat->st_mode);
2154 
2155     if (ctx->operation == OP_DELETE)
2156     {
2157         if (is_file)
2158             value = erase_file (ctx, src_vpath);
2159         else
2160             value = erase_dir (ctx, src_vpath);
2161     }
2162     else
2163     {
2164         char *temp;
2165 
2166         src = vfs_path_as_str (src_vpath);
2167 
2168         temp = build_dest (ctx, src, dest, &value);
2169         if (temp != NULL)
2170         {
2171             dest = temp;
2172 
2173             switch (ctx->operation)
2174             {
2175             case OP_COPY:
2176                 // we use file_mask_op_follow_links only with OP_COPY
2177                 ctx->stat_func (src_vpath, src_stat);
2178                 is_file = !S_ISDIR (src_stat->st_mode);
2179 
2180                 if (is_file)
2181                     value = copy_file_file (ctx, src, dest);
2182                 else
2183                     value = copy_dir_dir (ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2184                 dest_dirs = free_linklist (dest_dirs);
2185                 break;
2186 
2187             case OP_MOVE:
2188                 if (is_file)
2189                     value = move_file_file (NULL, ctx, src, dest);
2190                 else
2191                     value = do_move_dir_dir (NULL, ctx, src, dest);
2192                 break;
2193 
2194             default:
2195                 // Unknown file operation
2196                 abort ();
2197             }
2198 
2199             g_free (temp);
2200         }
2201     }
2202 
2203     vfs_path_free (src_vpath, TRUE);
2204 
2205     return value;
2206 }
2207 
2208 /* --------------------------------------------------------------------------------------------- */
2209 
2210 #ifdef ENABLE_BACKGROUND
2211 static int
2212 end_bg_process (file_op_context_t *ctx, enum OperationMode mode)
     /* [previous][next][first][last][top][bottom][index][help]  */
2213 {
2214     int pid = ctx->pid;
2215 
2216     (void) mode;
2217     ctx->pid = 0;
2218 
2219     unregister_task_with_pid (pid);
2220     //     file_op_context_destroy(ctx);
2221     return 1;
2222 }
2223 #endif
2224 /* }}} */
2225 
2226 /* --------------------------------------------------------------------------------------------- */
2227 
2228 /**
2229  * On Solaris, ENOTSUP != EOPNOTSUPP. Some FS also return ENOSYS or EINVAL as "not implemented".
2230  * On some Linux kernels (tested on 4.9, 5.4) there is ENOTTY on tmpfs.
2231  */
2232 static inline gboolean
2233 attrs_ignore_error (const int e)
     /* [previous][next][first][last][top][bottom][index][help]  */
2234 {
2235     return (e == ENOTSUP || e == EOPNOTSUPP || e == ENOSYS || e == EINVAL || e == ENOTTY
2236             || e == ELOOP || e == ENXIO);
2237 }
2238 
2239 /* --------------------------------------------------------------------------------------------- */
2240 /*** public functions ****************************************************************************/
2241 /* --------------------------------------------------------------------------------------------- */
2242 
2243 /* Is file symlink to directory or not.
2244  *
2245  * @param path file or directory
2246  * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2247  * @param stale_link TRUE if file is stale link to directory
2248  *
2249  * @return TRUE if file symlink to directory, ELSE otherwise.
2250  */
2251 gboolean
2252 file_is_symlink_to_dir (const vfs_path_t *vpath, struct stat *st, gboolean *stale_link)
     /* [previous][next][first][last][top][bottom][index][help]  */
2253 {
2254     struct stat st2;
2255     gboolean stale = FALSE;
2256     gboolean res = FALSE;
2257 
2258     if (st == NULL)
2259     {
2260         st = &st2;
2261 
2262         if (mc_lstat (vpath, st) != 0)
2263             goto ret;
2264     }
2265 
2266     if (S_ISLNK (st->st_mode))
2267     {
2268         struct stat st3;
2269 
2270         stale = (mc_stat (vpath, &st3) != 0);
2271 
2272         if (!stale)
2273             res = (S_ISDIR (st3.st_mode) != 0);
2274     }
2275 
2276 ret:
2277     if (stale_link != NULL)
2278         *stale_link = stale;
2279 
2280     return res;
2281 }
2282 
2283 /* --------------------------------------------------------------------------------------------- */
2284 
2285 FileProgressStatus
2286 copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_path)
     /* [previous][next][first][last][top][bottom][index][help]  */
2287 {
2288     uid_t src_uid = (uid_t) (-1);
2289     gid_t src_gid = (gid_t) (-1);
2290 
2291     int src_desc, dest_desc = -1;
2292     mode_t src_mode = 0;  // The mode of the source file
2293     struct stat src_stat, dst_stat;
2294     mc_timesbuf_t times;
2295     unsigned long attrs = 0;
2296     gboolean attrs_ok = copymove_persistent_ext2_attr;
2297     gboolean dst_exists = FALSE, appending = FALSE;
2298     off_t file_size = -1;
2299     FileProgressStatus return_status, temp_status;
2300     dest_status_t dst_status = DEST_NONE;
2301     int open_flags;
2302     vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
2303     char *buf = NULL;
2304 
2305     /* Keep the non-default value applied in chain of calls:
2306        move_file_file() -> file_progress_real_query_replace()
2307        move_file_file() -> copy_file_file() */
2308     if (ctx->do_reget < 0)
2309         ctx->do_reget = 0;
2310 
2311     return_status = FILE_RETRY;
2312 
2313     dst_vpath = vfs_path_from_str (dst_path);
2314     src_vpath = vfs_path_from_str (src_path);
2315 
2316     file_progress_show_source (ctx, src_vpath);
2317     file_progress_show_target (ctx, dst_vpath);
2318 
2319     if (file_progress_check_buttons (ctx) == FILE_ABORT)
2320     {
2321         return_status = FILE_ABORT;
2322         goto ret_fast;
2323     }
2324 
2325     mc_refresh ();
2326 
2327     while (mc_stat (dst_vpath, &dst_stat) == 0)
2328     {
2329         if (S_ISDIR (dst_stat.st_mode))
2330         {
2331             if (ctx->ignore_all)
2332                 return_status = FILE_IGNORE_ALL;
2333             else
2334             {
2335                 return_status =
2336                     file_error (ctx, TRUE, _ ("Cannot overwrite directory\n%s"), dst_path);
2337                 if (return_status == FILE_IGNORE_ALL)
2338                     ctx->ignore_all = TRUE;
2339                 if (return_status == FILE_RETRY)
2340                     continue;
2341             }
2342             goto ret_fast;
2343         }
2344 
2345         dst_exists = TRUE;
2346         break;
2347     }
2348 
2349     while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2350     {
2351         if (ctx->ignore_all)
2352             return_status = FILE_IGNORE_ALL;
2353         else
2354         {
2355             return_status = file_error (ctx, TRUE, _ ("Cannot stat source file\n%s"), src_path);
2356             if (return_status == FILE_IGNORE_ALL)
2357                 ctx->ignore_all = TRUE;
2358         }
2359 
2360         if (return_status != FILE_RETRY)
2361         {
2362             // unknown size
2363             progress_update_one (FALSE, ctx, 0);
2364             goto ret_fast;
2365         }
2366     }
2367 
2368     // After ctx->stat_func()
2369     src_mode = src_stat.st_mode;
2370     src_uid = src_stat.st_uid;
2371     src_gid = src_stat.st_gid;
2372     file_size = src_stat.st_size;
2373 
2374     while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2375     {
2376         attrs_ok = FALSE;
2377 
2378         // don't show an error message if attributes aren't supported in this FS
2379         if (attrs_ignore_error (errno))
2380             return_status = FILE_CONT;
2381         else if (ctx->ignore_all)
2382             return_status = FILE_IGNORE_ALL;
2383         else
2384         {
2385             return_status = file_error (
2386                 ctx, TRUE, _ ("Cannot get ext2 attributes of source file\n%s"), src_path);
2387             if (return_status == FILE_IGNORE_ALL)
2388                 ctx->ignore_all = TRUE;
2389             if (return_status == FILE_ABORT)
2390                 goto ret_fast;
2391         }
2392 
2393         if (return_status != FILE_RETRY)
2394             break;
2395 
2396         // yet another attempt
2397         attrs_ok = TRUE;
2398     }
2399 
2400     if (dst_exists)
2401     {
2402         // Destination already exists
2403         if (check_same_file (ctx, src_path, &src_stat, dst_path, &dst_stat, &return_status))
2404             goto ret_fast;
2405 
2406         // Should we replace destination?
2407         if (ctx->ask_overwrite)
2408         {
2409             ctx->do_reget = 0;
2410             return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
2411             if (return_status != FILE_CONT)
2412                 goto ret_fast;
2413         }
2414     }
2415 
2416     vfs_get_timesbuf_from_stat (&src_stat, &times);
2417 
2418     if (!ctx->do_append)
2419     {
2420         // Check the hardlinks
2421         if (!ctx->follow_links)
2422         {
2423             switch (check_hardlinks (ctx, src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
2424             {
2425             case HARDLINK_OK:
2426                 // We have made a hardlink - no more processing is necessary
2427                 return_status = FILE_CONT;
2428                 goto ret_fast;
2429 
2430             case HARDLINK_ABORT:
2431                 return_status = FILE_ABORT;
2432                 goto ret_fast;
2433 
2434             default:
2435                 break;
2436             }
2437         }
2438 
2439         if (S_ISLNK (src_stat.st_mode))
2440         {
2441             return_status = make_symlink (ctx, src_vpath, dst_vpath);
2442             if (return_status == FILE_CONT && ctx->preserve)
2443             {
2444                 mc_utime (dst_vpath, &times);
2445 
2446                 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2447                 {
2448                     attrs_ok = FALSE;
2449 
2450                     // don't show an error message if attributes aren't supported in this FS
2451                     if (attrs_ignore_error (errno))
2452                         return_status = FILE_CONT;
2453                     else if (return_status == FILE_IGNORE_ALL)
2454                         ctx->ignore_all = TRUE;
2455                     else
2456                         return_status = file_error (
2457                             ctx, TRUE, _ ("Cannot set ext2 attributes of target file\n%s"),
2458                             dst_path);
2459 
2460                     if (return_status != FILE_RETRY)
2461                         break;
2462 
2463                     // yet another attempt
2464                     attrs_ok = TRUE;
2465                 }
2466             }
2467             goto ret_fast;
2468         }
2469 
2470         if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
2471             || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
2472         {
2473             dev_t rdev = 0;
2474 
2475 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2476             rdev = src_stat.st_rdev;
2477 #endif
2478 
2479             while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
2480                    && !ctx->ignore_all)
2481             {
2482                 return_status =
2483                     file_error (ctx, TRUE, _ ("Cannot create special file\n%s"), dst_path);
2484                 if (return_status == FILE_RETRY)
2485                     continue;
2486                 if (return_status == FILE_IGNORE_ALL)
2487                     ctx->ignore_all = TRUE;
2488                 goto ret_fast;
2489             }
2490             // Success
2491 
2492             while (ctx->preserve_uidgid
2493                    && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0
2494                    && !ctx->ignore_all)
2495             {
2496                 temp_status = file_error (ctx, TRUE, _ ("Cannot chown target file\n%s"), dst_path);
2497                 if (temp_status == FILE_IGNORE)
2498                     break;
2499                 if (temp_status == FILE_IGNORE_ALL)
2500                     ctx->ignore_all = TRUE;
2501                 if (temp_status != FILE_RETRY)
2502                 {
2503                     return_status = temp_status;
2504                     goto ret_fast;
2505                 }
2506             }
2507 
2508             while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
2509                    && !ctx->ignore_all)
2510             {
2511                 temp_status = file_error (ctx, TRUE, _ ("Cannot chmod target file\n%s"), dst_path);
2512                 if (temp_status == FILE_IGNORE)
2513                     break;
2514                 if (temp_status == FILE_IGNORE_ALL)
2515                     ctx->ignore_all = TRUE;
2516                 if (temp_status != FILE_RETRY)
2517                 {
2518                     return_status = temp_status;
2519                     goto ret_fast;
2520                 }
2521             }
2522 
2523             while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2524             {
2525                 attrs_ok = FALSE;
2526 
2527                 // don't show an error message if attributes aren't supported in this FS
2528                 if (attrs_ignore_error (errno))
2529                     break;
2530 
2531                 temp_status = file_error (
2532                     ctx, TRUE, _ ("Cannot set ext2 attributes of target file\n%s"), dst_path);
2533                 if (temp_status == FILE_IGNORE)
2534                     break;
2535                 if (temp_status == FILE_IGNORE_ALL)
2536                     ctx->ignore_all = TRUE;
2537                 if (temp_status != FILE_RETRY)
2538                 {
2539                     return_status = temp_status;
2540                     goto ret_fast;
2541                 }
2542 
2543                 // yet another attempt
2544                 attrs_ok = TRUE;
2545             }
2546 
2547             return_status = FILE_CONT;
2548             mc_utime (dst_vpath, &times);
2549             goto ret_fast;
2550         }
2551     }
2552 
2553     ctx->transfer_start = g_get_monotonic_time ();
2554 
2555     while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0)
2556     {
2557         if (ctx->ignore_all)
2558             return_status = FILE_IGNORE_ALL;
2559         else
2560         {
2561             return_status = file_error (ctx, TRUE, _ ("Cannot open source file\n%s"), src_path);
2562             if (return_status == FILE_RETRY)
2563                 continue;
2564             if (return_status == FILE_IGNORE_ALL)
2565                 ctx->ignore_all = TRUE;
2566             ctx->do_append = FALSE;
2567         }
2568         goto ret;
2569     }
2570 
2571     if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
2572     {
2573         message (D_ERROR, _ ("Warning"), _ ("Reget failed, about to overwrite file"));
2574         ctx->do_reget = 0;
2575         ctx->do_append = FALSE;
2576     }
2577 
2578     while (mc_fstat (src_desc, &src_stat) != 0)
2579     {
2580         if (ctx->ignore_all)
2581             return_status = FILE_IGNORE_ALL;
2582         else
2583         {
2584             return_status = file_error (ctx, TRUE, _ ("Cannot fstat source file\n%s"), src_path);
2585             if (return_status == FILE_RETRY)
2586                 continue;
2587             if (return_status == FILE_IGNORE_ALL)
2588                 ctx->ignore_all = TRUE;
2589             ctx->do_append = FALSE;
2590         }
2591         goto ret;
2592     }
2593 
2594     // After mc_fstat()
2595     src_mode = src_stat.st_mode;
2596     src_uid = src_stat.st_uid;
2597     src_gid = src_stat.st_gid;
2598     file_size = src_stat.st_size;
2599 
2600 #ifdef HAVE_FILE_CLONING_BY_PATH
2601     // On macOS 10.12+ and Solaris 11.4+, the syscalls for file cloning respond for creation of the
2602     // destination file, so try them before mc_open to avoid handling various races later.
2603     // Full file cloning is not supported in append and reget modes.
2604     // Destination file must not exist before the cloning syscall, and removing it before the
2605     // cloning attempt has undesirable side effects (#5176).
2606     if (mc_global.vfs.file_cloning && !dst_exists)
2607     {
2608         // Passing preserve_uidgid to the syscall to reduce racing
2609         if (vfs_clone_file_by_path (dst_vpath, src_vpath, ctx->preserve_uidgid) == 0)
2610         {
2611             dst_status = DEST_FULL;
2612             return_status = FILE_CONT;
2613             goto ret;
2614         }
2615     }
2616 #endif
2617 
2618     open_flags = O_WRONLY;
2619     if (!dst_exists)
2620         open_flags |= O_CREAT | O_EXCL;
2621     else if (ctx->do_append)
2622 #ifdef HAVE_FILE_CLONING_BY_RANGE
2623         // FICLONERANGE on Linux and copy_file_range(2) on FreeBSD support block-aligned ranges for
2624         // cloning, but not in O_APPEND mode. Use O_WRONLY + mc_lseek instead as we don't care
2625         // about atomicity in our use cases.
2626         open_flags |= mc_global.vfs.file_cloning ? 0 : O_APPEND;
2627 #else
2628         open_flags |= O_APPEND;
2629 #endif
2630     else
2631         open_flags |= O_CREAT | O_TRUNC;
2632 
2633 #ifdef HAVE_FILE_CLONING_BY_RANGE
2634 open_dest:
2635 #endif
2636     while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
2637     {
2638         if (errno != EEXIST)
2639         {
2640             if (ctx->ignore_all)
2641                 return_status = FILE_IGNORE_ALL;
2642             else
2643             {
2644                 return_status =
2645                     file_error (ctx, TRUE, _ ("Cannot create target file\n%s"), dst_path);
2646                 if (return_status == FILE_RETRY)
2647                     continue;
2648                 if (return_status == FILE_IGNORE_ALL)
2649                     ctx->ignore_all = TRUE;
2650                 ctx->do_append = FALSE;
2651             }
2652         }
2653         goto ret;
2654     }
2655 
2656     // file opened, but not fully copied
2657     dst_status = DEST_SHORT_QUERY;
2658 
2659     appending = ctx->do_append;
2660     ctx->do_append = FALSE;
2661 
2662 #ifdef HAVE_FILE_CLONING_BY_RANGE
2663     // Try clone the file first, but not if the file is in O_APPEND mode
2664     if (mc_global.vfs.file_cloning && (open_flags & O_APPEND) == 0)
2665     {
2666         if ((appending ? mc_lseek (dest_desc, 0, SEEK_END) >= 0 : TRUE)
2667             && vfs_clone_file (dest_desc, src_desc) == 0)
2668         {
2669             dst_status = DEST_FULL;
2670             return_status = FILE_CONT;
2671             goto ret;
2672         }
2673         if (appending && (open_flags & O_APPEND) == 0)
2674         {
2675             // Cloning append has failed, resort to normal append
2676             ctx->do_append = TRUE;
2677             mc_close (dest_desc);
2678             dst_status = DEST_NONE;
2679             open_flags |= O_APPEND;
2680             goto open_dest;
2681         }
2682     }
2683 #endif
2684 
2685     // Find out the optimal buffer size.
2686     while (mc_fstat (dest_desc, &dst_stat) != 0)
2687     {
2688         if (ctx->ignore_all)
2689             return_status = FILE_IGNORE_ALL;
2690         else
2691         {
2692             return_status = file_error (ctx, TRUE, _ ("Cannot fstat target file\n%s"), dst_path);
2693             if (return_status == FILE_RETRY)
2694                 continue;
2695             if (return_status == FILE_IGNORE_ALL)
2696                 ctx->ignore_all = TRUE;
2697         }
2698         goto ret;
2699     }
2700 
2701     // try preallocate space; if fail, try copy anyway
2702     while (mc_global.vfs.preallocate_space
2703            && vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
2704     {
2705         if (ctx->ignore_all)
2706         {
2707             // cannot allocate, start the file copying anyway
2708             return_status = FILE_CONT;
2709             break;
2710         }
2711 
2712         return_status =
2713             file_error (ctx, TRUE, _ ("Cannot preallocate space for target file\n%s"), dst_path);
2714 
2715         if (return_status == FILE_IGNORE_ALL)
2716             ctx->ignore_all = TRUE;
2717 
2718         if (ctx->ignore_all || return_status == FILE_IGNORE)
2719         {
2720             // skip the space allocation error, start file copying
2721             return_status = FILE_CONT;
2722             break;
2723         }
2724 
2725         if (return_status == FILE_ABORT)
2726         {
2727             mc_close (dest_desc);
2728             dest_desc = -1;
2729             mc_unlink (dst_vpath);
2730             dst_status = DEST_NONE;
2731             goto ret;
2732         }
2733 
2734         // return_status == FILE_RETRY -- try allocate space again
2735     }
2736 
2737     ctx->eta_secs = 0.0;
2738     ctx->bps = 0;
2739 
2740     if (verbose)
2741     {
2742         if (ctx->total_bps == 0 || (file_size / ctx->total_bps) > FILEOP_UPDATE_INTERVAL)
2743             file_progress_show (ctx, 0, file_size, "", TRUE);
2744         else
2745             file_progress_show (ctx, 1, 1, "", TRUE);
2746     }
2747 
2748     return_status = file_progress_check_buttons (ctx);
2749     mc_refresh ();
2750 
2751     if (return_status == FILE_CONT)
2752     {
2753         off_t file_part = 0;
2754         gint64 tv_last_update = ctx->transfer_start;
2755         gint64 tv_last_input = 0;
2756         gboolean is_first_time = TRUE;
2757 
2758         const size_t bufsize = io_blksize (dst_stat);
2759         buf = g_malloc (bufsize);
2760 
2761         while (TRUE)
2762         {
2763             ssize_t n_read = -1;
2764 
2765             // src_read
2766             if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
2767                 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->ignore_all)
2768                 {
2769                     return_status =
2770                         file_error (ctx, TRUE, _ ("Cannot read source file\n%s"), src_path);
2771                     if (return_status == FILE_RETRY)
2772                         continue;
2773                     if (return_status == FILE_IGNORE_ALL)
2774                         ctx->ignore_all = TRUE;
2775                     goto ret;
2776                 }
2777 
2778             if (n_read == 0)
2779                 break;
2780 
2781             const gint64 tv_current = g_get_monotonic_time ();
2782 
2783             if (n_read > 0)
2784             {
2785                 ssize_t n_written;
2786                 char *t = buf;
2787 
2788                 file_part += n_read;
2789 
2790                 tv_last_input = tv_current;
2791 
2792                 // dst_write
2793                 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
2794                 {
2795                     gboolean write_errno_nospace;
2796 
2797                     if (n_written > 0)
2798                     {
2799                         n_read -= n_written;
2800                         t += n_written;
2801                         continue;
2802                     }
2803 
2804                     write_errno_nospace = (n_written < 0 && errno == ENOSPC);
2805 
2806                     if (ctx->ignore_all)
2807                         return_status = FILE_IGNORE_ALL;
2808                     else
2809                         return_status =
2810                             file_error (ctx, TRUE, _ ("Cannot write target file\n%s"), dst_path);
2811 
2812                     if (return_status == FILE_IGNORE)
2813                     {
2814                         if (write_errno_nospace)
2815                             goto ret;
2816                         break;
2817                     }
2818                     if (return_status == FILE_IGNORE_ALL)
2819                     {
2820                         ctx->ignore_all = TRUE;
2821                         if (write_errno_nospace)
2822                             goto ret;
2823                     }
2824                     if (return_status != FILE_RETRY)
2825                         goto ret;
2826                 }
2827             }
2828 
2829             ctx->progress_bytes = file_part + ctx->do_reget;
2830 
2831             const gint64 usecs = tv_current - tv_last_update;
2832 
2833             if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US)
2834             {
2835                 calc_copy_file_progress (ctx, tv_current, file_part, file_size - ctx->do_reget);
2836                 tv_last_update = tv_current;
2837             }
2838 
2839             is_first_time = FALSE;
2840 
2841             if (verbose)
2842             {
2843                 const gint64 total_usecs = tv_current - ctx->total_transfer_start;
2844                 const gboolean force_update = total_usecs > FILEOP_UPDATE_INTERVAL_US;
2845 
2846                 const gint64 update_usecs = tv_current - tv_last_input;
2847                 const char *stalled_msg =
2848                     update_usecs > FILEOP_STALLING_INTERVAL_US ? _ ("(stalled)") : "";
2849 
2850                 file_progress_show (ctx, ctx->progress_bytes, file_size, stalled_msg, force_update);
2851                 if (ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
2852                 {
2853                     file_progress_show_count (ctx);
2854                     file_progress_show_total (ctx, ctx->total_progress_bytes + ctx->progress_bytes,
2855                                               tv_current, force_update);
2856                 }
2857 
2858                 mc_refresh ();
2859             }
2860 
2861             return_status = file_progress_check_buttons (ctx);
2862             if (return_status != FILE_CONT)
2863             {
2864                 int query_res;
2865 
2866                 const gint64 t1 = g_get_monotonic_time ();
2867                 query_res =
2868                     query_dialog (Q_ ("DialogTitle|Copy"), _ ("Incomplete file was retrieved"),
2869                                   D_ERROR, 3, _ ("&Delete"), _ ("&Keep"), _ ("&Continue copy"));
2870                 const gint64 t2 = g_get_monotonic_time ();
2871                 ctx->pauses += t2 - t1;
2872 
2873                 // update info forced
2874                 calc_copy_file_progress (ctx, t2, file_part, file_size - ctx->do_reget);
2875 
2876                 switch (query_res)
2877                 {
2878                 case 0:
2879                     // delete
2880                     dst_status = DEST_SHORT_DELETE;
2881                     goto ret;
2882 
2883                 case 1:
2884                     // keep
2885                     dst_status = DEST_SHORT_KEEP;
2886                     goto ret;
2887 
2888                 default:
2889                     // continue copy
2890                     break;
2891                 }
2892             }
2893         }
2894 
2895         // copy successful
2896         dst_status = DEST_FULL;
2897     }
2898 
2899 ret:
2900     g_free (buf);
2901 
2902     rotate_dash (FALSE);
2903     while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->ignore_all)
2904     {
2905         temp_status = file_error (ctx, TRUE, _ ("Cannot close source file\n%s"), src_path);
2906         if (temp_status == FILE_RETRY)
2907             continue;
2908         if (temp_status == FILE_ABORT)
2909             return_status = temp_status;
2910         if (temp_status == FILE_IGNORE_ALL)
2911             ctx->ignore_all = TRUE;
2912         break;
2913     }
2914 
2915     while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->ignore_all)
2916     {
2917         temp_status = file_error (ctx, TRUE, _ ("Cannot close target file\n%s"), dst_path);
2918         if (temp_status == FILE_RETRY)
2919             continue;
2920         if (temp_status == FILE_IGNORE_ALL)
2921             ctx->ignore_all = TRUE;
2922         return_status = temp_status;
2923         break;
2924     }
2925 
2926     if (dst_status == DEST_SHORT_QUERY)
2927     {
2928         // Query to remove short file
2929         if (query_dialog (Q_ ("DialogTitle|Copy"), _ ("Incomplete file was retrieved"), D_ERROR, 2,
2930                           _ ("&Delete"), _ ("&Keep"))
2931             == 0)
2932             dst_status = DEST_SHORT_DELETE;
2933         else
2934             dst_status = DEST_SHORT_KEEP;
2935     }
2936 
2937     if (dst_status == DEST_SHORT_DELETE)
2938         mc_unlink (dst_vpath);
2939     else if (dst_status == DEST_FULL && !appending)
2940     {
2941         // Copy has succeeded
2942 
2943         while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
2944                && !ctx->ignore_all)
2945         {
2946             temp_status = file_error (ctx, TRUE, _ ("Cannot chown target file\n%s"), dst_path);
2947             if (temp_status == FILE_ABORT)
2948             {
2949                 return_status = FILE_ABORT;
2950                 goto ret_fast;
2951             }
2952             if (temp_status == FILE_RETRY)
2953                 continue;
2954             if (temp_status == FILE_IGNORE_ALL)
2955             {
2956                 ctx->ignore_all = TRUE;
2957                 return_status = FILE_CONT;
2958             }
2959             if (temp_status == FILE_IGNORE)
2960                 return_status = FILE_CONT;
2961             break;
2962         }
2963 
2964         while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
2965                && !ctx->ignore_all)
2966         {
2967             temp_status = file_error (ctx, TRUE, _ ("Cannot chmod target file\n%s"), dst_path);
2968             if (temp_status == FILE_ABORT)
2969             {
2970                 return_status = FILE_ABORT;
2971                 goto ret_fast;
2972             }
2973             if (temp_status == FILE_RETRY)
2974                 continue;
2975             if (temp_status == FILE_IGNORE_ALL)
2976             {
2977                 ctx->ignore_all = TRUE;
2978                 return_status = FILE_CONT;
2979             }
2980             if (temp_status == FILE_IGNORE)
2981                 return_status = FILE_CONT;
2982             break;
2983         }
2984 
2985         if (!ctx->preserve && !dst_exists)
2986         {
2987             src_mode = umask (-1);
2988             umask (src_mode);
2989             src_mode = 0100666 & ~src_mode;
2990             mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2991         }
2992 
2993         while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->ignore_all)
2994         {
2995             attrs_ok = FALSE;
2996 
2997             // don't show an error message if attributes aren't supported in this FS
2998             if (attrs_ignore_error (errno))
2999             {
3000                 return_status = FILE_CONT;
3001                 break;
3002             }
3003 
3004             temp_status = file_error (
3005                 ctx, TRUE, _ ("Cannot set ext2 attributes for target file\n%s"), dst_path);
3006             if (temp_status == FILE_ABORT)
3007             {
3008                 return_status = FILE_ABORT;
3009                 goto ret_fast;
3010             }
3011             if (temp_status == FILE_RETRY)
3012             {
3013                 attrs_ok = TRUE;
3014                 continue;
3015             }
3016             if (temp_status == FILE_IGNORE_ALL)
3017             {
3018                 ctx->ignore_all = TRUE;
3019                 return_status = FILE_CONT;
3020             }
3021             if (temp_status == FILE_IGNORE)
3022                 return_status = FILE_CONT;
3023             break;
3024         }
3025     }
3026 
3027     // Always sync timestamps
3028     if (dst_status == DEST_FULL || dst_status == DEST_SHORT_KEEP)
3029         mc_utime (dst_vpath, &times);
3030 
3031     progress_update_one (return_status == FILE_CONT, ctx, file_size);
3032     if (return_status == FILE_CONT)
3033         return_status = file_progress_check_buttons (ctx);
3034 
3035 ret_fast:
3036     vfs_path_free (src_vpath, TRUE);
3037     vfs_path_free (dst_vpath, TRUE);
3038     ctx->do_reget = -1;  // reinit for a next file
3039     return return_status;
3040 }
3041 
3042 /* --------------------------------------------------------------------------------------------- */
3043 /**
3044  * I think these copy_*_* functions should have a return type.
3045  * anyway, this function *must* have two directories as arguments.
3046  */
3047 /* FIXME: This function needs to check the return values of the
3048    function calls */
3049 
3050 FileProgressStatus
3051 copy_dir_dir (file_op_context_t *ctx, const char *s, const char *d, gboolean toplevel,
     /* [previous][next][first][last][top][bottom][index][help]  */
3052               gboolean move_over, gboolean do_delete, GSList *parent_dirs)
3053 {
3054     struct vfs_dirent *next;
3055     struct stat dst_stat, src_stat;
3056     unsigned long attrs = 0;
3057     gboolean attrs_ok = copymove_persistent_ext2_attr;
3058     DIR *reading;
3059     FileProgressStatus return_status = FILE_CONT;
3060     link_t *lp;
3061     vfs_path_t *src_vpath, *dst_vpath;
3062     gboolean do_mkdir = TRUE;
3063 
3064     src_vpath = vfs_path_from_str (s);
3065     dst_vpath = vfs_path_from_str (d);
3066 
3067     // First get the mode of the source dir
3068 
3069 retry_src_stat:
3070     while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
3071     {
3072         if (ctx->ignore_all)
3073             return_status = FILE_IGNORE_ALL;
3074         else
3075         {
3076             return_status = file_error (ctx, TRUE, _ ("Cannot stat source directory\n%s"), s);
3077             if (return_status == FILE_IGNORE_ALL)
3078                 ctx->ignore_all = TRUE;
3079         }
3080 
3081         if (return_status != FILE_RETRY)
3082             goto ret_fast;
3083     }
3084 
3085     while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
3086     {
3087         attrs_ok = FALSE;
3088 
3089         // don't show an error message if attributes aren't supported in this FS
3090         if (attrs_ignore_error (errno))
3091             return_status = FILE_CONT;
3092         else if (ctx->ignore_all)
3093             return_status = FILE_IGNORE_ALL;
3094         else
3095         {
3096             return_status =
3097                 file_error (ctx, TRUE, _ ("Cannot get ext2 attributes of source directory\n%s"), s);
3098             if (return_status == FILE_IGNORE_ALL)
3099                 ctx->ignore_all = TRUE;
3100             if (return_status == FILE_ABORT)
3101                 goto ret_fast;
3102         }
3103 
3104         if (return_status != FILE_RETRY)
3105             break;
3106 
3107         // yet another attempt
3108         attrs_ok = TRUE;
3109     }
3110 
3111     if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
3112     {
3113         /* Don't copy a directory we created before (we don't want to copy
3114            infinitely if a directory is copied into itself) */
3115         // FIXME: should there be an error message and FILE_SKIP? - Norbert
3116         return_status = FILE_CONT;
3117         goto ret_fast;
3118     }
3119 
3120     // Hmm, hardlink to directory??? - Norbert
3121     // FIXME: In this step we should do something in case the destination already exist
3122     // Check the hardlinks
3123     if (ctx->preserve)
3124     {
3125         switch (check_hardlinks (ctx, src_vpath, &src_stat, dst_vpath, &ctx->ignore_all))
3126         {
3127         case HARDLINK_OK:
3128             // We have made a hardlink - no more processing is necessary
3129             goto ret_fast;
3130 
3131         case HARDLINK_ABORT:
3132             return_status = FILE_ABORT;
3133             goto ret_fast;
3134 
3135         default:
3136             break;
3137         }
3138     }
3139 
3140     if (!S_ISDIR (src_stat.st_mode))
3141     {
3142         if (ctx->ignore_all)
3143             return_status = FILE_IGNORE_ALL;
3144         else
3145         {
3146             return_status = file_error (ctx, TRUE, _ ("Source \"%s\" is not a directory\n%s"), s);
3147             if (return_status == FILE_RETRY)
3148                 goto retry_src_stat;
3149             if (return_status == FILE_IGNORE_ALL)
3150                 ctx->ignore_all = TRUE;
3151         }
3152         goto ret_fast;
3153     }
3154 
3155     if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
3156     {
3157         // we found a cyclic symbolic link
3158         message (D_ERROR, MSG_ERROR, _ ("Cannot copy cyclic symbolic link\n\"%s\""), s);
3159         return_status = FILE_SKIP;
3160         goto ret_fast;
3161     }
3162 
3163     lp = g_new0 (link_t, 1);
3164     lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
3165     lp->ino = src_stat.st_ino;
3166     lp->dev = src_stat.st_dev;
3167     parent_dirs = g_slist_prepend (parent_dirs, lp);
3168 
3169 retry_dst_stat:
3170     // Now, check if the dest dir exists, if not, create it.
3171     if (mc_stat (dst_vpath, &dst_stat) != 0)
3172     {
3173         // Here the dir doesn't exist : make it !
3174         if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
3175         {
3176             return_status = FILE_CONT;
3177             goto ret;
3178         }
3179     }
3180     else
3181     {
3182         /*
3183          * If the destination directory exists, we want to copy the whole
3184          * directory, but we only want this to happen once.
3185          *
3186          * Escape sequences added to the * to compiler warnings.
3187          * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3188          * or ( /bla doesn't exist )       /tmp/\* to /bla     ->  /bla/\*
3189          */
3190         if (!S_ISDIR (dst_stat.st_mode))
3191         {
3192             if (ctx->ignore_all)
3193                 return_status = FILE_IGNORE_ALL;
3194             else
3195             {
3196                 return_status =
3197                     file_error (ctx, TRUE, _ ("Destination\n%s\nmust be a directory\n%s"), d);
3198                 if (return_status == FILE_IGNORE_ALL)
3199                     ctx->ignore_all = TRUE;
3200                 if (return_status == FILE_RETRY)
3201                     goto retry_dst_stat;
3202             }
3203             goto ret;
3204         }
3205         // Dive into subdir if exists
3206         if (toplevel && ctx->dive_into_subdirs)
3207         {
3208             vfs_path_t *tmp;
3209 
3210             tmp = dst_vpath;
3211             dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
3212             vfs_path_free (tmp, TRUE);
3213         }
3214         else
3215             do_mkdir = FALSE;
3216     }
3217 
3218     d = vfs_path_as_str (dst_vpath);
3219 
3220     if (do_mkdir)
3221     {
3222         while (mc_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
3223         {
3224             if (ctx->ignore_all)
3225                 return_status = FILE_IGNORE_ALL;
3226             else
3227             {
3228                 return_status = file_error (ctx, TRUE, _ ("Cannot create target directory\n%s"), d);
3229                 if (return_status == FILE_IGNORE_ALL)
3230                     ctx->ignore_all = TRUE;
3231             }
3232             if (return_status != FILE_RETRY)
3233                 goto ret;
3234         }
3235 
3236         lp = g_new0 (link_t, 1);
3237         mc_stat (dst_vpath, &dst_stat);
3238         lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
3239         lp->ino = dst_stat.st_ino;
3240         lp->dev = dst_stat.st_dev;
3241         dest_dirs = g_slist_prepend (dest_dirs, lp);
3242     }
3243 
3244     if (ctx->preserve_uidgid)
3245     {
3246         while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
3247         {
3248             if (ctx->ignore_all)
3249                 return_status = FILE_IGNORE_ALL;
3250             else
3251             {
3252                 return_status = file_error (ctx, TRUE, _ ("Cannot chown target directory\n%s"), d);
3253                 if (return_status == FILE_IGNORE_ALL)
3254                     ctx->ignore_all = TRUE;
3255             }
3256             if (return_status != FILE_RETRY)
3257                 goto ret;
3258         }
3259     }
3260 
3261     // open the source dir for reading
3262     reading = mc_opendir (src_vpath);
3263     if (reading == NULL)
3264         goto ret;
3265 
3266     while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
3267     {
3268         char *path;
3269         vfs_path_t *tmp_vpath;
3270 
3271         /*
3272          * Now, we don't want '.' and '..' to be created / copied at any time
3273          */
3274         if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
3275             continue;
3276 
3277         // get the filename and add it to the src directory
3278         path = mc_build_filename (s, next->d_name, (char *) NULL);
3279         tmp_vpath = vfs_path_from_str (path);
3280 
3281         (*ctx->stat_func) (tmp_vpath, &dst_stat);
3282         if (S_ISDIR (dst_stat.st_mode))
3283         {
3284             char *mdpath;
3285 
3286             mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
3287             /*
3288              * From here, we just intend to recursively copy subdirs, not
3289              * the double functionality of copying different when the target
3290              * dir already exists. So, we give the recursive call the flag 0
3291              * meaning no toplevel.
3292              */
3293             return_status = copy_dir_dir (ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
3294             g_free (mdpath);
3295         }
3296         else
3297         {
3298             char *dest_file;
3299 
3300             dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
3301             return_status = copy_file_file (ctx, path, dest_file);
3302             g_free (dest_file);
3303         }
3304 
3305         g_free (path);
3306 
3307         if (do_delete && return_status == FILE_CONT)
3308         {
3309             if (ctx->erase_at_end)
3310             {
3311                 if (erase_list == NULL)
3312                     erase_list = g_queue_new ();
3313 
3314                 lp = g_new0 (link_t, 1);
3315                 lp->src_vpath = tmp_vpath;
3316                 lp->st_mode = dst_stat.st_mode;
3317                 g_queue_push_tail (erase_list, lp);
3318                 tmp_vpath = NULL;
3319             }
3320             else if (S_ISDIR (dst_stat.st_mode))
3321                 return_status = erase_dir_iff_empty (ctx, tmp_vpath);
3322             else
3323                 return_status = erase_file (ctx, tmp_vpath);
3324         }
3325         vfs_path_free (tmp_vpath, TRUE);
3326     }
3327     mc_closedir (reading);
3328 
3329     if (ctx->preserve)
3330     {
3331         mc_timesbuf_t times;
3332 
3333         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3334 
3335         if (attrs_ok)
3336             mc_fsetflags (dst_vpath, attrs);
3337 
3338         vfs_get_timesbuf_from_stat (&src_stat, &times);
3339         mc_utime (dst_vpath, &times);
3340     }
3341     else
3342     {
3343         src_stat.st_mode = umask (-1);
3344         umask (src_stat.st_mode);
3345         src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3346         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3347     }
3348 
3349 ret:
3350     free_link (parent_dirs->data);
3351     g_slist_free_1 (parent_dirs);
3352 ret_fast:
3353     vfs_path_free (src_vpath, TRUE);
3354     vfs_path_free (dst_vpath, TRUE);
3355     return return_status;
3356 }
3357 
3358 /* }}} */
3359 
3360 /* --------------------------------------------------------------------------------------------- */
3361 /* {{{ Move routines */
3362 
3363 FileProgressStatus
3364 move_dir_dir (file_op_context_t *ctx, const char *s, const char *d)
     /* [previous][next][first][last][top][bottom][index][help]  */
3365 {
3366     return do_move_dir_dir (NULL, ctx, s, d);
3367 }
3368 
3369 /* }}} */
3370 
3371 /* --------------------------------------------------------------------------------------------- */
3372 /* {{{ Erase routines */
3373 
3374 FileProgressStatus
3375 erase_dir (file_op_context_t *ctx, const vfs_path_t *vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
3376 {
3377     FileProgressStatus error = FILE_CONT;
3378 
3379     file_progress_show_deleting (ctx, vpath, NULL);
3380     file_progress_show_count (ctx);
3381     if (file_progress_check_buttons (ctx) == FILE_ABORT)
3382         return FILE_ABORT;
3383 
3384     mc_refresh ();
3385 
3386     const int res = check_dir_is_empty (ctx, vpath, &error);
3387 
3388     if (res == -1)
3389         return error;
3390 
3391     if (res == 0)
3392     {
3393         // not empty
3394         error = query_recursive (ctx, vfs_path_as_str (vpath));
3395         if (error == FILE_CONT)
3396             error = recursive_erase (ctx, vpath);
3397         return error;
3398     }
3399 
3400     return try_erase_dir (ctx, vpath);
3401 }
3402 
3403 /* }}} */
3404 
3405 /* --------------------------------------------------------------------------------------------- */
3406 /* {{{ Panel operate routines */
3407 
3408 void
3409 dirsize_status_init_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3410 {
3411     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3412     WGroup *gd = GROUP (sm->dlg);
3413     Widget *wd = WIDGET (sm->dlg);
3414     WRect r = wd->rect;
3415 
3416     const char *b1_name = _ ("&Abort");
3417     const char *b2_name = _ ("&Skip");
3418     int b_width, ui_width;
3419 
3420     b_width = str_term_width1 (b1_name) + 4;
3421     if (dsm->allow_skip)
3422         b_width += str_term_width1 (b2_name) + 4 + 1;
3423 
3424     ui_width = MAX (COLS / 2, b_width + 6);
3425     dsm->dirname = label_new (2, 3, NULL);
3426     group_add_widget (gd, dsm->dirname);
3427     dsm->count_size = label_new (3, 3, NULL);
3428     group_add_widget (gd, dsm->count_size);
3429     group_add_widget (gd, hline_new (4, -1, -1));
3430 
3431     dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3432     group_add_widget (gd, dsm->abort_button);
3433     if (dsm->allow_skip)
3434     {
3435         dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3436         group_add_widget (gd, dsm->skip_button);
3437         widget_select (dsm->skip_button);
3438     }
3439 
3440     r.lines = 8;
3441     r.cols = ui_width;
3442     widget_set_size_rect (wd, &r);
3443     dirsize_status_locate_buttons (dsm);
3444 }
3445 
3446 /* --------------------------------------------------------------------------------------------- */
3447 
3448 int
3449 dirsize_status_update_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3450 {
3451     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3452     Widget *wd = WIDGET (sm->dlg);
3453     WRect r = wd->rect;
3454 
3455     // update second (longer label)
3456     label_set_textv (dsm->count_size, _ ("Directories: %zu, total size: %s"), dsm->dir_count,
3457                      size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3458 
3459     // enlarge dialog if required
3460     if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
3461     {
3462         r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
3463         widget_set_size_rect (wd, &r);
3464         dirsize_status_locate_buttons (dsm);
3465         widget_draw (wd);
3466         // TODO: ret rid of double redraw
3467     }
3468 
3469     // adjust first label
3470     label_set_text (dsm->dirname,
3471                     str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
3472 
3473     switch (status_msg_common_update (sm))
3474     {
3475     case B_CANCEL:
3476     case FILE_ABORT:
3477         return FILE_ABORT;
3478     case FILE_SKIP:
3479         return FILE_SKIP;
3480     default:
3481         return FILE_CONT;
3482     }
3483 }
3484 
3485 /* --------------------------------------------------------------------------------------------- */
3486 
3487 void
3488 dirsize_status_deinit_cb (status_msg_t *sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3489 {
3490     (void) sm;
3491 
3492     // schedule to update passive panel
3493     if (get_other_type () == view_listing)
3494         other_panel->dirty = TRUE;
3495 }
3496 
3497 /* --------------------------------------------------------------------------------------------- */
3498 /**
3499  * compute_dir_size:
3500  *
3501  * Computes the number of bytes used by the files in a directory
3502  */
3503 
3504 FileProgressStatus
3505 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]  */
3506                   size_t *ret_marked_count, uintmax_t *ret_total, gboolean follow_symlinks)
3507 {
3508     return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3509                                 follow_symlinks ? mc_stat : mc_lstat);
3510 }
3511 
3512 /* --------------------------------------------------------------------------------------------- */
3513 /**
3514  * panel_operate:
3515  *
3516  * Performs one of the operations on the current on the source_panel
3517  * (copy, delete, move).
3518  *
3519  * Returns TRUE if did change the directory
3520  * structure, Returns FALSE if user aborted
3521  *
3522  * force_single forces operation on the current entry and affects
3523  * default destination.  Current filename is used as default.
3524  */
3525 
3526 gboolean
3527 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
     /* [previous][next][first][last][top][bottom][index][help]  */
3528 {
3529     WPanel *panel = PANEL (source_panel);
3530     const gboolean single_entry =
3531         force_single || (panel->marked <= 1) || (get_current_type () == view_tree);
3532 
3533     const char *source = NULL;
3534     char *dest = NULL;
3535     vfs_path_t *dest_vpath = NULL;
3536     vfs_path_t *save_cwd = NULL, *save_dest = NULL;
3537     struct stat src_stat;
3538     gboolean ret_val = TRUE;
3539     int i;
3540     FileProgressStatus value;
3541     file_op_context_t *ctx;
3542     filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3543 
3544     gboolean do_bg = FALSE;  // do background operation?
3545 
3546     linklist = free_linklist (linklist);
3547     dest_dirs = free_linklist (dest_dirs);
3548 
3549     save_cwds_stat ();
3550 
3551     if (single_entry)
3552     {
3553         source = check_single_entry (panel, force_single, &src_stat);
3554 
3555         if (source == NULL)
3556             return FALSE;
3557     }
3558 
3559     ctx = file_op_context_new (operation);
3560 
3561     // Show confirmation dialog
3562     if (operation != OP_DELETE)
3563     {
3564         dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
3565         if (dest == NULL)
3566         {
3567             ret_val = FALSE;
3568             goto ret_fast;
3569         }
3570 
3571         dest_vpath = vfs_path_from_str (dest);
3572     }
3573     else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3574     {
3575         ret_val = FALSE;
3576         goto ret_fast;
3577     }
3578 
3579     ctx->total_transfer_start = g_get_monotonic_time ();
3580 
3581 #ifdef ENABLE_BACKGROUND
3582     // Did the user select to do a background operation?
3583     if (do_bg)
3584     {
3585         int v;
3586 
3587         v = do_background (ctx,
3588                            g_strconcat (op_names[operation], ": ",
3589                                         vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3590         if (v == -1)
3591             message (D_ERROR, MSG_ERROR, _ ("Sorry, I could not put the job in background"));
3592 
3593         // If we are the parent
3594         if (v == 1)
3595         {
3596             mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3597 
3598             mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3599             vfs_path_free (dest_vpath, TRUE);
3600             g_free (dest);
3601             //          file_op_context_destroy (ctx);
3602             return FALSE;
3603         }
3604     }
3605     else
3606 #endif
3607     {
3608         const file_entry_t *fe;
3609 
3610         if (operation == OP_DELETE)
3611             dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3612         else if (single_entry
3613                  && ((fe = panel_current_entry (panel)) == NULL ? FALSE : S_ISDIR (fe->st.st_mode)))
3614             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3615         else if (single_entry || force_single)
3616             dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3617         else
3618             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3619     }
3620 
3621     // Initialize things
3622     /* We do not want to trash cache every time file is
3623        created/touched. However, this will make our cache contain
3624        invalid data. */
3625     if ((dest != NULL)
3626         && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3627         save_dest = vfs_path_from_str (dest);
3628 
3629     if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3630         && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3631         save_cwd = vfs_path_clone (panel->cwd_vpath);
3632 
3633     // Now, let's do the job
3634 
3635     // This code is only called by the tree and panel code
3636     if (single_entry)
3637     {
3638         // We now have ETA in all cases
3639 
3640         // One file: FIXME mc_chdir will take user out of any vfs
3641         if ((operation != OP_COPY) && (get_current_type () == view_tree))
3642         {
3643             vfs_path_t *vpath;
3644             int chdir_retcode;
3645 
3646             vpath = vfs_path_from_str (PATH_SEP_STR);
3647             chdir_retcode = mc_chdir (vpath);
3648             vfs_path_free (vpath, TRUE);
3649             if (chdir_retcode < 0)
3650             {
3651                 ret_val = FALSE;
3652                 goto clean_up;
3653             }
3654         }
3655 
3656         value = operate_single_file (panel, ctx, source, &src_stat, dest, dialog_type);
3657         if ((value == FILE_CONT) && !force_single)
3658             unmark_files (panel);
3659     }
3660     else
3661     {
3662         // Many files
3663 
3664         // Check destination for copy or move operation
3665         while (operation != OP_DELETE)
3666         {
3667             int dst_result;
3668             struct stat dst_stat;
3669 
3670             dst_result = mc_stat (dest_vpath, &dst_stat);
3671 
3672             if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3673                 break;
3674 
3675             if (ctx->ignore_all
3676                 || file_error (ctx, TRUE, _ ("Destination\n%s\nmust be a directory"), dest)
3677                     != FILE_RETRY)
3678                 goto clean_up;
3679         }
3680 
3681         /* TODO: the good way is required to skip directories scanning in case of rename/move
3682          * of several directories. Since reqular expression can be used for destination,
3683          * some directory movements can be a cross-filesystem and directory scanning is useful
3684          * for those directories only. */
3685 
3686         value =
3687             panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type);
3688         if (value == FILE_CONT)
3689             // Loop for every file, perform the actual copy operation
3690             for (i = 0; i < panel->dir.len; i++)
3691             {
3692                 const char *source2;
3693 
3694                 if (panel->dir.list[i].f.marked == 0)
3695                     continue;  // Skip the unmarked ones
3696 
3697                 source2 = panel->dir.list[i].fname->str;
3698                 src_stat = panel->dir.list[i].st;
3699 
3700                 value = operate_one_file (panel, ctx, source2, &src_stat, dest);
3701                 if (value == FILE_ABORT)
3702                     break;
3703 
3704                 if (value == FILE_CONT)
3705                     do_file_mark (panel, i, 0);
3706 
3707                 mc_refresh ();
3708             }  // Loop for every file
3709     }  // Many entries
3710 
3711 clean_up:
3712     // Clean up
3713     if (save_cwd != NULL)
3714     {
3715         mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
3716         vfs_path_free (save_cwd, TRUE);
3717     }
3718 
3719     if (save_dest != NULL)
3720     {
3721         mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
3722         vfs_path_free (save_dest, TRUE);
3723     }
3724 
3725     linklist = free_linklist (linklist);
3726     dest_dirs = free_linklist (dest_dirs);
3727     g_free (dest);
3728     vfs_path_free (dest_vpath, TRUE);
3729     MC_PTR_FREE (ctx->dest_mask);
3730 
3731 #ifdef ENABLE_BACKGROUND
3732     // Let our parent know we are saying bye bye
3733     if (mc_global.we_are_background)
3734     {
3735         /* Send pid to parent with child context, it is fork and
3736            don't modify real parent ctx */
3737         ctx->pid = getpid ();
3738         parent_call ((void *) end_bg_process, ctx, 0);
3739 
3740         vfs_shut ();
3741         my_exit (EXIT_SUCCESS);
3742     }
3743 #endif
3744 
3745 ret_fast:
3746     file_op_context_destroy (ctx);
3747 
3748     update_panels (UP_OPTIMIZE, UP_KEEPSEL);
3749     repaint_screen ();
3750 
3751     return ret_val;
3752 }
3753 
3754 /* }}} */
3755 
3756 /* --------------------------------------------------------------------------------------------- */
3757 
3758 /* {{{ Query/status report routines */
3759 /** Report error with one file */
3760 FileProgressStatus
3761 file_error (file_op_context_t *ctx, gboolean allow_retry, const char *format, const char *file)
     /* [previous][next][first][last][top][bottom][index][help]  */
3762 {
3763     return files_error (ctx, allow_retry, format, file, "");
3764 }
3765 
3766 /* --------------------------------------------------------------------------------------------- */
3767 
3768 /*
3769    Cause emacs to enter folding mode for this file:
3770    Local variables:
3771    end:
3772  */

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