view toys/posix/cp.c @ 694:786841fdb1e0

Reindent to two spaces per level. Remove vi: directives that haven't worked right in years (ubuntu broke its' vim implementation). Remove trailing spaces. Add/remove blank lines. Re-wordwrap in places. Update documentation with new coding style. The actual code should be the same afterward, this is just cosmetic refactoring.
author Rob Landley <rob@landley.net>
date Tue, 13 Nov 2012 17:14:08 -0600
parents 7e846e281e38
children 7bbb49149bb6
line wrap: on
line source

/* Copyright 2008 Rob Landley <rob@landley.net>
 *
 * See http://opengroup.org/onlinepubs/9699919799/utilities/cp.html
 *
 * TODO: "R+ra+d+p+r" sHLPR

USE_CP(NEWTOY(cp, "<2"USE_CP_MORE("rdavsl")"RHLPfip", TOYFLAG_BIN))

config CP
  bool "cp (broken by dirtree changes)"
  default n
  help
    usage: cp [-fipRHLP] SOURCE... DEST

    Copy files from SOURCE to DEST.  If more than one SOURCE, DEST must
    be a directory.

    -f	force copy by deleting destination file
    -i	interactive, prompt before overwriting existing DEST
    -p	preserve timestamps, ownership, and permissions
    -R	recurse into subdirectories (DEST must be a directory)
    -H	Follow symlinks listed on command line
    -L	Follow all symlinks
    -P	Do not follow symlinks [default]

config CP_MORE
  bool "cp -rdavsl options"
  default y
  depends on CP
  help
    usage: cp [-rdavsl]

    -r	synonym for -R
    -d	don't dereference symlinks
    -a	same as -dpr
    -l	hard link instead of copy
    -s	symlink instead of copy
    -v	verbose
*/

#define FOR_cp
#include "toys.h"

// TODO: PLHlsd

GLOBALS(
  char *destname;
  int destisdir;
  int keep_symlinks;
)

// Copy an individual file or directory to target.

void cp_file(char *src, char *dst, struct stat *srcst)
{
  int fdout = -1;

  // -i flag is specified and dst file exists.
  if ((toys.optflags&FLAG_i) && !access(dst, R_OK)
    && !yesno("cp: overwrite", 1))
      return;

  if (toys.optflags & FLAG_v) printf("'%s' -> '%s'\n", src, dst);

  // Copy directory or file to destination.

  if (S_ISDIR(srcst->st_mode)) {
    struct stat st2;

    // Always make directory writeable to us, so we can create files in it.
    //
    // Yes, there's a race window between mkdir() and open() so it's
    // possible that -p can be made to chown a directory other than the one
    // we created.  The closest we can do to closing this is make sure
    // that what we open _is_ a directory rather than something else.

    if ((mkdir(dst, srcst->st_mode | 0200) && errno != EEXIST)
      || 0>(fdout=open(dst, 0)) || fstat(fdout, &st2) || !S_ISDIR(st2.st_mode))
    {
      perror_exit("mkdir '%s'", dst);
    }
  } else if (TT.keep_symlinks && S_ISLNK(srcst->st_mode)) {
    char *link = xreadlink(src);

    // Note: -p currently has no effect on symlinks.  How do you get a
    // filehandle to them?  O_NOFOLLOW causes the open to fail.
    if (!link || symlink(link, dst)) perror_msg("link '%s'", dst);
    free(link);
    return;
  } else if (toys.optflags & FLAG_l) {
    if (link(src, dst)) perror_msg("link '%s'");
    return;
  } else {
    int fdin, i;

    fdin = xopen(src, O_RDONLY);
    for (i=2 ; i; i--) {
      fdout = open(dst, O_RDWR|O_CREAT|O_TRUNC, srcst->st_mode);
      if (fdout>=0 || !(toys.optflags & FLAG_f)) break;
      unlink(dst);
    }
    if (fdout<0) perror_exit("%s", dst);
    xsendfile(fdin, fdout);
    close(fdin);
  }

  // Inability to set these isn't fatal, some require root access.
  // Can't do fchmod() etc here because -p works on mkdir, too.

  if (toys.optflags & (FLAG_p|FLAG_a)) {
    int mask = umask(0);
    struct utimbuf ut;

    (void) fchown(fdout,srcst->st_uid, srcst->st_gid);
    ut.actime = srcst->st_atime;
    ut.modtime = srcst->st_mtime;
    utime(dst, &ut);
    umask(mask);
  }
  xclose(fdout);
}

// Callback from dirtree_read() for each file/directory under a source dir.

int cp_node(struct dirtree *node)
{
  char *path = dirtree_path(node, 0); // TODO: use openat() instead
  char *s = path+strlen(path);
  struct dirtree *n;

  // Find appropriate chunk of path for destination.

  n = node;
  if (!TT.destisdir) n = n->parent;
  for (;;n = n->parent) {
    while (s!=path) if (*(--s)=='/') break;
    if (!n) break;
  }
  if (s != path) s++;

  s = xmsprintf("%s/%s", TT.destname, s);
  cp_file(path, s, &(node->st));
  free(s);
  free(path); // redo this whole darn function.

  return 0;
}

void cp_main(void)
{
  char *dpath = NULL;
  struct stat st, std;
  int i;

  // Identify destination

  if (!stat(TT.destname, &std) && S_ISDIR(std.st_mode)) TT.destisdir++;
  else if (toys.optc>1) error_exit("'%s' not directory", TT.destname);

   // TODO: This is too early: we haven't created it yet if we need to
  if (toys.optflags & (FLAG_R|FLAG_r|FLAG_a))
    dpath = realpath(TT.destname = toys.optargs[--toys.optc], NULL);

  // Loop through sources

  for (i=0; i<toys.optc; i++) {
    char *dst, *src = toys.optargs[i];

    // Skip src==dest (TODO check inodes to catch "cp blah ./blah").

    if (!strncmp(src, TT.destname)) continue;

    // Skip nonexistent sources.

    TT.keep_symlinks = toys.optflags & (FLAG_d|FLAG_a);
    if (TT.keep_symlinks ? lstat(src, &st) : stat(src, &st)
      || (st.st_dev = dst.st_dev && st.st_ino == dst.dst_ino))
    {
objection:
      perror_msg("bad '%s'", src);
      toys.exitval = 1;
      continue;
    }

    // Copy directory or file.

    if (TT.destisdir) {
      char *s;

      // Catch "cp -R .. ." and friends that would go on forever
      if (dpath && (s = realpath(src, NULL)) {
        int i = strlen(s);
        i = (!strncmp(s, dst, i) && (!s[i] || s[i]=='/'));
        free(s);

        if (i) goto objection;
      }

      // Create destination filename within directory
      dst = strrchr(src, '/');
      if (dst) dst++;
      else dst=src;
      dst = xmsprintf("%s/%s", TT.destname, dst);
    } else dst = TT.destname;

    if (S_ISDIR(st.st_mode)) {
      if (toys.optflags & (FLAG_r|FLAG_R|FLAG_a)) {
        cp_file(src, dst, &st);

        TT.keep_symlinks++;
        dirtree_read(src, cp_node);
      } else error_msg("Skipped dir '%s'", src);
    } else cp_file(src, dst, &st);
    if (TT.destisdir) free(dst);
  }

  if (CFG_TOYBOX_FREE) free(dpath);
  return;
}