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                   &copy_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