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

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