1 /* mv -- move or rename files
2 Copyright (C) 86, 89, 90, 91, 95, 1996 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19 -f, --force Assume a 'y' answer to all questions it would
20 normally ask, and not ask the questions.
22 -i, --interactive Require confirmation from the user before
23 performing any move that would destroy an
26 -u, --update Do not move a nondirectory that has an
27 existing destination with the same or newer
30 -v, --verbose List the name of each file as it is moved, and
31 the name it is moved to.
38 Written by Mike Parker and David MacKenzie */
47 #include <sys/types.h>
50 #include "backupfile.h"
54 # define chown(PATH, OWNER, GROUP) lchown(PATH, OWNER, GROUP)
57 #ifndef _POSIX_VERSION
62 enum backup_type
get_version ();
67 void strip_trailing_slashes ();
71 /* The name this program was run with. */
74 /* If nonzero, query the user before overwriting files. */
75 static int interactive
;
77 /* If nonzero, do not query the user before overwriting unwritable
79 static int override_mode
;
81 /* If nonzero, do not move a nondirectory that has an existing destination
82 with the same or newer modification time. */
83 static int update
= 0;
85 /* If nonzero, list each file as it is moved. */
88 /* If nonzero, stdin is a tty. */
91 /* This process's effective user ID. */
95 static struct stat dest_stats
, source_stats
;
97 /* If nonzero, display usage information and exit. */
100 /* If nonzero, print the version on standard output and exit. */
101 static int show_version
;
103 static struct option
const long_options
[] =
105 {"backup", no_argument
, NULL
, 'b'},
106 {"force", no_argument
, NULL
, 'f'},
107 {"interactive", no_argument
, NULL
, 'i'},
108 {"suffix", required_argument
, NULL
, 'S'},
109 {"update", no_argument
, &update
, 1},
110 {"verbose", no_argument
, &verbose
, 1},
111 {"version-control", required_argument
, NULL
, 'V'},
112 {"help", no_argument
, &show_help
, 1},
113 {"version", no_argument
, &show_version
, 1},
117 /* If PATH is an existing directory, return nonzero, else 0. */
120 is_real_dir (char *path
)
124 return lstat (path
, &stats
) == 0 && S_ISDIR (stats
.st_mode
);
127 /* Copy regular file SOURCE onto file DEST.
128 Return 1 if an error occurred, 0 if successful. */
131 copy_reg (char *source
, char *dest
)
136 int len
; /* Number of bytes read into `buf'. */
138 if (!S_ISREG (source_stats
.st_mode
))
140 error (0, 0, _("cannot move `%s' across filesystems: Not a regular file"),
145 if (unlink (dest
) && errno
!= ENOENT
)
147 error (0, errno
, _("cannot remove `%s'"), dest
);
151 ifd
= open (source
, O_RDONLY
, 0);
154 error (0, errno
, "%s", source
);
157 ofd
= open (dest
, O_WRONLY
| O_CREAT
| O_TRUNC
, 0600);
160 error (0, errno
, "%s", dest
);
165 while ((len
= safe_read (ifd
, buf
, sizeof (buf
))) > 0)
167 if (full_write (ofd
, buf
, len
) < 0)
169 error (0, errno
, "%s", dest
);
178 error (0, errno
, "%s", source
);
187 error (0, errno
, "%s", source
);
193 error (0, errno
, "%s", dest
);
197 /* chown turns off set[ug]id bits for non-root,
198 so do the chmod last. */
200 /* Try to copy the old file's modtime and access time. */
204 tv
.actime
= source_stats
.st_atime
;
205 tv
.modtime
= source_stats
.st_mtime
;
206 if (utime (dest
, &tv
))
208 error (0, errno
, "%s", dest
);
213 /* Try to preserve ownership. For non-root it might fail, but that's ok.
214 But root probably wants to know, e.g. if NFS disallows it. */
215 if (chown (dest
, source_stats
.st_uid
, source_stats
.st_gid
)
216 && (errno
!= EPERM
|| myeuid
== 0))
218 error (0, errno
, "%s", dest
);
222 if (chmod (dest
, source_stats
.st_mode
& 07777))
224 error (0, errno
, "%s", dest
);
231 /* Move SOURCE onto DEST. Handles cross-filesystem moves.
232 If DEST is a directory, SOURCE must be also.
233 Return 0 if successful, 1 if an error occurred. */
236 do_move (char *source
, char *dest
)
238 char *dest_backup
= NULL
;
240 if (lstat (source
, &source_stats
) != 0)
242 error (0, errno
, "%s", source
);
246 if (lstat (dest
, &dest_stats
) == 0)
248 if (source_stats
.st_dev
== dest_stats
.st_dev
249 && source_stats
.st_ino
== dest_stats
.st_ino
)
251 error (0, 0, _("`%s' and `%s' are the same file"), source
, dest
);
255 if (S_ISDIR (dest_stats
.st_mode
))
257 error (0, 0, _("%s: cannot overwrite directory"), dest
);
261 if (!S_ISDIR (source_stats
.st_mode
) && update
262 && source_stats
.st_mtime
<= dest_stats
.st_mtime
)
265 if (!override_mode
&& (interactive
|| stdin_tty
)
266 && euidaccess (dest
, W_OK
))
268 fprintf (stderr
, _("%s: replace `%s', overriding mode %04o? "),
270 (unsigned int) (dest_stats
.st_mode
& 07777));
274 else if (interactive
)
276 fprintf (stderr
, _("%s: replace `%s'? "), program_name
, dest
);
281 if (backup_type
!= none
)
283 char *tmp_backup
= find_backup_file_name (dest
);
284 if (tmp_backup
== NULL
)
285 error (1, 0, _("virtual memory exhausted"));
286 dest_backup
= (char *) alloca (strlen (tmp_backup
) + 1);
287 strcpy (dest_backup
, tmp_backup
);
289 if (rename (dest
, dest_backup
))
293 error (0, errno
, _("cannot backup `%s'"), dest
);
301 else if (errno
!= ENOENT
)
303 error (0, errno
, "%s", dest
);
308 printf ("%s -> %s\n", source
, dest
);
310 if (rename (source
, dest
) == 0)
317 error (0, errno
, _("cannot move `%s' to `%s'"), source
, dest
);
321 /* rename failed on cross-filesystem link. Copy the file instead. */
323 if (copy_reg (source
, dest
))
328 error (0, errno
, _("cannot remove `%s'"), source
);
337 if (rename (dest_backup
, dest
))
338 error (0, errno
, _("cannot un-backup `%s'"), dest
);
343 /* Move file SOURCE onto DEST. Handles the case when DEST is a directory.
344 Return 0 if successful, 1 if an error occurred. */
347 movefile (char *source
, char *dest
)
349 strip_trailing_slashes (source
);
351 if ((dest
[strlen (dest
) - 1] == '/' && !is_real_dir (source
))
354 /* Target is a directory; build full target filename. */
358 base
= basename (source
);
359 /* Remove a (single) trailing slash if there is at least one. */
360 if (dest
[strlen (dest
) - 1] == '/')
361 dest
[strlen (dest
) - 1] = '\0';
362 new_dest
= (char *) alloca (strlen (dest
) + 1 + strlen (base
) + 1);
363 stpcpy (stpcpy (stpcpy (new_dest
, dest
), "/"), base
);
364 return do_move (source
, new_dest
);
367 return do_move (source
, dest
);
374 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
379 Usage: %s [OPTION]... SOURCE DEST\n\
380 or: %s [OPTION]... SOURCE... DIRECTORY\n\
382 program_name
, program_name
);
384 Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\
386 -b, --backup make backup before removal\n\
387 -f, --force remove existing destinations, never prompt\n\
388 -i, --interactive prompt before overwrite\n\
389 -u, --update move only older or brand new files\n\
390 -v, --verbose explain what is being done\n\
391 -S, --suffix=SUFFIX override the usual backup suffix\n\
392 -V, --version-control=WORD override the usual version control\n\
393 --help display this help and exit\n\
394 --version output version information and exit\n\
396 The backup suffix is ~, unless set with SIMPLE_BACKUP_SUFFIX. The\n\
397 version control may be set with VERSION_CONTROL, values are:\n\
399 t, numbered make numbered backups\n\
400 nil, existing numbered if numbered backups exist, simple otherwise\n\
401 never, simple always make simple backups \n"));
407 main (int argc
, char **argv
)
411 int make_backups
= 0;
414 program_name
= argv
[0];
415 setlocale (LC_ALL
, "");
416 bindtextdomain (PACKAGE
, LOCALEDIR
);
417 textdomain (PACKAGE
);
419 version
= getenv ("SIMPLE_BACKUP_SUFFIX");
421 simple_backup_suffix
= version
;
422 version
= getenv ("VERSION_CONTROL");
425 interactive
= override_mode
= verbose
= update
= 0;
428 while ((c
= getopt_long (argc
, argv
, "bfiuvS:V:", long_options
, (int *) 0))
453 simple_backup_suffix
= optarg
;
465 printf ("mv - %s\n", PACKAGE_VERSION
);
472 if (argc
< optind
+ 2)
474 error (0, 0, _("%s"), (argc
== optind
475 ? _("missing file arguments")
476 : _("missing file argument")));
481 backup_type
= get_version (version
);
483 stdin_tty
= isatty (STDIN_FILENO
);
485 if (argc
> optind
+ 2 && !isdir (argv
[argc
- 1]))
487 _("when moving multiple files, last argument must be a directory"));
489 /* Move each arg but the last onto the last. */
490 for (; optind
< argc
- 1; ++optind
)
491 errors
|= movefile (argv
[optind
], argv
[argc
- 1]);