/* mv -- move or rename files This is the mv utility
Copyright (C) 1986-2018 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. */ The GNUv3 license
/* Written by Mike Parker, David MacKenzie, and Jim Meyering */
#include <config.h> Provides system specific information
#include <stdio.h> Provides standard I/O capability
#include <getopt.h> ...!includes auto-comment...
#include <sys/types.h> Provides system data types
#include <assert.h> ...!includes auto-comment...
#include <selinux/selinux.h> ...!includes auto-comment......!includes auto-comment...
#include "system.h" ...!includes auto-comment...
#include "backupfile.h" ...!includes auto-comment...
#include "copy.h" ...!includes auto-comment...
#include "cp-hash.h" ...!includes auto-comment......!includes auto-comment...
#include "die.h" ...!includes auto-comment...
#include "error.h" ...!includes auto-comment...
#include "filenamecat.h" ...!includes auto-comment...
#include "remove.h" ...!includes auto-comment...
#include "renameatu.h" ...!includes auto-comment...
#include "root-dev-ino.h" ...!includes auto-comment......!includes auto-comment...
#include "priv-set.h" ...!includes auto-comment...
/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "mv" Line 39
#define AUTHORS \ Line 41
proper_name ("Mike Parker"), \ Line 42
proper_name ("David MacKenzie"), \ Line 43
proper_name ("Jim Meyering") Line 44
/* For long options that have no equivalent short option, use a
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum Line 48
{
STRIP_TRAILING_SLASHES_OPTION = CHAR_MAX + 1 Line 50
}; Block 1
/* Remove any trailing slashes from each SOURCE argument. */
static bool remove_trailing_slashes; Line 54
static struct option const long_options[] = Line 56
{
{"backup", optional_argument, NULL, 'b'}, Line 58
{"context", no_argument, NULL, 'Z'}, Line 59
{"force", no_argument, NULL, 'f'}, Line 60
{"interactive", no_argument, NULL, 'i'}, Line 61
{"no-clobber", no_argument, NULL, 'n'}, Line 62
{"no-target-directory", no_argument, NULL, 'T'}, Line 63
{"strip-trailing-slashes", no_argument, NULL, STRIP_TRAILING_SLASHES_OPTION}, Line 64
{"suffix", required_argument, NULL, 'S'}, Line 65
{"target-directory", required_argument, NULL, 't'}, Line 66
{"update", no_argument, NULL, 'u'}, Line 67
{"verbose", no_argument, NULL, 'v'}, Line 68
{GETOPT_HELP_OPTION_DECL}, Line 69
{GETOPT_VERSION_OPTION_DECL}, Line 70
{NULL, 0, NULL, 0} Line 71
}; Block 2
static void Line 74
rm_option_init (struct rm_options *x) Line 75
{
x->ignore_missing_files = false; Line 77
x->remove_empty_directories = true; Line 78
x->recursive = true; Line 79
x->one_file_system = false; Line 80
/* Should we prompt for removal, too? No. Prompting for the 'move'
part is enough. It implies removal. */
x->interactive = RMI_NEVER; Line 84
x->stdin_tty = false; Line 85
x->verbose = false; Line 87
/* Since this program may well have to process additional command
line arguments after any call to 'rm', that function must preserve
the initial working directory, in case one of those is a
'.'-relative name. */
x->require_restore_cwd = true; Line 93
{
static struct dev_ino dev_ino_buf; Line 96
x->root_dev_ino = get_root_dev_ino (&dev_ino_buf); Line 97
if (x->root_dev_ino == NULL) Line 98
die (EXIT_FAILURE, errno, _("failed to get attributes of %s"), Line 99
quoteaf ("/")); Line 100
}
x->preserve_all_root = false; Line 103
} Block 3
static void Line 106
cp_option_init (struct cp_options *x) Line 107
{
bool selinux_enabled = (0 < is_selinux_enabled ()); ...!common auto-comment...
cp_options_default (x); Line 111
x->copy_as_regular = false; /* FIXME: maybe make this an option */ Line 112
x->reflink_mode = REFLINK_AUTO; Line 113
x->dereference = DEREF_NEVER; Line 114
x->unlink_dest_before_opening = false; Line 115
x->unlink_dest_after_failed_open = false; Line 116
x->hard_link = false; Line 117
x->interactive = I_UNSPECIFIED; Line 118
x->move_mode = true; Line 119
x->install_mode = false; Line 120
x->one_file_system = false; Line 121
x->preserve_ownership = true; Line 122
x->preserve_links = true; Line 123
x->preserve_mode = true; Line 124
x->preserve_timestamps = true; Line 125
x->explicit_no_preserve_mode= false; Line 126
x->preserve_security_context = selinux_enabled; Line 127
x->set_security_context = false; Line 128
x->reduce_diagnostics = false; Line 129
x->data_copy_required = true; Line 130
x->require_preserve = false; /* FIXME: maybe make this an option */ Line 131
x->require_preserve_context = false; Line 132
x->preserve_xattr = true; Line 133
x->require_preserve_xattr = false; Line 134
x->recursive = true; Line 135
x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */ Line 136
x->symbolic_link = false; Line 137
x->set_mode = false; Line 138
x->mode = 0; Line 139
x->stdin_tty = isatty (STDIN_FILENO); Line 140
x->open_dangling_dest_symlink = false; Line 142
x->update = false; Line 143
x->verbose = false; Line 144
x->dest_info = NULL; Line 145
x->src_info = NULL; Line 146
}
/* FILE is the last operand of this command. Return true if FILE is a
directory. But report an error if there is a problem accessing FILE, other
than nonexistence (errno == ENOENT). */
static bool Line 153
target_directory_operand (char const *file) Line 154
{
struct stat st; Line 156
int err = (stat (file, &st) == 0 ? 0 : errno); Line 157...!syscalls auto-comment...
bool is_a_dir = !err && S_ISDIR (st.st_mode); Line 158
if (err && err != ENOENT) Line 159
die (EXIT_FAILURE, err, _("failed to access %s"), quoteaf (file)); Line 160
return is_a_dir; Line 161
} Block 5
/* Move SOURCE onto DEST. Handles cross-file-system moves.
If SOURCE is a directory, DEST must not exist.
Return true if successful. */
static bool Line 168
do_move (const char *source, const char *dest, const struct cp_options *x) Line 169
{
bool copy_into_self; Line 171
bool rename_succeeded; Line 172
bool ok = copy (source, dest, false, x, ©_into_self, &rename_succeeded); Line 173
if (ok) Line 175
{
char const *dir_to_remove; Line 177
if (copy_into_self) Line 178
{
/* In general, when copy returns with copy_into_self set, SOURCE is
the same as, or a parent of DEST. In this case we know it's a
parent. It doesn't make sense to move a directory into itself, and
besides in some situations doing so would give highly nonintuitive
results. Run this 'mkdir b; touch a c; mv * b' in an empty
directory. Here's the result of running echo $(find b -print):
b b/a b/b b/b/a b/c. Notice that only file 'a' was copied
into b/b. Handle this by giving a diagnostic, removing the
copied-into-self directory, DEST ('b/b' in the example),
and failing. */
dir_to_remove = NULL; Line 191
ok = false; Line 192
}
else if (rename_succeeded) Line 194
{
/* No need to remove anything. SOURCE was successfully
renamed to DEST. Or the user declined to rename a file. */
dir_to_remove = NULL; Line 198
}
else Line 200
{
/* This may mean SOURCE and DEST referred to different devices.
It may also conceivably mean that even though they referred
to the same device, rename wasn't implemented for that device.
E.g., (from Joel N. Weber),
[...] there might someday be cases where you can't rename
but you can copy where the device name is the same, especially
on Hurd. Consider an ftpfs with a primitive ftp server that
supports uploading, downloading and deleting, but not renaming.
Also, note that comparing device numbers is not a reliable
check for 'can-rename'. Some systems can be set up so that
files from many different physical devices all have the same
st_dev field. This is a feature of some NFS mounting
configurations.
We reach this point if SOURCE has been successfully copied
to DEST. Now we have to remove SOURCE.
This function used to resort to copying only when rename
failed and set errno to EXDEV. */
dir_to_remove = source; Line 224
}
if (dir_to_remove != NULL) Line 227
{
struct rm_options rm_options; Line 229
enum RM_status status; Line 230
char const *dir[2]; Line 231
rm_option_init (&rm_options); Line 233
rm_options.verbose = x->verbose; Line 234
dir[0] = dir_to_remove; Line 235
dir[1] = NULL; Line 236
status = rm ((void*) dir, &rm_options); Line 238
assert (VALID_STATUS (status)); Line 239
if (status == RM_ERROR) Line 240
ok = false; Line 241
}
}
return ok; Line 245
} Block 6
/* Move file SOURCE onto DEST. Handles the case when DEST is a directory.
Treat DEST as a directory if DEST_IS_DIR.
Return true if successful. */
static bool Line 252
movefile (char *source, char *dest, bool dest_is_dir, Line 253
const struct cp_options *x) Line 254
{
bool ok; Line 256
/* This code was introduced to handle the ambiguity in the semantics
of mv that is induced by the varying semantics of the rename function.
Some systems (e.g., GNU/Linux) have a rename function that honors a
trailing slash, while others (like Solaris 5,6,7) have a rename
function that ignores a trailing slash. I believe the GNU/Linux
rename semantics are POSIX and susv2 compliant. */
if (remove_trailing_slashes) Line 265
strip_trailing_slashes (source); Line 266
if (dest_is_dir) Line 268
{
/* Treat DEST as a directory; build the full filename. */
char const *src_basename = last_component (source); Line 271
char *new_dest = file_name_concat (dest, src_basename, NULL); Line 272
strip_trailing_slashes (new_dest); Line 273
ok = do_move (source, new_dest, x); Line 274
free (new_dest); Line 275
}
else Line 277
{
ok = do_move (source, dest, x); Line 279
}
return ok; Line 282
} Block 7
void Line 285
usage (int status) Line 286
{
if (status != EXIT_SUCCESS) Line 288
emit_try_help (); ...!common auto-comment...
else Line 290
{
printf (_("\ Line 292
Usage: %s [OPTION]... [-T] SOURCE DEST\n\ Line 293
or: %s [OPTION]... SOURCE... DIRECTORY\n\ Line 294
or: %s [OPTION]... -t DIRECTORY SOURCE...\n\ Line 295
"), Line 296
program_name, program_name, program_name); Line 297
fputs (_("\ Line 298
Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\ Line 299
"), stdout); Line 300
emit_mandatory_arg_note (); ...!common auto-comment...
fputs (_("\ Line 304
--backup[=CONTROL] make a backup of each existing destination file\ Line 305
\n\
-b like --backup but does not accept an argument\n\ Line 307
-f, --force do not prompt before overwriting\n\ Line 308
-i, --interactive prompt before overwrite\n\ Line 309
-n, --no-clobber do not overwrite an existing file\n\ Line 310
If you specify more than one of -i, -f, -n, only the final one takes effect.\n\ Line 311
"), stdout); Line 312
fputs (_("\ Line 313
--strip-trailing-slashes remove any trailing slashes from each SOURCE\n\ Line 314
argument\n\ Line 315
-S, --suffix=SUFFIX override the usual backup suffix\n\ Line 316
"), stdout); Line 317
fputs (_("\ Line 318
-t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\ Line 319
-T, --no-target-directory treat DEST as a normal file\n\ Line 320
-u, --update move only when the SOURCE file is newer\n\ Line 321
than the destination file or when the\n\ Line 322
destination file is missing\n\ Line 323
-v, --verbose explain what is being done\n\ Line 324
-Z, --context set SELinux security context of destination\n\ Line 325
file to default type\n\ Line 326
"), stdout); Line 327
fputs (HELP_OPTION_DESCRIPTION, stdout); Line 328
fputs (VERSION_OPTION_DESCRIPTION, stdout); Line 329
emit_backup_suffix_note (); Line 330
emit_ancillary_info (PROGRAM_NAME); Line 331
}
exit (status); Line 333
} Block 8
int
main (int argc, char **argv) Line 337
{
int c; Line 339
bool ok; Line 340
bool make_backups = false; Line 341
char const *backup_suffix = NULL; Line 342
char *version_control_string = NULL; Line 343
struct cp_options x; Line 344
char *target_directory = NULL; Line 345
bool no_target_directory = false; Line 346
int n_files; Line 347
char **file; Line 348
bool selinux_enabled = (0 < is_selinux_enabled ()); ...!common auto-comment...
initialize_main (&argc, &argv); VMS-specific entry point handling wildcard expansion
set_program_name (argv[0]); Retains program name and discards path
setlocale (LC_ALL, ""); Sets up internationalization (i18n)
bindtextdomain (PACKAGE, LOCALEDIR); Assigns i18n directorySets text domain for _() [gettext()] function
textdomain (PACKAGE); Sets text domain for _() [gettext()] function
atexit (close_stdin); Close stdout on exit (see gnulib)
cp_option_init (&x); Line 359
/* Try to disable the ability to unlink a directory. */
priv_set_remove_linkdir (); Line 362
while ((c = getopt_long (argc, argv, "bfint:uvS:TZ", long_options, NULL)) Line 364
!= -1) Line 365
{
switch (c) Line 367
{
case 'b': Line 369
make_backups = true; Line 370
if (optarg) Line 371
version_control_string = optarg; Line 372
break; Line 373
case 'f': Line 374
x.interactive = I_ALWAYS_YES; Line 375
break; Line 376
case 'i': Line 377
x.interactive = I_ASK_USER; Line 378
break; Line 379
case 'n': Line 380
x.interactive = I_ALWAYS_NO; Line 381
break; Line 382
case STRIP_TRAILING_SLASHES_OPTION: Line 383
remove_trailing_slashes = true; Line 384
break; Line 385
case 't': Line 386
if (target_directory) Line 387
die (EXIT_FAILURE, 0, _("multiple target directories specified")); Line 388
else Line 389
{
struct stat st; Line 391
if (stat (optarg, &st) != 0) Line 392...!syscalls auto-comment...
die (EXIT_FAILURE, errno, _("failed to access %s"), Line 393
quoteaf (optarg)); Line 394
if (! S_ISDIR (st.st_mode)) Line 395
die (EXIT_FAILURE, 0, _("target %s is not a directory"), Line 396
quoteaf (optarg)); Line 397
}
target_directory = optarg; Line 399
break; Line 400
case 'T': Line 401
no_target_directory = true; Line 402
break; Line 403
case 'u': Line 404
x.update = true; Line 405
break; Line 406
case 'v': Line 407
x.verbose = true; Line 408
break; Line 409
case 'S': Line 410
make_backups = true; Line 411
backup_suffix = optarg; Line 412
break; Line 413
case 'Z': Line 414
/* As a performance enhancement, don't even bother trying
to "restorecon" when not on an selinux-enabled kernel. */
if (selinux_enabled) Line 417
{
x.preserve_security_context = false; Line 419
x.set_security_context = true; Line 420
}
break; Line 422
case_GETOPT_HELP_CHAR; Line 423
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); Line 424
default: Line 425
usage (EXIT_FAILURE); Line 426
}
}
n_files = argc - optind; Line 430
file = argv + optind; Line 431
if (n_files <= !target_directory) Line 433
{
if (n_files <= 0) Line 435
error (0, 0, _("missing file operand")); Line 436
else Line 437
error (0, 0, _("missing destination file operand after %s"), Line 438
quoteaf (file[0])); Line 439
usage (EXIT_FAILURE); Line 440
}
if (no_target_directory) Line 443
{
if (target_directory) Line 445
die (EXIT_FAILURE, 0, Line 446
_("cannot combine --target-directory (-t) " Line 447
"and --no-target-directory (-T)")); Line 448
if (2 < n_files) Line 449
{
error (0, 0, _("extra operand %s"), quoteaf (file[2])); Line 451
usage (EXIT_FAILURE); Line 452
}
}
else if (!target_directory) Line 455
{
assert (2 <= n_files); Line 457
if (n_files == 2) Line 458
x.rename_errno = (renameatu (AT_FDCWD, file[0], AT_FDCWD, file[1], Line 459
RENAME_NOREPLACE) Line 460
? errno : 0); Line 461
if (x.rename_errno != 0 && target_directory_operand (file[n_files - 1])) Line 462
{
x.rename_errno = -1; Line 464
target_directory = file[--n_files]; Line 465
}
else if (2 < n_files) Line 467
die (EXIT_FAILURE, 0, _("target %s is not a directory"), Line 468
quoteaf (file[n_files - 1])); Line 469
}
if (x.interactive == I_ALWAYS_NO) Line 472
x.update = false; Line 473
if (make_backups && x.interactive == I_ALWAYS_NO) Line 475
{
error (0, 0, Line 477
_("options --backup and --no-clobber are mutually exclusive")); Line 478
usage (EXIT_FAILURE); Line 479
}
x.backup_type = (make_backups Line 482
? xget_version (_("backup type"), Line 483
version_control_string) Line 484
: no_backups); Line 485
set_simple_backup_suffix (backup_suffix); Line 486
hash_init (); Line 488
if (target_directory) Line 490
{
/* Initialize the hash table only if we'll need it.
The problem it is used to detect can arise only if there are
two or more files to move. */
if (2 <= n_files) Line 495
dest_info_init (&x); Line 496
ok = true; Line 498
for (int i = 0; i < n_files; ++i) Line 499
{
x.last_file = i + 1 == n_files; Line 501
ok &= movefile (file[i], target_directory, true, &x); Line 502
}
}
else Line 505
{
x.last_file = true; Line 507
ok = movefile (file[0], file[1], false, &x); Line 508
}
return ok ? EXIT_SUCCESS : EXIT_FAILURE; Line 511
} Block 9