Manual pages: mcmcdiffmceditmcview

root/src/filemanager/file.c

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

DEFINITIONS

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

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

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