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-2021
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Janne Kukonlehto, 1994, 1995
   9    Fred Leeflang, 1994, 1995
  10    Miguel de Icaza, 1994, 1995, 1996
  11    Jakub Jelinek, 1995, 1996
  12    Norbert Warmuth, 1997
  13    Pavel Machek, 1998
  14    Andrew Borodin <aborodin@vmail.ru>, 2011-2014
  15 
  16    The copy code was based in GNU's cp, and was written by:
  17    Torbjorn Granlund, David MacKenzie, and Jim Meyering.
  18 
  19    The move code was based in GNU's mv, and was written by:
  20    Mike Parker and David MacKenzie.
  21 
  22    Janne Kukonlehto added much error recovery to them for being used
  23    in an interactive program.
  24 
  25    This file is part of the Midnight Commander.
  26 
  27    The Midnight Commander is free software: you can redistribute it
  28    and/or modify it under the terms of the GNU General Public License as
  29    published by the Free Software Foundation, either version 3 of the License,
  30    or (at your option) any later version.
  31 
  32    The Midnight Commander is distributed in the hope that it will be useful,
  33    but WITHOUT ANY WARRANTY; without even the implied warranty of
  34    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  35    GNU General Public License for more details.
  36 
  37    You should have received a copy of the GNU General Public License
  38    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  39  */
  40 
  41 /*
  42  * Please note that all dialogs used here must be safe for background
  43  * operations.
  44  */
  45 
  46 /** \file src/filemanager/file.c
  47  *  \brief Source: file management
  48  */
  49 
  50 /* {{{ Include files */
  51 
  52 #include <config.h>
  53 
  54 #include <ctype.h>
  55 #include <errno.h>
  56 #include <stdlib.h>
  57 #include <stdio.h>
  58 #include <string.h>
  59 #include <sys/types.h>
  60 #include <sys/stat.h>
  61 #include <unistd.h>
  62 
  63 #include "lib/global.h"
  64 #include "lib/tty/tty.h"
  65 #include "lib/tty/key.h"
  66 #include "lib/search.h"
  67 #include "lib/strescape.h"
  68 #include "lib/strutil.h"
  69 #include "lib/util.h"
  70 #include "lib/vfs/vfs.h"
  71 #include "lib/widget.h"
  72 
  73 #include "src/setup.h"
  74 #ifdef ENABLE_BACKGROUND
  75 #include "src/background.h"     /* do_background() */
  76 #endif
  77 
  78 /* Needed for other_panel and WTree */
  79 #include "dir.h"
  80 #include "filegui.h"
  81 #include "filenot.h"
  82 #include "tree.h"
  83 #include "filemanager.h"        /* other_panel */
  84 #include "layout.h"             /* rotate_dash() */
  85 #include "ioblksize.h"          /* io_blksize() */
  86 
  87 #include "file.h"
  88 
  89 /* }}} */
  90 
  91 /*** global variables ****************************************************************************/
  92 
  93 /* TRANSLATORS: no need to translate 'DialogTitle', it's just a context prefix  */
  94 const char *op_names[3] = {
  95     N_("DialogTitle|Copy"),
  96     N_("DialogTitle|Move"),
  97     N_("DialogTitle|Delete")
  98 };
  99 
 100 /*** file scope macro definitions ****************************************************************/
 101 
 102 #define FILEOP_UPDATE_INTERVAL 2
 103 #define FILEOP_STALLING_INTERVAL 4
 104 
 105 /*** file scope type declarations ****************************************************************/
 106 
 107 /* This is a hard link cache */
 108 struct link
 109 {
 110     const struct vfs_class *vfs;
 111     dev_t dev;
 112     ino_t ino;
 113     short linkcount;
 114     mode_t st_mode;
 115     vfs_path_t *src_vpath;
 116     vfs_path_t *dst_vpath;
 117 };
 118 
 119 /* Status of the destination file */
 120 typedef enum
 121 {
 122     DEST_NONE = 0,              /* Not created */
 123     DEST_SHORT = 1,             /* Created, not fully copied */
 124     DEST_FULL = 2               /* Created, fully copied */
 125 } dest_status_t;
 126 
 127 /* Status of hard link creation */
 128 typedef enum
 129 {
 130     HARDLINK_OK = 0,            /**< Hardlink was created successfully */
 131     HARDLINK_CACHED,            /**< Hardlink was added to the cache */
 132     HARDLINK_NOTLINK,           /**< This is not a hard link */
 133     HARDLINK_UNSUPPORTED,       /**< VFS doesn't support hard links */
 134     HARDLINK_ERROR,             /**< Hard link creation error */
 135     HARDLINK_ABORT              /**< Stop file operation after hardlink creation error */
 136 } hardlink_status_t;
 137 
 138 /*
 139  * This array introduced to avoid translation problems. The former (op_names)
 140  * is assumed to be nouns, suitable in dialog box titles; this one should
 141  * contain whatever is used in prompt itself (i.e. in russian, it's verb).
 142  * (I don't use spaces around the words, because someday they could be
 143  * dropped, when widgets get smarter)
 144  */
 145 
 146 /* TRANSLATORS: no need to translate 'FileOperation', it's just a context prefix  */
 147 static const char *op_names1[] = {
 148     N_("FileOperation|Copy"),
 149     N_("FileOperation|Move"),
 150     N_("FileOperation|Delete")
 151 };
 152 
 153 /*
 154  * These are formats for building a prompt. Parts encoded as follows:
 155  * %o - operation from op_names1
 156  * %f - file/files or files/directories, as appropriate
 157  * %m - "with source mask" or question mark for delete
 158  * %s - source name (truncated)
 159  * %d - number of marked files
 160  * %n - the '\n' symbol to form two-line prompt for delete or space for other operations
 161  */
 162 /* xgettext:no-c-format */
 163 static const char *one_format = N_("%o %f%n\"%s\"%m");
 164 /* xgettext:no-c-format */
 165 static const char *many_format = N_("%o %d %f%m");
 166 
 167 static const char *prompt_parts[] = {
 168     N_("file"),
 169     N_("files"),
 170     N_("directory"),
 171     N_("directories"),
 172     N_("files/directories"),
 173     /* TRANSLATORS: keep leading space here to split words in Copy/Move dialog */
 174     N_(" with source mask:")
 175 };
 176 
 177 /*** file scope variables ************************************************************************/
 178 
 179 /* the hard link cache */
 180 static GSList *linklist = NULL;
 181 
 182 /* the files-to-be-erased list */
 183 static GSList *erase_list = NULL;
 184 
 185 /*
 186  * In copy_dir_dir we use two additional single linked lists: The first -
 187  * variable name 'parent_dirs' - holds information about already copied
 188  * directories and is used to detect cyclic symbolic links.
 189  * The second ('dest_dirs' below) holds information about just created
 190  * target directories and is used to detect when an directory is copied
 191  * into itself (we don't want to copy infinitly).
 192  * Both lists don't use the linkcount and name structure members of struct
 193  * link.
 194  */
 195 static GSList *dest_dirs = NULL;
 196 
 197 /* --------------------------------------------------------------------------------------------- */
 198 /*** file scope functions ************************************************************************/
 199 /* --------------------------------------------------------------------------------------------- */
 200 
 201 static void
 202 dirsize_status_locate_buttons (dirsize_status_msg_t * dsm)
     /* [previous][next][first][last][top][bottom][index][help]  */
 203 {
 204     status_msg_t *sm = STATUS_MSG (dsm);
 205     Widget *wd = WIDGET (sm->dlg);
 206     int y, x;
 207 
 208     y = wd->y + 5;
 209     x = wd->x;
 210 
 211     if (!dsm->allow_skip)
 212     {
 213         /* single button: "Abort" */
 214         x += (wd->cols - dsm->abort_button->cols) / 2;
 215         widget_set_size (dsm->abort_button, y, x,
 216                          dsm->abort_button->lines, dsm->abort_button->cols);
 217     }
 218     else
 219     {
 220         /* two buttons: "Abort" and "Skip" */
 221         int cols;
 222 
 223         cols = dsm->abort_button->cols + dsm->skip_button->cols + 1;
 224         x += (wd->cols - cols) / 2;
 225         widget_set_size (dsm->abort_button, y, x, dsm->abort_button->lines,
 226                          dsm->abort_button->cols);
 227         x += dsm->abort_button->cols + 1;
 228         widget_set_size (dsm->skip_button, y, x, dsm->skip_button->lines, dsm->skip_button->cols);
 229     }
 230 }
 231 
 232 /* --------------------------------------------------------------------------------------------- */
 233 
 234 static char *
 235 build_dest (file_op_context_t * ctx, const char *src, const char *dest, FileProgressStatus * status)
     /* [previous][next][first][last][top][bottom][index][help]  */
 236 {
 237     char *s, *q;
 238     const char *fnsource;
 239 
 240     *status = FILE_CONT;
 241 
 242     s = g_strdup (src);
 243 
 244     /* We remove \n from the filename since regex routines would use \n as an anchor */
 245     /* this is just to be allowed to maniupulate file names with \n on it */
 246     for (q = s; *q != '\0'; q++)
 247         if (*q == '\n')
 248             *q = ' ';
 249 
 250     fnsource = x_basename (s);
 251 
 252     if (!mc_search_run (ctx->search_handle, fnsource, 0, strlen (fnsource), NULL))
 253     {
 254         q = NULL;
 255         *status = FILE_SKIP;
 256     }
 257     else
 258     {
 259         q = mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
 260         if (ctx->search_handle->error != MC_SEARCH_E_OK)
 261         {
 262             if (ctx->search_handle->error_str != NULL)
 263                 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
 264 
 265             *status = FILE_ABORT;
 266         }
 267     }
 268 
 269     MC_PTR_FREE (s);
 270 
 271     if (*status == FILE_CONT)
 272     {
 273         char *repl_dest;
 274 
 275         repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
 276         if (ctx->search_handle->error == MC_SEARCH_E_OK)
 277             s = mc_build_filename (repl_dest, q, (char *) NULL);
 278         else
 279         {
 280             if (ctx->search_handle->error_str != NULL)
 281                 message (D_ERROR, MSG_ERROR, "%s", ctx->search_handle->error_str);
 282 
 283             *status = FILE_ABORT;
 284         }
 285 
 286         g_free (repl_dest);
 287     }
 288 
 289     g_free (q);
 290 
 291     return s;
 292 }
 293 
 294 /* --------------------------------------------------------------------------------------------- */
 295 
 296 static void
 297 free_link (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 298 {
 299     struct link *lp = (struct link *) data;
 300 
 301     vfs_path_free (lp->src_vpath, TRUE);
 302     vfs_path_free (lp->dst_vpath, TRUE);
 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, TRUE);
 557                 vfs_path_free (tmp_vpath2, TRUE);
 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, TRUE);
 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, TRUE);
 602     vfs_path_free (dst_vpath, TRUE);
 603     vfs_path_free (link_target_vpath, TRUE);
 604     return return_status;
 605 }
 606 
 607 /* --------------------------------------------------------------------------------------------- */
 608 /**
 609  * do_compute_dir_size:
 610  *
 611  * Computes the number of bytes used by the files in a directory
 612  */
 613 
 614 static FileProgressStatus
 615 do_compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * dsm,
     /* [previous][next][first][last][top][bottom][index][help]  */
 616                      size_t * dir_count, size_t * ret_marked, uintmax_t * ret_total,
 617                      mc_stat_fn stat_func)
 618 {
 619     static gint64 timestamp = 0;
 620     /* update with 25 FPS rate */
 621     static const gint64 delay = G_USEC_PER_SEC / 25;
 622 
 623     status_msg_t *sm = STATUS_MSG (dsm);
 624     int res;
 625     struct stat s;
 626     DIR *dir;
 627     struct vfs_dirent *dirent;
 628     FileProgressStatus ret = FILE_CONT;
 629 
 630     (*dir_count)++;
 631 
 632     dir = mc_opendir (dirname_vpath);
 633     if (dir == NULL)
 634         return ret;
 635 
 636     while (ret == FILE_CONT && (dirent = mc_readdir (dir)) != NULL)
 637     {
 638         vfs_path_t *tmp_vpath;
 639 
 640         if (DIR_IS_DOT (dirent->d_name) || DIR_IS_DOTDOT (dirent->d_name))
 641             continue;
 642 
 643         tmp_vpath = vfs_path_append_new (dirname_vpath, dirent->d_name, (char *) NULL);
 644 
 645         res = stat_func (tmp_vpath, &s);
 646         if (res == 0)
 647         {
 648             if (S_ISDIR (s.st_mode))
 649                 ret =
 650                     do_compute_dir_size (tmp_vpath, dsm, dir_count, ret_marked, ret_total,
 651                                          stat_func);
 652             else
 653             {
 654                 ret = FILE_CONT;
 655 
 656                 (*ret_marked)++;
 657                 *ret_total += (uintmax_t) s.st_size;
 658             }
 659 
 660             if (ret == FILE_CONT && sm->update != NULL && mc_time_elapsed (&timestamp, delay))
 661             {
 662                 dsm->dirname_vpath = tmp_vpath;
 663                 dsm->dir_count = *dir_count;
 664                 dsm->total_size = *ret_total;
 665                 ret = sm->update (sm);
 666             }
 667         }
 668 
 669         vfs_path_free (tmp_vpath, TRUE);
 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->str, (char *) NULL);
 710             status = do_compute_dir_size (p, sm, &dir_count, ret_count, ret_total, stat_func);
 711             vfs_path_free (p, TRUE);
 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             {
1255                 if (ctx->preserve)
1256                 {
1257                     mc_timesbuf_t times;
1258 
1259                     get_times (&src_stat, &times);
1260                     mc_utime (dst_vpath, &times);
1261                 }
1262                 goto retry_src_remove;
1263             }
1264             goto ret;
1265         }
1266 
1267         if (mc_rename (src_vpath, dst_vpath) == 0)
1268         {
1269             /* FIXME: do we really need to update progress in case of single file? */
1270             return_status = progress_update_one (tctx, ctx, src_stat.st_size);
1271             goto ret;
1272         }
1273     }
1274 #if 0
1275     /* Comparison to EXDEV seems not to work in nfs if you're moving from
1276        one nfs to the same, but on the server it is on two different
1277        filesystems. Then nfs returns EIO instead of EXDEV.
1278        Hope it will not hurt if we always in case of error try to copy/delete. */
1279     else
1280         errno = EXDEV;          /* Hack to copy (append) the file and then delete it */
1281 
1282     if (errno != EXDEV)
1283     {
1284         if (ctx->skip_all)
1285             return_status = FILE_SKIPALL;
1286         else
1287         {
1288             return_status = files_error (_("Cannot move file \"%s\" to \"%s\"\n%s"), s, d);
1289             if (return_status == FILE_SKIPALL)
1290                 ctx->skip_all = TRUE;
1291             if (return_status == FILE_RETRY)
1292                 goto retry_rename;
1293         }
1294 
1295         goto ret;
1296     }
1297 #endif
1298 
1299     /* Failed rename -> copy the file instead */
1300     if (panel != NULL)
1301     {
1302         /* In case of single file, calculate totals. In case of many files,
1303            totals are calcuated already. */
1304         return_status =
1305             panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1306                                        FILEGUI_DIALOG_ONE_ITEM);
1307         if (return_status != FILE_CONT)
1308             goto ret;
1309     }
1310 
1311     old_ask_overwrite = tctx->ask_overwrite;
1312     tctx->ask_overwrite = FALSE;
1313     return_status = copy_file_file (tctx, ctx, s, d);
1314     tctx->ask_overwrite = old_ask_overwrite;
1315     if (return_status != FILE_CONT)
1316         goto ret;
1317 
1318     copy_done = TRUE;
1319 
1320     /* FIXME: there is no need to update progress and check buttons
1321        at the finish of single file operation. */
1322     if (panel == NULL)
1323     {
1324         file_progress_show_source (ctx, NULL);
1325         file_progress_show (ctx, 0, 0, "", FALSE);
1326 
1327         return_status = check_progress_buttons (ctx);
1328         if (return_status != FILE_CONT)
1329             goto ret;
1330     }
1331 
1332     mc_refresh ();
1333 
1334   retry_src_remove:
1335     if (!try_remove_file (ctx, src_vpath, &return_status) && panel == NULL)
1336         goto ret;
1337 
1338     if (!copy_done)
1339         return_status = progress_update_one (tctx, ctx, src_stat.st_size);
1340 
1341   ret:
1342     vfs_path_free (src_vpath, TRUE);
1343     vfs_path_free (dst_vpath, TRUE);
1344 
1345     return return_status;
1346 }
1347 
1348 /* }}} */
1349 
1350 /* --------------------------------------------------------------------------------------------- */
1351 /* {{{ Erase routines */
1352 /** Don't update progress status if progress_count==NULL */
1353 
1354 static FileProgressStatus
1355 erase_file (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1356 {
1357     struct stat buf;
1358     FileProgressStatus return_status;
1359 
1360     /* check buttons if deleting info was changed */
1361     if (file_progress_show_deleting (ctx, vfs_path_as_str (vpath), &tctx->progress_count))
1362     {
1363         file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1364         if (check_progress_buttons (ctx) == FILE_ABORT)
1365             return FILE_ABORT;
1366 
1367         mc_refresh ();
1368     }
1369 
1370     if (tctx->progress_count != 0 && mc_lstat (vpath, &buf) != 0)
1371     {
1372         /* ignore, most likely the mc_unlink fails, too */
1373         buf.st_size = 0;
1374     }
1375 
1376     if (!try_remove_file (ctx, vpath, &return_status) && return_status == FILE_ABORT)
1377         return FILE_ABORT;
1378 
1379     if (tctx->progress_count == 0)
1380         return FILE_CONT;
1381 
1382     return check_progress_buttons (ctx);
1383 }
1384 
1385 /* --------------------------------------------------------------------------------------------- */
1386 
1387 static FileProgressStatus
1388 try_erase_dir (file_op_context_t * ctx, const char *dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
1389 {
1390     FileProgressStatus return_status = FILE_CONT;
1391 
1392     while (my_rmdir (dir) != 0 && !ctx->skip_all)
1393     {
1394         return_status = file_error (TRUE, _("Cannot remove directory \"%s\"\n%s"), dir);
1395         if (return_status == FILE_SKIPALL)
1396             ctx->skip_all = TRUE;
1397         if (return_status != FILE_RETRY)
1398             break;
1399     }
1400 
1401     return return_status;
1402 }
1403 
1404 /* --------------------------------------------------------------------------------------------- */
1405 
1406 /**
1407   Recursive remove of files
1408   abort->cancel stack
1409   skip ->warn every level, gets default
1410   skipall->remove as much as possible
1411 */
1412 static FileProgressStatus
1413 recursive_erase (file_op_total_context_t * tctx, file_op_context_t * ctx, const vfs_path_t * vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1414 {
1415     struct vfs_dirent *next;
1416     DIR *reading;
1417     const char *s;
1418     FileProgressStatus return_status = FILE_CONT;
1419 
1420     reading = mc_opendir (vpath);
1421     if (reading == NULL)
1422         return FILE_RETRY;
1423 
1424     while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
1425     {
1426         vfs_path_t *tmp_vpath;
1427         struct stat buf;
1428 
1429         if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
1430             continue;
1431 
1432         tmp_vpath = vfs_path_append_new (vpath, next->d_name, (char *) NULL);
1433         if (mc_lstat (tmp_vpath, &buf) != 0)
1434         {
1435             mc_closedir (reading);
1436             vfs_path_free (tmp_vpath, TRUE);
1437             return FILE_RETRY;
1438         }
1439         if (S_ISDIR (buf.st_mode))
1440             return_status = recursive_erase (tctx, ctx, tmp_vpath);
1441         else
1442             return_status = erase_file (tctx, ctx, tmp_vpath);
1443         vfs_path_free (tmp_vpath, TRUE);
1444     }
1445     mc_closedir (reading);
1446 
1447     if (return_status == FILE_ABORT)
1448         return FILE_ABORT;
1449 
1450     s = vfs_path_as_str (vpath);
1451 
1452     file_progress_show_deleting (ctx, s, NULL);
1453     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
1454     if (check_progress_buttons (ctx) == FILE_ABORT)
1455         return FILE_ABORT;
1456 
1457     mc_refresh ();
1458 
1459     return try_erase_dir (ctx, s);
1460 }
1461 
1462 /* --------------------------------------------------------------------------------------------- */
1463 /** Return -1 on error, 1 if there are no entries besides "." and ".." 
1464    in the directory path points to, 0 else. */
1465 
1466 static int
1467 check_dir_is_empty (const vfs_path_t * vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1468 {
1469     DIR *dir;
1470     struct vfs_dirent *d;
1471     int i = 1;
1472 
1473     dir = mc_opendir (vpath);
1474     if (dir == NULL)
1475         return -1;
1476 
1477     for (d = mc_readdir (dir); d != NULL; d = mc_readdir (dir))
1478         if (!DIR_IS_DOT (d->d_name) && !DIR_IS_DOTDOT (d->d_name))
1479         {
1480             i = 0;
1481             break;
1482         }
1483 
1484     mc_closedir (dir);
1485     return i;
1486 }
1487 
1488 /* --------------------------------------------------------------------------------------------- */
1489 
1490 static FileProgressStatus
1491 erase_dir_iff_empty (file_op_context_t * ctx, const vfs_path_t * vpath, size_t count)
     /* [previous][next][first][last][top][bottom][index][help]  */
1492 {
1493     const char *s;
1494 
1495     s = vfs_path_as_str (vpath);
1496 
1497     file_progress_show_deleting (ctx, s, NULL);
1498     file_progress_show_count (ctx, count, ctx->progress_count);
1499     if (check_progress_buttons (ctx) == FILE_ABORT)
1500         return FILE_ABORT;
1501 
1502     mc_refresh ();
1503 
1504     if (check_dir_is_empty (vpath) != 1)
1505         return FILE_CONT;
1506 
1507     /* not empty or error */
1508     return try_erase_dir (ctx, s);
1509 }
1510 
1511 
1512 /* --------------------------------------------------------------------------------------------- */
1513 
1514 static void
1515 erase_dir_after_copy (file_op_total_context_t * tctx, file_op_context_t * ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
1516                       const vfs_path_t * vpath, FileProgressStatus * status)
1517 {
1518     if (ctx->erase_at_end)
1519     {
1520         /* Reset progress count before delete to avoid counting files twice */
1521         tctx->progress_count = tctx->prev_progress_count;
1522 
1523         while (erase_list != NULL && *status != FILE_ABORT)
1524         {
1525             struct link *lp = (struct link *) erase_list->data;
1526 
1527             if (S_ISDIR (lp->st_mode))
1528                 *status = erase_dir_iff_empty (ctx, lp->src_vpath, tctx->progress_count);
1529             else
1530                 *status = erase_file (tctx, ctx, lp->src_vpath);
1531 
1532             erase_list = g_slist_remove (erase_list, lp);
1533             free_link (lp);
1534         }
1535 
1536         /* Save progress counter before move next directory */
1537         tctx->prev_progress_count = tctx->progress_count;
1538     }
1539 
1540     erase_dir_iff_empty (ctx, vpath, tctx->progress_count);
1541 }
1542 
1543 /* }}} */
1544 
1545 /* --------------------------------------------------------------------------------------------- */
1546 
1547 /**
1548  * Move single directory or one of many directories from one location to another.
1549  *
1550  * @panel pointer to panel in case of single directory, NULL otherwise
1551  * @tctx file operation total context object
1552  * @ctx file operation context object
1553  * @s source directory name
1554  * @d destination directory name
1555  *
1556  * @return operation result
1557  */
1558 static FileProgressStatus
1559 do_move_dir_dir (const WPanel * panel, file_op_total_context_t * tctx, file_op_context_t * ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
1560                  const char *s, const char *d)
1561 {
1562     struct stat src_stat, dst_stat;
1563     FileProgressStatus return_status = FILE_CONT;
1564     gboolean move_over = FALSE;
1565     gboolean dstat_ok;
1566     vfs_path_t *src_vpath, *dst_vpath;
1567     gboolean calc_total = FALSE;
1568 
1569     src_vpath = vfs_path_from_str (s);
1570     dst_vpath = vfs_path_from_str (d);
1571 
1572     file_progress_show_source (ctx, src_vpath);
1573     file_progress_show_target (ctx, dst_vpath);
1574 
1575     /* FIXME: do we really need to check buttons in case of single directory? */
1576     if (panel != NULL && check_progress_buttons (ctx) == FILE_ABORT)
1577     {
1578         return_status = FILE_ABORT;
1579         goto ret_fast;
1580     }
1581 
1582     mc_refresh ();
1583 
1584     mc_stat (src_vpath, &src_stat);
1585 
1586     dstat_ok = (mc_stat (dst_vpath, &dst_stat) == 0);
1587 
1588     if (dstat_ok && check_same_file (s, &src_stat, d, &dst_stat, &return_status))
1589         goto ret_fast;
1590 
1591     if (!dstat_ok)
1592         ;                       /* destination doesn't exist */
1593     else if (!ctx->dive_into_subdirs)
1594         move_over = TRUE;
1595     else
1596     {
1597         vfs_path_t *tmp;
1598 
1599         tmp = dst_vpath;
1600         dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
1601         vfs_path_free (tmp, TRUE);
1602     }
1603 
1604     d = vfs_path_as_str (dst_vpath);
1605 
1606     /* Check if the user inputted an existing dir */
1607   retry_dst_stat:
1608     if (mc_stat (dst_vpath, &dst_stat) == 0)
1609     {
1610         if (move_over)
1611         {
1612             if (panel != NULL)
1613             {
1614                 /* In case of single directory, calculate totals. In case of many directories,
1615                    totals are calcuated already. */
1616                 return_status =
1617                     panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1618                                                FILEGUI_DIALOG_MULTI_ITEM);
1619                 if (return_status != FILE_CONT)
1620                     goto ret;
1621 
1622                 calc_total = TRUE;
1623             }
1624 
1625             return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, TRUE, TRUE, NULL);
1626 
1627             if (return_status != FILE_CONT)
1628                 goto ret;
1629             goto oktoret;
1630         }
1631         else if (ctx->skip_all)
1632             return_status = FILE_SKIPALL;
1633         else
1634         {
1635             if (S_ISDIR (dst_stat.st_mode))
1636                 return_status = file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), d);
1637             else
1638                 return_status = file_error (TRUE, _("Cannot overwrite file \"%s\"\n%s"), d);
1639             if (return_status == FILE_SKIPALL)
1640                 ctx->skip_all = TRUE;
1641             if (return_status == FILE_RETRY)
1642                 goto retry_dst_stat;
1643         }
1644 
1645         goto ret_fast;
1646     }
1647 
1648   retry_rename:
1649     if (mc_rename (src_vpath, dst_vpath) == 0)
1650     {
1651         return_status = FILE_CONT;
1652         goto ret;
1653     }
1654 
1655     if (errno != EXDEV)
1656     {
1657         if (!ctx->skip_all)
1658         {
1659             return_status = files_error (_("Cannot move directory \"%s\" to \"%s\"\n%s"), s, d);
1660             if (return_status == FILE_SKIPALL)
1661                 ctx->skip_all = TRUE;
1662             if (return_status == FILE_RETRY)
1663                 goto retry_rename;
1664         }
1665         goto ret;
1666     }
1667 
1668     /* Failed because of filesystem boundary -> copy dir instead */
1669     if (panel != NULL && !calc_total)
1670     {
1671         /* In case of single directory, calculate totals. In case of many directories,
1672            totals are calcuated already. */
1673         return_status =
1674             panel_operate_init_totals (panel, src_vpath, &src_stat, ctx, TRUE,
1675                                        FILEGUI_DIALOG_MULTI_ITEM);
1676         if (return_status != FILE_CONT)
1677             goto ret;
1678     }
1679 
1680     return_status = copy_dir_dir (tctx, ctx, s, d, FALSE, FALSE, TRUE, NULL);
1681 
1682     if (return_status != FILE_CONT)
1683         goto ret;
1684 
1685   oktoret:
1686     /* FIXME: there is no need to update progress and check buttons
1687        at the finish of single directory operation. */
1688     if (panel == NULL)
1689     {
1690         file_progress_show_source (ctx, NULL);
1691         file_progress_show_target (ctx, NULL);
1692         file_progress_show (ctx, 0, 0, "", FALSE);
1693 
1694         return_status = check_progress_buttons (ctx);
1695         if (return_status != FILE_CONT)
1696             goto ret;
1697     }
1698 
1699     mc_refresh ();
1700 
1701     erase_dir_after_copy (tctx, ctx, src_vpath, &return_status);
1702 
1703   ret:
1704     erase_list = free_linklist (erase_list);
1705   ret_fast:
1706     vfs_path_free (src_vpath, TRUE);
1707     vfs_path_free (dst_vpath, TRUE);
1708     return return_status;
1709 }
1710 
1711 /* --------------------------------------------------------------------------------------------- */
1712 
1713 /* {{{ Panel operate routines */
1714 
1715 /**
1716  * Return currently selected entry name or the name of the first marked
1717  * entry if there is one.
1718  */
1719 
1720 static const char *
1721 panel_get_file (const WPanel * panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1722 {
1723     if (get_current_type () == view_tree)
1724     {
1725         WTree *tree;
1726         const vfs_path_t *selected_name;
1727 
1728         tree = (WTree *) get_panel_widget (get_current_index ());
1729         selected_name = tree_selected_name (tree);
1730         return vfs_path_as_str (selected_name);
1731     }
1732 
1733     if (panel->marked != 0)
1734     {
1735         int i;
1736 
1737         for (i = 0; i < panel->dir.len; i++)
1738             if (panel->dir.list[i].f.marked)
1739                 return panel->dir.list[i].fname->str;
1740     }
1741 
1742     return panel->dir.list[panel->selected].fname->str;
1743 }
1744 
1745 /* --------------------------------------------------------------------------------------------- */
1746 
1747 static const char *
1748 check_single_entry (const WPanel * panel, gboolean force_single, struct stat *src_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
1749 {
1750     const char *source;
1751     gboolean ok;
1752 
1753     if (force_single)
1754         source = selection (panel)->fname->str;
1755     else
1756         source = panel_get_file (panel);
1757 
1758     ok = !DIR_IS_DOTDOT (source);
1759 
1760     if (!ok)
1761         message (D_ERROR, MSG_ERROR, _("Cannot operate on \"..\"!"));
1762     else
1763     {
1764         vfs_path_t *source_vpath;
1765 
1766         source_vpath = vfs_path_from_str (source);
1767 
1768         /* Update stat to get actual info */
1769         ok = mc_lstat (source_vpath, src_stat) == 0;
1770         if (!ok)
1771         {
1772             message (D_ERROR, MSG_ERROR, _("Cannot stat \"%s\"\n%s"),
1773                      path_trunc (source, 30), unix_error_string (errno));
1774 
1775             /* Directory was changed outside MC. Reload it forced */
1776             if (!panel->is_panelized)
1777             {
1778                 panel_update_flags_t flags = UP_RELOAD;
1779 
1780                 /* don't update panelized panel */
1781                 if (get_other_type () == view_listing && other_panel->is_panelized)
1782                     flags |= UP_ONLY_CURRENT;
1783 
1784                 update_panels (flags, UP_KEEPSEL);
1785             }
1786         }
1787 
1788         vfs_path_free (source_vpath, TRUE);
1789     }
1790 
1791     return ok ? source : NULL;
1792 }
1793 
1794 /* --------------------------------------------------------------------------------------------- */
1795 /**
1796  * Generate user prompt for panel operation.
1797  * src_stat must be not NULL for single source, and NULL for multiple sources
1798  */
1799 
1800 static char *
1801 panel_operate_generate_prompt (const WPanel * panel, FileOperation operation,
     /* [previous][next][first][last][top][bottom][index][help]  */
1802                                const struct stat *src_stat)
1803 {
1804     char *sp;
1805     char *format_string;
1806     const char *cp;
1807 
1808     static gboolean i18n_flag = FALSE;
1809     if (!i18n_flag)
1810     {
1811         size_t i;
1812 
1813         for (i = G_N_ELEMENTS (op_names1); i-- != 0;)
1814             op_names1[i] = Q_ (op_names1[i]);
1815 
1816 #ifdef ENABLE_NLS
1817         for (i = G_N_ELEMENTS (prompt_parts); i-- != 0;)
1818             prompt_parts[i] = _(prompt_parts[i]);
1819 
1820         one_format = _(one_format);
1821         many_format = _(many_format);
1822 #endif /* ENABLE_NLS */
1823         i18n_flag = TRUE;
1824     }
1825 
1826     /* Possible prompts:
1827      *   OP_COPY:
1828      *       "Copy file \"%s\" with source mask:"
1829      *       "Copy %d files with source mask:"
1830      *       "Copy directory \"%s\" with source mask:"
1831      *       "Copy %d directories with source mask:"
1832      *       "Copy %d files/directories with source mask:"
1833      *   OP_MOVE:
1834      *       "Move file \"%s\" with source mask:"
1835      *       "Move %d files with source mask:"
1836      *       "Move directory \"%s\" with source mask:"
1837      *       "Move %d directories with source mask:"
1838      *       "Move %d files/directories with source mask:"
1839      *   OP_DELETE:
1840      *       "Delete file \"%s\"?"
1841      *       "Delete %d files?"
1842      *       "Delete directory \"%s\"?"
1843      *       "Delete %d directories?"
1844      *       "Delete %d files/directories?"
1845      */
1846 
1847     cp = (src_stat != NULL ? one_format : many_format);
1848 
1849     /* 1. Substitute %o */
1850     format_string = str_replace_all (cp, "%o", op_names1[(int) operation]);
1851 
1852     /* 2. Substitute %n */
1853     cp = operation == OP_DELETE ? "\n" : " ";
1854     sp = format_string;
1855     format_string = str_replace_all (sp, "%n", cp);
1856     g_free (sp);
1857 
1858     /* 3. Substitute %f */
1859     if (src_stat != NULL)
1860         cp = S_ISDIR (src_stat->st_mode) ? prompt_parts[2] : prompt_parts[0];
1861     else if (panel->marked == panel->dirs_marked)
1862         cp = prompt_parts[3];
1863     else
1864         cp = panel->dirs_marked != 0 ? prompt_parts[4] : prompt_parts[1];
1865 
1866     sp = format_string;
1867     format_string = str_replace_all (sp, "%f", cp);
1868     g_free (sp);
1869 
1870     /* 4. Substitute %m */
1871     cp = operation == OP_DELETE ? "?" : prompt_parts[5];
1872     sp = format_string;
1873     format_string = str_replace_all (sp, "%m", cp);
1874     g_free (sp);
1875 
1876     return format_string;
1877 }
1878 
1879 /* --------------------------------------------------------------------------------------------- */
1880 
1881 static char *
1882 do_confirm_copy_move (const WPanel * panel, FileOperation operation, gboolean force_single,
     /* [previous][next][first][last][top][bottom][index][help]  */
1883                       const char *source, struct stat *src_stat, file_op_context_t * ctx,
1884                       gboolean * do_bg)
1885 {
1886     const char *tmp_dest_dir;
1887     char *dest_dir;
1888     char *format;
1889     char *ret;
1890 
1891     /* Forced single operations default to the original name */
1892     if (force_single)
1893         tmp_dest_dir = source;
1894     else if (get_other_type () == view_listing)
1895         tmp_dest_dir = vfs_path_as_str (other_panel->cwd_vpath);
1896     else
1897         tmp_dest_dir = vfs_path_as_str (panel->cwd_vpath);
1898 
1899     /*
1900      * Add trailing backslash only when do non-local ops.
1901      * It saves user from occasional file renames (when destination
1902      * dir is deleted)
1903      */
1904     if (!force_single && tmp_dest_dir != NULL && tmp_dest_dir[0] != '\0'
1905         && !IS_PATH_SEP (tmp_dest_dir[strlen (tmp_dest_dir) - 1]))
1906     {
1907         /* add trailing separator */
1908         dest_dir = g_strconcat (tmp_dest_dir, PATH_SEP_STR, (char *) NULL);
1909     }
1910     else
1911     {
1912         /* just copy */
1913         dest_dir = g_strdup (tmp_dest_dir);
1914     }
1915 
1916     if (dest_dir == NULL)
1917         return NULL;
1918 
1919     if (source == NULL)
1920         src_stat = NULL;
1921 
1922     /* Generate confirmation prompt */
1923     format = panel_operate_generate_prompt (panel, operation, src_stat);
1924 
1925     ret = file_mask_dialog (ctx, operation, source != NULL, format,
1926                             source != NULL ? source : (const void *) &panel->marked, dest_dir,
1927                             do_bg);
1928 
1929     g_free (format);
1930     g_free (dest_dir);
1931 
1932     return ret;
1933 }
1934 
1935 /* --------------------------------------------------------------------------------------------- */
1936 
1937 static gboolean
1938 do_confirm_erase (const WPanel * panel, const char *source, struct stat *src_stat)
     /* [previous][next][first][last][top][bottom][index][help]  */
1939 {
1940     int i;
1941     char *format;
1942     char fmd_buf[BUF_MEDIUM];
1943 
1944     if (source == NULL)
1945         src_stat = NULL;
1946 
1947     /* Generate confirmation prompt */
1948     format = panel_operate_generate_prompt (panel, OP_DELETE, src_stat);
1949 
1950     if (source == NULL)
1951         g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1952     else
1953     {
1954         const int fmd_xlen = 64;
1955 
1956         i = fmd_xlen - str_term_width1 (format) - 4;
1957         g_snprintf (fmd_buf, sizeof (fmd_buf), format, str_trunc (source, i));
1958     }
1959 
1960     g_free (format);
1961 
1962     if (safe_delete)
1963         query_set_sel (1);
1964 
1965     i = query_dialog (op_names[OP_DELETE], fmd_buf, D_ERROR, 2, _("&Yes"), _("&No"));
1966 
1967     return (i == 0);
1968 }
1969 
1970 /* --------------------------------------------------------------------------------------------- */
1971 
1972 static FileProgressStatus
1973 operate_single_file (const WPanel * panel, FileOperation operation, file_op_total_context_t * tctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
1974                      file_op_context_t * ctx, const char *src, struct stat *src_stat,
1975                      const char *dest, filegui_dialog_type_t dialog_type)
1976 {
1977     FileProgressStatus value;
1978     vfs_path_t *src_vpath;
1979     gboolean is_file;
1980 
1981     if (g_path_is_absolute (src))
1982         src_vpath = vfs_path_from_str (src);
1983     else
1984         src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
1985 
1986     is_file = !S_ISDIR (src_stat->st_mode);
1987     /* Is link to directory? */
1988     if (is_file)
1989     {
1990         gboolean is_link;
1991 
1992         is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
1993         is_file = !(is_link && ctx->follow_links);
1994     }
1995 
1996 
1997     if (operation == OP_DELETE)
1998     {
1999         value = panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file, dialog_type);
2000         if (value == FILE_CONT)
2001         {
2002             if (is_file)
2003                 value = erase_file (tctx, ctx, src_vpath);
2004             else
2005                 value = erase_dir (tctx, ctx, src_vpath);
2006         }
2007     }
2008     else
2009     {
2010         char *temp;
2011 
2012         src = vfs_path_as_str (src_vpath);
2013 
2014         temp = build_dest (ctx, src, dest, &value);
2015         if (temp != NULL)
2016         {
2017             dest = temp;
2018 
2019             switch (operation)
2020             {
2021             case OP_COPY:
2022                 /* we use file_mask_op_follow_links only with OP_COPY */
2023                 ctx->stat_func (src_vpath, src_stat);
2024 
2025                 value =
2026                     panel_operate_init_totals (panel, src_vpath, src_stat, ctx, !is_file,
2027                                                dialog_type);
2028                 if (value == FILE_CONT)
2029                 {
2030                     is_file = !S_ISDIR (src_stat->st_mode);
2031                     /* Is link to directory? */
2032                     if (is_file)
2033                     {
2034                         gboolean is_link;
2035 
2036                         is_link = file_is_symlink_to_dir (src_vpath, src_stat, NULL);
2037                         is_file = !(is_link && ctx->follow_links);
2038                     }
2039 
2040                     if (is_file)
2041                         value = copy_file_file (tctx, ctx, src, dest);
2042                     else
2043                         value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2044                 }
2045                 break;
2046 
2047             case OP_MOVE:
2048 #ifdef ENABLE_BACKGROUND
2049                 /* create UI to show confirmation dialog */
2050                 if (!mc_global.we_are_background)
2051                     file_op_context_create_ui (ctx, TRUE, FILEGUI_DIALOG_ONE_ITEM);
2052 #endif
2053                 if (is_file)
2054                     value = move_file_file (panel, tctx, ctx, src, dest);
2055                 else
2056                     value = do_move_dir_dir (panel, tctx, ctx, src, dest);
2057                 break;
2058 
2059             default:
2060                 /* Unknown file operation */
2061                 abort ();
2062             }
2063 
2064             g_free (temp);
2065         }
2066     }
2067 
2068     vfs_path_free (src_vpath, TRUE);
2069 
2070     return value;
2071 }
2072 
2073 /* --------------------------------------------------------------------------------------------- */
2074 
2075 static FileProgressStatus
2076 operate_one_file (const WPanel * panel, FileOperation operation, file_op_total_context_t * tctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
2077                   file_op_context_t * ctx, const char *src, struct stat *src_stat, const char *dest)
2078 {
2079     FileProgressStatus value = FILE_CONT;
2080     vfs_path_t *src_vpath;
2081     gboolean is_file;
2082 
2083     if (g_path_is_absolute (src))
2084         src_vpath = vfs_path_from_str (src);
2085     else
2086         src_vpath = vfs_path_append_new (panel->cwd_vpath, src, (char *) NULL);
2087 
2088     is_file = !S_ISDIR (src_stat->st_mode);
2089 
2090     if (operation == OP_DELETE)
2091     {
2092         if (is_file)
2093             value = erase_file (tctx, ctx, src_vpath);
2094         else
2095             value = erase_dir (tctx, ctx, src_vpath);
2096     }
2097     else
2098     {
2099         char *temp;
2100 
2101         src = vfs_path_as_str (src_vpath);
2102 
2103         temp = build_dest (ctx, src, dest, &value);
2104         if (temp != NULL)
2105         {
2106             dest = temp;
2107 
2108             switch (operation)
2109             {
2110             case OP_COPY:
2111                 /* we use file_mask_op_follow_links only with OP_COPY */
2112                 ctx->stat_func (src_vpath, src_stat);
2113                 is_file = !S_ISDIR (src_stat->st_mode);
2114 
2115                 if (is_file)
2116                     value = copy_file_file (tctx, ctx, src, dest);
2117                 else
2118                     value = copy_dir_dir (tctx, ctx, src, dest, TRUE, FALSE, FALSE, NULL);
2119                 dest_dirs = free_linklist (dest_dirs);
2120                 break;
2121 
2122             case OP_MOVE:
2123                 if (is_file)
2124                     value = move_file_file (NULL, tctx, ctx, src, dest);
2125                 else
2126                     value = do_move_dir_dir (NULL, tctx, ctx, src, dest);
2127                 break;
2128 
2129             default:
2130                 /* Unknown file operation */
2131                 abort ();
2132             }
2133 
2134             g_free (temp);
2135         }
2136     }
2137 
2138     vfs_path_free (src_vpath, TRUE);
2139 
2140     return value;
2141 }
2142 
2143 /* --------------------------------------------------------------------------------------------- */
2144 
2145 #ifdef ENABLE_BACKGROUND
2146 static int
2147 end_bg_process (file_op_context_t * ctx, enum OperationMode mode)
     /* [previous][next][first][last][top][bottom][index][help]  */
2148 {
2149     int pid = ctx->pid;
2150 
2151     (void) mode;
2152     ctx->pid = 0;
2153 
2154     unregister_task_with_pid (pid);
2155     /*     file_op_context_destroy(ctx); */
2156     return 1;
2157 }
2158 #endif
2159 /* }}} */
2160 
2161 /* --------------------------------------------------------------------------------------------- */
2162 /*** public functions ****************************************************************************/
2163 /* --------------------------------------------------------------------------------------------- */
2164 
2165 /* Is file symlink to directory or not.
2166  *
2167  * @param path file or directory
2168  * @param st result of mc_lstat(vpath). If NULL, mc_lstat(vpath) is performed here
2169  * @param stale_link TRUE if file is stale link to directory
2170  *
2171  * @return TRUE if file symlink to directory, ELSE otherwise.
2172  */
2173 gboolean
2174 file_is_symlink_to_dir (const vfs_path_t * vpath, struct stat * st, gboolean * stale_link)
     /* [previous][next][first][last][top][bottom][index][help]  */
2175 {
2176     struct stat st2;
2177     gboolean stale = FALSE;
2178     gboolean res = FALSE;
2179 
2180     if (st == NULL)
2181     {
2182         st = &st2;
2183 
2184         if (mc_lstat (vpath, st) != 0)
2185             goto ret;
2186     }
2187 
2188     if (S_ISLNK (st->st_mode))
2189     {
2190         struct stat st3;
2191 
2192         stale = (mc_stat (vpath, &st3) != 0);
2193 
2194         if (!stale)
2195             res = (S_ISDIR (st3.st_mode) != 0);
2196     }
2197 
2198   ret:
2199     if (stale_link != NULL)
2200         *stale_link = stale;
2201 
2202     return res;
2203 }
2204 
2205 /* --------------------------------------------------------------------------------------------- */
2206 
2207 FileProgressStatus
2208 copy_file_file (file_op_total_context_t * tctx, file_op_context_t * ctx,
     /* [previous][next][first][last][top][bottom][index][help]  */
2209                 const char *src_path, const char *dst_path)
2210 {
2211     uid_t src_uid = (uid_t) (-1);
2212     gid_t src_gid = (gid_t) (-1);
2213 
2214     int src_desc, dest_desc = -1;
2215     mode_t src_mode = 0;        /* The mode of the source file */
2216     struct stat src_stat, dst_stat;
2217     mc_timesbuf_t times;
2218     gboolean dst_exists = FALSE, appending = FALSE;
2219     off_t file_size = -1;
2220     FileProgressStatus return_status, temp_status;
2221     struct timeval tv_transfer_start;
2222     dest_status_t dst_status = DEST_NONE;
2223     int open_flags;
2224     vfs_path_t *src_vpath = NULL, *dst_vpath = NULL;
2225     char *buf = NULL;
2226 
2227     /* FIXME: We should not be using global variables! */
2228     ctx->do_reget = 0;
2229     return_status = FILE_RETRY;
2230 
2231     dst_vpath = vfs_path_from_str (dst_path);
2232     src_vpath = vfs_path_from_str (src_path);
2233 
2234     file_progress_show_source (ctx, src_vpath);
2235     file_progress_show_target (ctx, dst_vpath);
2236 
2237     if (check_progress_buttons (ctx) == FILE_ABORT)
2238     {
2239         return_status = FILE_ABORT;
2240         goto ret_fast;
2241     }
2242 
2243     mc_refresh ();
2244 
2245     while (mc_stat (dst_vpath, &dst_stat) == 0)
2246     {
2247         if (S_ISDIR (dst_stat.st_mode))
2248         {
2249             if (ctx->skip_all)
2250                 return_status = FILE_SKIPALL;
2251             else
2252             {
2253                 return_status =
2254                     file_error (TRUE, _("Cannot overwrite directory \"%s\"\n%s"), dst_path);
2255                 if (return_status == FILE_SKIPALL)
2256                     ctx->skip_all = TRUE;
2257                 if (return_status == FILE_RETRY)
2258                     continue;
2259             }
2260             goto ret_fast;
2261         }
2262 
2263         dst_exists = TRUE;
2264         break;
2265     }
2266 
2267     while ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2268     {
2269         if (ctx->skip_all)
2270             return_status = FILE_SKIPALL;
2271         else
2272         {
2273             return_status = file_error (TRUE, _("Cannot stat source file \"%s\"\n%s"), src_path);
2274             if (return_status == FILE_SKIPALL)
2275                 ctx->skip_all = TRUE;
2276         }
2277 
2278         if (return_status != FILE_RETRY)
2279             goto ret_fast;
2280     }
2281 
2282     if (dst_exists)
2283     {
2284         /* Destination already exists */
2285         if (check_same_file (src_path, &src_stat, dst_path, &dst_stat, &return_status))
2286             goto ret_fast;
2287 
2288         /* Should we replace destination? */
2289         if (tctx->ask_overwrite)
2290         {
2291             ctx->do_reget = 0;
2292             return_status = query_replace (ctx, src_path, &src_stat, dst_path, &dst_stat);
2293             if (return_status != FILE_CONT)
2294                 goto ret_fast;
2295         }
2296     }
2297 
2298     get_times (&src_stat, &times);
2299 
2300     if (!ctx->do_append)
2301     {
2302         /* Check the hardlinks */
2303         if (!ctx->follow_links)
2304         {
2305             switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
2306             {
2307             case HARDLINK_OK:
2308                 /* We have made a hardlink - no more processing is necessary */
2309                 return_status = FILE_CONT;
2310                 goto ret_fast;
2311 
2312             case HARDLINK_ABORT:
2313                 return_status = FILE_ABORT;
2314                 goto ret_fast;
2315 
2316             default:
2317                 break;
2318             }
2319         }
2320 
2321         if (S_ISLNK (src_stat.st_mode))
2322         {
2323             return_status = make_symlink (ctx, src_path, dst_path);
2324             if (return_status == FILE_CONT && ctx->preserve)
2325                 mc_utime (dst_vpath, &times);
2326             goto ret_fast;
2327         }
2328 
2329         if (S_ISCHR (src_stat.st_mode) || S_ISBLK (src_stat.st_mode) || S_ISFIFO (src_stat.st_mode)
2330             || S_ISNAM (src_stat.st_mode) || S_ISSOCK (src_stat.st_mode))
2331         {
2332             dev_t rdev = 0;
2333 
2334 #ifdef HAVE_STRUCT_STAT_ST_RDEV
2335             rdev = src_stat.st_rdev;
2336 #endif
2337 
2338             while (mc_mknod (dst_vpath, src_stat.st_mode & ctx->umask_kill, rdev) < 0
2339                    && !ctx->skip_all)
2340             {
2341                 return_status =
2342                     file_error (TRUE, _("Cannot create special file \"%s\"\n%s"), dst_path);
2343                 if (return_status == FILE_RETRY)
2344                     continue;
2345                 if (return_status == FILE_SKIPALL)
2346                     ctx->skip_all = TRUE;
2347                 goto ret_fast;
2348             }
2349             /* Success */
2350 
2351             while (ctx->preserve_uidgid
2352                    && mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0 && !ctx->skip_all)
2353             {
2354                 temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2355                 if (temp_status == FILE_SKIP)
2356                     break;
2357                 if (temp_status == FILE_SKIPALL)
2358                     ctx->skip_all = TRUE;
2359                 if (temp_status != FILE_RETRY)
2360                 {
2361                     return_status = temp_status;
2362                     goto ret_fast;
2363                 }
2364             }
2365 
2366             while (ctx->preserve && mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill) != 0
2367                    && !ctx->skip_all)
2368             {
2369                 temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2370                 if (temp_status == FILE_SKIP)
2371                     break;
2372                 if (temp_status == FILE_SKIPALL)
2373                     ctx->skip_all = TRUE;
2374                 if (temp_status != FILE_RETRY)
2375                 {
2376                     return_status = temp_status;
2377                     goto ret_fast;
2378                 }
2379             }
2380 
2381             return_status = FILE_CONT;
2382             mc_utime (dst_vpath, &times);
2383             goto ret_fast;
2384         }
2385     }
2386 
2387     gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
2388 
2389     while ((src_desc = mc_open (src_vpath, O_RDONLY | O_LINEAR)) < 0 && !ctx->skip_all)
2390     {
2391         return_status = file_error (TRUE, _("Cannot open source file \"%s\"\n%s"), src_path);
2392         if (return_status == FILE_RETRY)
2393             continue;
2394         if (return_status == FILE_SKIPALL)
2395             ctx->skip_all = TRUE;
2396         if (return_status == FILE_SKIP)
2397             break;
2398         ctx->do_append = FALSE;
2399         goto ret_fast;
2400     }
2401 
2402     if (ctx->do_reget != 0 && mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget)
2403     {
2404         message (D_ERROR, _("Warning"), _("Reget failed, about to overwrite file"));
2405         ctx->do_reget = 0;
2406         ctx->do_append = FALSE;
2407     }
2408 
2409     while (mc_fstat (src_desc, &src_stat) != 0)
2410     {
2411         if (ctx->skip_all)
2412             return_status = FILE_SKIPALL;
2413         else
2414         {
2415             return_status = file_error (TRUE, _("Cannot fstat source file \"%s\"\n%s"), src_path);
2416             if (return_status == FILE_RETRY)
2417                 continue;
2418             if (return_status == FILE_SKIPALL)
2419                 ctx->skip_all = TRUE;
2420             ctx->do_append = FALSE;
2421         }
2422         goto ret;
2423     }
2424 
2425     src_mode = src_stat.st_mode;
2426     src_uid = src_stat.st_uid;
2427     src_gid = src_stat.st_gid;
2428     file_size = src_stat.st_size;
2429 
2430     open_flags = O_WRONLY;
2431     if (!dst_exists)
2432         open_flags |= O_CREAT | O_EXCL;
2433     else if (ctx->do_append)
2434         open_flags |= O_APPEND;
2435     else
2436         open_flags |= O_CREAT | O_TRUNC;
2437 
2438     while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
2439     {
2440         if (errno != EEXIST)
2441         {
2442             if (ctx->skip_all)
2443                 return_status = FILE_SKIPALL;
2444             else
2445             {
2446                 return_status =
2447                     file_error (TRUE, _("Cannot create target file \"%s\"\n%s"), dst_path);
2448                 if (return_status == FILE_RETRY)
2449                     continue;
2450                 if (return_status == FILE_SKIPALL)
2451                     ctx->skip_all = TRUE;
2452                 ctx->do_append = FALSE;
2453             }
2454         }
2455         goto ret;
2456     }
2457 
2458     dst_status = DEST_SHORT;    /* file opened, but not fully copied */
2459 
2460     appending = ctx->do_append;
2461     ctx->do_append = FALSE;
2462 
2463     /* Try clone the file first. */
2464     if (vfs_clone_file (dest_desc, src_desc) == 0)
2465     {
2466         dst_status = DEST_FULL;
2467         return_status = FILE_CONT;
2468         goto ret;
2469     }
2470 
2471     /* Find out the optimal buffer size.  */
2472     while (mc_fstat (dest_desc, &dst_stat) != 0)
2473     {
2474         if (ctx->skip_all)
2475             return_status = FILE_SKIPALL;
2476         else
2477         {
2478             return_status = file_error (TRUE, _("Cannot fstat target file \"%s\"\n%s"), dst_path);
2479             if (return_status == FILE_RETRY)
2480                 continue;
2481             if (return_status == FILE_SKIPALL)
2482                 ctx->skip_all = TRUE;
2483         }
2484         goto ret;
2485     }
2486 
2487     /* try preallocate space; if fail, try copy anyway */
2488     while (mc_global.vfs.preallocate_space &&
2489            vfs_preallocate (dest_desc, file_size, appending ? dst_stat.st_size : 0) != 0)
2490     {
2491         if (ctx->skip_all)
2492         {
2493             /* cannot allocate, start the file copying anyway */
2494             return_status = FILE_CONT;
2495             break;
2496         }
2497 
2498         return_status =
2499             file_error (TRUE, _("Cannot preallocate space for target file \"%s\"\n%s"), dst_path);
2500 
2501         if (return_status == FILE_SKIPALL)
2502             ctx->skip_all = TRUE;
2503 
2504         if (ctx->skip_all || return_status == FILE_SKIP)
2505         {
2506             /* skip the space allocation error, start file copying */
2507             return_status = FILE_CONT;
2508             break;
2509         }
2510 
2511         if (return_status == FILE_ABORT)
2512         {
2513             mc_close (dest_desc);
2514             dest_desc = -1;
2515             mc_unlink (dst_vpath);
2516             dst_status = DEST_NONE;
2517             goto ret;
2518         }
2519 
2520         /* return_status == FILE_RETRY -- try allocate space again */
2521     }
2522 
2523     ctx->eta_secs = 0.0;
2524     ctx->bps = 0;
2525 
2526     if (tctx->bps == 0 || (file_size / (tctx->bps)) > FILEOP_UPDATE_INTERVAL)
2527         file_progress_show (ctx, 0, file_size, "", TRUE);
2528     else
2529         file_progress_show (ctx, 1, 1, "", TRUE);
2530     return_status = check_progress_buttons (ctx);
2531     mc_refresh ();
2532 
2533     if (return_status == FILE_CONT)
2534     {
2535         size_t bufsize;
2536         off_t n_read_total = 0;
2537         struct timeval tv_current, tv_last_update, tv_last_input;
2538         int secs, update_secs;
2539         const char *stalled_msg = "";
2540         gboolean is_first_time = TRUE;
2541 
2542         tv_last_update = tv_transfer_start;
2543 
2544         bufsize = io_blksize (dst_stat);
2545         buf = g_malloc (bufsize);
2546 
2547         while (TRUE)
2548         {
2549             ssize_t n_read = -1, n_written;
2550             gboolean force_update;
2551 
2552             /* src_read */
2553             if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0) == 0)
2554                 while ((n_read = mc_read (src_desc, buf, bufsize)) < 0 && !ctx->skip_all)
2555                 {
2556                     return_status =
2557                         file_error (TRUE, _("Cannot read source file \"%s\"\n%s"), src_path);
2558                     if (return_status == FILE_RETRY)
2559                         continue;
2560                     if (return_status == FILE_SKIPALL)
2561                         ctx->skip_all = TRUE;
2562                     goto ret;
2563                 }
2564 
2565             if (n_read == 0)
2566                 break;
2567 
2568             gettimeofday (&tv_current, NULL);
2569 
2570             if (n_read > 0)
2571             {
2572                 char *t = buf;
2573 
2574                 n_read_total += n_read;
2575 
2576                 gettimeofday (&tv_last_input, NULL);
2577 
2578                 /* dst_write */
2579                 while ((n_written = mc_write (dest_desc, t, (size_t) n_read)) < n_read)
2580                 {
2581                     gboolean write_errno_nospace;
2582 
2583                     if (n_written > 0)
2584                     {
2585                         n_read -= n_written;
2586                         t += n_written;
2587                         continue;
2588                     }
2589 
2590                     write_errno_nospace = (n_written < 0 && errno == ENOSPC);
2591 
2592                     if (ctx->skip_all)
2593                         return_status = FILE_SKIPALL;
2594                     else
2595                         return_status =
2596                             file_error (TRUE, _("Cannot write target file \"%s\"\n%s"), dst_path);
2597 
2598                     if (return_status == FILE_SKIP)
2599                     {
2600                         if (write_errno_nospace)
2601                             goto ret;
2602                         break;
2603                     }
2604                     if (return_status == FILE_SKIPALL)
2605                     {
2606                         ctx->skip_all = TRUE;
2607                         if (write_errno_nospace)
2608                             goto ret;
2609                     }
2610                     if (return_status != FILE_RETRY)
2611                         goto ret;
2612                 }
2613             }
2614 
2615             tctx->copied_bytes = tctx->progress_bytes + n_read_total + ctx->do_reget;
2616 
2617             secs = (tv_current.tv_sec - tv_last_update.tv_sec);
2618             update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
2619 
2620             if (is_first_time || secs > FILEOP_UPDATE_INTERVAL)
2621             {
2622                 copy_file_file_display_progress (tctx, ctx,
2623                                                  tv_current,
2624                                                  tv_transfer_start, file_size, n_read_total);
2625                 tv_last_update = tv_current;
2626             }
2627 
2628             is_first_time = FALSE;
2629 
2630             if (update_secs > FILEOP_STALLING_INTERVAL)
2631                 stalled_msg = _("(stalled)");
2632 
2633             force_update =
2634                 (tv_current.tv_sec - tctx->transfer_start.tv_sec) > FILEOP_UPDATE_INTERVAL;
2635 
2636             if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
2637             {
2638                 file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
2639                 file_progress_show_total (tctx, ctx, tctx->copied_bytes, force_update);
2640             }
2641 
2642             file_progress_show (ctx, n_read_total + ctx->do_reget, file_size, stalled_msg,
2643                                 force_update);
2644             mc_refresh ();
2645 
2646             return_status = check_progress_buttons (ctx);
2647 
2648             if (return_status != FILE_CONT)
2649             {
2650                 mc_refresh ();
2651                 goto ret;
2652             }
2653         }
2654 
2655         dst_status = DEST_FULL; /* copy successful, don't remove target file */
2656     }
2657 
2658   ret:
2659     g_free (buf);
2660 
2661     rotate_dash (FALSE);
2662     while (src_desc != -1 && mc_close (src_desc) < 0 && !ctx->skip_all)
2663     {
2664         temp_status = file_error (TRUE, _("Cannot close source file \"%s\"\n%s"), src_path);
2665         if (temp_status == FILE_RETRY)
2666             continue;
2667         if (temp_status == FILE_ABORT)
2668             return_status = temp_status;
2669         if (temp_status == FILE_SKIPALL)
2670             ctx->skip_all = TRUE;
2671         break;
2672     }
2673 
2674     while (dest_desc != -1 && mc_close (dest_desc) < 0 && !ctx->skip_all)
2675     {
2676         temp_status = file_error (TRUE, _("Cannot close target file \"%s\"\n%s"), dst_path);
2677         if (temp_status == FILE_RETRY)
2678             continue;
2679         if (temp_status == FILE_SKIPALL)
2680             ctx->skip_all = TRUE;
2681         return_status = temp_status;
2682         break;
2683     }
2684 
2685     if (dst_status == DEST_SHORT)
2686     {
2687         /* Query to remove short file */
2688         if (query_dialog (Q_ ("DialogTitle|Copy"), _("Incomplete file was retrieved. Keep it?"),
2689                           D_ERROR, 2, _("&Delete"), _("&Keep")) == 0)
2690             mc_unlink (dst_vpath);
2691     }
2692     else if (dst_status == DEST_FULL && !appending)
2693     {
2694         /* Copy has succeeded */
2695 
2696         while (ctx->preserve_uidgid && mc_chown (dst_vpath, src_uid, src_gid) != 0
2697                && !ctx->skip_all)
2698         {
2699             temp_status = file_error (TRUE, _("Cannot chown target file \"%s\"\n%s"), dst_path);
2700             if (temp_status == FILE_RETRY)
2701                 continue;
2702             if (temp_status == FILE_SKIPALL)
2703             {
2704                 ctx->skip_all = TRUE;
2705                 return_status = FILE_CONT;
2706             }
2707             if (temp_status == FILE_SKIP)
2708                 return_status = FILE_CONT;
2709             break;
2710         }
2711 
2712         while (ctx->preserve && mc_chmod (dst_vpath, (src_mode & ctx->umask_kill)) != 0
2713                && !ctx->skip_all)
2714         {
2715             temp_status = file_error (TRUE, _("Cannot chmod target file \"%s\"\n%s"), dst_path);
2716             if (temp_status == FILE_RETRY)
2717                 continue;
2718             if (temp_status == FILE_SKIPALL)
2719             {
2720                 ctx->skip_all = TRUE;
2721                 return_status = FILE_CONT;
2722             }
2723             if (temp_status == FILE_SKIP)
2724                 return_status = FILE_CONT;
2725             break;
2726         }
2727 
2728         if (!ctx->preserve && !dst_exists)
2729         {
2730             src_mode = umask (-1);
2731             umask (src_mode);
2732             src_mode = 0100666 & ~src_mode;
2733             mc_chmod (dst_vpath, (src_mode & ctx->umask_kill));
2734         }
2735 
2736         mc_utime (dst_vpath, &times);
2737     }
2738 
2739     if (return_status == FILE_CONT)
2740         return_status = progress_update_one (tctx, ctx, file_size);
2741 
2742   ret_fast:
2743     vfs_path_free (src_vpath, TRUE);
2744     vfs_path_free (dst_vpath, TRUE);
2745     return return_status;
2746 }
2747 
2748 /* --------------------------------------------------------------------------------------------- */
2749 /**
2750  * I think these copy_*_* functions should have a return type.
2751  * anyway, this function *must* have two directories as arguments.
2752  */
2753 /* FIXME: This function needs to check the return values of the
2754    function calls */
2755 
2756 FileProgressStatus
2757 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]  */
2758               gboolean toplevel, gboolean move_over, gboolean do_delete, GSList * parent_dirs)
2759 {
2760     struct vfs_dirent *next;
2761     struct stat dst_stat, src_stat;
2762     DIR *reading;
2763     FileProgressStatus return_status = FILE_CONT;
2764     struct link *lp;
2765     vfs_path_t *src_vpath, *dst_vpath;
2766     gboolean do_mkdir = TRUE;
2767 
2768     src_vpath = vfs_path_from_str (s);
2769     dst_vpath = vfs_path_from_str (d);
2770 
2771     /* First get the mode of the source dir */
2772 
2773   retry_src_stat:
2774     if ((*ctx->stat_func) (src_vpath, &src_stat) != 0)
2775     {
2776         if (ctx->skip_all)
2777             return_status = FILE_SKIPALL;
2778         else
2779         {
2780             return_status = file_error (TRUE, _("Cannot stat source directory \"%s\"\n%s"), s);
2781             if (return_status == FILE_RETRY)
2782                 goto retry_src_stat;
2783             if (return_status == FILE_SKIPALL)
2784                 ctx->skip_all = TRUE;
2785         }
2786         goto ret_fast;
2787     }
2788 
2789     if (is_in_linklist (dest_dirs, src_vpath, &src_stat) != NULL)
2790     {
2791         /* Don't copy a directory we created before (we don't want to copy 
2792            infinitely if a directory is copied into itself) */
2793         /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
2794         return_status = FILE_CONT;
2795         goto ret_fast;
2796     }
2797 
2798     /* Hmm, hardlink to directory??? - Norbert */
2799     /* FIXME: In this step we should do something in case the destination already exist */
2800     /* Check the hardlinks */
2801     if (ctx->preserve)
2802     {
2803         switch (check_hardlinks (src_vpath, &src_stat, dst_vpath, &ctx->skip_all))
2804         {
2805         case HARDLINK_OK:
2806             /* We have made a hardlink - no more processing is necessary */
2807             goto ret_fast;
2808 
2809         case HARDLINK_ABORT:
2810             return_status = FILE_ABORT;
2811             goto ret_fast;
2812 
2813         default:
2814             break;
2815         }
2816     }
2817 
2818     if (!S_ISDIR (src_stat.st_mode))
2819     {
2820         if (ctx->skip_all)
2821             return_status = FILE_SKIPALL;
2822         else
2823         {
2824             return_status = file_error (TRUE, _("Source \"%s\" is not a directory\n%s"), s);
2825             if (return_status == FILE_RETRY)
2826                 goto retry_src_stat;
2827             if (return_status == FILE_SKIPALL)
2828                 ctx->skip_all = TRUE;
2829         }
2830         goto ret_fast;
2831     }
2832 
2833     if (is_in_linklist (parent_dirs, src_vpath, &src_stat) != NULL)
2834     {
2835         /* we found a cyclic symbolic link */
2836         message (D_ERROR, MSG_ERROR, _("Cannot copy cyclic symbolic link\n\"%s\""), s);
2837         return_status = FILE_SKIP;
2838         goto ret_fast;
2839     }
2840 
2841     lp = g_new0 (struct link, 1);
2842     lp->vfs = vfs_path_get_by_index (src_vpath, -1)->class;
2843     lp->ino = src_stat.st_ino;
2844     lp->dev = src_stat.st_dev;
2845     parent_dirs = g_slist_prepend (parent_dirs, lp);
2846 
2847   retry_dst_stat:
2848     /* Now, check if the dest dir exists, if not, create it. */
2849     if (mc_stat (dst_vpath, &dst_stat) != 0)
2850     {
2851         /* Here the dir doesn't exist : make it ! */
2852         if (move_over && mc_rename (src_vpath, dst_vpath) == 0)
2853         {
2854             return_status = FILE_CONT;
2855             goto ret;
2856         }
2857     }
2858     else
2859     {
2860         /*
2861          * If the destination directory exists, we want to copy the whole
2862          * directory, but we only want this to happen once.
2863          *
2864          * Escape sequences added to the * to compiler warnings.
2865          * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
2866          * or ( /bla doesn't exist )       /tmp/\* to /bla     ->  /bla/\*
2867          */
2868         if (!S_ISDIR (dst_stat.st_mode))
2869         {
2870             if (ctx->skip_all)
2871                 return_status = FILE_SKIPALL;
2872             else
2873             {
2874                 return_status =
2875                     file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), d);
2876                 if (return_status == FILE_SKIPALL)
2877                     ctx->skip_all = TRUE;
2878                 if (return_status == FILE_RETRY)
2879                     goto retry_dst_stat;
2880             }
2881             goto ret;
2882         }
2883         /* Dive into subdir if exists */
2884         if (toplevel && ctx->dive_into_subdirs)
2885         {
2886             vfs_path_t *tmp;
2887 
2888             tmp = dst_vpath;
2889             dst_vpath = vfs_path_append_new (dst_vpath, x_basename (s), (char *) NULL);
2890             vfs_path_free (tmp, TRUE);
2891 
2892         }
2893         else
2894             do_mkdir = FALSE;
2895     }
2896 
2897     d = vfs_path_as_str (dst_vpath);
2898 
2899     if (do_mkdir)
2900     {
2901         while (my_mkdir (dst_vpath, (src_stat.st_mode & ctx->umask_kill) | S_IRWXU) != 0)
2902         {
2903             if (ctx->skip_all)
2904                 return_status = FILE_SKIPALL;
2905             else
2906             {
2907                 return_status =
2908                     file_error (TRUE, _("Cannot create target directory \"%s\"\n%s"), d);
2909                 if (return_status == FILE_SKIPALL)
2910                     ctx->skip_all = TRUE;
2911             }
2912             if (return_status != FILE_RETRY)
2913                 goto ret;
2914         }
2915 
2916         lp = g_new0 (struct link, 1);
2917         mc_stat (dst_vpath, &dst_stat);
2918         lp->vfs = vfs_path_get_by_index (dst_vpath, -1)->class;
2919         lp->ino = dst_stat.st_ino;
2920         lp->dev = dst_stat.st_dev;
2921         dest_dirs = g_slist_prepend (dest_dirs, lp);
2922     }
2923 
2924     if (ctx->preserve_uidgid)
2925     {
2926         while (mc_chown (dst_vpath, src_stat.st_uid, src_stat.st_gid) != 0)
2927         {
2928             if (ctx->skip_all)
2929                 return_status = FILE_SKIPALL;
2930             else
2931             {
2932                 return_status = file_error (TRUE, _("Cannot chown target directory \"%s\"\n%s"), d);
2933                 if (return_status == FILE_SKIPALL)
2934                     ctx->skip_all = TRUE;
2935             }
2936             if (return_status != FILE_RETRY)
2937                 goto ret;
2938         }
2939     }
2940 
2941     /* open the source dir for reading */
2942     reading = mc_opendir (src_vpath);
2943     if (reading == NULL)
2944         goto ret;
2945 
2946     while ((next = mc_readdir (reading)) && return_status != FILE_ABORT)
2947     {
2948         char *path;
2949         vfs_path_t *tmp_vpath;
2950 
2951         /*
2952          * Now, we don't want '.' and '..' to be created / copied at any time
2953          */
2954         if (DIR_IS_DOT (next->d_name) || DIR_IS_DOTDOT (next->d_name))
2955             continue;
2956 
2957         /* get the filename and add it to the src directory */
2958         path = mc_build_filename (s, next->d_name, (char *) NULL);
2959         tmp_vpath = vfs_path_from_str (path);
2960 
2961         (*ctx->stat_func) (tmp_vpath, &dst_stat);
2962         if (S_ISDIR (dst_stat.st_mode))
2963         {
2964             char *mdpath;
2965 
2966             mdpath = mc_build_filename (d, next->d_name, (char *) NULL);
2967             /*
2968              * From here, we just intend to recursively copy subdirs, not
2969              * the double functionality of copying different when the target
2970              * dir already exists. So, we give the recursive call the flag 0
2971              * meaning no toplevel.
2972              */
2973             return_status =
2974                 copy_dir_dir (tctx, ctx, path, mdpath, FALSE, FALSE, do_delete, parent_dirs);
2975             g_free (mdpath);
2976         }
2977         else
2978         {
2979             char *dest_file;
2980 
2981             dest_file = mc_build_filename (d, x_basename (path), (char *) NULL);
2982             return_status = copy_file_file (tctx, ctx, path, dest_file);
2983             g_free (dest_file);
2984         }
2985 
2986         g_free (path);
2987 
2988         if (do_delete && return_status == FILE_CONT)
2989         {
2990             if (ctx->erase_at_end)
2991             {
2992                 lp = g_new0 (struct link, 1);
2993                 lp->src_vpath = tmp_vpath;
2994                 lp->st_mode = dst_stat.st_mode;
2995                 erase_list = g_slist_append (erase_list, lp);
2996                 tmp_vpath = NULL;
2997             }
2998             else if (S_ISDIR (dst_stat.st_mode))
2999                 return_status = erase_dir_iff_empty (ctx, tmp_vpath, tctx->progress_count);
3000             else
3001                 return_status = erase_file (tctx, ctx, tmp_vpath);
3002         }
3003         vfs_path_free (tmp_vpath, TRUE);
3004     }
3005     mc_closedir (reading);
3006 
3007     if (ctx->preserve)
3008     {
3009         mc_timesbuf_t times;
3010 
3011         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3012         get_times (&src_stat, &times);
3013         mc_utime (dst_vpath, &times);
3014     }
3015     else
3016     {
3017         src_stat.st_mode = umask (-1);
3018         umask (src_stat.st_mode);
3019         src_stat.st_mode = 0100777 & ~src_stat.st_mode;
3020         mc_chmod (dst_vpath, src_stat.st_mode & ctx->umask_kill);
3021     }
3022 
3023   ret:
3024     free_link (parent_dirs->data);
3025     g_slist_free_1 (parent_dirs);
3026   ret_fast:
3027     vfs_path_free (src_vpath, TRUE);
3028     vfs_path_free (dst_vpath, TRUE);
3029     return return_status;
3030 }
3031 
3032 /* }}} */
3033 
3034 /* --------------------------------------------------------------------------------------------- */
3035 /* {{{ Move routines */
3036 
3037 FileProgressStatus
3038 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]  */
3039 {
3040     return do_move_dir_dir (NULL, tctx, ctx, s, d);
3041 }
3042 
3043 /* }}} */
3044 
3045 /* --------------------------------------------------------------------------------------------- */
3046 /* {{{ Erase routines */
3047 
3048 FileProgressStatus
3049 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]  */
3050 {
3051     file_progress_show_deleting (ctx, vfs_path_as_str (vpath), NULL);
3052     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3053     if (check_progress_buttons (ctx) == FILE_ABORT)
3054         return FILE_ABORT;
3055 
3056     mc_refresh ();
3057 
3058     /* The old way to detect a non empty directory was:
3059        error = my_rmdir (s);
3060        if (error && (errno == ENOTEMPTY || errno == EEXIST))){
3061        For the linux user space nfs server (nfs-server-2.2beta29-2)
3062        we would have to check also for EIO. I hope the new way is
3063        fool proof. (Norbert)
3064      */
3065     if (check_dir_is_empty (vpath) == 0)
3066     {                           /* not empty */
3067         FileProgressStatus error;
3068 
3069         error = query_recursive (ctx, vfs_path_as_str (vpath));
3070         if (error == FILE_CONT)
3071             error = recursive_erase (tctx, ctx, vpath);
3072         return error;
3073     }
3074 
3075     return try_erase_dir (ctx, vfs_path_as_str (vpath));
3076 }
3077 
3078 /* }}} */
3079 
3080 /* --------------------------------------------------------------------------------------------- */
3081 /* {{{ Panel operate routines */
3082 
3083 void
3084 dirsize_status_init_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3085 {
3086     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3087     WGroup *gd = GROUP (sm->dlg);
3088     Widget *wd = WIDGET (sm->dlg);
3089 
3090     const char *b1_name = N_("&Abort");
3091     const char *b2_name = N_("&Skip");
3092     int b_width, ui_width;
3093 
3094 #ifdef ENABLE_NLS
3095     b1_name = _(b1_name);
3096     b2_name = _(b2_name);
3097 #endif
3098 
3099     b_width = str_term_width1 (b1_name) + 4;
3100     if (dsm->allow_skip)
3101         b_width += str_term_width1 (b2_name) + 4 + 1;
3102 
3103     ui_width = MAX (COLS / 2, b_width + 6);
3104     dsm->dirname = label_new (2, 3, "");
3105     group_add_widget (gd, dsm->dirname);
3106     dsm->count_size = label_new (3, 3, "");
3107     group_add_widget (gd, dsm->count_size);
3108     group_add_widget (gd, hline_new (4, -1, -1));
3109 
3110     dsm->abort_button = WIDGET (button_new (5, 3, FILE_ABORT, NORMAL_BUTTON, b1_name, NULL));
3111     group_add_widget (gd, dsm->abort_button);
3112     if (dsm->allow_skip)
3113     {
3114         dsm->skip_button = WIDGET (button_new (5, 3, FILE_SKIP, NORMAL_BUTTON, b2_name, NULL));
3115         group_add_widget (gd, dsm->skip_button);
3116         widget_select (dsm->skip_button);
3117     }
3118 
3119     widget_set_size (wd, wd->y, wd->x, 8, ui_width);
3120     dirsize_status_locate_buttons (dsm);
3121 }
3122 
3123 /* --------------------------------------------------------------------------------------------- */
3124 
3125 int
3126 dirsize_status_update_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3127 {
3128     dirsize_status_msg_t *dsm = (dirsize_status_msg_t *) sm;
3129     Widget *wd = WIDGET (sm->dlg);
3130 
3131     /* update second (longer label) */
3132     label_set_textv (dsm->count_size, _("Directories: %zu, total size: %s"),
3133                      dsm->dir_count, size_trunc_sep (dsm->total_size, panels_options.kilobyte_si));
3134 
3135     /* enlarge dialog if required */
3136     if (WIDGET (dsm->count_size)->cols + 6 > wd->cols)
3137     {
3138         widget_set_size (wd, wd->y, wd->x, wd->lines, WIDGET (dsm->count_size)->cols + 6);
3139         dirsize_status_locate_buttons (dsm);
3140         widget_draw (wd);
3141         /* TODO: ret rid of double redraw */
3142     }
3143 
3144     /* adjust first label */
3145     label_set_text (dsm->dirname, str_trunc (vfs_path_as_str (dsm->dirname_vpath), wd->cols - 6));
3146 
3147     switch (status_msg_common_update (sm))
3148     {
3149     case B_CANCEL:
3150     case FILE_ABORT:
3151         return FILE_ABORT;
3152     case FILE_SKIP:
3153         return FILE_SKIP;
3154     default:
3155         return FILE_CONT;
3156     }
3157 }
3158 
3159 /* --------------------------------------------------------------------------------------------- */
3160 
3161 void
3162 dirsize_status_deinit_cb (status_msg_t * sm)
     /* [previous][next][first][last][top][bottom][index][help]  */
3163 {
3164     (void) sm;
3165 
3166     /* schedule to update passive panel */
3167     if (get_other_type () == view_listing)
3168         other_panel->dirty = TRUE;
3169 }
3170 
3171 /* --------------------------------------------------------------------------------------------- */
3172 /**
3173  * compute_dir_size:
3174  *
3175  * Computes the number of bytes used by the files in a directory
3176  */
3177 
3178 FileProgressStatus
3179 compute_dir_size (const vfs_path_t * dirname_vpath, dirsize_status_msg_t * sm,
     /* [previous][next][first][last][top][bottom][index][help]  */
3180                   size_t * ret_dir_count, size_t * ret_marked_count, uintmax_t * ret_total,
3181                   gboolean follow_symlinks)
3182 {
3183     return do_compute_dir_size (dirname_vpath, sm, ret_dir_count, ret_marked_count, ret_total,
3184                                 follow_symlinks ? mc_stat : mc_lstat);
3185 }
3186 
3187 /* --------------------------------------------------------------------------------------------- */
3188 /**
3189  * panel_operate:
3190  *
3191  * Performs one of the operations on the selection on the source_panel
3192  * (copy, delete, move).
3193  *
3194  * Returns TRUE if did change the directory
3195  * structure, Returns FALSE if user aborted
3196  *
3197  * force_single forces operation on the current entry and affects
3198  * default destination.  Current filename is used as default.
3199  */
3200 
3201 gboolean
3202 panel_operate (void *source_panel, FileOperation operation, gboolean force_single)
     /* [previous][next][first][last][top][bottom][index][help]  */
3203 {
3204     WPanel *panel = PANEL (source_panel);
3205     const gboolean single_entry = force_single || (panel->marked <= 1)
3206         || (get_current_type () == view_tree);
3207 
3208     const char *source = NULL;
3209     char *dest = NULL;
3210     vfs_path_t *dest_vpath = NULL;
3211     char *save_cwd = NULL, *save_dest = NULL;
3212     struct stat src_stat;
3213     gboolean ret_val = TRUE;
3214     int i;
3215     FileProgressStatus value;
3216     file_op_context_t *ctx;
3217     file_op_total_context_t *tctx;
3218     vfs_path_t *tmp_vpath;
3219     filegui_dialog_type_t dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3220 
3221     gboolean do_bg = FALSE;     /* do background operation? */
3222 
3223     static gboolean i18n_flag = FALSE;
3224     if (!i18n_flag)
3225     {
3226         for (i = G_N_ELEMENTS (op_names); i-- != 0;)
3227             op_names[i] = Q_ (op_names[i]);
3228         i18n_flag = TRUE;
3229     }
3230 
3231     linklist = free_linklist (linklist);
3232     dest_dirs = free_linklist (dest_dirs);
3233 
3234     if (single_entry)
3235     {
3236         source = check_single_entry (panel, force_single, &src_stat);
3237 
3238         if (source == NULL)
3239             return FALSE;
3240     }
3241 
3242     ctx = file_op_context_new (operation);
3243 
3244     /* Show confirmation dialog */
3245     if (operation != OP_DELETE)
3246     {
3247         dest =
3248             do_confirm_copy_move (panel, operation, force_single, source, &src_stat, ctx, &do_bg);
3249 
3250         if (dest == NULL)
3251         {
3252             ret_val = FALSE;
3253             goto ret_fast;
3254         }
3255 
3256         dest_vpath = vfs_path_from_str (dest);
3257     }
3258     else if (confirm_delete && !do_confirm_erase (panel, source, &src_stat))
3259     {
3260         ret_val = FALSE;
3261         goto ret_fast;
3262     }
3263 
3264     tctx = file_op_total_context_new ();
3265     gettimeofday (&tctx->transfer_start, (struct timezone *) NULL);
3266 
3267 #ifdef ENABLE_BACKGROUND
3268     /* Did the user select to do a background operation? */
3269     if (do_bg)
3270     {
3271         int v;
3272 
3273         v = do_background (ctx,
3274                            g_strconcat (op_names[operation], ": ",
3275                                         vfs_path_as_str (panel->cwd_vpath), (char *) NULL));
3276         if (v == -1)
3277             message (D_ERROR, MSG_ERROR, _("Sorry, I could not put the job in background"));
3278 
3279         /* If we are the parent */
3280         if (v == 1)
3281         {
3282             mc_setctl (panel->cwd_vpath, VFS_SETCTL_FORGET, NULL);
3283 
3284             mc_setctl (dest_vpath, VFS_SETCTL_FORGET, NULL);
3285             vfs_path_free (dest_vpath, TRUE);
3286             g_free (dest);
3287             /*          file_op_context_destroy (ctx); */
3288             return FALSE;
3289         }
3290     }
3291     else
3292 #endif /* ENABLE_BACKGROUND */
3293     {
3294         if (operation == OP_DELETE)
3295             dialog_type = FILEGUI_DIALOG_DELETE_ITEM;
3296         else if (single_entry && S_ISDIR (selection (panel)->st.st_mode))
3297             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3298         else if (single_entry || force_single)
3299             dialog_type = FILEGUI_DIALOG_ONE_ITEM;
3300         else
3301             dialog_type = FILEGUI_DIALOG_MULTI_ITEM;
3302     }
3303 
3304     /* Initialize things */
3305     /* We do not want to trash cache every time file is
3306        created/touched. However, this will make our cache contain
3307        invalid data. */
3308     if ((dest != NULL)
3309         && (mc_setctl (dest_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3310         save_dest = g_strdup (dest);
3311 
3312     if ((vfs_path_tokens_count (panel->cwd_vpath) != 0)
3313         && (mc_setctl (panel->cwd_vpath, VFS_SETCTL_STALE_DATA, GUINT_TO_POINTER (1)) != 0))
3314         save_cwd = g_strdup (vfs_path_as_str (panel->cwd_vpath));
3315 
3316     /* Now, let's do the job */
3317 
3318     /* This code is only called by the tree and panel code */
3319     if (single_entry)
3320     {
3321         /* We now have ETA in all cases */
3322 
3323         /* One file: FIXME mc_chdir will take user out of any vfs */
3324         if ((operation != OP_COPY) && (get_current_type () == view_tree))
3325         {
3326             vfs_path_t *vpath;
3327             int chdir_retcode;
3328 
3329             vpath = vfs_path_from_str (PATH_SEP_STR);
3330             chdir_retcode = mc_chdir (vpath);
3331             vfs_path_free (vpath, TRUE);
3332             if (chdir_retcode < 0)
3333             {
3334                 ret_val = FALSE;
3335                 goto clean_up;
3336             }
3337         }
3338 
3339         value =
3340             operate_single_file (panel, operation, tctx, ctx, source, &src_stat, dest, dialog_type);
3341 
3342         if ((value == FILE_CONT) && !force_single)
3343             unmark_files (panel);
3344     }
3345     else
3346     {
3347         /* Many files */
3348 
3349         /* Check destination for copy or move operation */
3350         while (operation != OP_DELETE)
3351         {
3352             int dst_result;
3353             struct stat dst_stat;
3354 
3355             dst_result = mc_stat (dest_vpath, &dst_stat);
3356 
3357             if ((dst_result != 0) || S_ISDIR (dst_stat.st_mode))
3358                 break;
3359 
3360             if (ctx->skip_all
3361                 || file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"),
3362                                dest) != FILE_RETRY)
3363                 goto clean_up;
3364         }
3365 
3366         /* TODO: the good way is required to skip directories scanning in case of rename/move
3367          * of several directories. Since reqular expression can be used for destination,
3368          * some directory movements can be a cross-filesystem and directory scanning is useful
3369          * for those directories only. */
3370 
3371         if (panel_operate_init_totals (panel, NULL, NULL, ctx, file_op_compute_totals, dialog_type)
3372             == FILE_CONT)
3373         {
3374             /* Loop for every file, perform the actual copy operation */
3375             for (i = 0; i < panel->dir.len; i++)
3376             {
3377                 const char *source2;
3378 
3379                 if (!panel->dir.list[i].f.marked)
3380                     continue;   /* Skip the unmarked ones */
3381 
3382                 source2 = panel->dir.list[i].fname->str;
3383                 src_stat = panel->dir.list[i].st;
3384 
3385                 value = operate_one_file (panel, operation, tctx, ctx, source2, &src_stat, dest);
3386 
3387                 if (value == FILE_ABORT)
3388                     break;
3389 
3390                 if (value == FILE_CONT)
3391                     do_file_mark (panel, i, 0);
3392 
3393                 if (verbose && ctx->dialog_type == FILEGUI_DIALOG_MULTI_ITEM)
3394                 {
3395                     file_progress_show_count (ctx, tctx->progress_count, ctx->progress_count);
3396                     file_progress_show_total (tctx, ctx, tctx->progress_bytes, FALSE);
3397                 }
3398 
3399                 if (operation != OP_DELETE)
3400                     file_progress_show (ctx, 0, 0, "", FALSE);
3401 
3402                 if (check_progress_buttons (ctx) == FILE_ABORT)
3403                     break;
3404 
3405                 mc_refresh ();
3406             }                   /* Loop for every file */
3407         }
3408     }                           /* Many entries */
3409 
3410   clean_up:
3411     /* Clean up */
3412     if (save_cwd != NULL)
3413     {
3414         tmp_vpath = vfs_path_from_str (save_cwd);
3415         mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3416         vfs_path_free (tmp_vpath, TRUE);
3417         g_free (save_cwd);
3418     }
3419 
3420     if (save_dest != NULL)
3421     {
3422         tmp_vpath = vfs_path_from_str (save_dest);
3423         mc_setctl (tmp_vpath, VFS_SETCTL_STALE_DATA, NULL);
3424         vfs_path_free (tmp_vpath, TRUE);
3425         g_free (save_dest);
3426     }
3427 
3428     linklist = free_linklist (linklist);
3429     dest_dirs = free_linklist (dest_dirs);
3430     g_free (dest);
3431     vfs_path_free (dest_vpath, TRUE);
3432     MC_PTR_FREE (ctx->dest_mask);
3433 
3434 #ifdef ENABLE_BACKGROUND
3435     /* Let our parent know we are saying bye bye */
3436     if (mc_global.we_are_background)
3437     {
3438         int cur_pid = getpid ();
3439         /* Send pid to parent with child context, it is fork and
3440            don't modify real parent ctx */
3441         ctx->pid = cur_pid;
3442         parent_call ((void *) end_bg_process, ctx, 0);
3443 
3444         vfs_shut ();
3445         my_exit (EXIT_SUCCESS);
3446     }
3447 #endif /* ENABLE_BACKGROUND */
3448 
3449     file_op_total_context_destroy (tctx);
3450   ret_fast:
3451     file_op_context_destroy (ctx);
3452 
3453     return ret_val;
3454 }
3455 
3456 /* }}} */
3457 
3458 /* --------------------------------------------------------------------------------------------- */
3459 /* {{{ Query/status report routines */
3460 /** Report error with one file */
3461 FileProgressStatus
3462 file_error (gboolean allow_retry, const char *format, const char *file)
     /* [previous][next][first][last][top][bottom][index][help]  */
3463 {
3464     char buf[BUF_MEDIUM];
3465 
3466     g_snprintf (buf, sizeof (buf), format, path_trunc (file, 30), unix_error_string (errno));
3467 
3468     return do_file_error (allow_retry, buf);
3469 }
3470 
3471 /* --------------------------------------------------------------------------------------------- */
3472 
3473 /*
3474    Cause emacs to enter folding mode for this file:
3475    Local variables:
3476    end:
3477  */

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