(root)/
coreutils-9.4/
src/
mv.c
       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
      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
     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
     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
     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
     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  }