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

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