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 */
22 #include <sys/types.h>
24 #include <selinux/label.h>
27 #include "backupfile.h"
32 #include "filenamecat.h"
34 #include "renameatu.h"
35 #include "root-dev-ino.h"
36 #include "targetdir.h"
39 /* The official name of this program (e.g., no 'g' prefix). */
40 #define PROGRAM_NAME "mv"
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. */
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
},
73 rm_option_init (struct rm_options
*x
)
75 x
->ignore_missing_files
= false;
76 x
->remove_empty_directories
= 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
;
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
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"),
101 x
->preserve_all_root
= false;
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
;
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;
134 x
->sparse_mode
= SPARSE_AUTO
; /* FIXME: maybe make this an option */
135 x
->symbolic_link
= false;
138 x
->stdin_tty
= isatty (STDIN_FILENO
);
140 x
->open_dangling_dest_symlink
= false;
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. */
153 do_move (char const *source
, char const *dest
,
154 int dest_dirfd
, char const *dest_relname
, const struct cp_options
*x
)
157 bool rename_succeeded
;
158 bool ok
= copy (source
, dest
, dest_dirfd
, dest_relname
, 0, x
,
159 ©_into_self
, &rename_succeeded
);
163 char const *dir_to_remove
;
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),
177 dir_to_remove
= NULL
;
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
;
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
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
;
219 rm_option_init (&rm_options
);
220 rm_options
.verbose
= x
->verbose
;
221 dir
[0] = dir_to_remove
;
224 status
= rm ((void *) dir
, &rm_options
);
225 assert (VALID_STATUS (status
));
226 if (status
== RM_ERROR
)
237 if (status
!= EXIT_SUCCESS
)
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
);
248 Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\
251 emit_mandatory_arg_note ();
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\
263 --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\
265 -S, --suffix=SUFFIX override the usual backup suffix\n\
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\
277 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
278 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
279 emit_backup_suffix_note ();
280 emit_ancillary_info (PROGRAM_NAME
);
286 main (int argc
, char **argv
)
290 bool make_backups
= false;
291 char const *backup_suffix
= NULL
;
292 char *version_control_string
= NULL
;
294 bool remove_trailing_slashes
= false;
295 char const *target_directory
= NULL
;
296 bool no_target_directory
= false;
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
);
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
))
322 version_control_string
= optarg
;
325 x
.interactive
= I_ALWAYS_YES
;
328 x
.interactive
= I_ASK_USER
;
331 x
.interactive
= I_ALWAYS_NO
;
333 case STRIP_TRAILING_SLASHES_OPTION
:
334 remove_trailing_slashes
= true;
337 if (target_directory
)
338 die (EXIT_FAILURE
, 0, _("multiple target directories specified"));
339 target_directory
= optarg
;
342 no_target_directory
= true;
352 backup_suffix
= optarg
;
355 /* As a performance enhancement, don't even bother trying
356 to "restorecon" when not on an selinux-enabled kernel. */
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"));
365 case_GETOPT_HELP_CHAR
;
366 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
368 usage (EXIT_FAILURE
);
372 n_files
= argc
- optind
;
373 file
= argv
+ optind
;
375 if (n_files
<= !target_directory
)
378 error (0, 0, _("missing file operand"));
380 error (0, 0, _("missing destination file operand after %s"),
382 usage (EXIT_FAILURE
);
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)"));
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
));
409 char const *lastfile
= file
[n_files
- 1];
411 x
.rename_errno
= (renameatu (AT_FDCWD
, file
[0], AT_FDCWD
, lastfile
,
414 if (x
.rename_errno
!= 0)
416 int fd
= target_directory_operand (lastfile
, &sb
);
417 if (target_dirfd_valid (fd
))
421 target_directory
= lastfile
;
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. */
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
)
457 if (make_backups
&& x
.interactive
== I_ALWAYS_NO
)
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
)
468 set_simple_backup_suffix (backup_suffix
);
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. */
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
);
487 char *dest
= file_name_concat (target_directory
, source_basename
,
489 strip_trailing_slashes (dest_relname
);
490 ok
&= do_move (source
, dest
, target_dirfd
, dest_relname
, &x
);
497 ok
= do_move (file
[0], file
[1], AT_FDCWD
, file
[1], &x
);
500 main_exit (ok
? EXIT_SUCCESS
: EXIT_FAILURE
);