root/src/filemanager/file.c

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

DEFINITIONS

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

   1 /*
   2    File management.
   3 
   4    Copyright (C) 1994-2020
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Janne Kukonlehto, 1994, 1995
   9    Fred Leeflang, 1994, 1995
  10    Miguel de Icaza, 1994, 1995, 1996
  11    Jakub Jelinek, 1995, 1996
  12    Norbert Warmuth, 1997
  13    Pavel Machek, 1998
  14    Andrew Borodin <aborodin@vmail.ru>, 2011-2014
  15 
  16    The copy code was based in GNU's cp, and was written by:
  17    Torbjorn Granlund, David MacKenzie, and Jim Meyering.
  18 
  19    The move code was based in GNU's mv, and was written by:
  20    Mike Parker and David MacKenzie.
  21 
  22    Janne Kukonlehto added much error recovery to them for being used
  23    in an interactive program.
  24 
  25    This file is part of the Midnight Commander.
  26 
  27    The Midnight Commander is free software: you can redistribute it
  28    and/or modify it under the terms of the GNU General Public License as
  29    published by the Free Software Foundation, either version 3 of the License,
  30    or (at your option) any later version.
  31 
  32    The Midnight Commander is distributed in the hope that it will be useful,
  33    but WITHOUT ANY WARRANTY; without even the implied warranty of
  34    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  35    GNU General Public License for more details.
  36 
  37    You should have received a copy of the GNU General Public License
  38    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  39  */
  40 
  41 /*
  42  * Please note that all dialogs used here must be safe for background
  43  * operations.
  44  */
  45 
  46 /** \file src/filemanager/file.c
  47  *  \brief Source: file management
  48  */
  49 
  50 /* {{{ Include files */
  51 
  52 #include <config.h>
  53 
  54 #include <ctype.h>
  55 #include <errno.h>
  56 #include <stdlib.h>
  57 #include <stdio.h>
  58 #include <string.h>
  59 #include <sys/types.h>
  60 #include <sys/stat.h>
  61 #include <unistd.h>
  62 
  63 #include "lib/global.h"
  64 #include "lib/tty/tty.h"
  65 #include "lib/tty/key.h"
  66 #include "lib/search.h"
  67 #include "lib/strescape.h"
  68 #include "lib/strutil.h"
  69 #include "lib/util.h"
  70 #include "lib/vfs/vfs.h"
  71 #include "lib/widget.h"
  72 
  73 #include "src/setup.h"
  74 #ifdef ENABLE_BACKGROUND
  75 #include "src/background.h"     /* do_background() */
  76 #endif
  77 
  78 /* Needed for current_panel, other_panel and WTree */
  79 #include "dir.h"
  80 #include "filegui.h"
  81 #include "filenot.h"
  82 #include "tree.h"
  83 #include "midnight.h"           /* current_panel */
  84 #include "layout.h"             /* rotate_dash() */
  85 #include "ioblksize.h"          /* io_blksize() */
  86 
  87 #include "file.h"
  88 
  89 /* }}} */
  90 
  91 /*** global variables ****************************************************************************/
  92 
  93 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix  */
  94 const char *op_names[3] = {
  95     N_("DialogTitle|Copy"),
  96     N_("DialogTitle|Move"),
  97     N_("DialogTitle|Delete")
  98 };
  99 
 100 /*** file scope macro definitions ****************************************************************/
 101 
 102 #define FILEOP_UPDATE_INTERVAL 2
 103 #define FILEOP_STALLING_INTERVAL 4
 104 
 105 /*** file scope type declarations ****************************************************************/
 106 
 107 /* This is a hard link cache */
 108 struct link
 109 {
 110     const struct vfs_class *vfs;
 111     dev_t dev;
 112     ino_t ino;
 113     short linkcount;
 114     mode_t st_mode;
 115     vfs_path_t *src_vpath;
 116     vfs_path_t *dst_vpath;
 117 };
 118 
 119 /* Status of the destination file */
 120 typedef enum
 121 {
 122     DEST_NONE = 0,              /* Not created */
 123     DEST_SHORT = 1,             /* Created, not fully copied */
 124     DEST_FULL = 2               /* Created, fully copied */
 125 } dest_status_t;
 126 
 127 /* Status of hard link creation */
 128 typedef enum
 129 {
 130     HARDLINK_OK = 0,            /**< Hardlink was created successfully */
 131     HARDLINK_CACHED,            /**< Hardlink was added to the cache */
 132     HARDLINK_NOTLINK,           /**< This is not a hard link */
 133     HARDLINK_UNSUPPORTED,       /**< VFS doesn't support hard links */
 134     HARDLINK_ERROR,             /**< Hard link creation error */
 135     HARDLINK_ABORT              /**< Stop file operation after hardlink creation error */
 136 } hardlink_status_t;
 137 
 138 /*
 139  * This array introduced to avoid translation problems. The former (op_names)
 140  * is assumed to be nouns, suitable in dialog box titles; this one should
 141  * contain whatever is used in prompt itself (i.e. in russian, it's verb).
 142  * (I don't use spaces around the words, because someday they could be
 143  * dropped, when widgets get smarter)
 144  */
 145 
 146 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix  */
 147 static const char *op_names1[] = {
 148     N_("FileOperation|Copy"),
 149     N_("FileOperation|Move"),
 150     N_("FileOperation|Delete")
 151 };
 152 
 153 /*
 154  * These are formats for building a prompt. Parts encoded as follows:
 155  * %o - operation from op_names1
 156  * %f - file/files or files/directories, as appropriate
 157  * %m - "with source mask" or question mark for delete
 158  * %s - source name (truncated)
 159  * %d - number of marked files
 160  * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
 161  */
 162 /* xgettext:no-c-format */
 163 static const char *one_format = N_("%o %f%n\"%s\"%m");
 164 /* xgettext:no-c-format */
 165 static const char *many_format = N_("%o %d %f%m");
 166 
 167 static const char *prompt_parts[] = {
 168     N_("file"),
 169     N_("files"),
 170     N_("directory"),
 171     N_("directories"),
 172     N_("files/directories"),
 173     /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
 174     N_(" with source mask:")
 175 };
 176 
 177 /*** file scope variables ************************************************************************/
 178 
 179 /* the hard link cache */
 180 static GSList *linklist = NULL;
 181 
 182 /* the files-to-be-erased list */
 183 static GSList *erase_list = NULL;
 184 
 185 /*
 186  * In copy_dir_dir we use two additional single linked lists: The first -
 187  * variable name 'parent_dirs' - holds information about already copied
 188  * directories and is used to detect cyclic symbolic links.
 189  * The second ('dest_dirs' below) holds information about just created
 190  * target directories and is used to detect when an directory is copied
 191  * into itself (we don't want to copy infinitly).
 192  * Both lists don't use the linkcount and name structure members of struct
 193  * link.
 194  */
 195 static GSList *dest_dirs = NULL;
 196 
 197 /* --------------------------------------------------------------------------------------------- */
 198 /*** file scope functions ************************************************************************/
 199 /* --------------------------------------------------------------------------------------------- */
 200 
 201 static void
 202 dirsize_status_locate_buttons (dirsize_status_msg_t * dsm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 203 {
 204     status_msg_t *sm = STATUS_MSG (dsm);
 205     Widget *wd = WIDGET (sm->dlg);
 206     int y, x;
 207 
 208     y = wd->y + 5;
 209     x = wd->x;
 210 
 211     if (!dsm->allow_skip)
 212     {
 213         /* single button: "Abort" */
 214         x += (wd->cols - dsm->abort_button->cols) / 2;
 215         widget_set_size (dsm->abort_button, y, x,
 216                          dsm->abort_button->lines, dsm->abort_button->cols);
 217     }
 218     else
 219     {
 220         /* two buttons: "Abort" and "Skip" */
 221         int cols;
 222 
 223         cols = dsm->abort_button->cols + dsm->skip_button->cols + 1;
 224         x += (wd->cols - cols) / 2;
 225         widget_set_size (dsm->abort_button, y, x, dsm->abort_button->lines,
 226                          dsm->abort_button->cols);
 227         x += dsm->abort_button->cols + 1;
 228         widget_set_size (dsm->skip_button, y, x, dsm->skip_button->lines, dsm->skip_button->cols);
 229     }
 230 }
 231 
 232 /* --------------------------------------------------------------------------------------------- */
 233 
 234 static char *
 235 build_dest (file_op_context_t * ctx, const char *src, const char *dest, FileProgressStatus * status)
     /* [previous][next][first][last][top][bottom][index][help]  */
 236 {
 237     char *s, *q;
 238     const char *fnsource;
 239 
 240     *status = FILE_CONT;
 241 
 242     s = g_strdup (src);
 243 
 244     /* We remove \n from the filename since regex routines would use \n as an anchor */
 245     /* this is just to be allowed to maniupulate file names with \n on it */
 246     for (q = s; *q != '\0'; q++)
 247         if (*q == '\n')
 248             *q = ' ';
 249 
 250     fnsource = x_basename (s);
 251 
 252     if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
 253     {
 254         q = NULL;
 255         *status = FILE_SKIP;
 256     }
 257     else
 258     {
 259         q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
 260         if (ctx->search_handle->error != MC_SEARCH_E_OK)
 261         {
 262             if (ctx->search_handle->error_str != NULL)
 263                 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
 264 
 265             *status = FILE_ABORT;
 266         }
 267     }
 268 
 269     MC_PTR_FREE (s);
 270 
 271     if (*status == FILE_CONT)
 272     {
 273         char *repl_dest;
 274 
 275         repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
 276         if (ctx->search_handle->error == MC_SEARCH_E_OK)
 277             s = mc_build_filename (repl_dest, q, (char *) NULL);
 278         else
 279         {
 280             if (ctx->search_handle->error_str != NULL)
 281                 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
 282 
 283             *status = FILE_ABORT;
 284         }
 285 
 286         g_free (repl_dest);
 287     }
 288 
 289     g_free (q);
 290 
 291     return s;
 292 }
 293 
 294 /* --------------------------------------------------------------------------------------------- */
 295 
 296 static void
 297 free_link (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 298 {
 299     struct link *lp = (struct link *) data;
 300 
 301     vfs_path_free (lp->src_vpath);
 302     vfs_path_free (lp->dst_vpath);
 303     g_free (lp);
 304 }
 305 
 306 /* --------------------------------------------------------------------------------------------- */
 307 
 308 static inline void *
 309 free_linklist (GSList * lp)
     /* [previous][next][first][last][top][bottom][index][help]  */
 310 {
 311     g_slist_free_full (lp, free_link);
 312 
 313     return NULL;
 314 }
 315 
 316 /* --------------------------------------------------------------------------------------------- */
 317 
 318 static const struct link *
 319 is_in_linklist (const GSList * lp, const vfs_path_t * vpath, const struct stat *sb)
     /* [previous][next][first][last][top][bottom][index][help]  */
 320 {
 321     const struct vfs_class *class;
 322     ino_t ino = sb->st_ino;
 323     dev_t dev = sb->st_dev;
 324 
 325     class = vfs_path_get_last_path_vfs (vpath);
 326 
 327     for (; lp != NULL; lp = (const GSList *) g_slist_next (lp))
 328     {
 329         const struct link *lnk = (const struct link *) lp->data;
 330 
 331         if (lnk->vfs == class && lnk->ino == ino && lnk->dev == dev)
 332             return lnk;
 333     }
 334 
 335     return NULL;
 336 }
 337 
 338 /* --------------------------------------------------------------------------------------------- */
 339 /**
 340  * Check and made hardlink
 341  *
 342  * @return FALSE if the inode wasn't found in the cache and TRUE if it was found
 343  * and a hardlink was successfully made
 344  */
 345 
 346 static hardlink_status_t
 347 check_hardlinks (const vfs_path_t * src_vpath, const struct stat *src_stat,
     /* [previous][next][first][last][top][bottom][index][help]  */
 348                  const vfs_path_t * dst_vpath, gboolean * skip_all)
 349 {
 350     struct link *lnk;
 351     ino_t ino = src_stat->st_ino;
 352     dev_t dev = src_stat->st_dev;
 353 
 354     if (src_stat->st_nlink < 2)
 355         return HARDLINK_NOTLINK;
 356     if ((vfs_file_class_flags (src_vpath) & VFSF_NOLINKS) != 0)
 357         return HARDLINK_UNSUPPORTED;
 358 
 359     lnk = (struct link *) is_in_linklist (linklist, src_vpath, src_stat);
 360     if (lnk != NULL)
 361     {
 362         int stat_result;
 363         struct stat link_stat;
 364 
 365         stat_result = mc_stat (lnk->src_vpath, &link_stat);
 366 
 367         if (stat_result == 0 && link_stat.st_ino == ino && link_stat.st_dev == dev)
 368         {
 369             const struct vfs_class *lp_name_class;
 370             const struct vfs_class *my_vfs;
 371 
 372             lp_name_class = vfs_path_get_last_path_vfs (lnk->src_vpath);
 373             my_vfs = vfs_path_get_last_path_vfs (src_vpath);
 374 
 375             if (lp_name_class == my_vfs)
 376             {
 377                 const struct vfs_class *p_class, *dst_name_class;
 378 
 379                 dst_name_class = vfs_path_get_last_path_vfs (dst_vpath);
 380                 p_class = vfs_path_get_last_path_vfs (lnk->dst_vpath);
 381 
 382                 if (dst_name_class == p_class)
 383                 {
 384                     gboolean ok;
 385 
 386                     while (!(ok = (mc_stat (lnk->dst_vpath, &link_stat) == 0)) && !*skip_all)
 387                     {
 388                         FileProgressStatus status;
 389 
 390                         status =
 391                             file_error (TRUE, _("Cannot stat hardlink source file \"%s\"\n%s"),
 392                                         vfs_path_as_str (lnk->dst_vpath));
 393                         if (status == FILE_ABORT)
 394                             return HARDLINK_ABORT;
 395                         if (status == FILE_RETRY)
 396                             continue;
 397                         if (status == FILE_SKIPALL)
 398                             *skip_all = TRUE;
 399                         break;
 400                     }
 401 
 402                     /* if stat() finished unsuccessfully, don't try to create link */
 403                     if (!ok)
 404                         return HARDLINK_ERROR;
 405 
 406                     while (!(ok = (mc_link (lnk->dst_vpath, dst_vpath) == 0)) && !*skip_all)
 407                     {
 408                         FileProgressStatus status;
 409 
 410                         status =
 411                             file_error (TRUE, _("Cannot create target hardlink \"%s\"\n%s"),
 412                                         vfs_path_as_str (dst_vpath));
 413                         if (status == FILE_ABORT)
 414                             return HARDLINK_ABORT;
 415                         if (status == FILE_RETRY)
 416                             continue;
 417                         if (status == FILE_SKIPALL)
 418                             *skip_all = TRUE;
 419                         break;
 420                     }
 421 
 422                     /* Success? */
 423                     return (ok ? HARDLINK_OK : HARDLINK_ERROR);
 424                 }
 425             }
 426         }
 427 
 428         if (!*skip_all)
 429         {
 430             FileProgressStatus status;
 431 
 432             /* Message w/o "Retry" action.
 433              *
 434              * FIXME: Can't say what errno is here. Define it and don't display.
 435              *
 436              * file_error() displays a message with text representation of errno
 437              * and the string passed to file_error() should provide the format "%s"
 438              * for that at end (see previous file_error() call for the reference).
 439              * But if format for errno isn't provided, it is safe, because C standard says:
 440              * "If the format is exhausted while arguments remain, the excess arguments
 441              * are evaluated (as always) but are otherwise ignored" (ISO/IEC 9899:1999,
 442              * section 7.19.6.1, paragraph 2).
 443              *
 444              */
 445             errno = 0;
 446             status =
 447                 file_error (FALSE, _("Cannot create target hardlink \"%s\""),
 448                             vfs_path_as_str (dst_vpath));
 449 
 450             if (status == FILE_ABORT)
 451                 return HARDLINK_ABORT;
 452 
 453             if (status == FILE_SKIPALL)
 454                 *skip_all = TRUE;
 455         }
 456 
 457         return HARDLINK_ERROR;
 458     }
 459 
 460     lnk = g_try_new (struct link, 1);
 461     if (lnk != NULL)
 462     {
 463         lnk->vfs = vfs_path_get_last_path_vfs (src_vpath);
 464         lnk->ino = ino;
 465         lnk->dev = dev;
 466         lnk->linkcount = 0;
 467         lnk->st_mode = 0;
 468         lnk->src_vpath = vfs_path_clone (src_vpath);
 469         lnk->dst_vpath = vfs_path_clone (dst_vpath);
 470 
 471         linklist = g_slist_prepend (linklist, lnk);
 472     }
 473 
 474     return HARDLINK_CACHED;
 475 }
 476 
 477 /* --------------------------------------------------------------------------------------------- */
 478 /**
 479  * Duplicate the contents of the symbolic link src_path in dst_path.
 480  * Try to make a stable symlink if the option "stable symlink" was
 481  * set in the file mask dialog.
 482  * If dst_path is an existing symlink it will be deleted silently
 483  * (upper levels take already care of existing files at dst_path).
 484  */
 485 
 486 static FileProgressStatus
 487 make_symlink (file_op_context_t * ctx, const char *src_path, const char *dst_path)
     /* [previous][next][first][last][top][bottom][index][help]  */
 488 {
 489     char link_target[MC_MAXPATHLEN];
 490     int len;
 491     FileProgressStatus return_status;
 492     struct stat dst_stat;
 493     vfs_path_t *src_vpath;
 494     vfs_path_t *dst_vpath;
 495     gboolean dst_is_symlink;
 496     vfs_path_t *link_target_vpath = NULL;
 497 
 498     src_vpath = vfs_path_from_str (src_path);
 499     dst_vpath = vfs_path_from_str (dst_path);
 500     dst_is_symlink = (mc_lstat (dst_vpath, &dst_stat) == 0) && S_ISLNK (dst_stat.st_mode);
 501 
 502   retry_src_readlink:
 503     len = mc_readlink (src_vpath, link_target, sizeof (link_target) - 1);
 504     if (len < 0)
 505     {
 506         if (ctx->skip_all)
 507             return_status = FILE_SKIPALL;
 508         else
 509         {
 510             return_status = file_error (TRUE, _("Cannot read source link \"%s\"\n%s"), src_path);
 511             if (return_status == FILE_SKIPALL)
 512                 ctx->skip_all = TRUE;
 513             if (return_status == FILE_RETRY)
 514                 goto retry_src_readlink;
 515         }
 516         goto ret;
 517     }
 518 
 519     link_target[len] = '\0';
 520 
 521     if (ctx->stable_symlinks && !(vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath)))
 522     {
 523         message (D_ERROR, MSG_ERROR,
 524                  _("Cannot make stable symlinks across "
 525                    "non-local filesystems:\n\nOption Stable Symlinks will be disabled"));
 526         ctx->stable_symlinks = FALSE;
 527     }
 528 
 529     if (ctx->stable_symlinks && !g_path_is_absolute (link_target))
 530     {
 531         const char *r;
 532 
 533         r = strrchr (src_path, PATH_SEP);
 534         if (r != NULL)
 535         {
 536             char *p;
 537             vfs_path_t *q;
 538 
 539             p = g_strndup (src_path, r - src_path + 1);
 540             if (g_path_is_absolute (dst_path))
 541                 q = vfs_path_from_str_flags (dst_path, VPF_NO_CANON);
 542             else
 543                 q = vfs_path_build_filename (p, dst_path, (char *) NULL);
 544 
 545             if (vfs_path_tokens_count (q) > 1)
 546             {
 547                 char *s;
 548                 vfs_path_t *tmp_vpath1, *tmp_vpath2;
 549 
 550                 tmp_vpath1 = vfs_path_vtokens_get (q, -1, 1);
 551                 s = g_strconcat (p, link_target, (char *) NULL);
 552                 g_strlcpy (link_target, s, sizeof (link_target));
 553                 g_free (s);
 554                 tmp_vpath2 = vfs_path_from_str (link_target);
 555                 s = diff_two_paths (tmp_vpath1, tmp_vpath2);
 556                 vfs_path_free (tmp_vpath1);
 557                 vfs_path_free (tmp_vpath2);
 558                 if (s != NULL)
 559                 {
 560                     g_strlcpy (link_target, s, sizeof (link_target));
 561                     g_free (s);
 562                 }
 563             }
 564             g_free (p);
 565             vfs_path_free (q);
 566         }
 567     }
 568     link_target_vpath = vfs_path_from_str_flags (link_target, VPF_NO_CANON);
 569 
 570   retry_dst_symlink:
 571     if (mc_symlink (link_target_vpath, dst_vpath) == 0)
 572     {
 573         /* Success */
 574         return_status = FILE_CONT;
 575         goto ret;
 576     }
 577     /*
 578      * if dst_exists, it is obvious that this had failed.
 579      * We can delete the old symlink and try again...
 580      */
 581     if (dst_is_symlink && mc_unlink (dst_vpath) == 0
 582         && mc_symlink (link_target_vpath, dst_vpath) == 0)
 583     {
 584         /* Success */
 585         return_status = FILE_CONT;
 586         goto ret;
 587     }
 588 
 589     if (ctx->skip_all)
 590         return_status = FILE_SKIPALL;
 591     else
 592     {
 593         return_status = file_error (TRUE, _("Cannot create target symlink \"%s\"\n%s"), dst_path);
 594         if (return_status == FILE_SKIPALL)
 595             ctx->skip_all = TRUE;
 596         if (return_status == FILE_RETRY)
 597             goto retry_dst_symlink;
 598     }
 599 
 600   ret:
 601     vfs_path_free (src_vpath);
 602     vfs_path_free (dst_vpath);
 603     vfs_path_free (link_target_vpath);
 604     return return_status;
 605 }
 606 
 607 /* --------------------------------------------------------------------------------------------- */
 608 /**
 609  * do_compute_dir_size:
 610  *
 611  * Computes the number of bytes used by the files in a directory
 612  */
 613 
 614 static FileProgressStatus
 615 do_compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * dsm,
     /* [previous][next][first][last][top][bottom][index][help]  */
 616                      size_t * dir_count, size_t * ret_marked, uintmax_t * ret_total,
 617                      mc_stat_fn stat_func)
 618 {
 619     static guint64 timestamp = 0;
 620     /* update with 25 FPS rate */
 621     static const guint64 delay = G_USEC_PER_SEC / 25;
 622 
 623     status_msg_t *sm = STATUS_MSG (dsm);
 624     int res;
 625     struct stat s;
 626     DIR *dir;
 627     struct dirent *dirent;
 628     FileProgressStatus ret = FILE_CONT;
 629 
 630     (*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 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 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)
2389     {
2390         if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
2391         {
2392             message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
2393             ctx->do_reget = 0;
2394             ctx->do_append = FALSE;
2395         }
2396     }
2397 
2398     while (mc_fstat (src_desc, &src_stat) != 0)
2399     {
2400         if (ctx->skip_all)
2401             return_status = FILE_SKIPALL;
2402         else
2403         {
2404             return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
2405             if (return_status == FILE_RETRY)
2406                 continue;
2407             if (return_status == FILE_SKIPALL)
2408                 ctx->skip_all = TRUE;
2409             ctx->do_append = FALSE;
2410         }
2411         goto ret;
2412     }
2413 
2414     src_mode = src_stat.st_mode;
2415     src_uid = src_stat.st_uid;
2416     src_gid = src_stat.st_gid;
2417     get_times (&src_stat, &times);
2418     file_size = src_stat.st_size;
2419 
2420     open_flags = O_WRONLY;
2421     if (dst_exists)
2422     {
2423         if (ctx->do_append)
2424             open_flags |= O_APPEND;
2425         else
2426             open_flags |= O_CREAT | O_TRUNC;
2427     }
2428     else
2429     {
2430         open_flags |= O_CREAT | O_EXCL;
2431     }
2432 
2433     while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
2434     {
2435         if (errno != EEXIST)
2436         {
2437             if (ctx->skip_all)
2438                 return_status = FILE_SKIPALL;
2439             else
2440             {
2441                 return_status =
2442                     file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
2443                 if (return_status == FILE_RETRY)
2444                     continue;
2445                 if (return_status == FILE_SKIPALL)
2446                     ctx->skip_all = TRUE;
2447                 ctx->do_append = FALSE;
2448             }
2449         }
2450         goto ret;
2451     }
2452     dst_status = DEST_SHORT;    /* file opened, but not fully copied */
2453 
2454     appending = ctx->do_append;
2455     ctx->do_append = FALSE;
2456 
2457     /* Try clone the file first. */
2458     if (vfs_clone_file (dest_desc, src_desc) == 0)
2459     {
2460         dst_status = DEST_FULL;
2461         return_status = FILE_CONT;
2462         goto ret;
2463     }
2464 
2465     /* Find out the optimal buffer size.  */
2466     while (mc_fstat (dest_desc, &dst_stat) != 0)
2467     {
2468         if (ctx->skip_all)
2469             return_status = FILE_SKIPALL;
2470         else
2471         {
2472             return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
2473             if (return_status == FILE_RETRY)
2474                 continue;
2475             if (return_status == FILE_SKIPALL)
2476                 ctx->skip_all = TRUE;
2477         }
2478         goto ret;
2479     }
2480 
2481     /* try preallocate space; if fail, try copy anyway */
2482     while (mc_global.vfs.preallocate_space &&
2483            vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
2484     {
2485         if (ctx->skip_all)
2486         {
2487             /* cannot allocate, start the file copying anyway */
2488             return_status = FILE_CONT;
2489             break;
2490         }
2491 
2492         return_status =
2493             file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
2494 
2495         if (return_status == FILE_SKIPALL)
2496             ctx->skip_all = TRUE;
2497 
2498         if (ctx->skip_all || return_status == FILE_SKIP)
2499         {
2500             /* skip the space allocation error, start file copying */
2501             return_status = FILE_CONT;
2502             break;
2503         }
2504 
2505         if (return_status == FILE_ABORT)
2506         {
2507             mc_close (dest_desc);
2508             dest_desc = -1;
2509             mc_unlink (dst_vpath);
2510             dst_status = DEST_NONE;
2511             goto ret;
2512         }
2513 
2514         /* return_status == FILE_RETRY -- try allocate space again */
2515     }
2516 
2517     ctx->eta_secs = 0.0;
2518     ctx->bps = 0;
2519 
2520     if (tctx->bps == 0 || (file_size / (tctx->bps)) > FILEOP_UPDATE_INTERVAL)
2521         file_progress_show (ctx, 0, file_size, "", TRUE);
2522     else
2523         file_progress_show (ctx, 1, 1, "", TRUE);
2524     return_status = check_progress_buttons (ctx);
2525     mc_refresh ();
2526 
2527     if (return_status == FILE_CONT)
2528     {
2529         size_t bufsize;
2530         off_t n_read_total = 0;
2531         struct timeval tv_current, tv_last_update, tv_last_input;
2532         int secs, update_secs;
2533         const char *stalled_msg = "";
2534         gboolean is_first_time = TRUE;
2535 
2536         tv_last_update = tv_transfer_start;
2537 
2538         bufsize = io_blksize (dst_stat);
2539         buf = g_malloc (bufsize);
2540 
2541         while (TRUE)
2542         {
2543             ssize_t n_read = -1, n_written;
2544 
2545             /* src_read */
2546             if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
2547                 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all)
2548                 {
2549                     return_status =
2550                         file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
2551                     if (return_status == FILE_RETRY)
2552                         continue;
2553                     if (return_status == FILE_SKIPALL)
2554                         ctx->skip_all = TRUE;
2555                     goto ret;
2556                 }
2557 
2558             if (n_read == 0)
2559                 break;
2560 
2561             gettimeofday (&tv_current, NULL);
2562 
2563             if (n_read > 0)
2564             {
2565                 char *t = buf;
2566 
2567                 n_read_total += n_read;
2568 
2569                 /* Windows NT ftp servers report that files have no
2570                  * permissions: -------, so if we happen to have actually
2571                  * read something, we should fix the permissions.
2572                  */
2573                 if ((src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == 0)
2574                     src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
2575                 gettimeofday (&tv_last_input, NULL);
2576 
2577                 /* dst_write */
2578                 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
2579                 {
2580                     gboolean write_errno_nospace;
2581 
2582                     if (n_written > 0)
2583                     {
2584                         n_read -= n_written;
2585                         t += n_written;
2586                         continue;
2587                     }
2588 
2589                     write_errno_nospace = (n_written < 0 && errno == ENOSPC);
2590 
2591                     if (ctx->skip_all)
2592                         return_status = FILE_SKIPALL;
2593                     else
2594                         return_status =
2595                             file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
2596 
2597                     if (return_status == FILE_SKIP)
2598                     {
2599                         if (write_errno_nospace)
2600                             goto ret;
2601                         break;
2602                     }
2603                     if (return_status == FILE_SKIPALL)
2604                     {
2605                         ctx->skip_all = TRUE;
2606                         if (write_errno_nospace)
2607                             goto ret;
2608                     }
2609                     if (return_status != FILE_RETRY)
2610                         goto ret;
2611                 }
2612             }
2613 
2614             tctx->copied_bytes = tctx->progress_bytes + n_read_total + ctx->do_reget;
2615 
2616             secs = (tv_current.tv_sec - tv_last_update.tv_sec);
2617             update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
2618 
2619             if (is_first_time || secs > FILEOP_UPDATE_INTERVAL)
2620             {
2621                 copy_file_file_display_progress (tctx, ctx,
2622                                                  tv_current,
2623                                                  tv_transfer_start, file_size, n_read_total);
2624                 tv_last_update = tv_current;
2625             }
2626             is_first_time = FALSE;
2627 
2628             if (update_secs > FILEOP_STALLING_INTERVAL)
2629             {
2630                 stalled_msg = _("(stalled)");
2631             }
2632 
2633             {
2634                 gboolean force_update;
2635 
2636                 force_update =
2637                     (tv_current.tv_sec - tctx->transfer_start.tv_sec) > FILEOP_UPDATE_INTERVAL;
2638 
2639                 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
2640                 {
2641                     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2642                     file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
2643                 }
2644 
2645                 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size, stalled_msg,
2646                                     force_update);
2647             }
2648             mc_refresh ();
2649 
2650             return_status = check_progress_buttons (ctx);
2651 
2652             if (return_status != FILE_CONT)
2653             {
2654                 mc_refresh ();
2655                 goto ret;
2656             }
2657         }
2658 
2659         dst_status = DEST_FULL; /* copy successful, don't remove target file */
2660     }
2661 
2662   ret:
2663     g_free (buf);
2664 
2665     rotate_dash (FALSE);
2666     while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all)
2667     {
2668         temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
2669         if (temp_status == FILE_RETRY)
2670             continue;
2671         if (temp_status == FILE_ABORT)
2672             return_status = temp_status;
2673         if (temp_status == FILE_SKIPALL)
2674             ctx->skip_all = TRUE;
2675         break;
2676     }
2677 
2678     while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all)
2679     {
2680         temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
2681         if (temp_status == FILE_RETRY)
2682             continue;
2683         if (temp_status == FILE_SKIPALL)
2684             ctx->skip_all = TRUE;
2685         return_status = temp_status;
2686         break;
2687     }
2688 
2689     if (dst_status == DEST_SHORT)
2690     {
2691         /* Query to remove short file */
2692         if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved. Keep it?"),
2693                           D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
2694             mc_unlink (dst_vpath);
2695     }
2696     else if (dst_status == DEST_FULL)
2697     {
2698         /* Copy has succeeded */
2699         if (!appending && ctx->preserve_uidgid)
2700         {
2701             while (mc_chown (dst_vpath, src_uid, src_gid) != 0 && !ctx->skip_all)
2702             {
2703                 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2704                 if (temp_status == FILE_RETRY)
2705                     continue;
2706                 if (temp_status == FILE_SKIPALL)
2707                 {
2708                     ctx->skip_all = TRUE;
2709                     return_status = FILE_CONT;
2710                 }
2711                 if (temp_status == FILE_SKIP)
2712                     return_status = FILE_CONT;
2713                 break;
2714             }
2715         }
2716 
2717         if (!appending)
2718         {
2719             if (ctx->preserve)
2720             {
2721                 while (mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0 && !ctx->skip_all)
2722                 {
2723                     temp_status =
2724                         file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2725                     if (temp_status == FILE_RETRY)
2726                         continue;
2727                     if (temp_status == FILE_SKIPALL)
2728                     {
2729                         ctx->skip_all = TRUE;
2730                         return_status = FILE_CONT;
2731                     }
2732                     if (temp_status == FILE_SKIP)
2733                         return_status = FILE_CONT;
2734                     break;
2735                 }
2736             }
2737             else if (!dst_exists)
2738             {
2739                 src_mode = umask (-1);
2740                 umask (src_mode);
2741                 src_mode = 0100666 & ~src_mode;
2742                 mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2743             }
2744             mc_utime (dst_vpath, &times);
2745         }
2746     }
2747 
2748     if (return_status == FILE_CONT)
2749         return_status = progress_update_one (tctx, ctx, file_size);
2750 
2751   ret_fast:
2752     vfs_path_free (src_vpath);
2753     vfs_path_free (dst_vpath);
2754     return return_status;
2755 }
2756 
2757 /* --------------------------------------------------------------------------------------------- */
2758 /**
2759  * I think these copy_*_* functions should have a return type.
2760  * anyway, this function *must* have two directories as arguments.
2761  */
2762 /* FIXME: This function needs to check the return values of the
2763    function calls */
2764 
2765 FileProgressStatus
2766 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]  */
2767               gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs)
2768 {
2769     struct dirent *next;
2770     struct stat dst_stat, src_stat;
2771     DIR *reading;
2772     FileProgressStatus return_status = FILE_CONT;
2773     struct link *lp;
2774     vfs_path_t *src_vpath, *dst_vpath;
2775     gboolean do_mkdir = TRUE;
2776 
2777     src_vpath = vfs_path_from_str (s);
2778     dst_vpath = vfs_path_from_str (d);
2779 
2780     /* First get the mode of the source dir */
2781 
2782   retry_src_stat:
2783     if ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2784     {
2785         if (ctx->skip_all)
2786             return_status = FILE_SKIPALL;
2787         else
2788         {
2789             return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
2790             if (return_status == FILE_RETRY)
2791                 goto retry_src_stat;
2792             if (return_status == FILE_SKIPALL)
2793                 ctx->skip_all = TRUE;
2794         }
2795         goto ret_fast;
2796     }
2797 
2798     if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
2799     {
2800         /* Don't copy a directory we created before (we don't want to copy 
2801            infinitely if a directory is copied into itself) */
2802         /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
2803         return_status = FILE_CONT;
2804         goto ret_fast;
2805     }
2806 
2807     /* Hmm, hardlink to directory??? - Norbert */
2808     /* FIXME: In this step we should do something in case the destination already exist */
2809     /* Check the hardlinks */
2810     if (ctx->preserve)
2811     {
2812         switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
2813         {
2814         case HARDLINK_OK:
2815             /* We have made a hardlink - no more processing is necessary */
2816             goto ret_fast;
2817 
2818         case HARDLINK_ABORT:
2819             return_status = FILE_ABORT;
2820             goto ret_fast;
2821 
2822         default:
2823             break;
2824         }
2825     }
2826 
2827     if (!S_ISDIR (src_stat.st_mode))
2828     {
2829         if (ctx->skip_all)
2830             return_status = FILE_SKIPALL;
2831         else
2832         {
2833             return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
2834             if (return_status == FILE_RETRY)
2835                 goto retry_src_stat;
2836             if (return_status == FILE_SKIPALL)
2837                 ctx->skip_all = TRUE;
2838         }
2839         goto ret_fast;
2840     }
2841 
2842     if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
2843     {
2844         /* we found a cyclic symbolic link */
2845         message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
2846         return_status = FILE_SKIP;
2847         goto ret_fast;
2848     }
2849 
2850     lp = g_new0 (struct link, 1);
2851     lp->vfs = vfs_path_get_by_index (src_vpath, -1)->class;
2852     lp->ino = src_stat.st_ino;
2853     lp->dev = src_stat.st_dev;
2854     parent_dirs = g_slist_prepend (parent_dirs, lp);
2855 
2856   retry_dst_stat:
2857     /* Now, check if the dest dir exists, if not, create it. */
2858     if (mc_stat (dst_vpath, &dst_stat) != 0)
2859     {
2860         /* Here the dir doesn't exist : make it ! */
2861         if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
2862         {
2863             return_status = FILE_CONT;
2864             goto ret;
2865         }
2866     }
2867     else
2868     {
2869         /*
2870          * If the destination directory exists, we want to copy the whole
2871          * directory, but we only want this to happen once.
2872          *
2873          * Escape sequences added to the * to compiler warnings.
2874          * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
2875          * or ( /bla doesn't exist )       /tmp/\* to /bla     ->  /bla/\*
2876          */
2877         if (!S_ISDIR (dst_stat.st_mode))
2878         {
2879             if (ctx->skip_all)
2880                 return_status = FILE_SKIPALL;
2881             else
2882             {
2883                 return_status =
2884                     file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
2885                 if (return_status == FILE_SKIPALL)
2886                     ctx->skip_all = TRUE;
2887                 if (return_status == FILE_RETRY)
2888                     goto retry_dst_stat;
2889             }
2890             goto ret;
2891         }
2892         /* Dive into subdir if exists */
2893         if (toplevel && ctx->dive_into_subdirs)
2894         {
2895             vfs_path_t *tmp;
2896 
2897             tmp = dst_vpath;
2898             dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
2899             vfs_path_free (tmp);
2900 
2901         }
2902         else
2903             do_mkdir = FALSE;
2904     }
2905 
2906     d = vfs_path_as_str (dst_vpath);
2907 
2908     if (do_mkdir)
2909     {
2910         while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
2911         {
2912             if (ctx->skip_all)
2913                 return_status = FILE_SKIPALL;
2914             else
2915             {
2916                 return_status =
2917                     file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
2918                 if (return_status == FILE_SKIPALL)
2919                     ctx->skip_all = TRUE;
2920             }
2921             if (return_status != FILE_RETRY)
2922                 goto ret;
2923         }
2924 
2925         lp = g_new0 (struct link, 1);
2926         mc_stat (dst_vpath, &dst_stat);
2927         lp->vfs = vfs_path_get_by_index (dst_vpath, -1)->class;
2928         lp->ino = dst_stat.st_ino;
2929         lp->dev = dst_stat.st_dev;
2930         dest_dirs = g_slist_prepend (dest_dirs, lp);
2931     }
2932 
2933     if (ctx->preserve_uidgid)
2934     {
2935         while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
2936         {
2937             if (ctx->skip_all)
2938                 return_status = FILE_SKIPALL;
2939             else
2940             {
2941                 return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
2942                 if (return_status == FILE_SKIPALL)
2943                     ctx->skip_all = TRUE;
2944             }
2945             if (return_status != FILE_RETRY)
2946                 goto ret;
2947         }
2948     }
2949 
2950     /* open the source dir for reading */
2951     reading = mc_opendir (src_vpath);
2952     if (reading == NULL)
2953         goto ret;
2954 
2955     while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
2956     {
2957         char *path;
2958         vfs_path_t *tmp_vpath;
2959 
2960         /*
2961          * Now, we don't want '.' and '..' to be created / copied at any time
2962          */
2963         if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
2964             continue;
2965 
2966         /* get the filename and add it to the src directory */
2967         path = mc_build_filename (s, next->d_name, (char *) NULL);
2968         tmp_vpath = vfs_path_from_str (path);
2969 
2970         (*ctx->stat_func) (tmp_vpath, &dst_stat);
2971         if (S_ISDIR (dst_stat.st_mode))
2972         {
2973             char *mdpath;
2974 
2975             mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
2976             /*
2977              * From here, we just intend to recursively copy subdirs, not
2978              * the double functionality of copying different when the target
2979              * dir already exists. So, we give the recursive call the flag 0
2980              * meaning no toplevel.
2981              */
2982             return_status =
2983                 copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
2984             g_free (mdpath);
2985         }
2986         else
2987         {
2988             char *dest_file;
2989 
2990             dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
2991             return_status = copy_file_file (tctx, ctx, path, dest_file);
2992             g_free (dest_file);
2993         }
2994 
2995         g_free (path);
2996 
2997         if (do_delete && return_status == FILE_CONT)
2998         {
2999             if (ctx->erase_at_end)
3000             {
3001                 lp = g_new0 (struct link, 1);
3002                 lp->src_vpath = tmp_vpath;
3003                 lp->st_mode = dst_stat.st_mode;
3004                 erase_list = g_slist_append (erase_list, lp);
3005                 tmp_vpath = NULL;
3006             }
3007             else if (S_ISDIR (dst_stat.st_mode))
3008                 return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
3009             else
3010                 return_status = erase_file (tctx, ctx, tmp_vpath);
3011         }
3012         vfs_path_free (tmp_vpath);
3013     }
3014     mc_closedir (reading);
3015 
3016     if (ctx->preserve)
3017     {
3018         mc_timesbuf_t times;
3019 
3020         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3021         get_times (&src_stat, &times);
3022         mc_utime (dst_vpath, &times);
3023     }
3024     else
3025     {
3026         src_stat.st_mode = umask (-1);
3027         umask (src_stat.st_mode);
3028         src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3029         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3030     }
3031 
3032   ret:
3033     free_link (parent_dirs->data);
3034     g_slist_free_1 (parent_dirs);
3035   ret_fast:
3036     vfs_path_free (src_vpath);
3037     vfs_path_free (dst_vpath);
3038     return return_status;
3039 }
3040 
3041 /* }}} */
3042 
3043 /* --------------------------------------------------------------------------------------------- */
3044 /* {{{ Move routines */
3045 
3046 FileProgressStatus
3047 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]  */
3048 {
3049     return do_move_dir_dir (NULL, tctx, ctx, s, d);
3050 }
3051 
3052 /* }}} */
3053 
3054 /* --------------------------------------------------------------------------------------------- */
3055 /* {{{ Erase routines */
3056 
3057 FileProgressStatus
3058 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]  */
3059 {
3060     file_progress_show_deleting (ctx, vfs_path_as_str (vpath), NULL);
3061     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3062     if (check_progress_buttons (ctx) == FILE_ABORT)
3063         return FILE_ABORT;
3064 
3065     mc_refresh ();
3066 
3067     /* The old way to detect a non empty directory was:
3068        error = my_rmdir (s);
3069        if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3070        For the linux user space nfs server (nfs-server-2.2beta29-2)
3071        we would have to check also for EIO. I hope the new way is
3072        fool proof. (Norbert)
3073      */
3074     if (check_dir_is_empty (vpath) == 0)
3075     {                           /* not empty */
3076         FileProgressStatus error;
3077 
3078         error = query_recursive (ctx, vfs_path_as_str (vpath));
3079         if (error == FILE_CONT)
3080             error = recursive_erase (tctx, ctx, vpath);
3081         return error;
3082     }
3083 
3084     return try_erase_dir (ctx, vfs_path_as_str (vpath));
3085 }
3086 
3087 /* }}} */
3088 
3089 /* --------------------------------------------------------------------------------------------- */
3090 /* {{{ Panel operate routines */
3091 
3092 void
3093 dirsize_status_init_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3094 {
3095     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3096     WGroup *gd = GROUP (sm->dlg);
3097     Widget *wd = WIDGET (sm->dlg);
3098 
3099     const char *b1_name = N_("&Abort");
3100     const char *b2_name = N_("&Skip");
3101     int b_width, ui_width;
3102 
3103 #ifdef ENABLE_NLS
3104     b1_name = _(b1_name);
3105     b2_name = _(b2_name);
3106 #endif
3107 
3108     b_width = str_term_width1 (b1_name) + 4;
3109     if (dsm->allow_skip)
3110         b_width += str_term_width1 (b2_name) + 4 + 1;
3111 
3112     ui_width = MAX (COLS / 2, b_width + 6);
3113     dsm->dirname = label_new (2, 3, "");
3114     group_add_widget (gd, dsm->dirname);
3115     dsm->count_size = label_new (3, 3, "");
3116     group_add_widget (gd, dsm->count_size);
3117     group_add_widget (gd, hline_new (4, -1, -1));
3118 
3119     dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3120     group_add_widget (gd, dsm->abort_button);
3121     if (dsm->allow_skip)
3122     {
3123         dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3124         group_add_widget (gd, dsm->skip_button);
3125         widget_select (dsm->skip_button);
3126     }
3127 
3128     widget_set_size (wd, wd->y, wd->x, 8, ui_width);
3129     dirsize_status_locate_buttons (dsm);
3130 }
3131 
3132 /* --------------------------------------------------------------------------------------------- */
3133 
3134 int
3135 dirsize_status_update_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3136 {
3137     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3138     Widget *wd = WIDGET (sm->dlg);
3139 
3140     /* update second (longer label) */
3141     label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
3142                      dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3143 
3144     /* enlarge dialog if required */
3145     if (WIDGET (dsm->count_size)->cols + 6 > wd->cols)
3146     {
3147         widget_set_size (wd, wd->y, wd->x, wd->lines, WIDGET (dsm->count_size)->cols + 6);
3148         dirsize_status_locate_buttons (dsm);
3149         widget_draw (wd);
3150         /* TODO: ret rid of double redraw */
3151     }
3152 
3153     /* adjust first label */
3154     label_set_text (dsm->dirname, str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->cols - 6));
3155 
3156     switch (status_msg_common_update (sm))
3157     {
3158     case B_CANCEL:
3159     case FILE_ABORT:
3160         return FILE_ABORT;
3161     case FILE_SKIP:
3162         return FILE_SKIP;
3163     default:
3164         return FILE_CONT;
3165     }
3166 }
3167 
3168 /* --------------------------------------------------------------------------------------------- */
3169 
3170 void
3171 dirsize_status_deinit_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3172 {
3173     (void) sm;
3174 
3175     /* schedule to update passive panel */
3176     if (get_other_type () == view_listing)
3177         other_panel->dirty = TRUE;
3178 }
3179 
3180 /* --------------------------------------------------------------------------------------------- */
3181 /**
3182  * compute_dir_size:
3183  *
3184  * Computes the number of bytes used by the files in a directory
3185  */
3186 
3187 FileProgressStatus
3188 compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
     /* [previous][next][first][last][top][bottom][index][help]  */
3189                   size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total,
3190                   gboolean follow_symlinks)
3191 {
3192     return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3193                                 follow_symlinks ? mc_stat : mc_lstat);
3194 }
3195 
3196 /* --------------------------------------------------------------------------------------------- */
3197 /**
3198  * panel_operate:
3199  *
3200  * Performs one of the operations on the selection on the source_panel
3201  * (copy, delete, move).
3202  *
3203  * Returns TRUE if did change the directory
3204  * structure, Returns FALSE if user aborted
3205  *
3206  * force_single forces operation on the current entry and affects
3207  * default destination.  Current filename is used as default.
3208  */
3209 
3210 gboolean
3211 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
     /* [previous][next][first][last][top][bottom][index][help]  */
3212 {
3213     WPanel *panel = PANEL (source_panel);
3214     const gboolean single_entry = force_single || (panel->marked <= 1)
3215         || (get_current_type () == view_tree);
3216 
3217     const char *source = NULL;
3218     char *dest = NULL;
3219     vfs_path_t *dest_vpath = NULL;
3220     char *save_cwd = NULL, *save_dest = NULL;
3221     struct stat src_stat;
3222     gboolean ret_val = TRUE;
3223     int i;
3224     FileProgressStatus value;
3225     file_op_context_t *ctx;
3226     file_op_total_context_t *tctx;
3227     vfs_path_t *tmp_vpath;
3228     filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3229 
3230     gboolean do_bg = FALSE;     /* do background operation? */
3231 
3232     static gboolean i18n_flag = FALSE;
3233     if (!i18n_flag)
3234     {
3235         for (i = G_N_ELEMENTS (op_names); i-- != 0;)
3236             op_names[i] = Q_ (op_names[i]);
3237         i18n_flag = TRUE;
3238     }
3239 
3240     linklist = free_linklist (linklist);
3241     dest_dirs = free_linklist (dest_dirs);
3242 
3243     if (single_entry)
3244     {
3245         source = check_single_entry (panel, force_single, &src_stat);
3246 
3247         if (source == NULL)
3248             return FALSE;
3249     }
3250 
3251     ctx = file_op_context_new (operation);
3252 
3253     /* Show confirmation dialog */
3254     if (operation != OP_DELETE)
3255     {
3256         dest =
3257             do_confirm_copy_move (panel, operation, force_single, source, &src_stat, ctx, &do_bg);
3258 
3259         if (dest == NULL)
3260         {
3261             ret_val = FALSE;
3262             goto ret_fast;
3263         }
3264 
3265         dest_vpath = vfs_path_from_str (dest);
3266     }
3267     else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3268     {
3269         ret_val = FALSE;
3270         goto ret_fast;
3271     }
3272 
3273     tctx = file_op_total_context_new ();
3274     gettimeofday (&tctx->transfer_start, (struct timezone *) NULL);
3275 
3276 #ifdef ENABLE_BACKGROUND
3277     /* Did the user select to do a background operation? */
3278     if (do_bg)
3279     {
3280         int v;
3281 
3282         v = do_background (ctx,
3283                            g_strconcat (op_names[operation], ": ",
3284                                         vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3285         if (v == -1)
3286             message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
3287 
3288         /* If we are the parent */
3289         if (v == 1)
3290         {
3291             mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3292 
3293             mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3294             vfs_path_free (dest_vpath);
3295             g_free (dest);
3296             /*          file_op_context_destroy (ctx); */
3297             return FALSE;
3298         }
3299     }
3300     else
3301 #endif /* ENABLE_BACKGROUND */
3302     {
3303         if (operation == OP_DELETE)
3304             dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3305         else if (single_entry && S_ISDIR (selection (panel)->st.st_mode))
3306             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3307         else if (single_entry || force_single)
3308             dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3309         else
3310             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3311     }
3312 
3313     /* Initialize things */
3314     /* We do not want to trash cache every time file is
3315        created/touched. However, this will make our cache contain
3316        invalid data. */
3317     if ((dest != NULL)
3318         && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3319         save_dest = g_strdup (dest);
3320 
3321     if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3322         && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3323         save_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath));
3324 
3325     /* Now, let's do the job */
3326 
3327     /* This code is only called by the tree and panel code */
3328     if (single_entry)
3329     {
3330         /* We now have ETA in all cases */
3331 
3332         /* One file: FIXME mc_chdir will take user out of any vfs */
3333         if ((operation != OP_COPY) && (get_current_type () == view_tree))
3334         {
3335             vfs_path_t *vpath;
3336             int chdir_retcode;
3337 
3338             vpath = vfs_path_from_str (PATH_SEP_STR);
3339             chdir_retcode = mc_chdir (vpath);
3340             vfs_path_free (vpath);
3341             if (chdir_retcode < 0)
3342             {
3343                 ret_val = FALSE;
3344                 goto clean_up;
3345             }
3346         }
3347 
3348         value =
3349             operate_single_file (panel, operation, tctx, ctx, source, &src_stat, dest, dialog_type);
3350 
3351         if ((value == FILE_CONT) && !force_single)
3352             unmark_files (panel);
3353     }
3354     else
3355     {
3356         /* Many files */
3357 
3358         /* Check destination for copy or move operation */
3359         while (operation != OP_DELETE)
3360         {
3361             int dst_result;
3362             struct stat dst_stat;
3363 
3364             dst_result = mc_stat (dest_vpath, &dst_stat);
3365 
3366             if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3367                 break;
3368 
3369             if (ctx->skip_all
3370                 || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
3371                                dest) != FILE_RETRY)
3372                 goto clean_up;
3373         }
3374 
3375         /* TODO: the good way is required to skip directories scanning in case of rename/move
3376          * of several directories. Since reqular expression can be used for destination,
3377          * some directory movements can be a cross-filesystem and directory scanning is useful
3378          * for those directories only. */
3379 
3380         if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type)
3381             == FILE_CONT)
3382         {
3383             /* Loop for every file, perform the actual copy operation */
3384             for (i = 0; i < panel->dir.len; i++)
3385             {
3386                 const char *source2;
3387 
3388                 if (!panel->dir.list[i].f.marked)
3389                     continue;   /* Skip the unmarked ones */
3390 
3391                 source2 = panel->dir.list[i].fname;
3392                 src_stat = panel->dir.list[i].st;
3393 
3394                 value = operate_one_file (panel, operation, tctx, ctx, source2, &src_stat, dest);
3395 
3396                 if (value == FILE_ABORT)
3397                     break;
3398 
3399                 if (value == FILE_CONT)
3400                     do_file_mark (panel, i, 0);
3401 
3402                 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3403                 {
3404                     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3405                     file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
3406                 }
3407 
3408                 if (operation != OP_DELETE)
3409                     file_progress_show (ctx, 0, 0, "", FALSE);
3410 
3411                 if (check_progress_buttons (ctx) == FILE_ABORT)
3412                     break;
3413 
3414                 mc_refresh ();
3415             }                   /* Loop for every file */
3416         }
3417     }                           /* Many entries */
3418 
3419   clean_up:
3420     /* Clean up */
3421     if (save_cwd != NULL)
3422     {
3423         tmp_vpath = vfs_path_from_str (save_cwd);
3424         mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3425         vfs_path_free (tmp_vpath);
3426         g_free (save_cwd);
3427     }
3428 
3429     if (save_dest != NULL)
3430     {
3431         tmp_vpath = vfs_path_from_str (save_dest);
3432         mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3433         vfs_path_free (tmp_vpath);
3434         g_free (save_dest);
3435     }
3436 
3437     linklist = free_linklist (linklist);
3438     dest_dirs = free_linklist (dest_dirs);
3439     g_free (dest);
3440     vfs_path_free (dest_vpath);
3441     MC_PTR_FREE (ctx->dest_mask);
3442 
3443 #ifdef ENABLE_BACKGROUND
3444     /* Let our parent know we are saying bye bye */
3445     if (mc_global.we_are_background)
3446     {
3447         int cur_pid = getpid ();
3448         /* Send pid to parent with child context, it is fork and
3449            don't modify real parent ctx */
3450         ctx->pid = cur_pid;
3451         parent_call ((void *) end_bg_process, ctx, 0);
3452 
3453         vfs_shut ();
3454         my_exit (EXIT_SUCCESS);
3455     }
3456 #endif /* ENABLE_BACKGROUND */
3457 
3458     file_op_total_context_destroy (tctx);
3459   ret_fast:
3460     file_op_context_destroy (ctx);
3461 
3462     return ret_val;
3463 }
3464 
3465 /* }}} */
3466 
3467 /* --------------------------------------------------------------------------------------------- */
3468 /* {{{ Query/status report routines */
3469 /** Report error with one file */
3470 FileProgressStatus
3471 file_error (gboolean allow_retry, const char *format, const char *file)
     /* [previous][next][first][last][top][bottom][index][help]  */
3472 {
3473     char buf[BUF_MEDIUM];
3474 
3475     g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3476 
3477     return do_file_error (allow_retry, buf);
3478 }
3479 
3480 /* --------------------------------------------------------------------------------------------- */
3481 
3482 /*
3483    Cause emacs to enter folding mode for this file:
3484    Local variables:
3485    end:
3486  */

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