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-2024
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Janne Kukonlehto, 1994, 1995
   9    Fred Leeflang, 1994, 1995
  10    Miguel de Icaza, 1994, 1995, 1996
  11    Jakub Jelinek, 1995, 1996
  12    Norbert Warmuth, 1997
  13    Pavel Machek, 1998
  14    Andrew Borodin <aborodin@vmail.ru>, 2011-2022
  15 
  16    The copy code was based in GNU's cp, and was written by:
  17    Torbjorn Granlund, David MacKenzie, and Jim Meyering.
  18 
  19    The move code was based in GNU's mv, and was written by:
  20    Mike Parker and David MacKenzie.
  21 
  22    Janne Kukonlehto added much error recovery to them for being used
  23    in an interactive program.
  24 
  25    This file is part of the Midnight Commander.
  26 
  27    The Midnight Commander is free software: you can redistribute it
  28    and/or modify it under the terms of the GNU General Public License as
  29    published by the Free Software Foundation, either version 3 of the License,
  30    or (at your option) any later version.
  31 
  32    The Midnight Commander is distributed in the hope that it will be useful,
  33    but WITHOUT ANY WARRANTY; without even the implied warranty of
  34    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  35    GNU General Public License for more details.
  36 
  37    You should have received a copy of the GNU General Public License
  38    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  39  */
  40 
  41 /*
  42  * Please note that all dialogs used here must be safe for background
  43  * operations.
  44  */
  45 
  46 /** \file src/filemanager/file.c
  47  *  \brief Source: file management
  48  */
  49 
  50 /* {{{ Include files */
  51 
  52 #include <config.h>
  53 
  54 #include <ctype.h>
  55 #include <errno.h>
  56 #include <stdlib.h>
  57 #include <stdio.h>
  58 #include <string.h>
  59 #include <sys/types.h>
  60 #include <sys/stat.h>
  61 #include <unistd.h>
  62 
  63 #include "lib/global.h"
  64 #include "lib/tty/tty.h"
  65 #include "lib/tty/key.h"
  66 #include "lib/search.h"
  67 #include "lib/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 SHELL) 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     unsigned long attrs;
2276     gboolean attrs_ok = ctx->preserve;
2277     gboolean dst_exists = FALSE, appending = FALSE;
2278     off_t file_size = -1;
2279     FileProgressStatus return_status, temp_status;
2280     gint64 tv_transfer_start;
2281     dest_status_t dst_status = DEST_NONE;
2282     int open_flags;
2283     vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
2284     char *buf = NULL;
2285 
2286     /* FIXME: We should not be using global variables! */
2287     ctx->do_reget = 0;
2288     return_status = FILE_RETRY;
2289 
2290     dst_vpath = vfs_path_from_str (dst_path);
2291     src_vpath = vfs_path_from_str (src_path);
2292 
2293     file_progress_show_source (ctx, src_vpath);
2294     file_progress_show_target (ctx, dst_vpath);
2295 
2296     if (check_progress_buttons (ctx) == FILE_ABORT)
2297     {
2298         return_status = FILE_ABORT;
2299         goto ret_fast;
2300     }
2301 
2302     mc_refresh ();
2303 
2304     while (mc_stat (dst_vpath, &dst_stat) == 0)
2305     {
2306         if (S_ISDIR (dst_stat.st_mode))
2307         {
2308             if (ctx->skip_all)
2309                 return_status = FILE_SKIPALL;
2310             else
2311             {
2312                 return_status =
2313                     file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path);
2314                 if (return_status == FILE_SKIPALL)
2315                     ctx->skip_all = TRUE;
2316                 if (return_status == FILE_RETRY)
2317                     continue;
2318             }
2319             goto ret_fast;
2320         }
2321 
2322         dst_exists = TRUE;
2323         break;
2324     }
2325 
2326     while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2327     {
2328         if (ctx->skip_all)
2329             return_status = FILE_SKIPALL;
2330         else
2331         {
2332             return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path);
2333             if (return_status == FILE_SKIPALL)
2334                 ctx->skip_all = TRUE;
2335         }
2336 
2337         if (return_status != FILE_RETRY)
2338             goto ret_fast;
2339     }
2340 
2341     while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2342     {
2343         attrs_ok = FALSE;
2344 
2345         /* don't show an error message if attributes aren't supported in this FS */
2346         if (errno == ENOTSUP)
2347             return_status = FILE_CONT;
2348         else if (ctx->skip_all)
2349             return_status = FILE_SKIPALL;
2350         else
2351         {
2352             return_status =
2353                 file_error (TRUE, _("Cannot get attributes of source file \"%s\"\n%s"), src_path);
2354             if (return_status == FILE_SKIPALL)
2355                 ctx->skip_all = TRUE;
2356         }
2357 
2358         if (return_status != FILE_RETRY)
2359             break;
2360 
2361         /* yet another attempt */
2362         attrs_ok = TRUE;
2363     }
2364 
2365     if (dst_exists)
2366     {
2367         /* Destination already exists */
2368         if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status))
2369             goto ret_fast;
2370 
2371         /* Should we replace destination? */
2372         if (tctx->ask_overwrite)
2373         {
2374             ctx->do_reget = 0;
2375             return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
2376             if (return_status != FILE_CONT)
2377                 goto ret_fast;
2378         }
2379     }
2380 
2381     get_times (&src_stat, &times);
2382 
2383     if (!ctx->do_append)
2384     {
2385         /* Check the hardlinks */
2386         if (!ctx->follow_links)
2387         {
2388             switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
2389             {
2390             case HARDLINK_OK:
2391                 /* We have made a hardlink - no more processing is necessary */
2392                 return_status = FILE_CONT;
2393                 goto ret_fast;
2394 
2395             case HARDLINK_ABORT:
2396                 return_status = FILE_ABORT;
2397                 goto ret_fast;
2398 
2399             default:
2400                 break;
2401             }
2402         }
2403 
2404         if (S_ISLNK (src_stat.st_mode))
2405         {
2406             return_status = make_symlink (ctx, src_vpath, dst_vpath);
2407             if (return_status == FILE_CONT && ctx->preserve)
2408             {
2409                 mc_utime (dst_vpath, &times);
2410 
2411                 while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->skip_all)
2412                 {
2413                     attrs_ok = FALSE;
2414 
2415                     /* don't show an error message if attributes aren't supported in this FS */
2416                     if (errno == ENOTSUP)
2417                         return_status = FILE_CONT;
2418                     else if (return_status == FILE_SKIPALL)
2419                         ctx->skip_all = TRUE;
2420                     else
2421                         return_status =
2422                             file_error (TRUE, _("Cannot set attributes of target file \"%s\"\n%s"),
2423                                         dst_path);
2424 
2425                     if (return_status != FILE_RETRY)
2426                         break;
2427 
2428                     /* yet another attempt */
2429                     attrs_ok = TRUE;
2430                 }
2431             }
2432             goto ret_fast;
2433         }
2434 
2435         if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
2436             || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
2437         {
2438             dev_t rdev = 0;
2439 
2440 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2441             rdev = src_stat.st_rdev;
2442 #endif
2443 
2444             while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
2445                    && !ctx->skip_all)
2446             {
2447                 return_status =
2448                     file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path);
2449                 if (return_status == FILE_RETRY)
2450                     continue;
2451                 if (return_status == FILE_SKIPALL)
2452                     ctx->skip_all = TRUE;
2453                 goto ret_fast;
2454             }
2455             /* Success */
2456 
2457             while (ctx->preserve_uidgid
2458                    && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all)
2459             {
2460                 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2461                 if (temp_status == FILE_SKIP)
2462                     break;
2463                 if (temp_status == FILE_SKIPALL)
2464                     ctx->skip_all = TRUE;
2465                 if (temp_status != FILE_RETRY)
2466                 {
2467                     return_status = temp_status;
2468                     goto ret_fast;
2469                 }
2470             }
2471 
2472             while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
2473                    && !ctx->skip_all)
2474             {
2475                 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2476                 if (temp_status == FILE_SKIP)
2477                     break;
2478                 if (temp_status == FILE_SKIPALL)
2479                     ctx->skip_all = TRUE;
2480                 if (temp_status != FILE_RETRY)
2481                 {
2482                     return_status = temp_status;
2483                     goto ret_fast;
2484                 }
2485             }
2486 
2487             while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->skip_all)
2488             {
2489                 attrs_ok = FALSE;
2490 
2491                 /* don't show an error message if attributes aren't supported in this FS */
2492                 if (errno == ENOTSUP)
2493                     break;
2494 
2495                 temp_status =
2496                     file_error (TRUE, _("Cannot set attributes of target file \"%s\"\n%s"),
2497                                 dst_path);
2498                 if (temp_status == FILE_SKIP)
2499                     break;
2500                 if (temp_status == FILE_SKIPALL)
2501                     ctx->skip_all = TRUE;
2502                 if (temp_status != FILE_RETRY)
2503                 {
2504                     return_status = temp_status;
2505                     goto ret_fast;
2506                 }
2507 
2508                 /* yet another attempt */
2509                 attrs_ok = TRUE;
2510             }
2511 
2512             return_status = FILE_CONT;
2513             mc_utime (dst_vpath, &times);
2514             goto ret_fast;
2515         }
2516     }
2517 
2518     tv_transfer_start = g_get_monotonic_time ();
2519 
2520     while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all)
2521     {
2522         return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path);
2523         if (return_status == FILE_RETRY)
2524             continue;
2525         if (return_status == FILE_SKIPALL)
2526             ctx->skip_all = TRUE;
2527         if (return_status == FILE_SKIP)
2528             break;
2529         ctx->do_append = FALSE;
2530         goto ret_fast;
2531     }
2532 
2533     if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
2534     {
2535         message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
2536         ctx->do_reget = 0;
2537         ctx->do_append = FALSE;
2538     }
2539 
2540     while (mc_fstat (src_desc, &src_stat) != 0)
2541     {
2542         if (ctx->skip_all)
2543             return_status = FILE_SKIPALL;
2544         else
2545         {
2546             return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
2547             if (return_status == FILE_RETRY)
2548                 continue;
2549             if (return_status == FILE_SKIPALL)
2550                 ctx->skip_all = TRUE;
2551             ctx->do_append = FALSE;
2552         }
2553         goto ret;
2554     }
2555 
2556     src_mode = src_stat.st_mode;
2557     src_uid = src_stat.st_uid;
2558     src_gid = src_stat.st_gid;
2559     file_size = src_stat.st_size;
2560 
2561     open_flags = O_WRONLY;
2562     if (!dst_exists)
2563         open_flags |= O_CREAT | O_EXCL;
2564     else if (ctx->do_append)
2565         open_flags |= O_APPEND;
2566     else
2567         open_flags |= O_CREAT | O_TRUNC;
2568 
2569     while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
2570     {
2571         if (errno != EEXIST)
2572         {
2573             if (ctx->skip_all)
2574                 return_status = FILE_SKIPALL;
2575             else
2576             {
2577                 return_status =
2578                     file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
2579                 if (return_status == FILE_RETRY)
2580                     continue;
2581                 if (return_status == FILE_SKIPALL)
2582                     ctx->skip_all = TRUE;
2583                 ctx->do_append = FALSE;
2584             }
2585         }
2586         goto ret;
2587     }
2588 
2589     /* file opened, but not fully copied */
2590     dst_status = DEST_SHORT_QUERY;
2591 
2592     appending = ctx->do_append;
2593     ctx->do_append = FALSE;
2594 
2595     /* Try clone the file first. */
2596     if (vfs_clone_file (dest_desc, src_desc) == 0)
2597     {
2598         dst_status = DEST_FULL;
2599         return_status = FILE_CONT;
2600         goto ret;
2601     }
2602 
2603     /* Find out the optimal buffer size.  */
2604     while (mc_fstat (dest_desc, &dst_stat) != 0)
2605     {
2606         if (ctx->skip_all)
2607             return_status = FILE_SKIPALL;
2608         else
2609         {
2610             return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
2611             if (return_status == FILE_RETRY)
2612                 continue;
2613             if (return_status == FILE_SKIPALL)
2614                 ctx->skip_all = TRUE;
2615         }
2616         goto ret;
2617     }
2618 
2619     /* try preallocate space; if fail, try copy anyway */
2620     while (mc_global.vfs.preallocate_space &&
2621            vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
2622     {
2623         if (ctx->skip_all)
2624         {
2625             /* cannot allocate, start the file copying anyway */
2626             return_status = FILE_CONT;
2627             break;
2628         }
2629 
2630         return_status =
2631             file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
2632 
2633         if (return_status == FILE_SKIPALL)
2634             ctx->skip_all = TRUE;
2635 
2636         if (ctx->skip_all || return_status == FILE_SKIP)
2637         {
2638             /* skip the space allocation error, start file copying */
2639             return_status = FILE_CONT;
2640             break;
2641         }
2642 
2643         if (return_status == FILE_ABORT)
2644         {
2645             mc_close (dest_desc);
2646             dest_desc = -1;
2647             mc_unlink (dst_vpath);
2648             dst_status = DEST_NONE;
2649             goto ret;
2650         }
2651 
2652         /* return_status == FILE_RETRY -- try allocate space again */
2653     }
2654 
2655     ctx->eta_secs = 0.0;
2656     ctx->bps = 0;
2657 
2658     if (tctx->bps == 0 || (file_size / tctx->bps) > FILEOP_UPDATE_INTERVAL)
2659         file_progress_show (ctx, 0, file_size, "", TRUE);
2660     else
2661         file_progress_show (ctx, 1, 1, "", TRUE);
2662     return_status = check_progress_buttons (ctx);
2663     mc_refresh ();
2664 
2665     if (return_status == FILE_CONT)
2666     {
2667         size_t bufsize;
2668         off_t file_part = 0;
2669         gint64 tv_current, tv_last_update;
2670         gint64 tv_last_input = 0;
2671         gint64 usecs, update_usecs;
2672         const char *stalled_msg = "";
2673         gboolean is_first_time = TRUE;
2674 
2675         tv_last_update = tv_transfer_start;
2676 
2677         bufsize = io_blksize (dst_stat);
2678         buf = g_malloc (bufsize);
2679 
2680         while (TRUE)
2681         {
2682             ssize_t n_read = -1, n_written;
2683             gboolean force_update;
2684 
2685             /* src_read */
2686             if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
2687                 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all)
2688                 {
2689                     return_status =
2690                         file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
2691                     if (return_status == FILE_RETRY)
2692                         continue;
2693                     if (return_status == FILE_SKIPALL)
2694                         ctx->skip_all = TRUE;
2695                     goto ret;
2696                 }
2697 
2698             if (n_read == 0)
2699                 break;
2700 
2701             tv_current = g_get_monotonic_time ();
2702 
2703             if (n_read > 0)
2704             {
2705                 char *t = buf;
2706 
2707                 file_part += n_read;
2708 
2709                 tv_last_input = tv_current;
2710 
2711                 /* dst_write */
2712                 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
2713                 {
2714                     gboolean write_errno_nospace;
2715 
2716                     if (n_written > 0)
2717                     {
2718                         n_read -= n_written;
2719                         t += n_written;
2720                         continue;
2721                     }
2722 
2723                     write_errno_nospace = (n_written < 0 && errno == ENOSPC);
2724 
2725                     if (ctx->skip_all)
2726                         return_status = FILE_SKIPALL;
2727                     else
2728                         return_status =
2729                             file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
2730 
2731                     if (return_status == FILE_SKIP)
2732                     {
2733                         if (write_errno_nospace)
2734                             goto ret;
2735                         break;
2736                     }
2737                     if (return_status == FILE_SKIPALL)
2738                     {
2739                         ctx->skip_all = TRUE;
2740                         if (write_errno_nospace)
2741                             goto ret;
2742                     }
2743                     if (return_status != FILE_RETRY)
2744                         goto ret;
2745                 }
2746             }
2747 
2748             tctx->copied_bytes = tctx->progress_bytes + file_part + ctx->do_reget;
2749 
2750             usecs = tv_current - tv_last_update;
2751             update_usecs = tv_current - tv_last_input;
2752 
2753             if (is_first_time || usecs > FILEOP_UPDATE_INTERVAL_US)
2754             {
2755                 copy_file_file_display_progress (tctx, ctx, tv_current, tv_transfer_start,
2756                                                  file_size, file_part);
2757                 tv_last_update = tv_current;
2758             }
2759 
2760             is_first_time = FALSE;
2761 
2762             if (update_usecs > FILEOP_STALLING_INTERVAL_US)
2763                 stalled_msg = _("(stalled)");
2764 
2765             force_update = (tv_current - tctx->transfer_start) > FILEOP_UPDATE_INTERVAL_US;
2766 
2767             if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
2768             {
2769                 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2770                 file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
2771             }
2772 
2773             file_progress_show (ctx, file_part + ctx->do_reget, file_size, stalled_msg,
2774                                 force_update);
2775             mc_refresh ();
2776 
2777             return_status = check_progress_buttons (ctx);
2778             if (return_status != FILE_CONT)
2779             {
2780                 int query_res;
2781 
2782                 query_res =
2783                     query_dialog (Q_ ("DialogTitle|Copy"),
2784                                   _("Incomplete file was retrieved"), D_ERROR, 3,
2785                                   _("&Delete"), _("&Keep"), _("&Continue copy"));
2786 
2787                 switch (query_res)
2788                 {
2789                 case 0:
2790                     /* delete */
2791                     dst_status = DEST_SHORT_DELETE;
2792                     goto ret;
2793 
2794                 case 1:
2795                     /* keep */
2796                     dst_status = DEST_SHORT_KEEP;
2797                     goto ret;
2798 
2799                 default:
2800                     /* continue copy */
2801                     break;
2802                 }
2803             }
2804         }
2805 
2806         /* copy successful */
2807         dst_status = DEST_FULL;
2808     }
2809 
2810   ret:
2811     g_free (buf);
2812 
2813     rotate_dash (FALSE);
2814     while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all)
2815     {
2816         temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
2817         if (temp_status == FILE_RETRY)
2818             continue;
2819         if (temp_status == FILE_ABORT)
2820             return_status = temp_status;
2821         if (temp_status == FILE_SKIPALL)
2822             ctx->skip_all = TRUE;
2823         break;
2824     }
2825 
2826     while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all)
2827     {
2828         temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
2829         if (temp_status == FILE_RETRY)
2830             continue;
2831         if (temp_status == FILE_SKIPALL)
2832             ctx->skip_all = TRUE;
2833         return_status = temp_status;
2834         break;
2835     }
2836 
2837     if (dst_status == DEST_SHORT_QUERY)
2838     {
2839         /* Query to remove short file */
2840         if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved"),
2841                           D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
2842             dst_status = DEST_SHORT_DELETE;
2843         else
2844             dst_status = DEST_SHORT_KEEP;
2845     }
2846 
2847     if (dst_status == DEST_SHORT_DELETE)
2848         mc_unlink (dst_vpath);
2849     else if (dst_status == DEST_FULL && !appending)
2850     {
2851         /* Copy has succeeded */
2852 
2853         while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
2854                && !ctx->skip_all)
2855         {
2856             temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2857             if (temp_status == FILE_RETRY)
2858                 continue;
2859             if (temp_status == FILE_SKIPALL)
2860             {
2861                 ctx->skip_all = TRUE;
2862                 return_status = FILE_CONT;
2863             }
2864             if (temp_status == FILE_SKIP)
2865                 return_status = FILE_CONT;
2866             break;
2867         }
2868 
2869         while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
2870                && !ctx->skip_all)
2871         {
2872             temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2873             if (temp_status == FILE_RETRY)
2874                 continue;
2875             if (temp_status == FILE_SKIPALL)
2876             {
2877                 ctx->skip_all = TRUE;
2878                 return_status = FILE_CONT;
2879             }
2880             if (temp_status == FILE_SKIP)
2881                 return_status = FILE_CONT;
2882             break;
2883         }
2884 
2885         if (!ctx->preserve && !dst_exists)
2886         {
2887             src_mode = umask (-1);
2888             umask (src_mode);
2889             src_mode = 0100666 & ~src_mode;
2890             mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2891         }
2892     }
2893 
2894     if (dst_status == DEST_FULL || dst_status == DEST_SHORT_KEEP)
2895     {
2896         /* Always sync timestamps */
2897         mc_utime (dst_vpath, &times);
2898 
2899         while (attrs_ok && mc_fsetflags (dst_vpath, attrs) != 0 && !ctx->skip_all)
2900         {
2901             attrs_ok = FALSE;
2902 
2903             /* don't show an error message if attributes aren't supported in this FS */
2904             if (errno == ENOTSUP)
2905             {
2906                 return_status = FILE_CONT;
2907                 break;
2908             }
2909 
2910             temp_status = file_error (TRUE, _("Cannot set attributes for target file \"%s\"\n%s"),
2911                                       dst_path);
2912             if (temp_status == FILE_RETRY)
2913             {
2914                 attrs_ok = TRUE;
2915                 continue;
2916             }
2917             if (temp_status == FILE_SKIPALL)
2918             {
2919                 ctx->skip_all = TRUE;
2920                 return_status = FILE_CONT;
2921             }
2922             if (temp_status == FILE_SKIP)
2923                 return_status = FILE_CONT;
2924             break;
2925         }
2926     }
2927 
2928     if (return_status == FILE_CONT)
2929         return_status = progress_update_one (tctx, ctx, file_size);
2930 
2931   ret_fast:
2932     vfs_path_free (src_vpath, TRUE);
2933     vfs_path_free (dst_vpath, TRUE);
2934     return return_status;
2935 }
2936 
2937 /* --------------------------------------------------------------------------------------------- */
2938 /**
2939  * I think these copy_*_* functions should have a return type.
2940  * anyway, this function *must* have two directories as arguments.
2941  */
2942 /* FIXME: This function needs to check the return values of the
2943    function calls */
2944 
2945 FileProgressStatus
2946 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]  */
2947               gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs)
2948 {
2949     struct vfs_dirent *next;
2950     struct stat dst_stat, src_stat;
2951     unsigned long attrs;
2952     gboolean attrs_ok = ctx->preserve;
2953     DIR *reading;
2954     FileProgressStatus return_status = FILE_CONT;
2955     struct link *lp;
2956     vfs_path_t *src_vpath, *dst_vpath;
2957     gboolean do_mkdir = TRUE;
2958 
2959     src_vpath = vfs_path_from_str (s);
2960     dst_vpath = vfs_path_from_str (d);
2961 
2962     /* First get the mode of the source dir */
2963 
2964   retry_src_stat:
2965     if ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2966     {
2967         if (ctx->skip_all)
2968             return_status = FILE_SKIPALL;
2969         else
2970         {
2971             return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
2972             if (return_status == FILE_RETRY)
2973                 goto retry_src_stat;
2974             if (return_status == FILE_SKIPALL)
2975                 ctx->skip_all = TRUE;
2976         }
2977         goto ret_fast;
2978     }
2979 
2980     while (attrs_ok && mc_fgetflags (src_vpath, &attrs) != 0)
2981     {
2982         attrs_ok = FALSE;
2983 
2984         /* don't show an error message if attributes aren't supported in this FS */
2985         if (errno == ENOTSUP)
2986         {
2987             return_status = FILE_CONT;
2988             break;
2989         }
2990 
2991         if (ctx->skip_all)
2992             return_status = FILE_SKIPALL;
2993         else
2994         {
2995             return_status =
2996                 file_error (TRUE, _("Cannot get attributes of source directory \"%s\"\n%s"), s);
2997             if (return_status == FILE_RETRY)
2998             {
2999                 attrs_ok = TRUE;
3000                 continue;
3001             }
3002             if (return_status == FILE_SKIPALL)
3003                 ctx->skip_all = TRUE;
3004         }
3005         goto ret_fast;
3006     }
3007 
3008     if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
3009     {
3010         /* Don't copy a directory we created before (we don't want to copy
3011            infinitely if a directory is copied into itself) */
3012         /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
3013         return_status = FILE_CONT;
3014         goto ret_fast;
3015     }
3016 
3017     /* Hmm, hardlink to directory??? - Norbert */
3018     /* FIXME: In this step we should do something in case the destination already exist */
3019     /* Check the hardlinks */
3020     if (ctx->preserve)
3021     {
3022         switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
3023         {
3024         case HARDLINK_OK:
3025             /* We have made a hardlink - no more processing is necessary */
3026             goto ret_fast;
3027 
3028         case HARDLINK_ABORT:
3029             return_status = FILE_ABORT;
3030             goto ret_fast;
3031 
3032         default:
3033             break;
3034         }
3035     }
3036 
3037     if (!S_ISDIR (src_stat.st_mode))
3038     {
3039         if (ctx->skip_all)
3040             return_status = FILE_SKIPALL;
3041         else
3042         {
3043             return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
3044             if (return_status == FILE_RETRY)
3045                 goto retry_src_stat;
3046             if (return_status == FILE_SKIPALL)
3047                 ctx->skip_all = TRUE;
3048         }
3049         goto ret_fast;
3050     }
3051 
3052     if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
3053     {
3054         /* we found a cyclic symbolic link */
3055         message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
3056         return_status = FILE_SKIP;
3057         goto ret_fast;
3058     }
3059 
3060     lp = g_new0 (struct link, 1);
3061     lp->vfs = vfs_path_get_last_path_vfs (src_vpath);
3062     lp->ino = src_stat.st_ino;
3063     lp->dev = src_stat.st_dev;
3064     parent_dirs = g_slist_prepend (parent_dirs, lp);
3065 
3066   retry_dst_stat:
3067     /* Now, check if the dest dir exists, if not, create it. */
3068     if (mc_stat (dst_vpath, &dst_stat) != 0)
3069     {
3070         /* Here the dir doesn't exist : make it ! */
3071         if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
3072         {
3073             return_status = FILE_CONT;
3074             goto ret;
3075         }
3076     }
3077     else
3078     {
3079         /*
3080          * If the destination directory exists, we want to copy the whole
3081          * directory, but we only want this to happen once.
3082          *
3083          * Escape sequences added to the * to compiler warnings.
3084          * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
3085          * or ( /bla doesn't exist )       /tmp/\* to /bla     ->  /bla/\*
3086          */
3087         if (!S_ISDIR (dst_stat.st_mode))
3088         {
3089             if (ctx->skip_all)
3090                 return_status = FILE_SKIPALL;
3091             else
3092             {
3093                 return_status =
3094                     file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
3095                 if (return_status == FILE_SKIPALL)
3096                     ctx->skip_all = TRUE;
3097                 if (return_status == FILE_RETRY)
3098                     goto retry_dst_stat;
3099             }
3100             goto ret;
3101         }
3102         /* Dive into subdir if exists */
3103         if (toplevel && ctx->dive_into_subdirs)
3104         {
3105             vfs_path_t *tmp;
3106 
3107             tmp = dst_vpath;
3108             dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
3109             vfs_path_free (tmp, TRUE);
3110 
3111         }
3112         else
3113             do_mkdir = FALSE;
3114     }
3115 
3116     d = vfs_path_as_str (dst_vpath);
3117 
3118     if (do_mkdir)
3119     {
3120         while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
3121         {
3122             if (ctx->skip_all)
3123                 return_status = FILE_SKIPALL;
3124             else
3125             {
3126                 return_status =
3127                     file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
3128                 if (return_status == FILE_SKIPALL)
3129                     ctx->skip_all = TRUE;
3130             }
3131             if (return_status != FILE_RETRY)
3132                 goto ret;
3133         }
3134 
3135         lp = g_new0 (struct link, 1);
3136         mc_stat (dst_vpath, &dst_stat);
3137         lp->vfs = vfs_path_get_last_path_vfs (dst_vpath);
3138         lp->ino = dst_stat.st_ino;
3139         lp->dev = dst_stat.st_dev;
3140         dest_dirs = g_slist_prepend (dest_dirs, lp);
3141     }
3142 
3143     if (ctx->preserve_uidgid)
3144     {
3145         while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
3146         {
3147             if (ctx->skip_all)
3148                 return_status = FILE_SKIPALL;
3149             else
3150             {
3151                 return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
3152                 if (return_status == FILE_SKIPALL)
3153                     ctx->skip_all = TRUE;
3154             }
3155             if (return_status != FILE_RETRY)
3156                 goto ret;
3157         }
3158     }
3159 
3160     /* open the source dir for reading */
3161     reading = mc_opendir (src_vpath);
3162     if (reading == NULL)
3163         goto ret;
3164 
3165     while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
3166     {
3167         char *path;
3168         vfs_path_t *tmp_vpath;
3169 
3170         /*
3171          * Now, we don't want '.' and '..' to be created / copied at any time
3172          */
3173         if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
3174             continue;
3175 
3176         /* get the filename and add it to the src directory */
3177         path = mc_build_filename (s, next->d_name, (char *) NULL);
3178         tmp_vpath = vfs_path_from_str (path);
3179 
3180         (*ctx->stat_func) (tmp_vpath, &dst_stat);
3181         if (S_ISDIR (dst_stat.st_mode))
3182         {
3183             char *mdpath;
3184 
3185             mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
3186             /*
3187              * From here, we just intend to recursively copy subdirs, not
3188              * the double functionality of copying different when the target
3189              * dir already exists. So, we give the recursive call the flag 0
3190              * meaning no toplevel.
3191              */
3192             return_status =
3193                 copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
3194             g_free (mdpath);
3195         }
3196         else
3197         {
3198             char *dest_file;
3199 
3200             dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
3201             return_status = copy_file_file (tctx, ctx, path, dest_file);
3202             g_free (dest_file);
3203         }
3204 
3205         g_free (path);
3206 
3207         if (do_delete && return_status == FILE_CONT)
3208         {
3209             if (ctx->erase_at_end)
3210             {
3211                 if (erase_list == NULL)
3212                     erase_list = g_queue_new ();
3213 
3214                 lp = g_new0 (struct link, 1);
3215                 lp->src_vpath = tmp_vpath;
3216                 lp->st_mode = dst_stat.st_mode;
3217                 g_queue_push_tail (erase_list, lp);
3218                 tmp_vpath = NULL;
3219             }
3220             else if (S_ISDIR (dst_stat.st_mode))
3221                 return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
3222             else
3223                 return_status = erase_file (tctx, ctx, tmp_vpath);
3224         }
3225         vfs_path_free (tmp_vpath, TRUE);
3226     }
3227     mc_closedir (reading);
3228 
3229     if (ctx->preserve)
3230     {
3231         mc_timesbuf_t times;
3232 
3233         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3234 
3235         if (attrs_ok)
3236             mc_fsetflags (dst_vpath, attrs);
3237 
3238         get_times (&src_stat, &times);
3239         mc_utime (dst_vpath, &times);
3240     }
3241     else
3242     {
3243         src_stat.st_mode = umask (-1);
3244         umask (src_stat.st_mode);
3245         src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3246         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3247     }
3248 
3249   ret:
3250     free_link (parent_dirs->data);
3251     g_slist_free_1 (parent_dirs);
3252   ret_fast:
3253     vfs_path_free (src_vpath, TRUE);
3254     vfs_path_free (dst_vpath, TRUE);
3255     return return_status;
3256 }
3257 
3258 /* }}} */
3259 
3260 /* --------------------------------------------------------------------------------------------- */
3261 /* {{{ Move routines */
3262 
3263 FileProgressStatus
3264 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]  */
3265 {
3266     return do_move_dir_dir (NULL, tctx, ctx, s, d);
3267 }
3268 
3269 /* }}} */
3270 
3271 /* --------------------------------------------------------------------------------------------- */
3272 /* {{{ Erase routines */
3273 
3274 FileProgressStatus
3275 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]  */
3276 {
3277     file_progress_show_deleting (ctx, vfs_path_as_str (vpath), NULL);
3278     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3279     if (check_progress_buttons (ctx) == FILE_ABORT)
3280         return FILE_ABORT;
3281 
3282     mc_refresh ();
3283 
3284     /* The old way to detect a non empty directory was:
3285        error = my_rmdir (s);
3286        if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3287        For the linux user space nfs server (nfs-server-2.2beta29-2)
3288        we would have to check also for EIO. I hope the new way is
3289        fool proof. (Norbert)
3290      */
3291     if (check_dir_is_empty (vpath) == 0)
3292     {                           /* not empty */
3293         FileProgressStatus error;
3294 
3295         error = query_recursive (ctx, vfs_path_as_str (vpath));
3296         if (error == FILE_CONT)
3297             error = recursive_erase (tctx, ctx, vpath);
3298         return error;
3299     }
3300 
3301     return try_erase_dir (ctx, vfs_path_as_str (vpath));
3302 }
3303 
3304 /* }}} */
3305 
3306 /* --------------------------------------------------------------------------------------------- */
3307 /* {{{ Panel operate routines */
3308 
3309 void
3310 dirsize_status_init_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3311 {
3312     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3313     WGroup *gd = GROUP (sm->dlg);
3314     Widget *wd = WIDGET (sm->dlg);
3315     WRect r = wd->rect;
3316 
3317     const char *b1_name = N_("&Abort");
3318     const char *b2_name = N_("&Skip");
3319     int b_width, ui_width;
3320 
3321 #ifdef ENABLE_NLS
3322     b1_name = _(b1_name);
3323     b2_name = _(b2_name);
3324 #endif
3325 
3326     b_width = str_term_width1 (b1_name) + 4;
3327     if (dsm->allow_skip)
3328         b_width += str_term_width1 (b2_name) + 4 + 1;
3329 
3330     ui_width = MAX (COLS / 2, b_width + 6);
3331     dsm->dirname = label_new (2, 3, NULL);
3332     group_add_widget (gd, dsm->dirname);
3333     dsm->count_size = label_new (3, 3, NULL);
3334     group_add_widget (gd, dsm->count_size);
3335     group_add_widget (gd, hline_new (4, -1, -1));
3336 
3337     dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3338     group_add_widget (gd, dsm->abort_button);
3339     if (dsm->allow_skip)
3340     {
3341         dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3342         group_add_widget (gd, dsm->skip_button);
3343         widget_select (dsm->skip_button);
3344     }
3345 
3346     r.lines = 8;
3347     r.cols = ui_width;
3348     widget_set_size_rect (wd, &r);
3349     dirsize_status_locate_buttons (dsm);
3350 }
3351 
3352 /* --------------------------------------------------------------------------------------------- */
3353 
3354 int
3355 dirsize_status_update_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3356 {
3357     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3358     Widget *wd = WIDGET (sm->dlg);
3359     WRect r = wd->rect;
3360 
3361     /* update second (longer label) */
3362     label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
3363                      dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3364 
3365     /* enlarge dialog if required */
3366     if (WIDGET (dsm->count_size)->rect.cols + 6 > r.cols)
3367     {
3368         r.cols = WIDGET (dsm->count_size)->rect.cols + 6;
3369         widget_set_size_rect (wd, &r);
3370         dirsize_status_locate_buttons (dsm);
3371         widget_draw (wd);
3372         /* TODO: ret rid of double redraw */
3373     }
3374 
3375     /* adjust first label */
3376     label_set_text (dsm->dirname,
3377                     str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->rect.cols - 6));
3378 
3379     switch (status_msg_common_update (sm))
3380     {
3381     case B_CANCEL:
3382     case FILE_ABORT:
3383         return FILE_ABORT;
3384     case FILE_SKIP:
3385         return FILE_SKIP;
3386     default:
3387         return FILE_CONT;
3388     }
3389 }
3390 
3391 /* --------------------------------------------------------------------------------------------- */
3392 
3393 void
3394 dirsize_status_deinit_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3395 {
3396     (void) sm;
3397 
3398     /* schedule to update passive panel */
3399     if (get_other_type () == view_listing)
3400         other_panel->dirty = TRUE;
3401 }
3402 
3403 /* --------------------------------------------------------------------------------------------- */
3404 /**
3405  * compute_dir_size:
3406  *
3407  * Computes the number of bytes used by the files in a directory
3408  */
3409 
3410 FileProgressStatus
3411 compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
     /* [previous][next][first][last][top][bottom][index][help]  */
3412                   size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total,
3413                   gboolean follow_symlinks)
3414 {
3415     return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3416                                 follow_symlinks ? mc_stat : mc_lstat);
3417 }
3418 
3419 /* --------------------------------------------------------------------------------------------- */
3420 /**
3421  * panel_operate:
3422  *
3423  * Performs one of the operations on the current on the source_panel
3424  * (copy, delete, move).
3425  *
3426  * Returns TRUE if did change the directory
3427  * structure, Returns FALSE if user aborted
3428  *
3429  * force_single forces operation on the current entry and affects
3430  * default destination.  Current filename is used as default.
3431  */
3432 
3433 gboolean
3434 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
     /* [previous][next][first][last][top][bottom][index][help]  */
3435 {
3436     WPanel *panel = PANEL (source_panel);
3437     const gboolean single_entry = force_single || (panel->marked <= 1)
3438         || (get_current_type () == view_tree);
3439 
3440     const char *source = NULL;
3441     char *dest = NULL;
3442     vfs_path_t *dest_vpath = NULL;
3443     vfs_path_t *save_cwd = NULL, *save_dest = NULL;
3444     struct stat src_stat;
3445     gboolean ret_val = TRUE;
3446     int i;
3447     FileProgressStatus value;
3448     file_op_context_t *ctx;
3449     file_op_total_context_t *tctx;
3450     filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3451 
3452     gboolean do_bg = FALSE;     /* do background operation? */
3453 
3454     static gboolean i18n_flag = FALSE;
3455     if (!i18n_flag)
3456     {
3457         for (i = G_N_ELEMENTS (op_names); i-- != 0;)
3458             op_names[i] = Q_ (op_names[i]);
3459         i18n_flag = TRUE;
3460     }
3461 
3462     linklist = free_linklist (linklist);
3463     dest_dirs = free_linklist (dest_dirs);
3464 
3465     save_cwds_stat ();
3466 
3467     if (single_entry)
3468     {
3469         source = check_single_entry (panel, force_single, &src_stat);
3470 
3471         if (source == NULL)
3472             return FALSE;
3473     }
3474 
3475     ctx = file_op_context_new (operation);
3476 
3477     /* Show confirmation dialog */
3478     if (operation != OP_DELETE)
3479     {
3480         dest = do_confirm_copy_move (panel, force_single, source, &src_stat, ctx, &do_bg);
3481         if (dest == NULL)
3482         {
3483             ret_val = FALSE;
3484             goto ret_fast;
3485         }
3486 
3487         dest_vpath = vfs_path_from_str (dest);
3488     }
3489     else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3490     {
3491         ret_val = FALSE;
3492         goto ret_fast;
3493     }
3494 
3495     tctx = file_op_total_context_new ();
3496     tctx->transfer_start = g_get_monotonic_time ();
3497 
3498 #ifdef ENABLE_BACKGROUND
3499     /* Did the user select to do a background operation? */
3500     if (do_bg)
3501     {
3502         int v;
3503 
3504         v = do_background (ctx,
3505                            g_strconcat (op_names[operation], ": ",
3506                                         vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3507         if (v == -1)
3508             message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
3509 
3510         /* If we are the parent */
3511         if (v == 1)
3512         {
3513             mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3514 
3515             mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3516             vfs_path_free (dest_vpath, TRUE);
3517             g_free (dest);
3518             /*          file_op_context_destroy (ctx); */
3519             return FALSE;
3520         }
3521     }
3522     else
3523 #endif /* ENABLE_BACKGROUND */
3524     {
3525         if (operation == OP_DELETE)
3526             dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3527         else if (single_entry && S_ISDIR (panel_current_entry (panel)->st.st_mode))
3528             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3529         else if (single_entry || force_single)
3530             dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3531         else
3532             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3533     }
3534 
3535     /* Initialize things */
3536     /* We do not want to trash cache every time file is
3537        created/touched. However, this will make our cache contain
3538        invalid data. */
3539     if ((dest != NULL)
3540         && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3541         save_dest = vfs_path_from_str (dest);
3542 
3543     if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3544         && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3545         save_cwd = vfs_path_clone (panel->cwd_vpath);
3546 
3547     /* Now, let's do the job */
3548 
3549     /* This code is only called by the tree and panel code */
3550     if (single_entry)
3551     {
3552         /* We now have ETA in all cases */
3553 
3554         /* One file: FIXME mc_chdir will take user out of any vfs */
3555         if ((operation != OP_COPY) && (get_current_type () == view_tree))
3556         {
3557             vfs_path_t *vpath;
3558             int chdir_retcode;
3559 
3560             vpath = vfs_path_from_str (PATH_SEP_STR);
3561             chdir_retcode = mc_chdir (vpath);
3562             vfs_path_free (vpath, TRUE);
3563             if (chdir_retcode < 0)
3564             {
3565                 ret_val = FALSE;
3566                 goto clean_up;
3567             }
3568         }
3569 
3570         value = operate_single_file (panel, tctx, ctx, source, &src_stat, dest, dialog_type);
3571         if ((value == FILE_CONT) && !force_single)
3572             unmark_files (panel);
3573     }
3574     else
3575     {
3576         /* Many files */
3577 
3578         /* Check destination for copy or move operation */
3579         while (operation != OP_DELETE)
3580         {
3581             int dst_result;
3582             struct stat dst_stat;
3583 
3584             dst_result = mc_stat (dest_vpath, &dst_stat);
3585 
3586             if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3587                 break;
3588 
3589             if (ctx->skip_all
3590                 || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
3591                                dest) != FILE_RETRY)
3592                 goto clean_up;
3593         }
3594 
3595         /* TODO: the good way is required to skip directories scanning in case of rename/move
3596          * of several directories. Since reqular expression can be used for destination,
3597          * some directory movements can be a cross-filesystem and directory scanning is useful
3598          * for those directories only. */
3599 
3600         if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type)
3601             == FILE_CONT)
3602         {
3603             /* Loop for every file, perform the actual copy operation */
3604             for (i = 0; i < panel->dir.len; i++)
3605             {
3606                 const char *source2;
3607 
3608                 if (panel->dir.list[i].f.marked == 0)
3609                     continue;   /* Skip the unmarked ones */
3610 
3611                 source2 = panel->dir.list[i].fname->str;
3612                 src_stat = panel->dir.list[i].st;
3613 
3614                 value = operate_one_file (panel, tctx, ctx, source2, &src_stat, dest);
3615 
3616                 if (value == FILE_ABORT)
3617                     break;
3618 
3619                 if (value == FILE_CONT)
3620                     do_file_mark (panel, i, 0);
3621 
3622                 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3623                 {
3624                     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3625                     file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
3626                 }
3627 
3628                 if (operation != OP_DELETE)
3629                     file_progress_show (ctx, 0, 0, "", FALSE);
3630 
3631                 if (check_progress_buttons (ctx) == FILE_ABORT)
3632                     break;
3633 
3634                 mc_refresh ();
3635             }                   /* Loop for every file */
3636         }
3637     }                           /* Many entries */
3638 
3639   clean_up:
3640     /* Clean up */
3641     if (save_cwd != NULL)
3642     {
3643         mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
3644         vfs_path_free (save_cwd, TRUE);
3645     }
3646 
3647     if (save_dest != NULL)
3648     {
3649         mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
3650         vfs_path_free (save_dest, TRUE);
3651     }
3652 
3653     linklist = free_linklist (linklist);
3654     dest_dirs = free_linklist (dest_dirs);
3655     g_free (dest);
3656     vfs_path_free (dest_vpath, TRUE);
3657     MC_PTR_FREE (ctx->dest_mask);
3658 
3659 #ifdef ENABLE_BACKGROUND
3660     /* Let our parent know we are saying bye bye */
3661     if (mc_global.we_are_background)
3662     {
3663         int cur_pid = getpid ();
3664         /* Send pid to parent with child context, it is fork and
3665            don't modify real parent ctx */
3666         ctx->pid = cur_pid;
3667         parent_call ((void *) end_bg_process, ctx, 0);
3668 
3669         vfs_shut ();
3670         my_exit (EXIT_SUCCESS);
3671     }
3672 #endif /* ENABLE_BACKGROUND */
3673 
3674     file_op_total_context_destroy (tctx);
3675   ret_fast:
3676     file_op_context_destroy (ctx);
3677 
3678     update_panels (UP_OPTIMIZE, UP_KEEPSEL);
3679     repaint_screen ();
3680 
3681     return ret_val;
3682 }
3683 
3684 /* }}} */
3685 
3686 /* --------------------------------------------------------------------------------------------- */
3687 /* {{{ Query/status report routines */
3688 /** Report error with one file */
3689 FileProgressStatus
3690 file_error (gboolean allow_retry, const char *format, const char *file)
     /* [previous][next][first][last][top][bottom][index][help]  */
3691 {
3692     char buf[BUF_MEDIUM];
3693 
3694     g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3695 
3696     return do_file_error (allow_retry, buf);
3697 }
3698 
3699 /* --------------------------------------------------------------------------------------------- */
3700 
3701 /*
3702    Cause emacs to enter folding mode for this file:
3703    Local variables:
3704    end:
3705  */

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