toybox/toys/posix/cp.c
<<
>>
Prefs
   1/* Copyright 2008 Rob Landley <rob@landley.net>
   2 *
   3 * See http://opengroup.org/onlinepubs/9699919799/utilities/cp.html
   4 * And http://opengroup.org/onlinepubs/9699919799/utilities/mv.html
   5 * And http://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic.html#INSTALL
   6 *
   7 * Posix says "cp -Rf dir file" shouldn't delete file, but our -f does.
   8 *
   9 * Deviations from posix: -adlnrsvF, --preserve... about half the
  10 * functionality in this cp isn't in posix. Posix is stuck in the 1970's.
  11 *
  12 * TODO: --preserve=links
  13 * TODO: what's this _CP_mode system.posix_acl_ business? We chmod()?
  14
  15// options shared between mv/cp must be in same order (right to left)
  16// for FLAG macros to work out right in shared infrastructure.
  17
  18USE_CP(NEWTOY(cp, "<2(preserve):;D(parents)RHLPprdaslvnF(remove-destination)fiT[-HLPd][-ni]", TOYFLAG_BIN))
  19USE_MV(NEWTOY(mv, "<2vnF(remove-destination)fiT[-ni]", TOYFLAG_BIN))
  20USE_INSTALL(NEWTOY(install, "<1cdDpsvm:o:g:", TOYFLAG_USR|TOYFLAG_BIN))
  21
  22config CP
  23  bool "cp"
  24  default y
  25  help
  26    usage: cp [-adfHiLlnPpRrsTv] [--preserve=motcxa] SOURCE... DEST
  27
  28    Copy files from SOURCE to DEST.  If more than one SOURCE, DEST must
  29    be a directory.
  30
  31    -a  Same as -dpr
  32    -D  Create leading dirs under DEST (--parents)
  33    -d  Don't dereference symlinks
  34    -F  Delete any existing destination file first (--remove-destination)
  35    -f  Delete destination files we can't write to
  36    -H  Follow symlinks listed on command line
  37    -i  Interactive, prompt before overwriting existing DEST
  38    -L  Follow all symlinks
  39    -l  Hard link instead of copy
  40    -n  No clobber (don't overwrite DEST)
  41    -P  Do not follow symlinks
  42    -p  Preserve timestamps, ownership, and mode
  43    -R  Recurse into subdirectories (DEST must be a directory)
  44    -r  Synonym for -R
  45    -s  Symlink instead of copy
  46    -T  DEST always treated as file, max 2 arguments
  47    -v  Verbose
  48
  49    Arguments to --preserve are the first letter(s) of:
  50
  51            mode - permissions (ignore umask for rwx, copy suid and sticky bit)
  52       ownership - user and group
  53      timestamps - file creation, modification, and access times.
  54         context - security context
  55           xattr - extended attributes
  56             all - all of the above
  57
  58config MV
  59  bool "mv"
  60  default y
  61  help
  62    usage: mv [-finTv] SOURCE... DEST
  63
  64    -f  Force copy by deleting destination file
  65    -i  Interactive, prompt before overwriting existing DEST
  66    -n  No clobber (don't overwrite DEST)
  67    -T  DEST always treated as file, max 2 arguments
  68    -v  Verbose
  69
  70config INSTALL
  71  bool "install"
  72  default y
  73  help
  74    usage: install [-dDpsv] [-o USER] [-g GROUP] [-m MODE] [SOURCE...] DEST
  75
  76    Copy files and set attributes.
  77
  78    -d  Act like mkdir -p
  79    -D  Create leading directories for DEST
  80    -g  Make copy belong to GROUP
  81    -m  Set permissions to MODE
  82    -o  Make copy belong to USER
  83    -p  Preserve timestamps
  84    -s  Call "strip -p"
  85    -v  Verbose
  86*/
  87
  88#define FORCE_FLAGS
  89#define FOR_cp
  90#include "toys.h"
  91
  92GLOBALS(
  93  union {
  94    // install's options
  95    struct {
  96      char *g, *o, *m;
  97    } i;
  98    // cp's options
  99    struct {
 100      char *preserve;
 101    } c;
 102  };
 103
 104  char *destname;
 105  struct stat top;
 106  int (*callback)(struct dirtree *try);
 107  uid_t uid;
 108  gid_t gid;
 109  int pflags;
 110)
 111
 112struct cp_preserve {
 113  char *name;
 114} static const cp_preserve[] = TAGGED_ARRAY(CP,
 115  {"mode"}, {"ownership"}, {"timestamps"}, {"context"}, {"xattr"},
 116);
 117
 118// Callback from dirtree_read() for each file/directory under a source dir.
 119
 120static int cp_node(struct dirtree *try)
 121{
 122  int fdout = -1, cfd = try->parent ? try->parent->extra : AT_FDCWD,
 123      tfd = dirtree_parentfd(try);
 124  unsigned flags = toys.optflags;
 125  char *catch = try->parent ? try->name : TT.destname, *err = "%s";
 126  struct stat cst;
 127
 128  if (!dirtree_notdotdot(try)) return 0;
 129
 130  // If returning from COMEAGAIN, jump straight to -p logic at end.
 131  if (S_ISDIR(try->st.st_mode) && try->again) {
 132    fdout = try->extra;
 133    err = 0;
 134  } else {
 135
 136    // -d is only the same as -r for symlinks, not for directories
 137    if (S_ISLNK(try->st.st_mode) && (flags & FLAG_d)) flags |= FLAG_r;
 138
 139    // Detect recursive copies via repeated top node (cp -R .. .) or
 140    // identical source/target (fun with hardlinks).
 141    if ((TT.top.st_dev == try->st.st_dev && TT.top.st_ino == try->st.st_ino
 142         && (catch = TT.destname))
 143        || (!fstatat(cfd, catch, &cst, 0) && cst.st_dev == try->st.st_dev
 144         && cst.st_ino == try->st.st_ino))
 145    {
 146      error_msg("'%s' is '%s'", catch, err = dirtree_path(try, 0));
 147      free(err);
 148
 149      return 0;
 150    }
 151
 152    // Handle -invF
 153
 154    if (!faccessat(cfd, catch, F_OK, 0) && !S_ISDIR(cst.st_mode)) {
 155      char *s;
 156
 157      if (S_ISDIR(try->st.st_mode)) {
 158        error_msg("dir at '%s'", s = dirtree_path(try, 0));
 159        free(s);
 160        return 0;
 161      } else if ((flags & FLAG_F) && unlinkat(cfd, catch, 0)) {
 162        error_msg("unlink '%s'", catch);
 163        return 0;
 164      } else if (flags & FLAG_n) return 0;
 165      else if (flags & FLAG_i) {
 166        fprintf(stderr, "%s: overwrite '%s'", toys.which->name,
 167          s = dirtree_path(try, 0));
 168        free(s);
 169        if (!yesno(0)) return 0;
 170      }
 171    }
 172
 173    if (flags & FLAG_v) {
 174      char *s = dirtree_path(try, 0);
 175      printf("%s '%s'\n", toys.which->name, s);
 176      free(s);
 177    }
 178
 179    // Loop for -f retry after unlink
 180    do {
 181
 182      // directory, hardlink, symlink, mknod (char, block, fifo, socket), file
 183
 184      // Copy directory
 185
 186      if (S_ISDIR(try->st.st_mode)) {
 187        struct stat st2;
 188
 189        if (!(flags & (FLAG_a|FLAG_r|FLAG_R))) {
 190          err = "Skipped dir '%s'";
 191          catch = try->name;
 192          break;
 193        }
 194
 195        // Always make directory writeable to us, so we can create files in it.
 196        //
 197        // Yes, there's a race window between mkdir() and open() so it's
 198        // possible that -p can be made to chown a directory other than the one
 199        // we created. The closest we can do to closing this is make sure
 200        // that what we open _is_ a directory rather than something else.
 201
 202        if (!mkdirat(cfd, catch, try->st.st_mode | 0200) || errno == EEXIST)
 203          if (-1 != (try->extra = openat(cfd, catch, O_NOFOLLOW)))
 204            if (!fstat(try->extra, &st2) && S_ISDIR(st2.st_mode))
 205              return DIRTREE_COMEAGAIN | (DIRTREE_SYMFOLLOW*!!FLAG(L));
 206
 207      // Hardlink
 208
 209      } else if (flags & FLAG_l) {
 210        if (!linkat(tfd, try->name, cfd, catch, 0)) err = 0;
 211
 212      // Copy tree as symlinks. For non-absolute paths this involves
 213      // appending the right number of .. entries as you go down the tree.
 214
 215      } else if (flags & FLAG_s) {
 216        char *s;
 217        struct dirtree *or;
 218        int dotdots = 0;
 219
 220        s = dirtree_path(try, 0);
 221        for (or = try; or->parent; or = or->parent) dotdots++;
 222
 223        if (*or->name == '/') dotdots = 0;
 224        if (dotdots) {
 225          char *s2 = xmprintf("%*c%s", 3*dotdots, ' ', s);
 226          free(s);
 227          s = s2;
 228          while(dotdots--) {
 229            memcpy(s2, "../", 3);
 230            s2 += 3;
 231          }
 232        }
 233        if (!symlinkat(s, cfd, catch)) {
 234          err = 0;
 235          fdout = AT_FDCWD;
 236        }
 237        free(s);
 238
 239      // Do something _other_ than copy contents of a file?
 240      } else if (!S_ISREG(try->st.st_mode)
 241                 && (try->parent || (flags & (FLAG_a|FLAG_P|FLAG_r))))
 242      {
 243        int i;
 244
 245        // make symlink, or make block/char/fifo/socket
 246        if (S_ISLNK(try->st.st_mode)
 247            ? ((i = readlinkat0(tfd, try->name, toybuf, sizeof(toybuf))) &&
 248               ((!unlinkat(cfd, catch, 0) || ENOENT == errno) &&
 249                !symlinkat(toybuf, cfd, catch)))
 250            : !mknodat(cfd, catch, try->st.st_mode, try->st.st_rdev))
 251        {
 252          err = 0;
 253          fdout = AT_FDCWD;
 254        }
 255
 256      // Copy contents of file.
 257      } else {
 258        int fdin;
 259
 260        fdin = openat(tfd, try->name, O_RDONLY);
 261        if (fdin < 0) {
 262          catch = try->name;
 263          break;
 264        }
 265        // When copying contents use symlink target's attributes
 266        if (S_ISLNK(try->st.st_mode)) fstat(fdin, &try->st);
 267        fdout = openat(cfd, catch, O_RDWR|O_CREAT|O_TRUNC, try->st.st_mode);
 268        if (fdout >= 0) {
 269          xsendfile(fdin, fdout);
 270          err = 0;
 271        }
 272
 273        // We only copy xattrs for files because there's no flistxattrat()
 274        if (TT.pflags&(_CP_xattr|_CP_context)) {
 275          ssize_t listlen = xattr_flist(fdin, 0, 0), len;
 276          char *name, *value, *list;
 277
 278          if (listlen>0) {
 279            list = xmalloc(listlen);
 280            xattr_flist(fdin, list, listlen);
 281            list[listlen-1] = 0; // I do not trust this API.
 282            for (name = list; name-list < listlen; name += strlen(name)+1) {
 283              if (!(TT.pflags&_CP_xattr) && strncmp(name, "security.", 9))
 284                continue;
 285              if ((len = xattr_fget(fdin, name, 0, 0))>0) {
 286                value = xmalloc(len);
 287                if (len == xattr_fget(fdin, name, value, len))
 288                  if (xattr_fset(fdout, name, value, len, 0))
 289                    perror_msg("%s setxattr(%s=%s)", catch, name, value);
 290                free(value);
 291              }
 292            }
 293            free(list);
 294          }
 295        }
 296
 297        close(fdin);
 298      }
 299    } while (err && (flags & (FLAG_f|FLAG_n)) && !unlinkat(cfd, catch, 0));
 300  }
 301
 302  // Did we make a thing?
 303  if (fdout != -1) {
 304    int rc;
 305
 306    // Inability to set --preserve isn't fatal, some require root access.
 307
 308    // ownership
 309    if (TT.pflags & _CP_ownership) {
 310
 311      // permission bits already correct for mknod and don't apply to symlink
 312      // If we can't get a filehandle to the actual object, use racy functions
 313      if (fdout == AT_FDCWD)
 314        rc = fchownat(cfd, catch, try->st.st_uid, try->st.st_gid,
 315                      AT_SYMLINK_NOFOLLOW);
 316      else rc = fchown(fdout, try->st.st_uid, try->st.st_gid);
 317      if (rc && !geteuid()) {
 318        char *pp;
 319
 320        perror_msg("chown '%s'", pp = dirtree_path(try, 0));
 321        free(pp);
 322      }
 323    }
 324
 325    // timestamp
 326    if (TT.pflags & _CP_timestamps) {
 327      struct timespec times[] = {try->st.st_atim, try->st.st_mtim};
 328
 329      if (fdout == AT_FDCWD) utimensat(cfd, catch, times, AT_SYMLINK_NOFOLLOW);
 330      else futimens(fdout, times);
 331    }
 332
 333    // mode comes last because other syscalls can strip suid bit
 334    if (fdout != AT_FDCWD) {
 335      if (TT.pflags & _CP_mode) fchmod(fdout, try->st.st_mode);
 336      xclose(fdout);
 337    }
 338
 339    if (CFG_MV && toys.which->name[0] == 'm')
 340      if (unlinkat(tfd, try->name, S_ISDIR(try->st.st_mode) ? AT_REMOVEDIR :0))
 341        err = "%s";
 342  }
 343
 344  if (err) {
 345    char *f = 0;
 346
 347    if (catch == try->name) {
 348      f = dirtree_path(try, 0);
 349      while (try->parent) try = try->parent;
 350      catch = xmprintf("%s%s", TT.destname, f+strlen(try->name));
 351      free(f);
 352      f = catch;
 353    }
 354    perror_msg(err, catch);
 355    free(f);
 356  }
 357  return 0;
 358}
 359
 360void cp_main(void)
 361{
 362  char *destname = toys.optargs[--toys.optc];
 363  int i, destdir = !stat(destname, &TT.top) && S_ISDIR(TT.top.st_mode);
 364
 365  if (FLAG(T)) {
 366    if (toys.optc>1) help_exit("Max 2 arguments");
 367    if (destdir) error_exit("'%s' is a directory", destname);
 368  }
 369
 370  if ((toys.optc>1 || FLAG(D)) && !destdir)
 371    error_exit("'%s' not directory", destname);
 372
 373  if (FLAG(a)||FLAG(p)) TT.pflags = _CP_mode|_CP_ownership|_CP_timestamps;
 374
 375  // Not using comma_args() (yet?) because interpeting as letters.
 376  if (FLAG(preserve)) {
 377    char *pre = xstrdup(TT.c.preserve ? TT.c.preserve : "mot"), *s;
 378
 379    if (comma_remove(pre, "all")) TT.pflags = ~0;
 380    for (i=0; i<ARRAY_LEN(cp_preserve); i++)
 381      while (comma_remove(pre, cp_preserve[i].name)) TT.pflags |= 1<<i;
 382    if (*pre) {
 383
 384      // Try to interpret as letters, commas won't set anything this doesn't.
 385      for (s = pre; *s; s++) {
 386        for (i=0; i<ARRAY_LEN(cp_preserve); i++)
 387          if (*s == *cp_preserve[i].name) break;
 388        if (i == ARRAY_LEN(cp_preserve)) {
 389          if (*s == 'a') TT.pflags = ~0;
 390          else break;
 391        } else TT.pflags |= 1<<i;
 392      }
 393
 394      if (*s) error_exit("bad --preserve=%s", pre);
 395    }
 396    free(pre);
 397  }
 398  if (TT.pflags & _CP_mode) umask(0);
 399  if (!TT.callback) TT.callback = cp_node;
 400
 401  // Loop through sources
 402  for (i=0; i<toys.optc; i++) {
 403    char *src = toys.optargs[i], *trail = src;
 404    int rc = 1;
 405
 406    while (*++trail);
 407    if (*--trail == '/') *trail = 0;
 408
 409    if (destdir) {
 410      char *s = FLAG(D) ? src : getbasename(src);
 411
 412      TT.destname = xmprintf("%s/%s", destname, s);
 413      if (FLAG(D)) {
 414        if (!(s = fileunderdir(TT.destname, destname))) {
 415          error_msg("%s not under %s", TT.destname, destname);
 416          continue;
 417        }
 418        // TODO: .. follows abspath, not links...
 419        free(s);
 420        mkpath(TT.destname);
 421      }
 422    } else TT.destname = destname;
 423
 424    errno = EXDEV;
 425    if (CFG_MV && toys.which->name[0] == 'm') {
 426      int force = FLAG(f), no_clobber = FLAG(n);
 427
 428      if (!force || no_clobber) {
 429        struct stat st;
 430        int exists = !stat(TT.destname, &st);
 431
 432        // Prompt if -i or file isn't writable.  Technically "is writable" is
 433        // more complicated (022 is not writeable by the owner, just everybody
 434        // _else_) but I don't care.
 435        if (exists && (FLAG(i) || (!(st.st_mode & 0222) && isatty(0)))) {
 436          fprintf(stderr, "%s: overwrite '%s'", toys.which->name, TT.destname);
 437          if (!yesno(0)) rc = 0;
 438          else unlink(TT.destname);
 439        }
 440        // if -n and dest exists, don't try to rename() or copy
 441        if (exists && no_clobber) rc = 0;
 442      }
 443      if (rc) rc = rename(src, TT.destname);
 444      if (errno && !*trail) *trail = '/';
 445    }
 446
 447    // Copy if we didn't mv, skipping nonexistent sources
 448    if (rc) {
 449      if (errno!=EXDEV || dirtree_flagread(src, DIRTREE_SHUTUP+
 450        DIRTREE_SYMFOLLOW*!!(FLAG(H)||FLAG(L)), TT.callback))
 451          perror_msg("bad '%s'", src);
 452    }
 453    if (destdir) free(TT.destname);
 454  }
 455}
 456
 457void mv_main(void)
 458{
 459  toys.optflags |= FLAG_d|FLAG_p|FLAG_R;
 460
 461  cp_main();
 462}
 463
 464// Export cp flags into install's flag context.
 465
 466static inline int cp_flag_F(void) { return FLAG_F; };
 467static inline int cp_flag_p(void) { return FLAG_p; };
 468static inline int cp_flag_v(void) { return FLAG_v; };
 469
 470// Switch to install's flag context
 471#define CLEANUP_cp
 472#define FOR_install
 473#include <generated/flags.h>
 474
 475static int install_node(struct dirtree *try)
 476{
 477  try->st.st_mode = TT.i.m ? string_to_mode(TT.i.m, try->st.st_mode) : 0755;
 478  if (TT.i.g) try->st.st_gid = TT.gid;
 479  if (TT.i.o) try->st.st_uid = TT.uid;
 480
 481  // Always returns 0 because no -r
 482  cp_node(try);
 483
 484  // No -r so always one level deep, so destname as set by cp_node() is correct
 485  if (FLAG(s) && xrun((char *[]){"strip", "-p", TT.destname, 0}))
 486    toys.exitval = 1;
 487
 488  return 0;
 489}
 490
 491void install_main(void)
 492{
 493  char **ss;
 494
 495  TT.uid = TT.i.o ? xgetuid(TT.i.o) : -1;
 496  TT.gid = TT.i.g ? xgetgid(TT.i.g) : -1;
 497
 498  if (FLAG(d)) {
 499    for (ss = toys.optargs; *ss; ss++) {
 500      if (FLAG(v)) printf("%s\n", *ss);
 501      if (mkpathat(AT_FDCWD, *ss, 0777, MKPATHAT_MKLAST | MKPATHAT_MAKE))
 502        perror_msg_raw(*ss);
 503      if (FLAG(g)||FLAG(o))
 504        if (lchown(*ss, TT.uid, TT.gid)) perror_msg("chown '%s'", *ss);
 505    }
 506
 507    return;
 508  }
 509
 510  if (FLAG(D)) {
 511    TT.destname = toys.optargs[toys.optc-1];
 512    if (mkpathat(AT_FDCWD, TT.destname, 0, MKPATHAT_MAKE))
 513      perror_exit("-D '%s'", TT.destname);
 514    if (toys.optc == 1) return;
 515  }
 516  if (toys.optc < 2) error_exit("needs 2 args");
 517
 518  // Translate flags from install to cp
 519  toys.optflags = cp_flag_F() + cp_flag_v()*!!FLAG(v)
 520    + cp_flag_p()*!!(FLAG(p)|FLAG(o)|FLAG(g));
 521
 522  TT.callback = install_node;
 523  cp_main();
 524}
 525