busybox/coreutils/mv.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Mini mv implementation for busybox
   4 *
   5 * Copyright (C) 2000 by Matt Kraai <kraai@alumni.carnegiemellon.edu>
   6 * SELinux support by Yuichi Nakamura <ynakam@hitachisoft.jp>
   7 *
   8 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   9 */
  10/* Mar 16, 2003      Manuel Novoa III   (mjn3@codepoet.org)
  11 *
  12 * Size reduction and improved error checking.
  13 */
  14//config:config MV
  15//config:       bool "mv (10 kb)"
  16//config:       default y
  17//config:       help
  18//config:       mv is used to move or rename files or directories.
  19
  20//applet:IF_MV(APPLET_NOEXEC(mv, mv, BB_DIR_BIN, BB_SUID_DROP, mv))
  21/* NOEXEC despite cases when it can be a "runner" (mv LARGE_DIR OTHER_FS) */
  22
  23//kbuild:lib-$(CONFIG_MV) += mv.o
  24
  25//usage:#define mv_trivial_usage
  26//usage:       "[-finT] SOURCE DEST\n"
  27//usage:       "or: mv [-fin] SOURCE... { -t DIRECTORY | DIRECTORY }"
  28//usage:#define mv_full_usage "\n\n"
  29//usage:       "Rename SOURCE to DEST, or move SOURCEs to DIRECTORY\n"
  30//usage:     "\n        -f      Don't prompt before overwriting"
  31//usage:     "\n        -i      Interactive, prompt before overwrite"
  32//usage:     "\n        -n      Don't overwrite an existing file"
  33//usage:     "\n        -T      Refuse to move if DEST is a directory"
  34//usage:     "\n        -t DIR  Move all SOURCEs into DIR"
  35//usage:
  36//usage:#define mv_example_usage
  37//usage:       "$ mv /tmp/foo /bin/bar\n"
  38
  39#include "libbb.h"
  40#include "libcoreutils/coreutils.h"
  41
  42int mv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
  43int mv_main(int argc, char **argv)
  44{
  45        struct stat statbuf;
  46        const char *last;
  47        const char *dest;
  48        unsigned flags;
  49        int dest_exists;
  50        int status = 0;
  51        int copy_flag = 0;
  52
  53#define OPT_FORCE       (1 << 0)
  54#define OPT_INTERACTIVE (1 << 1)
  55#define OPT_NOCLOBBER   (1 << 2)
  56#define OPT_DESTNOTDIR  (1 << 3)
  57#define OPT_DESTDIR     (1 << 4)
  58#define OPT_VERBOSE     ((1 << 5) * ENABLE_FEATURE_VERBOSE)
  59        flags = getopt32long(argv, "^"
  60                        "finTt:v"
  61                        "\0"
  62        /* At least one argument. (Usually two+, but -t DIR can have only one) */
  63                        "-1"
  64        /* only the final one of -f, -i, -n takes effect */
  65                        ":f-in:i-fn:n-fi"
  66        /* -t and -T don't mix */
  67                        ":t--T:T--t",
  68                        "interactive\0" No_argument "i"
  69                        "force\0"       No_argument "f"
  70                        "no-clobber\0"  No_argument "n"
  71                        "no-target-directory\0" No_argument "T"
  72                        "target-directory\0" Required_argument "t"
  73                        IF_FEATURE_VERBOSE(
  74                        "verbose\0"     No_argument "v",
  75                        &last
  76                        )
  77        );
  78        argc -= optind;
  79        argv += optind;
  80
  81        if (!(flags & OPT_DESTDIR)) {
  82                last = argv[argc - 1];
  83                if (argc < 2)
  84                        bb_show_usage();
  85                if (argc != 2) {
  86                        if (flags & OPT_DESTNOTDIR)
  87                                bb_show_usage();
  88                        /* "mv A B C... DIR" - target must be dir */
  89                } else /* argc == 2 */ {
  90                        /* "mv A B" - only case where target can be not a dir */
  91                        dest_exists = cp_mv_stat(last, &statbuf);
  92                        if (dest_exists < 0) { /* error other than ENOENT */
  93                                return EXIT_FAILURE;
  94                        }
  95                        if (!(dest_exists & 2)) {
  96                                /* last is not a directory */
  97                                dest = last;
  98                                goto DO_MOVE;
  99                        }
 100                        /* last is a directory */
 101                        if (flags & OPT_DESTNOTDIR) {
 102                                if (stat(argv[0], &statbuf) == 0 && !S_ISDIR(statbuf.st_mode))
 103                                        bb_error_msg_and_die("'%s' is a directory", last);
 104                                /* "mv -T DIR1 DIR2" is allowed (renames a dir) */
 105                                dest = last;
 106                                goto DO_MOVE;
 107                        }
 108                        /* else: fall through into "do { move SRC to DIR/SRC } while" loop */
 109                }
 110        }
 111        /* else: last is DIR from "-t DIR" */
 112
 113        do {
 114                dest = concat_path_file(last, bb_get_last_path_component_strip(*argv));
 115                dest_exists = cp_mv_stat(dest, &statbuf);
 116                if (dest_exists < 0) {
 117                        goto RET_1;
 118                }
 119
 120 DO_MOVE:
 121                if (dest_exists) {
 122                        if (flags & OPT_NOCLOBBER)
 123                                goto RET_0;
 124                        if (!(flags & OPT_FORCE)
 125                         && ((access(dest, W_OK) < 0 && isatty(0))
 126                            || (flags & OPT_INTERACTIVE))
 127                        ) {
 128                                if (fprintf(stderr, "mv: overwrite '%s'? ", dest) < 0) {
 129                                        goto RET_1;  /* Ouch! fprintf failed! */
 130                                }
 131                                if (!bb_ask_y_confirmation()) {
 132                                        goto RET_0;
 133                                }
 134                        }
 135                }
 136
 137                if (rename(*argv, dest) < 0) {
 138                        int source_exists;
 139
 140                        if (errno != EXDEV
 141                         || (source_exists = cp_mv_stat2(*argv, &statbuf, lstat)) < 1
 142                        ) {
 143                                bb_perror_msg("can't rename '%s'", *argv);
 144                        } else {
 145                                static const char fmt[] ALIGN1 =
 146                                        "can't overwrite %sdirectory with %sdirectory";
 147
 148                                if (dest_exists) {
 149                                        if (dest_exists == 3) {
 150                                                if (source_exists != 3) {
 151                                                        bb_error_msg(fmt, "", "non-");
 152                                                        goto RET_1;
 153                                                }
 154                                        } else {
 155                                                if (source_exists == 3) {
 156                                                        bb_error_msg(fmt, "non-", "");
 157                                                        goto RET_1;
 158                                                }
 159                                        }
 160                                        if (unlink(dest) < 0) {
 161                                                bb_perror_msg("can't remove '%s'", dest);
 162                                                goto RET_1;
 163                                        }
 164                                }
 165                                /* FILEUTILS_RECUR also prevents nasties like
 166                                 * "read from device and write contents to dst"
 167                                 * instead of "create same device node" */
 168                                copy_flag = FILEUTILS_RECUR | FILEUTILS_PRESERVE_STATUS;
 169#if ENABLE_SELINUX
 170                                copy_flag |= FILEUTILS_PRESERVE_SECURITY_CONTEXT;
 171#endif
 172                                if ((copy_file(*argv, dest, copy_flag) >= 0)
 173                                 && (remove_file(*argv, FILEUTILS_RECUR | FILEUTILS_FORCE) >= 0)
 174                                ) {
 175                                        goto RET_0;
 176                                }
 177                        }
 178 RET_1:
 179                        status = 1;
 180                }
 181 RET_0:
 182                if (flags & OPT_VERBOSE) {
 183                        printf("'%s' -> '%s'\n", *argv, dest);
 184                }
 185                if (dest != last) {
 186                        free((void *) dest);
 187                }
 188        } while (*++argv && *argv != last);
 189
 190        return status;
 191}
 192