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