Ticket #1711: i18n: context and cleanup in file prompt strings
[midnight-commander.git] / src / file.c
blob38739e1b8bd515a0b0b4602d9ac770aa648d9675
1 /* File management.
2 Copyright (C) 1994, 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003,
3 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
5 Written by: 1994, 1995 Janne Kukonlehto
6 1994, 1995 Fred Leeflang
7 1994, 1995, 1996 Miguel de Icaza
8 1995, 1996 Jakub Jelinek
9 1997 Norbert Warmuth
10 1998 Pavel Machek
12 The copy code was based in GNU's cp, and was written by:
13 Torbjorn Granlund, David MacKenzie, and Jim Meyering.
15 The move code was based in GNU's mv, and was written by:
16 Mike Parker and David MacKenzie.
18 Janne Kukonlehto added much error recovery to them for being used
19 in an interactive program.
21 This program is free software; you can redistribute it and/or modify
22 it under the terms of the GNU General Public License as published by
23 the Free Software Foundation; either version 2 of the License, or
24 (at your option) any later version.
26 This program is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 GNU General Public License for more details.
31 You should have received a copy of the GNU General Public License
32 along with this program; if not, write to the Free Software
33 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
36 * Please note that all dialogs used here must be safe for background
37 * operations.
40 /** \file file.c
41 * \brief Source: file management
44 /* {{{ Include files */
46 #include <config.h>
48 #include <ctype.h>
49 #include <errno.h>
50 #include <stdlib.h>
51 #include <stdio.h>
52 #include <string.h>
53 #include <sys/types.h>
54 #include <sys/stat.h>
55 #include <unistd.h>
56 #include <fcntl.h>
57 #include <sys/time.h>
59 #include "global.h"
61 #include "../src/tty/tty.h"
62 #include "../src/tty/key.h"
64 #include "../src/search/search.h"
66 #include "setup.h"
67 #include "dialog.h"
68 #include "widget.h"
69 #include "main.h"
70 #include "layout.h"
71 #include "widget.h"
72 #include "wtools.h"
73 #include "background.h" /* we_are_background */
74 #include "../src/strescape.h"
75 #include "strutil.h"
77 /* Needed for current_panel, other_panel and WTree */
78 #include "dir.h"
79 #include "panel.h"
80 #include "file.h"
81 #include "filegui.h"
82 #include "tree.h"
83 #include "../vfs/vfs-impl.h"
85 /* }}} */
87 /* Hack: the vfs code should not rely on this */
88 #define WITH_FULL_PATHS 1
90 int verbose = 1;
93 * Whether the Midnight Commander tries to provide more
94 * information about copy/move sizes and bytes transfered
95 * at the expense of some speed
97 int file_op_compute_totals = 1;
99 /* This is a hard link cache */
100 struct link {
101 struct link *next;
102 struct vfs_class *vfs;
103 dev_t dev;
104 ino_t ino;
105 short linkcount;
106 mode_t st_mode;
107 char name[1];
110 /* the hard link cache */
111 static struct link *linklist = NULL;
113 /* the files-to-be-erased list */
114 static struct link *erase_list;
117 * In copy_dir_dir we use two additional single linked lists: The first -
118 * variable name `parent_dirs' - holds information about already copied
119 * directories and is used to detect cyclic symbolic links.
120 * The second (`dest_dirs' below) holds information about just created
121 * target directories and is used to detect when an directory is copied
122 * into itself (we don't want to copy infinitly).
123 * Both lists don't use the linkcount and name structure members of struct
124 * link.
126 static struct link *dest_dirs = 0;
128 const char *op_names[3] = {
129 N_(" Copy "),
130 N_(" Move "),
131 N_(" Delete ")
134 /* }}} */
136 static FileProgressStatus query_replace (FileOpContext * ctx, const char *destname,
137 struct stat *_s_stat, struct stat *_d_stat);
138 static FileProgressStatus query_recursive (FileOpContext * ctx, const char *s);
139 static FileProgressStatus do_file_error (const char *str);
140 static FileProgressStatus erase_dir_iff_empty (FileOpContext *ctx, const char *s);
141 static FileProgressStatus erase_file (FileOpContext *ctx, const char *s,
142 off_t *progress_count, double *progress_bytes,
143 int is_toplevel_file);
144 static FileProgressStatus files_error (const char *format, const char *file1,
145 const char *file2);
148 enum CaseConvs { NO_CONV = 0, UP_CHAR = 1, LOW_CHAR = 2, UP_SECT =
149 4, LOW_SECT = 8 };
151 static FileProgressStatus transform_error = FILE_CONT;
153 static char *
154 transform_source (FileOpContext *ctx, const char *source)
156 char *s = g_strdup (source);
157 char *q;
158 char *fnsource = (char*)x_basename (s);
160 /* We remove \n from the filename since regex routines would use \n as an anchor */
161 /* this is just to be allowed to maniupulate file names with \n on it */
162 for (q = s; *q; q++) {
163 if (*q == '\n')
164 *q = ' ';
167 str_fix_string (fnsource);
169 if ( ! mc_search_run(ctx->search_handle, fnsource, 0, strlen (fnsource), NULL) ){
170 transform_error = FILE_SKIP;
171 g_free (s);
172 return NULL;
174 g_free (s);
175 return mc_search_prepare_replace_str2 (ctx->search_handle, ctx->dest_mask);
178 static void
179 free_linklist (struct link **linklist)
181 struct link *lp, *lp2;
183 for (lp = *linklist; lp != NULL; lp = lp2) {
184 lp2 = lp->next;
185 g_free (lp);
187 *linklist = NULL;
190 static int
191 is_in_linklist (struct link *lp, const char *path, struct stat *sb)
193 ino_t ino = sb->st_ino;
194 dev_t dev = sb->st_dev;
195 #ifdef USE_VFS
196 struct vfs_class *vfs = vfs_get_class (path);
197 #endif /* USE_VFS */
199 while (lp) {
200 #ifdef USE_VFS
201 if (lp->vfs == vfs)
202 #endif /* USE_VFS */
203 if (lp->ino == ino && lp->dev == dev)
204 return 1;
205 lp = lp->next;
207 return 0;
211 * Returns 0 if the inode wasn't found in the cache and 1 if it was found
212 * and a hardlink was succesfully made
214 static int
215 check_hardlinks (const char *src_name, const char *dst_name, struct stat *pstat)
217 struct link *lp;
218 struct vfs_class *my_vfs = vfs_get_class (src_name);
219 ino_t ino = pstat->st_ino;
220 dev_t dev = pstat->st_dev;
221 struct stat link_stat;
222 const char *p;
224 if (vfs_file_class_flags (src_name) & VFSF_NOLINKS)
225 return 0;
227 for (lp = linklist; lp != NULL; lp = lp->next)
228 if (lp->vfs == my_vfs && lp->ino == ino && lp->dev == dev) {
229 if (!mc_stat (lp->name, &link_stat) && link_stat.st_ino == ino
230 && link_stat.st_dev == dev
231 && vfs_get_class (lp->name) == my_vfs) {
232 p = strchr (lp->name, 0) + 1; /* i.e. where the `name' file
233 was copied to */
234 if (vfs_get_class (dst_name) == vfs_get_class (p)) {
235 if (!mc_stat (p, &link_stat)) {
236 if (!mc_link (p, dst_name))
237 return 1;
241 message (D_ERROR, MSG_ERROR, _(" Cannot make the hardlink "));
242 return 0;
244 lp = (struct link *) g_malloc (sizeof (struct link) + strlen (src_name)
245 + strlen (dst_name) + 1);
246 if (lp) {
247 char *lpdstname;
248 lp->vfs = my_vfs;
249 lp->ino = ino;
250 lp->dev = dev;
251 strcpy (lp->name, src_name);
252 lpdstname = lp->name + strlen(lp->name) + 1;
253 strcpy (lpdstname, dst_name);
254 lp->next = linklist;
255 linklist = lp;
257 return 0;
261 * Duplicate the contents of the symbolic link src_path in dst_path.
262 * Try to make a stable symlink if the option "stable symlink" was
263 * set in the file mask dialog.
264 * If dst_path is an existing symlink it will be deleted silently
265 * (upper levels take already care of existing files at dst_path).
267 static FileProgressStatus
268 make_symlink (FileOpContext *ctx, const char *src_path, const char *dst_path)
270 char link_target[MC_MAXPATHLEN];
271 int len;
272 FileProgressStatus return_status;
273 struct stat sb;
274 int dst_is_symlink;
276 if (mc_lstat (dst_path, &sb) == 0 && S_ISLNK (sb.st_mode))
277 dst_is_symlink = 1;
278 else
279 dst_is_symlink = 0;
281 retry_src_readlink:
282 len = mc_readlink (src_path, link_target, MC_MAXPATHLEN - 1);
283 if (len < 0) {
284 return_status =
285 file_error (_(" Cannot read source link \"%s\" \n %s "),
286 src_path);
287 if (return_status == FILE_RETRY)
288 goto retry_src_readlink;
289 return return_status;
291 link_target[len] = 0;
293 if (ctx->stable_symlinks)
294 if (!vfs_file_is_local (src_path) || !vfs_file_is_local (dst_path)) {
295 message (D_ERROR, MSG_ERROR,
296 _(" Cannot make stable symlinks across "
297 "non-local filesystems: \n\n"
298 " Option Stable Symlinks will be disabled "));
299 ctx->stable_symlinks = 0;
302 if (ctx->stable_symlinks && *link_target != PATH_SEP) {
303 char *p, *q, *s;
305 const char *r = strrchr (src_path, PATH_SEP);
307 if (r) {
308 p = g_strndup (src_path, r - src_path + 1);
309 if (*dst_path == PATH_SEP)
310 q = g_strdup (dst_path);
311 else
312 q = g_strconcat (p, dst_path, (char *) NULL);
313 s = strrchr (q, PATH_SEP);
314 if (s) {
315 s[1] = 0;
316 s = g_strconcat (p, link_target, (char *) NULL);
317 g_free (p);
318 g_strlcpy (link_target, s, sizeof (link_target));
319 g_free (s);
320 s = diff_two_paths (q, link_target);
321 if (s) {
322 g_strlcpy (link_target, s, sizeof (link_target));
323 g_free (s);
325 } else
326 g_free (p);
327 g_free (q);
330 retry_dst_symlink:
331 if (mc_symlink (link_target, dst_path) == 0)
332 /* Success */
333 return FILE_CONT;
335 * if dst_exists, it is obvious that this had failed.
336 * We can delete the old symlink and try again...
338 if (dst_is_symlink) {
339 if (!mc_unlink (dst_path))
340 if (mc_symlink (link_target, dst_path) == 0)
341 /* Success */
342 return FILE_CONT;
344 return_status =
345 file_error (_(" Cannot create target symlink \"%s\" \n %s "),
346 dst_path);
347 if (return_status == FILE_RETRY)
348 goto retry_dst_symlink;
349 return return_status;
352 static int
353 progress_update_one (FileOpContext *ctx,
354 off_t *progress_count,
355 double *progress_bytes, off_t add, int is_toplevel_file)
357 int ret;
359 if (is_toplevel_file || ctx->progress_totals_computed) {
360 (*progress_count)++;
361 (*progress_bytes) += add;
364 /* Apply some heuristic here to not call the update stuff very often */
365 ret =
366 file_progress_show_count (ctx, *progress_count,
367 ctx->progress_count);
368 if (ret != FILE_CONT)
369 return ret;
370 ret =
371 file_progress_show_bytes (ctx, *progress_bytes,
372 ctx->progress_bytes);
374 return ret;
377 /* Status of the destination file */
378 enum {
379 DEST_NONE, /* Not created */
380 DEST_SHORT, /* Created, not fully copied */
381 DEST_FULL /* Created, fully copied */
384 static FileProgressStatus
385 warn_same_file (const char *fmt, const char *a, const char *b)
387 char *msg;
388 int result = 0;
389 msg = g_strdup_printf (fmt, a, b);
390 result = query_dialog (MSG_ERROR, msg, D_ERROR, 2, _("&Skip"), _("&Abort"));
391 g_free(msg);
392 do_refresh ();
393 if ( result ) { /* 1 == Abort */
394 return FILE_ABORT;
395 } else {
396 return FILE_SKIP;
400 FileProgressStatus
401 copy_file_file (FileOpContext *ctx, const char *src_path, const char *dst_path,
402 int ask_overwrite, off_t *progress_count,
403 double *progress_bytes, int is_toplevel_file)
405 uid_t src_uid = (uid_t) - 1;
406 gid_t src_gid = (gid_t) - 1;
408 char *buf = NULL;
409 int buf_size = BUF_8K;
410 int src_desc, dest_desc = -1;
411 int n_read, n_written;
412 mode_t src_mode = 0; /* The mode of the source file */
413 struct stat sb, sb2;
414 struct utimbuf utb;
415 int dst_exists = 0, appending = 0;
416 off_t n_read_total = 0, file_size = -1;
417 FileProgressStatus return_status, temp_status;
418 struct timeval tv_transfer_start;
419 int dst_status = DEST_NONE; /* 1 if the file is not fully copied */
420 int open_flags;
422 /* FIXME: We should not be using global variables! */
423 ctx->do_reget = 0;
424 return_status = FILE_RETRY;
426 if (file_progress_show_source (ctx, src_path) == FILE_ABORT ||
427 file_progress_show_target (ctx, dst_path) == FILE_ABORT)
428 return FILE_ABORT;
430 mc_refresh ();
432 while (mc_stat (dst_path, &sb2) == 0) {
433 if (S_ISDIR (sb2.st_mode)) {
434 return_status =
435 file_error (_(" Cannot overwrite directory \"%s\" \n %s "),
436 dst_path);
437 if (return_status == FILE_RETRY)
438 continue;
439 return return_status;
441 dst_exists = 1;
442 break;
445 while ((*ctx->stat_func) (src_path, &sb)) {
446 return_status =
447 file_error (_(" Cannot stat source file \"%s\" \n %s "),
448 src_path);
449 if (return_status != FILE_RETRY)
450 return return_status;
453 if (dst_exists) {
454 /* Destination already exists */
455 if (sb.st_dev == sb2.st_dev && sb.st_ino == sb2.st_ino)
456 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "),
457 src_path, dst_path);
458 /* Should we replace destination? */
459 if (ask_overwrite) {
460 ctx->do_reget = 0;
461 return_status = query_replace (ctx, dst_path, &sb, &sb2);
462 if (return_status != FILE_CONT)
463 return return_status;
467 if (!ctx->do_append) {
468 /* Check the hardlinks */
469 if (!ctx->follow_links && sb.st_nlink > 1 &&
470 check_hardlinks (src_path, dst_path, &sb) == 1) {
471 /* We have made a hardlink - no more processing is necessary */
472 return FILE_CONT;
475 if (S_ISLNK (sb.st_mode))
476 return make_symlink (ctx, src_path, dst_path);
478 if (S_ISCHR (sb.st_mode) || S_ISBLK (sb.st_mode) ||
479 S_ISFIFO (sb.st_mode) || S_ISNAM (sb.st_mode) ||
480 S_ISSOCK (sb.st_mode)) {
481 while (mc_mknod (dst_path, sb.st_mode & ctx->umask_kill,
482 sb.st_rdev) < 0) {
483 return_status = file_error (
484 _(" Cannot create special file \"%s\" \n %s "), dst_path);
485 if (return_status == FILE_RETRY)
486 continue;
487 return return_status;
489 /* Success */
491 while (ctx->preserve_uidgid
492 && mc_chown (dst_path, sb.st_uid, sb.st_gid)) {
493 temp_status = file_error (
494 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
495 if (temp_status == FILE_RETRY)
496 continue;
497 return temp_status;
499 while (ctx->preserve &&
500 mc_chmod (dst_path, sb.st_mode & ctx->umask_kill)) {
501 temp_status = file_error (
502 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
503 if (temp_status == FILE_RETRY)
504 continue;
505 return temp_status;
507 return FILE_CONT;
511 gettimeofday (&tv_transfer_start, (struct timezone *) NULL);
513 while ((src_desc = mc_open (src_path, O_RDONLY | O_LINEAR)) < 0) {
514 return_status = file_error (
515 _(" Cannot open source file \"%s\" \n %s "), src_path);
516 if (return_status == FILE_RETRY)
517 continue;
518 ctx->do_append = 0;
519 return return_status;
522 if (ctx->do_reget) {
523 if (mc_lseek (src_desc, ctx->do_reget, SEEK_SET) != ctx->do_reget) {
524 message (D_ERROR, _("Warning"),
525 _(" Reget failed, about to overwrite file "));
526 ctx->do_reget = ctx->do_append = 0;
530 while (mc_fstat (src_desc, &sb)) {
531 return_status = file_error (
532 _(" Cannot fstat source file \"%s\" \n %s "), src_path);
533 if (return_status == FILE_RETRY)
534 continue;
535 ctx->do_append = 0;
536 goto ret;
538 src_mode = sb.st_mode;
539 src_uid = sb.st_uid;
540 src_gid = sb.st_gid;
541 utb.actime = sb.st_atime;
542 utb.modtime = sb.st_mtime;
543 file_size = sb.st_size;
545 open_flags = O_WRONLY;
546 if (dst_exists != 0) {
547 if (ctx->do_append != 0)
548 open_flags |= O_APPEND;
549 else
550 open_flags |= O_CREAT | O_TRUNC;
551 } else {
552 open_flags |= O_CREAT | O_EXCL;
554 while ((dest_desc = mc_open (dst_path, open_flags, src_mode)) < 0) {
555 if (errno == EEXIST) {
556 goto ret;
558 return_status = file_error (
559 _(" Cannot create target file \"%s\" \n %s "), dst_path);
560 if (return_status == FILE_RETRY)
561 continue;
562 ctx->do_append = 0;
563 goto ret;
565 dst_status = DEST_SHORT; /* file opened, but not fully copied */
567 appending = ctx->do_append;
568 ctx->do_append = 0;
570 /* Find out the optimal buffer size. */
571 while (mc_fstat (dest_desc, &sb)) {
572 return_status = file_error (
573 _(" Cannot fstat target file \"%s\" \n %s "), dst_path);
574 if (return_status == FILE_RETRY)
575 continue;
576 goto ret;
578 buf = g_malloc (buf_size);
580 ctx->eta_secs = 0.0;
581 ctx->bps = 0;
583 return_status = file_progress_show (ctx, 0, file_size);
585 mc_refresh ();
587 if (return_status != FILE_CONT)
588 goto ret;
591 struct timeval tv_current, tv_last_update, tv_last_input;
592 int secs, update_secs;
593 long dt;
594 const char *stalled_msg;
596 tv_last_update = tv_transfer_start;
598 for (;;) {
599 /* src_read */
600 if (mc_ctl (src_desc, VFS_CTL_IS_NOTREADY, 0))
601 n_read = -1;
602 else
603 while ((n_read = mc_read (src_desc, buf, buf_size)) < 0) {
604 return_status = file_error (
605 _(" Cannot read source file \"%s\" \n %s "), src_path);
606 if (return_status == FILE_RETRY)
607 continue;
608 goto ret;
610 if (n_read == 0)
611 break;
613 gettimeofday (&tv_current, NULL);
615 if (n_read > 0) {
616 char *t = buf;
617 n_read_total += n_read;
619 /* Windows NT ftp servers report that files have no
620 * permissions: -------, so if we happen to have actually
621 * read something, we should fix the permissions.
623 if (!(src_mode & (S_IRWXU | S_IRWXG | S_IRWXO)))
624 src_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
625 gettimeofday (&tv_last_input, NULL);
627 /* dst_write */
628 while ((n_written =
629 mc_write (dest_desc, t, n_read)) < n_read) {
630 if (n_written > 0) {
631 n_read -= n_written;
632 t += n_written;
633 continue;
635 return_status =
636 file_error (_(" Cannot write target file \"%s\" \n %s "),
637 dst_path);
638 if (return_status != FILE_RETRY)
639 goto ret;
643 /* 1. Update rotating dash after some time (hardcoded to 2 seconds) */
644 secs = (tv_current.tv_sec - tv_last_update.tv_sec);
645 if (secs > 2) {
646 rotate_dash ();
647 tv_last_update = tv_current;
650 /* 2. Check for a stalled condition */
651 update_secs = (tv_current.tv_sec - tv_last_input.tv_sec);
652 stalled_msg = "";
653 if (update_secs > 4) {
654 stalled_msg = _("(stalled)");
657 /* 3. Compute ETA */
658 if (secs > 2) {
659 dt = (tv_current.tv_sec - tv_transfer_start.tv_sec);
661 if (n_read_total) {
662 ctx->eta_secs =
663 ((dt / (double) n_read_total) * file_size) - dt;
664 ctx->bps = n_read_total / ((dt < 1) ? 1 : dt);
665 } else
666 ctx->eta_secs = 0.0;
669 /* 4. Compute BPS rate */
670 if (secs > 2) {
671 ctx->bps_time =
672 (tv_current.tv_sec - tv_transfer_start.tv_sec);
673 if (ctx->bps_time < 1)
674 ctx->bps_time = 1;
675 ctx->bps = n_read_total / ctx->bps_time;
678 file_progress_set_stalled_label (ctx, stalled_msg);
679 return_status = file_progress_show_bytes (ctx, *progress_bytes +
680 n_read_total + ctx->do_reget, ctx->progress_bytes);
681 if (return_status == FILE_CONT) {
682 return_status =
683 file_progress_show (ctx, n_read_total + ctx->do_reget, file_size);
685 mc_refresh ();
686 if (return_status != FILE_CONT)
687 goto ret;
691 dst_status = DEST_FULL; /* copy successful, don't remove target file */
693 ret:
694 g_free (buf);
696 while (src_desc != -1 && mc_close (src_desc) < 0) {
697 temp_status = file_error (
698 _(" Cannot close source file \"%s\" \n %s "), src_path);
699 if (temp_status == FILE_RETRY)
700 continue;
701 if (temp_status == FILE_ABORT)
702 return_status = temp_status;
703 break;
706 while (dest_desc != -1 && mc_close (dest_desc) < 0) {
707 temp_status = file_error (
708 _(" Cannot close target file \"%s\" \n %s "), dst_path);
709 if (temp_status == FILE_RETRY)
710 continue;
711 return_status = temp_status;
712 break;
715 if (dst_status == DEST_SHORT) {
716 /* Remove short file */
717 int result;
718 result = query_dialog (_("Copy"),
719 _("Incomplete file was retrieved. Keep it?"),
720 D_ERROR, 2, _("&Delete"), _("&Keep"));
721 if (!result)
722 mc_unlink (dst_path);
723 } else if (dst_status == DEST_FULL) {
724 /* Copy has succeeded */
725 if (!appending && ctx->preserve_uidgid) {
726 while (mc_chown (dst_path, src_uid, src_gid)) {
727 temp_status = file_error (
728 _(" Cannot chown target file \"%s\" \n %s "), dst_path);
729 if (temp_status == FILE_RETRY)
730 continue;
731 return_status = temp_status;
732 break;
736 if (!appending) {
737 if (ctx->preserve){
738 while (mc_chmod (dst_path, (src_mode & ctx->umask_kill))) {
739 temp_status = file_error (
740 _(" Cannot chmod target file \"%s\" \n %s "), dst_path);
741 if (temp_status != FILE_RETRY) {
742 return_status = temp_status;
743 break;
746 } else {
747 src_mode = umask(-1);
748 umask(src_mode);
749 src_mode = 0100666 & ~src_mode;
750 mc_chmod (dst_path, (src_mode & ctx->umask_kill));
752 mc_utime (dst_path, &utb);
756 if (return_status == FILE_CONT)
757 return_status =
758 progress_update_one (ctx, progress_count, progress_bytes,
759 file_size, is_toplevel_file);
761 return return_status;
765 * I think these copy_*_* functions should have a return type.
766 * anyway, this function *must* have two directories as arguments.
768 /* FIXME: This function needs to check the return values of the
769 function calls */
770 FileProgressStatus
771 copy_dir_dir (FileOpContext *ctx, const char *s, const char *_d, int toplevel,
772 int move_over, int delete, struct link *parent_dirs,
773 off_t *progress_count, double *progress_bytes)
775 struct dirent *next;
776 struct stat buf, cbuf;
777 DIR *reading;
778 char *dest_dir = NULL;
779 FileProgressStatus return_status = FILE_CONT;
780 struct utimbuf utb;
781 struct link *lp;
782 char *d;
784 d = strutils_shell_unescape (_d);
786 /* First get the mode of the source dir */
787 retry_src_stat:
788 if ((*ctx->stat_func) (s, &cbuf)) {
789 return_status =
790 file_error (_(" Cannot stat source directory \"%s\" \n %s "), s);
791 if (return_status == FILE_RETRY)
792 goto retry_src_stat;
793 goto ret_fast;
796 if (is_in_linklist (dest_dirs, s, &cbuf)) {
797 /* Don't copy a directory we created before (we don't want to copy
798 infinitely if a directory is copied into itself) */
799 /* FIXME: should there be an error message and FILE_SKIP? - Norbert */
800 return_status = FILE_CONT;
801 goto ret_fast;
804 /* Hmm, hardlink to directory??? - Norbert */
805 /* FIXME: In this step we should do something
806 in case the destination already exist */
807 /* Check the hardlinks */
808 if (ctx->preserve && cbuf.st_nlink > 1
809 && check_hardlinks (s, d, &cbuf) == 1) {
810 /* We have made a hardlink - no more processing is necessary */
811 goto ret_fast;
814 if (!S_ISDIR (cbuf.st_mode)) {
815 return_status =
816 file_error (_(" Source \"%s\" is not a directory \n %s "), s);
817 if (return_status == FILE_RETRY)
818 goto retry_src_stat;
819 goto ret_fast;
822 if (is_in_linklist (parent_dirs, s, &cbuf)) {
823 /* we found a cyclic symbolic link */
824 message (D_ERROR, MSG_ERROR,
825 _(" Cannot copy cyclic symbolic link \n `%s' "), s);
826 return_status = FILE_SKIP;
827 goto ret_fast;
830 lp = g_new (struct link, 1);
831 lp->vfs = vfs_get_class (s);
832 lp->ino = cbuf.st_ino;
833 lp->dev = cbuf.st_dev;
834 lp->next = parent_dirs;
835 parent_dirs = lp;
837 retry_dst_stat:
838 /* Now, check if the dest dir exists, if not, create it. */
839 if (mc_stat (d, &buf)) {
840 /* Here the dir doesn't exist : make it ! */
841 if (move_over) {
842 if (mc_rename (s, d) == 0) {
843 return_status = FILE_CONT;
844 goto ret;
847 dest_dir = d;
848 d = NULL;
849 } else {
851 * If the destination directory exists, we want to copy the whole
852 * directory, but we only want this to happen once.
854 * Escape sequences added to the * to compiler warnings.
855 * so, say /bla exists, if we copy /tmp/\* to /bla, we get /bla/tmp/\*
856 * or ( /bla doesn't exist ) /tmp/\* to /bla -> /bla/\*
858 if (!S_ISDIR (buf.st_mode)) {
859 return_status = file_error(
860 _(" Destination \"%s\" must be a directory \n %s "), d);
861 if (return_status == FILE_RETRY)
862 goto retry_dst_stat;
863 goto ret;
865 /* Dive into subdir if exists */
866 if (toplevel && ctx->dive_into_subdirs) {
867 dest_dir = concat_dir_and_file (d, x_basename (s));
868 } else {
869 dest_dir = d;
870 d = NULL;
871 goto dont_mkdir;
874 while (my_mkdir (dest_dir, (cbuf.st_mode & ctx->umask_kill) | S_IRWXU)) {
875 return_status = file_error (
876 _(" Cannot create target directory \"%s\" \n %s "), dest_dir);
877 if (return_status != FILE_RETRY)
878 goto ret;
881 lp = g_new (struct link, 1);
882 mc_stat (dest_dir, &buf);
883 lp->vfs = vfs_get_class (dest_dir);
884 lp->ino = buf.st_ino;
885 lp->dev = buf.st_dev;
886 lp->next = dest_dirs;
887 dest_dirs = lp;
889 if (ctx->preserve_uidgid) {
890 while (mc_chown (dest_dir, cbuf.st_uid, cbuf.st_gid)) {
891 return_status = file_error (
892 _(" Cannot chown target directory \"%s\" \n %s "), dest_dir);
893 if (return_status != FILE_RETRY)
894 goto ret;
898 dont_mkdir:
899 /* open the source dir for reading */
900 reading = mc_opendir (s);
901 if (reading == NULL)
902 goto ret;
904 while ((next = mc_readdir (reading)) && return_status != FILE_ABORT) {
905 char *path;
907 * Now, we don't want '.' and '..' to be created / copied at any time
909 if (!strcmp (next->d_name, "."))
910 continue;
911 if (!strcmp (next->d_name, ".."))
912 continue;
914 /* get the filename and add it to the src directory */
915 path = concat_dir_and_file (s, next->d_name);
917 (*ctx->stat_func) (path, &buf);
918 if (S_ISDIR (buf.st_mode)) {
919 char *mdpath;
921 mdpath = concat_dir_and_file (dest_dir, next->d_name);
923 * From here, we just intend to recursively copy subdirs, not
924 * the double functionality of copying different when the target
925 * dir already exists. So, we give the recursive call the flag 0
926 * meaning no toplevel.
928 return_status = copy_dir_dir (ctx, path, mdpath, 0, 0, delete,
929 parent_dirs, progress_count, progress_bytes);
930 g_free (mdpath);
931 } else {
932 char *dest_file;
934 dest_file = concat_dir_and_file (dest_dir, x_basename (path));
935 return_status = copy_file_file (ctx, path, dest_file, 1,
936 progress_count, progress_bytes, 0);
937 g_free (dest_file);
939 if (delete && return_status == FILE_CONT) {
940 if (ctx->erase_at_end) {
941 static struct link *tail;
942 size_t len = strlen (path);
943 lp = g_malloc (sizeof (struct link) + len);
944 strncpy (lp->name, path, len + 1);
945 lp->st_mode = buf.st_mode;
946 lp->next = NULL;
947 if (erase_list != NULL) {
948 tail->next = lp;
949 tail = lp;
950 } else
951 erase_list = tail = lp;
952 } else {
953 if (S_ISDIR (buf.st_mode)) {
954 return_status = erase_dir_iff_empty (ctx, path);
955 } else
956 return_status = erase_file (ctx, path, 0, 0, 0);
959 g_free (path);
961 mc_closedir (reading);
963 if (ctx->preserve) {
964 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
965 utb.actime = cbuf.st_atime;
966 utb.modtime = cbuf.st_mtime;
967 mc_utime (dest_dir, &utb);
968 } else {
969 cbuf.st_mode = umask(-1);
970 umask(cbuf.st_mode);
971 cbuf.st_mode = 0100777 & ~cbuf.st_mode;
972 mc_chmod (dest_dir, cbuf.st_mode & ctx->umask_kill);
975 ret:
976 g_free (dest_dir);
977 g_free (parent_dirs);
978 ret_fast:
979 g_free (d);
980 return return_status;
983 /* }}} */
985 /* {{{ Move routines */
987 static FileProgressStatus
988 move_file_file (FileOpContext *ctx, const char *s, const char *d,
989 off_t *progress_count, double *progress_bytes)
991 struct stat src_stats, dst_stats;
992 FileProgressStatus return_status = FILE_CONT;
993 gboolean copy_done = FALSE;
995 if (file_progress_show_source (ctx, s) == FILE_ABORT
996 || file_progress_show_target (ctx, d) == FILE_ABORT)
997 return FILE_ABORT;
999 mc_refresh ();
1001 while (mc_lstat (s, &src_stats) != 0) {
1002 /* Source doesn't exist */
1003 return_status =
1004 file_error (_(" Cannot stat file \"%s\" \n %s "), s);
1005 if (return_status != FILE_RETRY)
1006 return return_status;
1009 if (mc_lstat (d, &dst_stats) == 0) {
1010 if (src_stats.st_dev == dst_stats.st_dev
1011 && src_stats.st_ino == dst_stats.st_ino)
1012 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same file "), s, d);
1014 if (S_ISDIR (dst_stats.st_mode)) {
1015 message (D_ERROR, MSG_ERROR,
1016 _(" Cannot overwrite directory `%s' "), d);
1017 do_refresh ();
1018 return FILE_SKIP;
1021 if (confirm_overwrite) {
1022 return_status = query_replace (ctx, d, &src_stats, &dst_stats);
1023 if (return_status != FILE_CONT)
1024 return return_status;
1026 /* Ok to overwrite */
1029 if (!ctx->do_append) {
1030 if (S_ISLNK (src_stats.st_mode) && ctx->stable_symlinks) {
1031 if ((return_status = make_symlink (ctx, s, d)) == FILE_CONT) {
1032 goto retry_src_remove;
1033 } else
1034 return return_status;
1037 if (mc_rename (s, d) == 0) {
1038 return progress_update_one (ctx, progress_count,
1039 progress_bytes,
1040 src_stats.st_size, 1);
1043 #if 0
1044 /* Comparison to EXDEV seems not to work in nfs if you're moving from
1045 one nfs to the same, but on the server it is on two different
1046 filesystems. Then nfs returns EIO instead of EXDEV.
1047 Hope it will not hurt if we always in case of error try to copy/delete. */
1048 else
1049 errno = EXDEV; /* Hack to copy (append) the file and then delete it */
1051 if (errno != EXDEV) {
1052 return_status =
1053 files_error (_(" Cannot move file \"%s\" to \"%s\" \n %s "), s,
1055 if (return_status == FILE_RETRY)
1056 goto retry_rename;
1057 return return_status;
1059 #endif
1061 /* Failed because filesystem boundary -> copy the file instead */
1062 return_status =
1063 copy_file_file (ctx, s, d, 0, progress_count, progress_bytes, 1);
1064 if (return_status != FILE_CONT)
1065 return return_status;
1067 copy_done = TRUE;
1069 if ((return_status =
1070 file_progress_show_source (ctx, NULL)) != FILE_CONT
1071 || (return_status = file_progress_show (ctx, 0, 0)) != FILE_CONT)
1072 return return_status;
1074 mc_refresh ();
1076 retry_src_remove:
1077 if (mc_unlink (s)) {
1078 return_status =
1079 file_error (_(" Cannot remove file \"%s\" \n %s "), s);
1080 if (return_status == FILE_RETRY)
1081 goto retry_src_remove;
1082 return return_status;
1085 if (!copy_done) {
1086 return_status = progress_update_one (ctx,
1087 progress_count,
1088 progress_bytes,
1089 src_stats.st_size, 1);
1092 return return_status;
1095 FileProgressStatus
1096 move_dir_dir (FileOpContext *ctx, const char *s, const char *d,
1097 off_t *progress_count, double *progress_bytes)
1099 struct stat sbuf, dbuf, destbuf;
1100 struct link *lp;
1101 char *destdir;
1102 FileProgressStatus return_status;
1103 gboolean move_over = FALSE;
1104 gboolean dstat_ok;
1106 if (file_progress_show_source (ctx, s) == FILE_ABORT ||
1107 file_progress_show_target (ctx, d) == FILE_ABORT)
1108 return FILE_ABORT;
1110 mc_refresh ();
1112 mc_stat (s, &sbuf);
1113 dstat_ok = (mc_stat (d, &dbuf) == 0);
1115 if (dstat_ok && sbuf.st_dev == dbuf.st_dev && sbuf.st_ino == dbuf.st_ino)
1116 return warn_same_file (_(" `%s' \n and \n `%s' \n are the same directory "), s, d);
1118 if (!dstat_ok)
1119 destdir = g_strdup (d); /* destination doesn't exist */
1120 else if (!ctx->dive_into_subdirs) {
1121 destdir = g_strdup (d);
1122 move_over = TRUE;
1123 } else
1124 destdir = concat_dir_and_file (d, x_basename (s));
1126 /* Check if the user inputted an existing dir */
1127 retry_dst_stat:
1128 if (!mc_stat (destdir, &destbuf)) {
1129 if (move_over) {
1130 return_status = copy_dir_dir (ctx, s, destdir, 0, 1, 1, 0,
1131 progress_count, progress_bytes);
1133 if (return_status != FILE_CONT)
1134 goto ret;
1135 goto oktoret;
1136 } else {
1137 if (S_ISDIR (destbuf.st_mode))
1138 return_status =
1139 file_error (_
1140 (" Cannot overwrite directory \"%s\" %s "),
1141 destdir);
1142 else
1143 return_status =
1144 file_error (_(" Cannot overwrite file \"%s\" %s "),
1145 destdir);
1146 if (return_status == FILE_RETRY)
1147 goto retry_dst_stat;
1149 g_free (destdir);
1150 return return_status;
1153 retry_rename:
1154 if (mc_rename (s, destdir) == 0) {
1155 return_status = FILE_CONT;
1156 goto ret;
1159 if (errno != EXDEV) {
1160 return_status =
1161 files_error (_
1162 (" Cannot move directory \"%s\" to \"%s\" \n %s "),
1163 s, d);
1164 if (return_status == FILE_RETRY)
1165 goto retry_rename;
1166 goto ret;
1168 /* Failed because of filesystem boundary -> copy dir instead */
1169 return_status =
1170 copy_dir_dir (ctx, s, destdir, 0, 0, 1, 0, progress_count,
1171 progress_bytes);
1173 if (return_status != FILE_CONT)
1174 goto ret;
1175 oktoret:
1176 if ((return_status =
1177 file_progress_show_source (ctx, NULL)) != FILE_CONT
1178 || (return_status = file_progress_show (ctx, 0, 0)) != FILE_CONT)
1179 goto ret;
1181 mc_refresh ();
1182 if (ctx->erase_at_end) {
1183 for (; erase_list && return_status != FILE_ABORT;) {
1184 if (S_ISDIR (erase_list->st_mode)) {
1185 return_status =
1186 erase_dir_iff_empty (ctx, erase_list->name);
1187 } else
1188 return_status =
1189 erase_file (ctx, erase_list->name, 0, 0, 0);
1190 lp = erase_list;
1191 erase_list = erase_list->next;
1192 g_free (lp);
1195 erase_dir_iff_empty (ctx, s);
1197 ret:
1198 g_free (destdir);
1199 while (erase_list) {
1200 lp = erase_list;
1201 erase_list = erase_list->next;
1202 g_free (lp);
1204 return return_status;
1207 /* }}} */
1209 /* {{{ Erase routines */
1210 /* Don't update progress status if progress_count==NULL */
1211 static FileProgressStatus
1212 erase_file (FileOpContext *ctx, const char *s, off_t *progress_count,
1213 double *progress_bytes, int is_toplevel_file)
1215 int return_status;
1216 struct stat buf;
1218 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1219 return FILE_ABORT;
1220 mc_refresh ();
1222 if (progress_count && mc_lstat (s, &buf)) {
1223 /* ignore, most likely the mc_unlink fails, too */
1224 buf.st_size = 0;
1227 while (mc_unlink (s)) {
1228 return_status =
1229 file_error (_(" Cannot delete file \"%s\" \n %s "), s);
1230 if (return_status != FILE_RETRY)
1231 return return_status;
1234 if (progress_count)
1235 return progress_update_one (ctx, progress_count, progress_bytes,
1236 buf.st_size, is_toplevel_file);
1237 else
1238 return FILE_CONT;
1241 static FileProgressStatus
1242 recursive_erase (FileOpContext *ctx, const char *s, off_t *progress_count,
1243 double *progress_bytes)
1245 struct dirent *next;
1246 struct stat buf;
1247 DIR *reading;
1248 char *path;
1249 FileProgressStatus return_status = FILE_CONT;
1251 if (!strcmp (s, ".."))
1252 return FILE_RETRY;
1254 reading = mc_opendir (s);
1256 if (!reading)
1257 return FILE_RETRY;
1259 while ((next = mc_readdir (reading)) && return_status == FILE_CONT) {
1260 if (!strcmp (next->d_name, "."))
1261 continue;
1262 if (!strcmp (next->d_name, ".."))
1263 continue;
1264 path = concat_dir_and_file (s, next->d_name);
1265 if (mc_lstat (path, &buf)) {
1266 g_free (path);
1267 mc_closedir (reading);
1268 return FILE_RETRY;
1270 if (S_ISDIR (buf.st_mode))
1271 return_status =
1272 (recursive_erase
1273 (ctx, path, progress_count, progress_bytes)
1274 != FILE_CONT) ? FILE_RETRY : FILE_CONT;
1275 else
1276 return_status =
1277 erase_file (ctx, path, progress_count, progress_bytes, 0);
1278 g_free (path);
1280 mc_closedir (reading);
1281 if (return_status != FILE_CONT)
1282 return return_status;
1283 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1284 return FILE_ABORT;
1285 mc_refresh ();
1287 while (my_rmdir (s)) {
1288 return_status =
1289 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1290 if (return_status != FILE_RETRY)
1291 return return_status;
1294 return FILE_CONT;
1297 /* Return -1 on error, 1 if there are no entries besides "." and ".."
1298 in the directory path points to, 0 else. */
1299 static int
1300 check_dir_is_empty (const char *path)
1302 DIR *dir;
1303 struct dirent *d;
1304 int i;
1306 dir = mc_opendir (path);
1307 if (!dir)
1308 return -1;
1310 for (i = 1, d = mc_readdir (dir); d; d = mc_readdir (dir)) {
1311 if (d->d_name[0] == '.' && (d->d_name[1] == '\0' ||
1312 (d->d_name[1] == '.'
1313 && d->d_name[2] == '\0')))
1314 continue; /* "." or ".." */
1315 i = 0;
1316 break;
1319 mc_closedir (dir);
1320 return i;
1323 FileProgressStatus
1324 erase_dir (FileOpContext *ctx, const char *s, off_t *progress_count,
1325 double *progress_bytes)
1327 FileProgressStatus error;
1329 if (strcmp (s, "..") == 0)
1330 return FILE_SKIP;
1332 if (strcmp (s, ".") == 0)
1333 return FILE_SKIP;
1335 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1336 return FILE_ABORT;
1337 mc_refresh ();
1339 /* The old way to detect a non empty directory was:
1340 error = my_rmdir (s);
1341 if (error && (errno == ENOTEMPTY || errno == EEXIST))){
1342 For the linux user space nfs server (nfs-server-2.2beta29-2)
1343 we would have to check also for EIO. I hope the new way is
1344 fool proof. (Norbert)
1346 error = check_dir_is_empty (s);
1347 if (error == 0) { /* not empty */
1348 error = query_recursive (ctx, s);
1349 if (error == FILE_CONT)
1350 return recursive_erase (ctx, s, progress_count,
1351 progress_bytes);
1352 else
1353 return error;
1356 while (my_rmdir (s) == -1) {
1357 error =
1358 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1359 if (error != FILE_RETRY)
1360 return error;
1363 return FILE_CONT;
1366 static FileProgressStatus
1367 erase_dir_iff_empty (FileOpContext *ctx, const char *s)
1369 FileProgressStatus error;
1371 if (strcmp (s, "..") == 0)
1372 return FILE_SKIP;
1374 if (strcmp (s, ".") == 0)
1375 return FILE_SKIP;
1377 if (file_progress_show_deleting (ctx, s) == FILE_ABORT)
1378 return FILE_ABORT;
1379 mc_refresh ();
1381 if (1 != check_dir_is_empty (s)) /* not empty or error */
1382 return FILE_CONT;
1384 while (my_rmdir (s)) {
1385 error =
1386 file_error (_(" Cannot remove directory \"%s\" \n %s "), s);
1387 if (error != FILE_RETRY)
1388 return error;
1391 return FILE_CONT;
1394 /* }}} */
1396 /* {{{ Panel operate routines */
1399 * Return currently selected entry name or the name of the first marked
1400 * entry if there is one.
1402 static char *
1403 panel_get_file (WPanel *panel, struct stat *stat_buf)
1405 int i;
1407 if (get_current_type () == view_tree) {
1408 WTree *tree = (WTree *) get_panel_widget (get_current_index ());
1409 char *tree_name = tree_selected_name (tree);
1411 mc_stat (tree_name, stat_buf);
1412 return tree_name;
1415 if (panel->marked) {
1416 for (i = 0; i < panel->count; i++)
1417 if (panel->dir.list[i].f.marked) {
1418 *stat_buf = panel->dir.list[i].st;
1419 return panel->dir.list[i].fname;
1421 } else {
1422 *stat_buf = panel->dir.list[panel->selected].st;
1423 return panel->dir.list[panel->selected].fname;
1425 g_assert_not_reached ();
1426 return NULL;
1430 ComputeDirSizeUI *
1431 compute_dir_size_create_ui (void)
1433 ComputeDirSizeUI *ui;
1435 const char *b_name = N_("&Abort");
1437 #ifdef ENABLE_NLS
1438 b_name = _(b_name);
1439 #endif
1441 ui = g_new (ComputeDirSizeUI, 1);
1443 ui->dlg = create_dlg (0, 0, 8, COLS/2, dialog_colors, NULL,
1444 NULL, _("Directory scanning"), DLG_CENTER);
1445 ui->dirname = label_new (3, 3, "");
1446 add_widget (ui->dlg, ui->dirname);
1448 add_widget (ui->dlg,
1449 button_new (5, (ui->dlg->cols - strlen (b_name))/2,
1450 FILE_ABORT, NORMAL_BUTTON, b_name, NULL));
1452 /* We will manage the dialog without any help,
1453 that's why we have to call init_dlg */
1454 init_dlg (ui->dlg);
1456 return ui;
1459 void
1460 compute_dir_size_destroy_ui (ComputeDirSizeUI *ui)
1462 if (ui != NULL) {
1463 /* schedule to update passive panel */
1464 other_panel->dirty = 1;
1466 /* close and destroy dialog */
1467 dlg_run_done (ui->dlg);
1468 destroy_dlg (ui->dlg);
1469 g_free (ui);
1473 FileProgressStatus
1474 compute_dir_size_update_ui (const void *ui, const char *dirname)
1476 const ComputeDirSizeUI *this = (const ComputeDirSizeUI *) ui;
1477 int c;
1478 Gpm_Event event;
1480 if (ui == NULL)
1481 return FILE_CONT;
1483 label_set_text (this->dirname, name_trunc (dirname, this->dlg->cols - 6));
1485 event.x = -1; /* Don't show the GPM cursor */
1486 c = tty_get_event (&event, FALSE, FALSE);
1487 if (c == EV_NONE)
1488 return FILE_CONT;
1490 /* Reinitialize to avoid old values after events other than
1491 selecting a button */
1492 this->dlg->ret_value = FILE_CONT;
1494 dlg_process_event (this->dlg, c, &event);
1496 switch (this->dlg->ret_value) {
1497 case B_CANCEL:
1498 case FILE_ABORT:
1499 return FILE_ABORT;
1500 default:
1501 return FILE_CONT;
1506 * compute_dir_size:
1508 * Computes the number of bytes used by the files in a directory
1510 FileProgressStatus
1511 compute_dir_size (const char *dirname, const void *ui,
1512 compute_dir_size_callback cback,
1513 off_t *ret_marked, double *ret_total)
1515 DIR *dir;
1516 struct dirent *dirent;
1517 FileProgressStatus ret = FILE_CONT;
1519 dir = mc_opendir (dirname);
1521 if (dir == NULL)
1522 return ret;
1524 while ((dirent = mc_readdir (dir)) != NULL) {
1525 char *fullname;
1526 int res;
1527 struct stat s;
1529 ret = (cback != NULL) ? cback (ui, dirname) : FILE_CONT;
1531 if (ret != FILE_CONT)
1532 break;
1534 if (strcmp (dirent->d_name, ".") == 0)
1535 continue;
1536 if (strcmp (dirent->d_name, "..") == 0)
1537 continue;
1539 fullname = concat_dir_and_file (dirname, dirent->d_name);
1540 res = mc_lstat (fullname, &s);
1542 if (res != 0) {
1543 g_free (fullname);
1544 continue;
1547 if (S_ISDIR (s.st_mode)) {
1548 off_t subdir_count = 0;
1549 double subdir_bytes = 0;
1551 ret = compute_dir_size (fullname, ui, cback, &subdir_count, &subdir_bytes);
1553 if (ret != FILE_CONT) {
1554 g_free (fullname);
1555 break;
1558 *ret_marked += subdir_count;
1559 *ret_total += subdir_bytes;
1560 } else {
1561 (*ret_marked)++;
1562 *ret_total += s.st_size;
1565 g_free (fullname);
1568 mc_closedir (dir);
1570 return ret;
1574 * panel_compute_totals:
1576 * compute the number of files and the number of bytes
1577 * used up by the whole selection, recursing directories
1578 * as required. In addition, it checks to see if it will
1579 * overwrite any files by doing the copy.
1581 static FileProgressStatus
1582 panel_compute_totals (WPanel *panel, const void *ui,
1583 compute_dir_size_callback cback,
1584 off_t *ret_marked, double *ret_total)
1586 int i;
1588 *ret_marked = 0;
1589 *ret_total = 0.0;
1591 for (i = 0; i < panel->count; i++) {
1592 struct stat *s;
1594 if (!panel->dir.list[i].f.marked)
1595 continue;
1597 s = &panel->dir.list[i].st;
1599 if (S_ISDIR (s->st_mode)) {
1600 char *dir_name;
1601 off_t subdir_count = 0;
1602 double subdir_bytes = 0;
1603 FileProgressStatus status;
1605 dir_name =
1606 concat_dir_and_file (panel->cwd, panel->dir.list[i].fname);
1608 status = compute_dir_size (dir_name, ui, cback,
1609 &subdir_count, &subdir_bytes);
1610 g_free (dir_name);
1612 if (status != FILE_CONT)
1613 return FILE_ABORT;
1615 *ret_marked += subdir_count;
1616 *ret_total += subdir_bytes;
1617 } else {
1618 (*ret_marked)++;
1619 *ret_total += s->st_size;
1623 return FILE_CONT;
1627 * This array introduced to avoid translation problems. The former (op_names)
1628 * is assumed to be nouns, suitable in dialog box titles; this one should
1629 * contain whatever is used in prompt itself (i.e. in russian, it's verb).
1630 * (I don't use spaces around the words, because someday they could be
1631 * dropped, when widgets get smarter)
1634 /* TRANSLATORS: no need to translate 'fileop', it's just a context prefix */
1635 static const char *op_names1[] = {
1636 N_("fileop|Copy"),
1637 N_("fileop|Move"),
1638 N_("fileop|Delete")
1642 * These are formats for building a prompt. Parts encoded as follows:
1643 * %o - operation from op_names1
1644 * %f - file/files or files/directories, as appropriate
1645 * %m - "with source mask" or question mark for delete
1646 * %s - source name (truncated)
1647 * %d - number of marked files
1648 * %e - "to:" or question mark for delete
1650 * xgettext:no-c-format */
1651 static const char *one_format = N_("%o %f \"%s\"%m");
1652 /* xgettext:no-c-format */
1653 static const char *many_format = N_("%o %d %f%m");
1655 static const char *prompt_parts[] = {
1656 N_("file"),
1657 N_("files"),
1658 N_("directory"),
1659 N_("directories"),
1660 N_("files/directories"),
1661 N_(" with source mask:"),
1662 N_(" to:")
1665 static const char *question_format = N_("%s?");
1668 * Generate user prompt for panel operation.
1669 * single_source is the name if the source entry or NULL for multiple
1670 * entries.
1671 * src_stat is only used when single_source is not NULL.
1673 static char *
1674 panel_operate_generate_prompt (const WPanel *panel, const int operation,
1675 gboolean single_source,
1676 const struct stat *src_stat)
1678 const char *sp, *cp;
1679 int i;
1680 char format_string[BUF_MEDIUM];
1681 char *dp = format_string;
1682 gboolean build_question = FALSE;
1684 #ifdef ENABLE_NLS
1685 static gboolean i18n_flag = FALSE;
1686 if (!i18n_flag) {
1687 for (i = sizeof (op_names1) / sizeof (op_names1[0]); i--;)
1688 op_names1[i] = Q_(op_names1[i]);
1690 for (i = sizeof (prompt_parts) / sizeof (prompt_parts[0]); i--;)
1691 prompt_parts[i] = _(prompt_parts[i]);
1693 one_format = _(one_format);
1694 many_format = _(many_format);
1695 question_format = _(question_format);
1696 i18n_flag = TRUE;
1698 #endif /* ENABLE_NLS */
1700 sp = single_source ? one_format : many_format;
1702 while (*sp != '\0') {
1703 switch (*sp) {
1704 case '%':
1705 cp = NULL;
1706 switch (sp[1]) {
1707 case 'o':
1708 cp = op_names1[operation];
1709 break;
1710 case 'm':
1711 if (operation == OP_DELETE) {
1712 cp = "";
1713 build_question = TRUE;
1714 } else
1715 cp = prompt_parts[5];
1716 break;
1717 case 'e':
1718 if (operation == OP_DELETE) {
1719 cp = "";
1720 build_question = TRUE;
1721 } else
1722 cp = prompt_parts[6];
1723 break;
1724 case 'f':
1725 if (single_source) {
1726 cp = S_ISDIR (src_stat->
1727 st_mode) ? prompt_parts[2] : prompt_parts[0];
1728 } else {
1729 cp = (panel->marked == panel->dirs_marked)
1730 ? prompt_parts[3]
1731 : (panel->dirs_marked ? prompt_parts[4] : prompt_parts[1]);
1733 break;
1734 default:
1735 *dp++ = *sp++;
1737 if (cp != NULL) {
1738 sp += 2;
1739 while (*cp != '\0')
1740 *dp++ = *cp++;
1742 break;
1743 default:
1744 *dp++ = *sp++;
1747 *dp = '\0';
1749 if (build_question) {
1750 const char *tmp;
1752 tmp = g_strdup (format_string);
1753 g_snprintf (format_string, sizeof (format_string),
1754 question_format, tmp);
1755 g_free (tmp);
1758 return g_strdup (format_string);
1762 * panel_operate:
1764 * Performs one of the operations on the selection on the source_panel
1765 * (copy, delete, move).
1767 * Returns 1 if did change the directory
1768 * structure, Returns 0 if user aborted
1770 * force_single forces operation on the current entry and affects
1771 * default destination. Current filename is used as default.
1774 panel_operate (void *source_panel, FileOperation operation,
1775 int force_single)
1777 WPanel *panel = source_panel;
1778 char *source = NULL;
1779 #ifdef WITH_FULL_PATHS
1780 char *source_with_path = NULL;
1781 #else
1782 # define source_with_path source
1783 #endif /* !WITH_FULL_PATHS */
1784 char *dest = NULL;
1785 char *temp = NULL;
1786 char *save_cwd = NULL, *save_dest = NULL;
1787 int single_entry = (get_current_type () == view_tree)
1788 || (panel->marked <= 1) || force_single;
1789 struct stat src_stat, dst_stat;
1790 int i;
1791 FileProgressStatus value;
1792 FileOpContext *ctx;
1794 off_t count = 0;
1795 double bytes = 0;
1797 int dst_result;
1798 int do_bg = 0; /* do background operation? */
1800 free_linklist (&linklist);
1801 free_linklist (&dest_dirs);
1803 /* Update panel contents to avoid actions on deleted files */
1804 if (!panel->is_panelized) {
1805 update_panels (UP_RELOAD, UP_KEEPSEL);
1806 repaint_screen ();
1809 if (single_entry) {
1810 if (force_single) {
1811 source = selection (panel)->fname;
1812 src_stat = selection (panel)->st;
1813 } else {
1814 source = panel_get_file (panel, &src_stat);
1817 if (!strcmp (source, "..")) {
1818 message (D_ERROR, MSG_ERROR, _(" Cannot operate on \"..\"! "));
1819 return 0;
1823 ctx = file_op_context_new (operation);
1825 /* Show confirmation dialog */
1826 if (operation != OP_DELETE) {
1827 char *dest_dir;
1828 char *dest_dir_;
1829 char *format;
1831 /* Forced single operations default to the original name */
1832 if (force_single)
1833 dest_dir = source;
1834 else if (get_other_type () == view_listing)
1835 dest_dir = other_panel->cwd;
1836 else
1837 dest_dir = panel->cwd;
1839 * Add trailing backslash only when do non-local ops.
1840 * It saves user from occasional file renames (when destination
1841 * dir is deleted)
1843 if (!force_single
1844 && dest_dir[0] != '\0'
1845 && dest_dir[strlen (dest_dir) - 1] != PATH_SEP) {
1846 /* add trailing separator */
1847 dest_dir_ = g_strconcat (dest_dir, PATH_SEP_STR, (char *) NULL);
1848 } else {
1849 /* just copy */
1850 dest_dir_ = g_strdup (dest_dir);
1852 if (dest_dir_ == NULL) {
1853 file_op_context_destroy (ctx);
1854 return 0;
1857 /* Generate confirmation prompt */
1858 format = panel_operate_generate_prompt (panel, operation,
1859 source != NULL, &src_stat);
1861 dest = file_mask_dialog (ctx, operation, source != NULL,
1862 format, source != NULL ? source : &panel->marked,
1863 dest_dir_, &do_bg);
1865 g_free (format);
1866 g_free (dest_dir_);
1868 if (dest == NULL || dest[0] == '\0') {
1869 file_op_context_destroy (ctx);
1870 g_free (dest);
1871 return 0;
1873 } else if (confirm_delete) {
1874 char *format;
1875 char fmd_buf [BUF_MEDIUM];
1877 /* Generate confirmation prompt */
1878 format = panel_operate_generate_prompt (panel, OP_DELETE,
1879 source != NULL, &src_stat);
1881 if (source == NULL)
1882 g_snprintf (fmd_buf, sizeof (fmd_buf), format, panel->marked);
1883 else {
1884 const int fmd_xlen = 64;
1885 i = fmd_xlen - str_term_width1 (format) - 4;
1886 g_snprintf (fmd_buf, sizeof (fmd_buf),
1887 format, str_trunc (source, i));
1890 g_free (format);
1892 if (safe_delete)
1893 query_set_sel (1);
1895 i = query_dialog (_(op_names[operation]), fmd_buf, D_ERROR, 2,
1896 _("&Yes"), _("&No"));
1898 if (i != 0) {
1899 file_op_context_destroy (ctx);
1900 return 0;
1904 #ifdef WITH_BACKGROUND
1905 /* Did the user select to do a background operation? */
1906 if (do_bg) {
1907 int v;
1909 v = do_background (ctx,
1910 g_strconcat (op_names[operation], ": ",
1911 panel->cwd, NULL));
1912 if (v == -1) {
1913 message (D_ERROR, MSG_ERROR,
1914 _(" Sorry, I could not put the job in background "));
1917 /* If we are the parent */
1918 if (v == 1) {
1919 mc_setctl (panel->cwd, VFS_SETCTL_FORGET, NULL);
1920 mc_setctl (dest, VFS_SETCTL_FORGET, NULL);
1921 /* file_op_context_destroy (ctx); */
1922 return 0;
1925 #endif /* WITH_BACKGROUND */
1927 /* Initialize things */
1928 /* We do not want to trash cache every time file is
1929 created/touched. However, this will make our cache contain
1930 invalid data. */
1931 if (dest) {
1932 if (mc_setctl (dest, VFS_SETCTL_STALE_DATA, (void *) 1))
1933 save_dest = g_strdup (dest);
1935 if (panel->cwd) {
1936 if (mc_setctl (panel->cwd, VFS_SETCTL_STALE_DATA, (void *) 1))
1937 save_cwd = g_strdup (panel->cwd);
1940 /* Now, let's do the job */
1942 if (do_bg)
1943 ctx->ui = NULL;
1944 else
1945 file_op_context_create_ui (ctx, 1);
1947 /* This code is only called by the tree and panel code */
1948 if (single_entry) {
1949 /* We now have ETA in all cases */
1951 /* One file: FIXME mc_chdir will take user out of any vfs */
1952 if (operation != OP_COPY && get_current_type () == view_tree)
1953 mc_chdir (PATH_SEP_STR);
1955 /* The source and src_stat variables have been initialized before */
1956 #ifdef WITH_FULL_PATHS
1957 source_with_path = concat_dir_and_file (panel->cwd, source);
1958 #endif /* WITH_FULL_PATHS */
1960 if (operation == OP_DELETE) {
1961 if (S_ISDIR (src_stat.st_mode))
1962 value = erase_dir (ctx, source_with_path, &count, &bytes);
1963 else
1964 value =
1965 erase_file (ctx, source_with_path, &count, &bytes, 1);
1966 } else {
1967 temp = transform_source (ctx, source_with_path);
1968 if (temp == NULL) {
1969 value = transform_error;
1970 } else {
1971 char *repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
1972 char *temp2 = concat_dir_and_file (repl_dest, temp);
1973 g_free (repl_dest);
1974 g_free (temp);
1975 g_free(dest);
1976 dest = temp2;
1978 switch (operation) {
1979 case OP_COPY:
1981 * we use file_mask_op_follow_links only with OP_COPY,
1983 (*ctx->stat_func) (source_with_path, &src_stat);
1985 if (S_ISDIR (src_stat.st_mode)) {
1986 value =
1987 copy_dir_dir (ctx, source_with_path, dest, 1,
1988 0, 0, 0, &count, &bytes);
1989 } else {
1990 value =
1991 copy_file_file (ctx, source_with_path, dest, 1,
1992 &count, &bytes, 1);
1994 break;
1996 case OP_MOVE:
1997 if (S_ISDIR (src_stat.st_mode))
1998 value =
1999 move_dir_dir (ctx, source_with_path, dest,
2000 &count, &bytes);
2001 else
2002 value =
2003 move_file_file (ctx, source_with_path, dest,
2004 &count, &bytes);
2005 break;
2007 default:
2008 /* Unknown file operation */
2009 abort ();
2012 } /* Copy or move operation */
2014 if ((value == FILE_CONT) && !force_single)
2015 unmark_files (panel);
2016 } else {
2017 /* Many files */
2018 /* Check destination for copy or move operation */
2019 if (operation != OP_DELETE) {
2020 retry_many_dst_stat:
2021 dst_result = mc_stat (dest, &dst_stat);
2022 if (dst_result == 0 && !S_ISDIR (dst_stat.st_mode)) {
2023 if (file_error
2024 (_(" Destination \"%s\" must be a directory \n %s "),
2025 dest) == FILE_RETRY)
2026 goto retry_many_dst_stat;
2027 goto clean_up;
2031 /* Initialize variables for progress bars */
2032 if (operation != OP_MOVE && verbose && file_op_compute_totals) {
2033 ComputeDirSizeUI *ui;
2034 FileProgressStatus status;
2036 ui = compute_dir_size_create_ui ();
2037 status = panel_compute_totals (panel,
2038 ui, compute_dir_size_update_ui,
2039 &ctx->progress_count, &ctx->progress_bytes);
2040 compute_dir_size_destroy_ui (ui);
2042 if (status != FILE_CONT)
2043 goto clean_up;
2045 ctx->progress_totals_computed = 1;
2046 } else {
2047 ctx->progress_totals_computed = 0;
2048 ctx->progress_count = panel->marked;
2049 ctx->progress_bytes = panel->total;
2052 /* Loop for every file, perform the actual copy operation */
2053 for (i = 0; i < panel->count; i++) {
2054 if (!panel->dir.list[i].f.marked)
2055 continue; /* Skip the unmarked ones */
2057 source = panel->dir.list[i].fname;
2058 src_stat = panel->dir.list[i].st;
2060 #ifdef WITH_FULL_PATHS
2061 g_free (source_with_path);
2062 source_with_path = concat_dir_and_file (panel->cwd, source);
2063 #endif /* WITH_FULL_PATHS */
2065 if (operation == OP_DELETE) {
2066 if (S_ISDIR (src_stat.st_mode))
2067 value =
2068 erase_dir (ctx, source_with_path, &count, &bytes);
2069 else
2070 value =
2071 erase_file (ctx, source_with_path, &count, &bytes,
2073 } else {
2074 temp = transform_source (ctx, source_with_path);
2075 if (temp == NULL)
2076 value = transform_error;
2077 else {
2078 char *temp3;
2079 char *repl_dest = mc_search_prepare_replace_str2 (ctx->search_handle, dest);
2080 char *temp2 = concat_dir_and_file (repl_dest, temp);
2081 g_free(repl_dest);
2083 g_free(temp);
2084 temp3 = source_with_path;
2085 source_with_path = strutils_shell_unescape(source_with_path);
2086 g_free(temp3);
2087 temp3 = temp2;
2088 temp2 = strutils_shell_unescape(temp2);
2089 g_free(temp3);
2091 switch (operation) {
2092 case OP_COPY:
2094 * we use file_mask_op_follow_links only with OP_COPY
2096 (*ctx->stat_func) (source_with_path, &src_stat);
2097 if (S_ISDIR (src_stat.st_mode))
2098 value =
2099 copy_dir_dir (ctx, source_with_path, temp2,
2100 1, 0, 0, 0, &count, &bytes);
2101 else
2102 value =
2103 copy_file_file (ctx, source_with_path,
2104 temp2, 1, &count, &bytes,
2106 free_linklist (&dest_dirs);
2107 break;
2109 case OP_MOVE:
2110 if (S_ISDIR (src_stat.st_mode))
2111 value =
2112 move_dir_dir (ctx, source_with_path, temp2,
2113 &count, &bytes);
2114 else
2115 value =
2116 move_file_file (ctx, source_with_path,
2117 temp2, &count, &bytes);
2118 break;
2120 default:
2121 /* Unknown file operation */
2122 abort ();
2124 g_free (temp2);
2126 } /* Copy or move operation */
2128 if (value == FILE_ABORT)
2129 goto clean_up;
2131 if (value == FILE_CONT)
2132 do_file_mark (panel, i, 0);
2134 if (file_progress_show_count (ctx, count, ctx->progress_count)
2135 == FILE_ABORT)
2136 goto clean_up;
2138 if (verbose
2139 && file_progress_show_bytes (ctx, bytes,
2140 ctx->progress_bytes) ==
2141 FILE_ABORT)
2142 goto clean_up;
2144 if (operation != OP_DELETE && verbose
2145 && file_progress_show (ctx, 0, 0) == FILE_ABORT)
2146 goto clean_up;
2148 mc_refresh ();
2149 } /* Loop for every file */
2150 } /* Many entries */
2151 clean_up:
2152 /* Clean up */
2154 if (save_cwd) {
2155 mc_setctl (save_cwd, VFS_SETCTL_STALE_DATA, NULL);
2156 g_free (save_cwd);
2158 if (save_dest) {
2159 mc_setctl (save_dest, VFS_SETCTL_STALE_DATA, NULL);
2160 g_free (save_dest);
2163 free_linklist (&linklist);
2164 free_linklist (&dest_dirs);
2165 #ifdef WITH_FULL_PATHS
2166 g_free (source_with_path);
2167 #endif /* WITH_FULL_PATHS */
2168 g_free (dest);
2169 g_free (ctx->dest_mask);
2170 ctx->dest_mask = NULL;
2171 #ifdef WITH_BACKGROUND
2172 /* Let our parent know we are saying bye bye */
2173 if (we_are_background) {
2174 vfs_shut ();
2175 _exit (0);
2177 #endif /* WITH_BACKGROUND */
2179 file_op_context_destroy (ctx);
2180 return 1;
2183 /* }}} */
2185 /* {{{ Query/status report routines */
2187 static FileProgressStatus
2188 real_do_file_error (enum OperationMode mode, const char *error)
2190 int result;
2191 const char *msg;
2193 msg = mode == Foreground ? MSG_ERROR : _(" Background process error ");
2194 result =
2195 query_dialog (msg, error, D_ERROR, 3, _("&Skip"), _("&Retry"),
2196 _("&Abort"));
2198 switch (result) {
2199 case 0:
2200 do_refresh ();
2201 return FILE_SKIP;
2203 case 1:
2204 do_refresh ();
2205 return FILE_RETRY;
2207 case 2:
2208 default:
2209 return FILE_ABORT;
2213 /* Report error with one file */
2214 FileProgressStatus
2215 file_error (const char *format, const char *file)
2217 char buf [BUF_MEDIUM];
2219 g_snprintf (buf, sizeof (buf), format,
2220 path_trunc (file, 30), unix_error_string (errno));
2222 return do_file_error (buf);
2225 /* Report error with two files */
2226 static FileProgressStatus
2227 files_error (const char *format, const char *file1, const char *file2)
2229 char buf [BUF_MEDIUM];
2230 char *nfile1 = g_strdup (path_trunc (file1, 15));
2231 char *nfile2 = g_strdup (path_trunc (file2, 15));
2233 g_snprintf (buf, sizeof (buf), format, nfile1, nfile2,
2234 unix_error_string (errno));
2236 g_free (nfile1);
2237 g_free (nfile2);
2239 return do_file_error (buf);
2242 static FileProgressStatus
2243 real_query_recursive (FileOpContext *ctx, enum OperationMode mode, const char *s)
2245 gchar *text;
2247 if (ctx->recursive_result < RECURSIVE_ALWAYS) {
2248 const char *msg =
2249 mode ==
2250 Foreground ?
2251 _("\n Directory not empty. \n"
2252 " Delete it recursively? ")
2253 : _("\n Background process: Directory not empty \n"
2254 " Delete it recursively? ");
2255 text = g_strconcat (_(" Delete: "), path_trunc (s, 30), " ", (char *) NULL);
2257 if (safe_delete)
2258 query_set_sel (1);
2259 ctx->recursive_result = query_dialog (text, msg, D_ERROR, 5,
2260 _("&Yes"), _("&No"),
2261 _("A&ll"), _("Non&e"),
2262 _("&Abort"));
2264 if (ctx->recursive_result != RECURSIVE_ABORT)
2265 do_refresh ();
2266 g_free (text);
2269 switch (ctx->recursive_result) {
2270 case RECURSIVE_YES:
2271 case RECURSIVE_ALWAYS:
2272 return FILE_CONT;
2274 case RECURSIVE_NO:
2275 case RECURSIVE_NEVER:
2276 return FILE_SKIP;
2278 case RECURSIVE_ABORT:
2280 default:
2281 return FILE_ABORT;
2285 #ifdef WITH_BACKGROUND
2286 static FileProgressStatus
2287 do_file_error (const char *str)
2289 union {
2290 void *p;
2291 FileProgressStatus (*f) (enum OperationMode, const char *);
2292 } pntr;
2293 pntr.f = real_do_file_error;
2295 if (we_are_background)
2296 return parent_call (pntr.p, NULL, 1, strlen (str),
2297 str);
2298 else
2299 return real_do_file_error (Foreground, str);
2302 static FileProgressStatus
2303 query_recursive (FileOpContext *ctx, const char *s)
2305 union {
2306 void *p;
2307 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *);
2308 } pntr;
2309 pntr.f = real_query_recursive;
2311 if (we_are_background)
2312 return parent_call (pntr.p, ctx, 1, strlen (s), s);
2313 else
2314 return real_query_recursive (ctx, Foreground, s);
2317 static FileProgressStatus
2318 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2319 struct stat *_d_stat)
2321 union {
2322 void *p;
2323 FileProgressStatus (*f) (FileOpContext *, enum OperationMode, const char *,
2324 struct stat *, struct stat *);
2325 } pntr;
2326 pntr.f = file_progress_real_query_replace;
2328 if (we_are_background)
2329 return parent_call (pntr.p,
2330 ctx,
2332 strlen (destname), destname,
2333 sizeof (struct stat), _s_stat,
2334 sizeof (struct stat), _d_stat);
2335 else
2336 return file_progress_real_query_replace (ctx, Foreground, destname,
2337 _s_stat, _d_stat);
2340 #else
2341 static FileProgressStatus
2342 do_file_error (const char *str)
2344 return real_do_file_error (Foreground, str);
2347 static FileProgressStatus
2348 query_recursive (FileOpContext *ctx, const char *s)
2350 return real_query_recursive (ctx, Foreground, s);
2353 static FileProgressStatus
2354 query_replace (FileOpContext *ctx, const char *destname, struct stat *_s_stat,
2355 struct stat *_d_stat)
2357 return file_progress_real_query_replace (ctx, Foreground, destname,
2358 _s_stat, _d_stat);
2361 #endif /* !WITH_BACKGROUND */
2364 Cause emacs to enter folding mode for this file:
2365 Local variables:
2366 end: