1 /* mv -- move or rename files
2 Copyright (C) 1986-2023 Free Software Foundation, Inc.
3
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 3 of the License, or
7 (at your option) any later version.
8
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.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 /* Written by Mike Parker, David MacKenzie, and Jim Meyering */
18
19 #include <config.h>
20 #include <stdio.h>
21 #include <getopt.h>
22 #include <sys/types.h>
23 #include <selinux/label.h>
24
25 #include "system.h"
26 #include "argmatch.h"
27 #include "assure.h"
28 #include "backupfile.h"
29 #include "copy.h"
30 #include "cp-hash.h"
31 #include "filenamecat.h"
32 #include "remove.h"
33 #include "renameatu.h"
34 #include "root-dev-ino.h"
35 #include "targetdir.h"
36 #include "priv-set.h"
37
38 /* The official name of this program (e.g., no 'g' prefix). */
39 #define PROGRAM_NAME "mv"
40
41 #define AUTHORS \
42 proper_name ("Mike Parker"), \
43 proper_name ("David MacKenzie"), \
44 proper_name ("Jim Meyering")
45
46 /* For long options that have no equivalent short option, use a
47 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
48 enum
49 {
50 DEBUG_OPTION = CHAR_MAX + 1,
51 NO_COPY_OPTION,
52 STRIP_TRAILING_SLASHES_OPTION
53 };
54
55 static char const *const update_type_string[] =
56 {
57 "all", "none", "older", nullptr
58 };
59 static enum Update_type const update_type[] =
60 {
61 UPDATE_ALL, UPDATE_NONE, UPDATE_OLDER,
62 };
63 ARGMATCH_VERIFY (update_type_string, update_type);
64
65 static struct option const long_options[] =
66 {
67 {"backup", optional_argument, nullptr, 'b'},
68 {"context", no_argument, nullptr, 'Z'},
69 {"debug", no_argument, nullptr, DEBUG_OPTION},
70 {"force", no_argument, nullptr, 'f'},
71 {"interactive", no_argument, nullptr, 'i'},
72 {"no-clobber", no_argument, nullptr, 'n'},
73 {"no-copy", no_argument, nullptr, NO_COPY_OPTION},
74 {"no-target-directory", no_argument, nullptr, 'T'},
75 {"strip-trailing-slashes", no_argument, nullptr,
76 STRIP_TRAILING_SLASHES_OPTION},
77 {"suffix", required_argument, nullptr, 'S'},
78 {"target-directory", required_argument, nullptr, 't'},
79 {"update", optional_argument, nullptr, 'u'},
80 {"verbose", no_argument, nullptr, 'v'},
81 {GETOPT_HELP_OPTION_DECL},
82 {GETOPT_VERSION_OPTION_DECL},
83 {nullptr, 0, nullptr, 0}
84 };
85
86 static void
rm_option_init(struct rm_options * x)87 rm_option_init (struct rm_options *x)
88 {
89 x->ignore_missing_files = false;
90 x->remove_empty_directories = true;
91 x->recursive = true;
92 x->one_file_system = false;
93
94 /* Should we prompt for removal, too? No. Prompting for the 'move'
95 part is enough. It implies removal. */
96 x->interactive = RMI_NEVER;
97 x->stdin_tty = false;
98
99 x->verbose = false;
100
101 /* Since this program may well have to process additional command
102 line arguments after any call to 'rm', that function must preserve
103 the initial working directory, in case one of those is a
104 '.'-relative name. */
105 x->require_restore_cwd = true;
106
107 {
108 static struct dev_ino dev_ino_buf;
109 x->root_dev_ino = get_root_dev_ino (&dev_ino_buf);
110 if (x->root_dev_ino == nullptr)
111 error (EXIT_FAILURE, errno, _("failed to get attributes of %s"),
112 quoteaf ("/"));
113 }
114
115 x->preserve_all_root = false;
116 }
117
118 static void
cp_option_init(struct cp_options * x)119 cp_option_init (struct cp_options *x)
120 {
121 bool selinux_enabled = (0 < is_selinux_enabled ());
122
123 cp_options_default (x);
124 x->copy_as_regular = false; /* FIXME: maybe make this an option */
125 x->reflink_mode = REFLINK_AUTO;
126 x->dereference = DEREF_NEVER;
127 x->unlink_dest_before_opening = false;
128 x->unlink_dest_after_failed_open = false;
129 x->hard_link = false;
130 x->interactive = I_UNSPECIFIED;
131 x->move_mode = true;
132 x->install_mode = false;
133 x->one_file_system = false;
134 x->preserve_ownership = true;
135 x->preserve_links = true;
136 x->preserve_mode = true;
137 x->preserve_timestamps = true;
138 x->explicit_no_preserve_mode= false;
139 x->preserve_security_context = selinux_enabled;
140 x->set_security_context = nullptr;
141 x->reduce_diagnostics = false;
142 x->data_copy_required = true;
143 x->require_preserve = false; /* FIXME: maybe make this an option */
144 x->require_preserve_context = false;
145 x->preserve_xattr = true;
146 x->require_preserve_xattr = false;
147 x->recursive = true;
148 x->sparse_mode = SPARSE_AUTO; /* FIXME: maybe make this an option */
149 x->symbolic_link = false;
150 x->set_mode = false;
151 x->mode = 0;
152 x->stdin_tty = isatty (STDIN_FILENO);
153
154 x->open_dangling_dest_symlink = false;
155 x->update = false;
156 x->verbose = false;
157 x->dest_info = nullptr;
158 x->src_info = nullptr;
159 }
160
161 /* Move SOURCE onto DEST aka DEST_DIRFD+DEST_RELNAME.
162 Handle cross-file-system moves.
163 If SOURCE is a directory, DEST must not exist.
164 Return true if successful. */
165
166 static bool
do_move(char const * source,char const * dest,int dest_dirfd,char const * dest_relname,const struct cp_options * x)167 do_move (char const *source, char const *dest,
168 int dest_dirfd, char const *dest_relname, const struct cp_options *x)
169 {
170 bool copy_into_self;
171 bool rename_succeeded;
172 bool ok = copy (source, dest, dest_dirfd, dest_relname, 0, x,
173 ©_into_self, &rename_succeeded);
174
175 if (ok)
176 {
177 char const *dir_to_remove;
178 if (copy_into_self)
179 {
180 /* In general, when copy returns with copy_into_self set, SOURCE is
181 the same as, or a parent of DEST. In this case we know it's a
182 parent. It doesn't make sense to move a directory into itself, and
183 besides in some situations doing so would give highly unintuitive
184 results. Run this 'mkdir b; touch a c; mv * b' in an empty
185 directory. Here's the result of running echo $(find b -print):
186 b b/a b/b b/b/a b/c. Notice that only file 'a' was copied
187 into b/b. Handle this by giving a diagnostic, removing the
188 copied-into-self directory, DEST ('b/b' in the example),
189 and failing. */
190
191 dir_to_remove = nullptr;
192 ok = false;
193 }
194 else if (rename_succeeded)
195 {
196 /* No need to remove anything. SOURCE was successfully
197 renamed to DEST. Or the user declined to rename a file. */
198 dir_to_remove = nullptr;
199 }
200 else
201 {
202 /* This may mean SOURCE and DEST referred to different devices.
203 It may also conceivably mean that even though they referred
204 to the same device, rename wasn't implemented for that device.
205
206 E.g., (from Joel N. Weber),
207 [...] there might someday be cases where you can't rename
208 but you can copy where the device name is the same, especially
209 on Hurd. Consider an ftpfs with a primitive ftp server that
210 supports uploading, downloading and deleting, but not renaming.
211
212 Also, note that comparing device numbers is not a reliable
213 check for 'can-rename'. Some systems can be set up so that
214 files from many different physical devices all have the same
215 st_dev field. This is a feature of some NFS mounting
216 configurations.
217
218 We reach this point if SOURCE has been successfully copied
219 to DEST. Now we have to remove SOURCE.
220
221 This function used to resort to copying only when rename
222 failed and set errno to EXDEV. */
223
224 dir_to_remove = source;
225 }
226
227 if (dir_to_remove != nullptr)
228 {
229 struct rm_options rm_options;
230 enum RM_status status;
231 char const *dir[2];
232
233 rm_option_init (&rm_options);
234 rm_options.verbose = x->verbose;
235 dir[0] = dir_to_remove;
236 dir[1] = nullptr;
237
238 status = rm ((void *) dir, &rm_options);
239 affirm (VALID_STATUS (status));
240 if (status == RM_ERROR)
241 ok = false;
242 }
243 }
244
245 return ok;
246 }
247
248 void
usage(int status)249 usage (int status)
250 {
251 if (status != EXIT_SUCCESS)
252 emit_try_help ();
253 else
254 {
255 printf (_("\
256 Usage: %s [OPTION]... [-T] SOURCE DEST\n\
257 or: %s [OPTION]... SOURCE... DIRECTORY\n\
258 or: %s [OPTION]... -t DIRECTORY SOURCE...\n\
259 "),
260 program_name, program_name, program_name);
261 fputs (_("\
262 Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\
263 "), stdout);
264
265 emit_mandatory_arg_note ();
266
267 fputs (_("\
268 --backup[=CONTROL] make a backup of each existing destination file\
269 \n\
270 -b like --backup but does not accept an argument\n\
271 "), stdout);
272 fputs (_("\
273 --debug explain how a file is copied. Implies -v\n\
274 "), stdout);
275 fputs (_("\
276 -f, --force do not prompt before overwriting\n\
277 -i, --interactive prompt before overwrite\n\
278 -n, --no-clobber do not overwrite an existing file\n\
279 If you specify more than one of -i, -f, -n, only the final one takes effect.\n\
280 "), stdout);
281 fputs (_("\
282 --no-copy do not copy if renaming fails\n\
283 --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\
284 argument\n\
285 -S, --suffix=SUFFIX override the usual backup suffix\n\
286 "), stdout);
287 fputs (_("\
288 -t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\
289 -T, --no-target-directory treat DEST as a normal file\n\
290 "), stdout);
291 fputs (_("\
292 --update[=UPDATE] control which existing files are updated;\n\
293 UPDATE={all,none,older(default)}. See below\n\
294 -u equivalent to --update[=older]\n\
295 "), stdout);
296 fputs (_("\
297 -v, --verbose explain what is being done\n\
298 -Z, --context set SELinux security context of destination\n\
299 file to default type\n\
300 "), stdout);
301 fputs (HELP_OPTION_DESCRIPTION, stdout);
302 fputs (VERSION_OPTION_DESCRIPTION, stdout);
303 emit_update_parameters_note ();
304 emit_backup_suffix_note ();
305 emit_ancillary_info (PROGRAM_NAME);
306 }
307 exit (status);
308 }
309
310 int
main(int argc,char ** argv)311 main (int argc, char **argv)
312 {
313 int c;
314 bool ok;
315 bool make_backups = false;
316 char const *backup_suffix = nullptr;
317 char *version_control_string = nullptr;
318 struct cp_options x;
319 bool remove_trailing_slashes = false;
320 char const *target_directory = nullptr;
321 bool no_target_directory = false;
322 int n_files;
323 char **file;
324 bool selinux_enabled = (0 < is_selinux_enabled ());
325
326 initialize_main (&argc, &argv);
327 set_program_name (argv[0]);
328 setlocale (LC_ALL, "");
329 bindtextdomain (PACKAGE, LOCALEDIR);
330 textdomain (PACKAGE);
331
332 atexit (close_stdin);
333
334 cp_option_init (&x);
335
336 /* Try to disable the ability to unlink a directory. */
337 priv_set_remove_linkdir ();
338
339 while ((c = getopt_long (argc, argv, "bfint:uvS:TZ", long_options, nullptr))
340 != -1)
341 {
342 switch (c)
343 {
344 case 'b':
345 make_backups = true;
346 if (optarg)
347 version_control_string = optarg;
348 break;
349 case 'f':
350 x.interactive = I_ALWAYS_YES;
351 break;
352 case 'i':
353 x.interactive = I_ASK_USER;
354 break;
355 case 'n':
356 x.interactive = I_ALWAYS_NO;
357 break;
358 case DEBUG_OPTION:
359 x.debug = x.verbose = true;
360 break;
361 case NO_COPY_OPTION:
362 x.no_copy = true;
363 break;
364 case STRIP_TRAILING_SLASHES_OPTION:
365 remove_trailing_slashes = true;
366 break;
367 case 't':
368 if (target_directory)
369 error (EXIT_FAILURE, 0, _("multiple target directories specified"));
370 target_directory = optarg;
371 break;
372 case 'T':
373 no_target_directory = true;
374 break;
375 case 'u':
376 if (optarg == nullptr)
377 x.update = true;
378 else if (x.interactive != I_ALWAYS_NO) /* -n takes precedence. */
379 {
380 enum Update_type update_opt;
381 update_opt = XARGMATCH ("--update", optarg,
382 update_type_string, update_type);
383 if (update_opt == UPDATE_ALL)
384 {
385 /* Default mv operation. */
386 x.update = false;
387 x.interactive = I_UNSPECIFIED;
388 }
389 else if (update_opt == UPDATE_NONE)
390 {
391 x.update = false;
392 x.interactive = I_ALWAYS_SKIP;
393 }
394 else if (update_opt == UPDATE_OLDER)
395 {
396 x.update = true;
397 x.interactive = I_UNSPECIFIED;
398 }
399 }
400 break;
401 case 'v':
402 x.verbose = true;
403 break;
404 case 'S':
405 make_backups = true;
406 backup_suffix = optarg;
407 break;
408 case 'Z':
409 /* As a performance enhancement, don't even bother trying
410 to "restorecon" when not on an selinux-enabled kernel. */
411 if (selinux_enabled)
412 {
413 x.preserve_security_context = false;
414 x.set_security_context = selabel_open (SELABEL_CTX_FILE,
415 nullptr, 0);
416 if (! x.set_security_context)
417 error (0, errno, _("warning: ignoring --context"));
418 }
419 break;
420 case_GETOPT_HELP_CHAR;
421 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
422 default:
423 usage (EXIT_FAILURE);
424 }
425 }
426
427 n_files = argc - optind;
428 file = argv + optind;
429
430 if (n_files <= !target_directory)
431 {
432 if (n_files <= 0)
433 error (0, 0, _("missing file operand"));
434 else
435 error (0, 0, _("missing destination file operand after %s"),
436 quoteaf (file[0]));
437 usage (EXIT_FAILURE);
438 }
439
440 struct stat sb;
441 sb.st_mode = 0;
442 int target_dirfd = AT_FDCWD;
443 if (no_target_directory)
444 {
445 if (target_directory)
446 error (EXIT_FAILURE, 0,
447 _("cannot combine --target-directory (-t) "
448 "and --no-target-directory (-T)"));
449 if (2 < n_files)
450 {
451 error (0, 0, _("extra operand %s"), quoteaf (file[2]));
452 usage (EXIT_FAILURE);
453 }
454 }
455 else if (target_directory)
456 {
457 target_dirfd = target_directory_operand (target_directory, &sb);
458 if (! target_dirfd_valid (target_dirfd))
459 error (EXIT_FAILURE, errno, _("target directory %s"),
460 quoteaf (target_directory));
461 }
462 else
463 {
464 char const *lastfile = file[n_files - 1];
465 if (n_files == 2)
466 x.rename_errno = (renameatu (AT_FDCWD, file[0], AT_FDCWD, lastfile,
467 RENAME_NOREPLACE)
468 ? errno : 0);
469 if (x.rename_errno != 0)
470 {
471 int fd = target_directory_operand (lastfile, &sb);
472 if (target_dirfd_valid (fd))
473 {
474 x.rename_errno = -1;
475 target_dirfd = fd;
476 target_directory = lastfile;
477 n_files--;
478 }
479 else
480 {
481 /* The last operand LASTFILE cannot be opened as a directory.
482 If there are more than two operands, report an error.
483
484 Also, report an error if LASTFILE is known to be a directory
485 even though it could not be opened, which can happen if
486 opening failed with EACCES on a platform lacking O_PATH.
487 In this case use stat to test whether LASTFILE is a
488 directory, in case opening a non-directory with (O_SEARCH
489 | O_DIRECTORY) failed with EACCES not ENOTDIR. */
490 int err = errno;
491 if (2 < n_files
492 || (O_PATHSEARCH == O_SEARCH && err == EACCES
493 && (sb.st_mode != 0 || stat (lastfile, &sb) == 0)
494 && S_ISDIR (sb.st_mode)))
495 error (EXIT_FAILURE, err, _("target %s"), quoteaf (lastfile));
496 }
497 }
498 }
499
500 /* Handle the ambiguity in the semantics of mv induced by the
501 varying semantics of the rename function. POSIX-compatible
502 systems (e.g., GNU/Linux) have a rename function that honors a
503 trailing slash in the source, while others (Solaris 9, FreeBSD
504 7.2) have a rename function that ignores it. */
505 if (remove_trailing_slashes)
506 for (int i = 0; i < n_files; i++)
507 strip_trailing_slashes (file[i]);
508
509 if (x.interactive == I_ALWAYS_NO)
510 x.update = false;
511
512 if (make_backups && x.interactive == I_ALWAYS_NO)
513 {
514 error (0, 0,
515 _("options --backup and --no-clobber are mutually exclusive"));
516 usage (EXIT_FAILURE);
517 }
518
519 x.backup_type = (make_backups
520 ? xget_version (_("backup type"),
521 version_control_string)
522 : no_backups);
523 set_simple_backup_suffix (backup_suffix);
524
525 hash_init ();
526
527 if (target_directory)
528 {
529 /* Initialize the hash table only if we'll need it.
530 The problem it is used to detect can arise only if there are
531 two or more files to move. */
532 if (2 <= n_files)
533 dest_info_init (&x);
534
535 ok = true;
536 for (int i = 0; i < n_files; ++i)
537 {
538 x.last_file = i + 1 == n_files;
539 char const *source = file[i];
540 char const *source_basename = last_component (source);
541 char *dest_relname;
542 char *dest = file_name_concat (target_directory, source_basename,
543 &dest_relname);
544 strip_trailing_slashes (dest_relname);
545 ok &= do_move (source, dest, target_dirfd, dest_relname, &x);
546 free (dest);
547 }
548 }
549 else
550 {
551 x.last_file = true;
552 ok = do_move (file[0], file[1], AT_FDCWD, file[1], &x);
553 }
554
555 main_exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
556 }
557