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

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