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

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