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

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