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

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