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_linklist
  5. is_in_linklist
  6. check_hardlinks
  7. make_symlink
  8. do_compute_dir_size
  9. panel_compute_totals
  10. panel_operate_init_totals
  11. progress_update_one
  12. real_warn_same_file
  13. warn_same_file
  14. check_same_file
  15. get_times
  16. real_do_file_error
  17. real_query_recursive
  18. do_file_error
  19. query_recursive
  20. query_replace
  21. do_file_error
  22. query_recursive
  23. query_replace
  24. files_error
  25. copy_file_file_display_progress
  26. try_remove_file
  27. move_file_file
  28. erase_file
  29. try_erase_dir
  30. recursive_erase
  31. check_dir_is_empty
  32. erase_dir_iff_empty
  33. erase_dir_after_copy
  34. do_move_dir_dir
  35. panel_get_file
  36. check_single_entry
  37. panel_operate_generate_prompt
  38. do_confirm_copy_move
  39. do_confirm_erase
  40. operate_single_file
  41. operate_one_file
  42. end_bg_process
  43. file_is_symlink_to_dir
  44. copy_file_file
  45. copy_dir_dir
  46. move_dir_dir
  47. erase_dir
  48. dirsize_status_init_cb
  49. dirsize_status_update_cb
  50. dirsize_status_deinit_cb
  51. compute_dir_size
  52. panel_operate
  53. file_error

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

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