1 /* mv -- move or rename files
2 Copyright (C) 1986-2023 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>
28 #include "backupfile.h"
33 #include "filenamecat.h"
35 #include "renameatu.h"
36 #include "root-dev-ino.h"
37 #include "targetdir.h"
40 /* The official name of this program (e.g., no 'g' prefix). */
41 #define PROGRAM_NAME "mv"
44 proper_name ("Mike Parker"), \
45 proper_name ("David MacKenzie"), \
46 proper_name ("Jim Meyering")
48 /* For long options that have no equivalent short option, use a
49 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
52 DEBUG_OPTION
= CHAR_MAX
+ 1,
54 STRIP_TRAILING_SLASHES_OPTION
57 static char const *const update_type_string
[] =
59 "all", "none", "older", nullptr
61 static enum Update_type
const update_type
[] =
63 UPDATE_ALL
, UPDATE_NONE
, UPDATE_OLDER
,
65 ARGMATCH_VERIFY (update_type_string
, update_type
);
67 static struct option
const long_options
[] =
69 {"backup", optional_argument
, nullptr, 'b'},
70 {"context", no_argument
, nullptr, 'Z'},
71 {"debug", no_argument
, nullptr, DEBUG_OPTION
},
72 {"force", no_argument
, nullptr, 'f'},
73 {"interactive", no_argument
, nullptr, 'i'},
74 {"no-clobber", no_argument
, nullptr, 'n'},
75 {"no-copy", no_argument
, nullptr, NO_COPY_OPTION
},
76 {"no-target-directory", no_argument
, nullptr, 'T'},
77 {"strip-trailing-slashes", no_argument
, nullptr,
78 STRIP_TRAILING_SLASHES_OPTION
},
79 {"suffix", required_argument
, nullptr, 'S'},
80 {"target-directory", required_argument
, nullptr, 't'},
81 {"update", optional_argument
, nullptr, 'u'},
82 {"verbose", no_argument
, nullptr, 'v'},
83 {GETOPT_HELP_OPTION_DECL
},
84 {GETOPT_VERSION_OPTION_DECL
},
85 {nullptr, 0, nullptr, 0}
89 rm_option_init (struct rm_options
*x
)
91 x
->ignore_missing_files
= false;
92 x
->remove_empty_directories
= true;
94 x
->one_file_system
= false;
96 /* Should we prompt for removal, too? No. Prompting for the 'move'
97 part is enough. It implies removal. */
98 x
->interactive
= RMI_NEVER
;
103 /* Since this program may well have to process additional command
104 line arguments after any call to 'rm', that function must preserve
105 the initial working directory, in case one of those is a
106 '.'-relative name. */
107 x
->require_restore_cwd
= true;
110 static struct dev_ino dev_ino_buf
;
111 x
->root_dev_ino
= get_root_dev_ino (&dev_ino_buf
);
112 if (x
->root_dev_ino
== nullptr)
113 die (EXIT_FAILURE
, errno
, _("failed to get attributes of %s"),
117 x
->preserve_all_root
= false;
121 cp_option_init (struct cp_options
*x
)
123 bool selinux_enabled
= (0 < is_selinux_enabled ());
125 cp_options_default (x
);
126 x
->copy_as_regular
= false; /* FIXME: maybe make this an option */
127 x
->reflink_mode
= REFLINK_AUTO
;
128 x
->dereference
= DEREF_NEVER
;
129 x
->unlink_dest_before_opening
= false;
130 x
->unlink_dest_after_failed_open
= false;
131 x
->hard_link
= false;
132 x
->interactive
= I_UNSPECIFIED
;
134 x
->install_mode
= false;
135 x
->one_file_system
= false;
136 x
->preserve_ownership
= true;
137 x
->preserve_links
= true;
138 x
->preserve_mode
= true;
139 x
->preserve_timestamps
= true;
140 x
->explicit_no_preserve_mode
= false;
141 x
->preserve_security_context
= selinux_enabled
;
142 x
->set_security_context
= nullptr;
143 x
->reduce_diagnostics
= false;
144 x
->data_copy_required
= true;
145 x
->require_preserve
= false; /* FIXME: maybe make this an option */
146 x
->require_preserve_context
= false;
147 x
->preserve_xattr
= true;
148 x
->require_preserve_xattr
= false;
150 x
->sparse_mode
= SPARSE_AUTO
; /* FIXME: maybe make this an option */
151 x
->symbolic_link
= false;
154 x
->stdin_tty
= isatty (STDIN_FILENO
);
156 x
->open_dangling_dest_symlink
= false;
159 x
->dest_info
= nullptr;
160 x
->src_info
= nullptr;
163 /* Move SOURCE onto DEST aka DEST_DIRFD+DEST_RELNAME.
164 Handle cross-file-system moves.
165 If SOURCE is a directory, DEST must not exist.
166 Return true if successful. */
169 do_move (char const *source
, char const *dest
,
170 int dest_dirfd
, char const *dest_relname
, const struct cp_options
*x
)
173 bool rename_succeeded
;
174 bool ok
= copy (source
, dest
, dest_dirfd
, dest_relname
, 0, x
,
175 ©_into_self
, &rename_succeeded
);
179 char const *dir_to_remove
;
182 /* In general, when copy returns with copy_into_self set, SOURCE is
183 the same as, or a parent of DEST. In this case we know it's a
184 parent. It doesn't make sense to move a directory into itself, and
185 besides in some situations doing so would give highly nonintuitive
186 results. Run this 'mkdir b; touch a c; mv * b' in an empty
187 directory. Here's the result of running echo $(find b -print):
188 b b/a b/b b/b/a b/c. Notice that only file 'a' was copied
189 into b/b. Handle this by giving a diagnostic, removing the
190 copied-into-self directory, DEST ('b/b' in the example),
193 dir_to_remove
= nullptr;
196 else if (rename_succeeded
)
198 /* No need to remove anything. SOURCE was successfully
199 renamed to DEST. Or the user declined to rename a file. */
200 dir_to_remove
= nullptr;
204 /* This may mean SOURCE and DEST referred to different devices.
205 It may also conceivably mean that even though they referred
206 to the same device, rename wasn't implemented for that device.
208 E.g., (from Joel N. Weber),
209 [...] there might someday be cases where you can't rename
210 but you can copy where the device name is the same, especially
211 on Hurd. Consider an ftpfs with a primitive ftp server that
212 supports uploading, downloading and deleting, but not renaming.
214 Also, note that comparing device numbers is not a reliable
215 check for 'can-rename'. Some systems can be set up so that
216 files from many different physical devices all have the same
217 st_dev field. This is a feature of some NFS mounting
220 We reach this point if SOURCE has been successfully copied
221 to DEST. Now we have to remove SOURCE.
223 This function used to resort to copying only when rename
224 failed and set errno to EXDEV. */
226 dir_to_remove
= source
;
229 if (dir_to_remove
!= nullptr)
231 struct rm_options rm_options
;
232 enum RM_status status
;
235 rm_option_init (&rm_options
);
236 rm_options
.verbose
= x
->verbose
;
237 dir
[0] = dir_to_remove
;
240 status
= rm ((void *) dir
, &rm_options
);
241 assert (VALID_STATUS (status
));
242 if (status
== RM_ERROR
)
253 if (status
!= EXIT_SUCCESS
)
258 Usage: %s [OPTION]... [-T] SOURCE DEST\n\
259 or: %s [OPTION]... SOURCE... DIRECTORY\n\
260 or: %s [OPTION]... -t DIRECTORY SOURCE...\n\
262 program_name
, program_name
, program_name
);
264 Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\
267 emit_mandatory_arg_note ();
270 --backup[=CONTROL] make a backup of each existing destination file\
272 -b like --backup but does not accept an argument\n\
275 --debug explain how a file is copied. Implies -v\n\
278 -f, --force do not prompt before overwriting\n\
279 -i, --interactive prompt before overwrite\n\
280 -n, --no-clobber do not overwrite an existing file\n\
281 If you specify more than one of -i, -f, -n, only the final one takes effect.\n\
284 --no-copy do not copy if renaming fails\n\
285 --strip-trailing-slashes remove any trailing slashes from each SOURCE\n\
287 -S, --suffix=SUFFIX override the usual backup suffix\n\
290 -t, --target-directory=DIRECTORY move all SOURCE arguments into DIRECTORY\n\
291 -T, --no-target-directory treat DEST as a normal file\n\
294 --update[=UPDATE] control which existing files are updated;\n\
295 UPDATE={all,none,older(default)}. See below\n\
296 -u equivalent to --update[=older]\n\
299 -v, --verbose explain what is being done\n\
300 -Z, --context set SELinux security context of destination\n\
301 file to default type\n\
303 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
304 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
305 emit_update_parameters_note ();
306 emit_backup_suffix_note ();
307 emit_ancillary_info (PROGRAM_NAME
);
313 main (int argc
, char **argv
)
317 bool make_backups
= false;
318 char const *backup_suffix
= nullptr;
319 char *version_control_string
= nullptr;
321 bool remove_trailing_slashes
= false;
322 char const *target_directory
= nullptr;
323 bool no_target_directory
= false;
326 bool selinux_enabled
= (0 < is_selinux_enabled ());
328 initialize_main (&argc
, &argv
);
329 set_program_name (argv
[0]);
330 setlocale (LC_ALL
, "");
331 bindtextdomain (PACKAGE
, LOCALEDIR
);
332 textdomain (PACKAGE
);
334 atexit (close_stdin
);
338 /* Try to disable the ability to unlink a directory. */
339 priv_set_remove_linkdir ();
341 while ((c
= getopt_long (argc
, argv
, "bfint:uvS:TZ", long_options
, nullptr))
349 version_control_string
= optarg
;
352 x
.interactive
= I_ALWAYS_YES
;
355 x
.interactive
= I_ASK_USER
;
358 x
.interactive
= I_ALWAYS_NO
;
361 x
.debug
= x
.verbose
= true;
366 case STRIP_TRAILING_SLASHES_OPTION
:
367 remove_trailing_slashes
= true;
370 if (target_directory
)
371 die (EXIT_FAILURE
, 0, _("multiple target directories specified"));
372 target_directory
= optarg
;
375 no_target_directory
= true;
378 if (optarg
== nullptr)
380 else if (x
.interactive
!= I_ALWAYS_NO
) /* -n takes precedence. */
382 enum Update_type update_opt
;
383 update_opt
= XARGMATCH ("--update", optarg
,
384 update_type_string
, update_type
);
385 if (update_opt
== UPDATE_ALL
)
387 /* Default mv operation. */
389 x
.interactive
= I_UNSPECIFIED
;
391 else if (update_opt
== UPDATE_NONE
)
394 x
.interactive
= I_ALWAYS_SKIP
;
396 else if (update_opt
== UPDATE_OLDER
)
399 x
.interactive
= I_UNSPECIFIED
;
408 backup_suffix
= optarg
;
411 /* As a performance enhancement, don't even bother trying
412 to "restorecon" when not on an selinux-enabled kernel. */
415 x
.preserve_security_context
= false;
416 x
.set_security_context
= selabel_open (SELABEL_CTX_FILE
,
418 if (! x
.set_security_context
)
419 error (0, errno
, _("warning: ignoring --context"));
422 case_GETOPT_HELP_CHAR
;
423 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
425 usage (EXIT_FAILURE
);
429 n_files
= argc
- optind
;
430 file
= argv
+ optind
;
432 if (n_files
<= !target_directory
)
435 error (0, 0, _("missing file operand"));
437 error (0, 0, _("missing destination file operand after %s"),
439 usage (EXIT_FAILURE
);
444 int target_dirfd
= AT_FDCWD
;
445 if (no_target_directory
)
447 if (target_directory
)
448 die (EXIT_FAILURE
, 0,
449 _("cannot combine --target-directory (-t) "
450 "and --no-target-directory (-T)"));
453 error (0, 0, _("extra operand %s"), quoteaf (file
[2]));
454 usage (EXIT_FAILURE
);
457 else if (target_directory
)
459 target_dirfd
= target_directory_operand (target_directory
, &sb
);
460 if (! target_dirfd_valid (target_dirfd
))
461 die (EXIT_FAILURE
, errno
, _("target directory %s"),
462 quoteaf (target_directory
));
466 char const *lastfile
= file
[n_files
- 1];
468 x
.rename_errno
= (renameatu (AT_FDCWD
, file
[0], AT_FDCWD
, lastfile
,
471 if (x
.rename_errno
!= 0)
473 int fd
= target_directory_operand (lastfile
, &sb
);
474 if (target_dirfd_valid (fd
))
478 target_directory
= lastfile
;
483 /* The last operand LASTFILE cannot be opened as a directory.
484 If there are more than two operands, report an error.
486 Also, report an error if LASTFILE is known to be a directory
487 even though it could not be opened, which can happen if
488 opening failed with EACCES on a platform lacking O_PATH.
489 In this case use stat to test whether LASTFILE is a
490 directory, in case opening a non-directory with (O_SEARCH
491 | O_DIRECTORY) failed with EACCES not ENOTDIR. */
494 || (O_PATHSEARCH
== O_SEARCH
&& err
== EACCES
495 && (sb
.st_mode
!= 0 || stat (lastfile
, &sb
) == 0)
496 && S_ISDIR (sb
.st_mode
)))
497 die (EXIT_FAILURE
, err
, _("target %s"), quoteaf (lastfile
));
502 /* Handle the ambiguity in the semantics of mv induced by the
503 varying semantics of the rename function. POSIX-compatible
504 systems (e.g., GNU/Linux) have a rename function that honors a
505 trailing slash in the source, while others (Solaris 9, FreeBSD
506 7.2) have a rename function that ignores it. */
507 if (remove_trailing_slashes
)
508 for (int i
= 0; i
< n_files
; i
++)
509 strip_trailing_slashes (file
[i
]);
511 if (x
.interactive
== I_ALWAYS_NO
)
514 if (make_backups
&& x
.interactive
== I_ALWAYS_NO
)
517 _("options --backup and --no-clobber are mutually exclusive"));
518 usage (EXIT_FAILURE
);
521 x
.backup_type
= (make_backups
522 ? xget_version (_("backup type"),
523 version_control_string
)
525 set_simple_backup_suffix (backup_suffix
);
529 if (target_directory
)
531 /* Initialize the hash table only if we'll need it.
532 The problem it is used to detect can arise only if there are
533 two or more files to move. */
538 for (int i
= 0; i
< n_files
; ++i
)
540 x
.last_file
= i
+ 1 == n_files
;
541 char const *source
= file
[i
];
542 char const *source_basename
= last_component (source
);
544 char *dest
= file_name_concat (target_directory
, source_basename
,
546 strip_trailing_slashes (dest_relname
);
547 ok
&= do_move (source
, dest
, target_dirfd
, dest_relname
, &x
);
554 ok
= do_move (file
[0], file
[1], AT_FDCWD
, file
[1], &x
);
557 main_exit (ok
? EXIT_SUCCESS
: EXIT_FAILURE
);