root/src/filemanager/file.c

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

DEFINITIONS

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

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

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