]> git.saurik.com Git - apple/file_cmds.git/blobdiff - cp/utils.c
file_cmds-321.100.10.0.1.tar.gz
[apple/file_cmds.git] / cp / utils.c
index b55bbc909103e94bb49f8cb8439280b396fcf0e3..feecc0c85528a475ec18da490a513ddd2258e741 100644 (file)
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *     This product includes software developed by the University of
- *     California, Berkeley and its contributors.
  * 4. Neither the name of the University nor the names of its contributors
  *    may be used to endorse or promote products derived from this software
  *    without specific prior written permission.
@@ -37,8 +33,10 @@ static char sccsid[] = "@(#)utils.c  8.3 (Berkeley) 4/1/94";
 #endif
 #endif /* not lint */
 #include <sys/cdefs.h>
-__RCSID("$FreeBSD: src/bin/cp/utils.c,v 1.38 2002/07/31 16:52:16 markm Exp $");
+__FBSDID("$FreeBSD: src/bin/cp/utils.c,v 1.46 2005/09/05 04:36:08 csjp Exp $");
 
+#include <sys/types.h>
+#include <sys/acl.h>
 #include <sys/param.h>
 #include <sys/stat.h>
 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
@@ -54,21 +52,52 @@ __RCSID("$FreeBSD: src/bin/cp/utils.c,v 1.38 2002/07/31 16:52:16 markm Exp $");
 #include <stdlib.h>
 #include <sysexits.h>
 #include <unistd.h>
+#include <locale.h>
+
+#ifdef __APPLE__
+#include <sys/time.h>
+#include <copyfile.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <get_compat.h> 
+#include <sys/attr.h>
+#include <sys/clonefile.h>
+#else 
+#define COMPAT_MODE(a,b) (1)
+#endif /* __APPLE__ */
 
 #include "extern.h"
+#define        cp_pct(x,y)     (int)(100.0 * (double)(x) / (double)(y))
+
+/* Memory strategy threshold, in pages: if physmem is larger then this, use a 
+ * large buffer */
+#define PHYSPAGES_THRESHOLD (32*1024)
+
+/* Maximum buffer size in bytes - do not allow it to grow larger than this */
+#define BUFSIZE_MAX (2*1024*1024)
+
+/* Small (default) buffer size in bytes. It's inefficient for this to be
+ * smaller than MAXPHYS */
+#define BUFSIZE_SMALL (MAXPHYS)
 
 int
-copy_file(FTSENT *entp, int dne)
+copy_file(const FTSENT *entp, int dne)
 {
-       static char buf[MAXBSIZE];
+       static char *buf = NULL;
+       static size_t bufsize;
        struct stat *fs;
-       int ch, checkch, from_fd, rcount, rval, to_fd;
+       int ch, checkch, from_fd, rval, to_fd;
+       ssize_t rcount;
        ssize_t wcount;
        size_t wresid;
+       off_t wtotal;
        char *bufp;
+       char resp[] = {'\0', '\0'};
 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
        char *p;
 #endif
+       mode_t mode = 0;
+       struct stat to_stat;
 
        if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
                warn("%s", entp->fts_path);
@@ -90,32 +119,73 @@ copy_file(FTSENT *entp, int dne)
                if (nflag) {
                        if (vflag)
                                printf("%s not overwritten\n", to.p_path);
-                       return (0);
+                       (void)close(from_fd);
+                       return (1);
                } else if (iflag) {
                        (void)fprintf(stderr, "overwrite %s? %s", 
                                        to.p_path, YESNO);
+
+                       /* Load user specified locale */
+                       setlocale(LC_MESSAGES, "");
+
                        checkch = ch = getchar();
                        while (ch != '\n' && ch != EOF)
                                ch = getchar();
-                       if (checkch != 'y' && checkch != 'Y') {
+
+                       /* only care about the first character */
+                       resp[0] = checkch;
+
+                       if (rpmatch(resp) != 1) {
                                (void)close(from_fd);
                                (void)fprintf(stderr, "not overwritten\n");
                                return (1);
                        }
                }
                
-               if (fflag) {
-                   /* remove existing destination file name, 
-                    * create a new file  */
-                   (void)unlink(to.p_path);
-                   to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
-                                fs->st_mode & ~(S_ISUID | S_ISGID));
-               } else 
-                   /* overwrite existing destination file name */
+               if (cflag) {
+                       (void)unlink(to.p_path);
+                       int error = clonefile(entp->fts_path, to.p_path, 0);
+                       if (error)
+                               warn("%s: clonefile failed", to.p_path);
+                       (void)close(from_fd);
+                       return error == 0 ? 0 : 1;
+               }
+
+               if (COMPAT_MODE("bin/cp", "unix2003")) {
+                   /* first try to overwrite existing destination file name */
                    to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
-       } else
+                   if (to_fd == -1) {
+                       if (fflag) {
+                           /* Only if it fails remove file and create a new one */
+                           (void)unlink(to.p_path);
+                           to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
+                                        fs->st_mode & ~(S_ISUID | S_ISGID));
+                       }
+                   }
+               } else {
+                       if (fflag) {
+                           /* remove existing destination file name, 
+                            * create a new file  */
+                           (void)unlink(to.p_path);
+                           to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
+                                        fs->st_mode & ~(S_ISUID | S_ISGID));
+                       } else 
+                           /* overwrite existing destination file name */
+                           to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
+               }
+       } else {
+
+               if (cflag) {
+                       int error = clonefile(entp->fts_path, to.p_path, 0);
+                       if (error)
+                               warn("%s: clonefile failed", to.p_path);
+                       (void)close(from_fd);
+                       return error == 0 ? 0 : 1;
+               }
+
                to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
                    fs->st_mode & ~(S_ISUID | S_ISGID));
+       }
 
        if (to_fd == -1) {
                warn("%s", to.p_path);
@@ -125,21 +195,65 @@ copy_file(FTSENT *entp, int dne)
 
        rval = 0;
 
+#ifdef __APPLE__
+       if (S_ISREG(fs->st_mode)) {
+               struct statfs sfs;
+
+               /*
+                * Pre-allocate blocks for the destination file if it
+                * resides on Xsan.
+                */
+               if (fstatfs(to_fd, &sfs) == 0 &&
+                   strcmp(sfs.f_fstypename, "acfs") == 0) {
+                       fstore_t fst;
+
+                       fst.fst_flags = 0;
+                       fst.fst_posmode = F_PEOFPOSMODE;
+                       fst.fst_offset = 0;
+                       fst.fst_length = fs->st_size;
+
+                       (void) fcntl(to_fd, F_PREALLOCATE, &fst);
+               }
+       }
+#endif /* __APPLE__ */
+
+       if (fstat(to_fd, &to_stat) != -1) {
+              mode = to_stat.st_mode;
+              if ((mode & (S_IRWXG|S_IRWXO))
+                  && fchmod(to_fd, mode & ~(S_IRWXG|S_IRWXO))) {
+                      if (errno != EPERM) /* we have write access but do not own the file */
+                              warn("%s: fchmod failed", to.p_path);
+                      mode = 0;
+              }
+       } else {
+              warn("%s", to.p_path);
+       }
        /*
         * Mmap and write if less than 8M (the limit is so we don't totally
         * trash memory on big files.  This is really a minor hack, but it
         * wins some CPU back.
         */
 #ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
-       if (S_ISREG(fs->st_mode) && fs->st_size <= 8 * 1048576) {
+       if (S_ISREG(fs->st_mode) && fs->st_size > 0 &&
+           fs->st_size <= 8 * 1048576) {
                if ((p = mmap(NULL, (size_t)fs->st_size, PROT_READ,
                    MAP_SHARED, from_fd, (off_t)0)) == MAP_FAILED) {
                        warn("%s", entp->fts_path);
                        rval = 1;
                } else {
+                       wtotal = 0;
                        for (bufp = p, wresid = fs->st_size; ;
                            bufp += wcount, wresid -= (size_t)wcount) {
                                wcount = write(to_fd, bufp, wresid);
+                               wtotal += wcount;
+                               if (info) {
+                                       info = 0;
+                                       (void)fprintf(stderr,
+                                               "%s -> %s %3d%%\n",
+                                               entp->fts_path, to.p_path,
+                                               cp_pct(wtotal, fs->st_size));
+                                               
+                               }
                                if (wcount >= (ssize_t)wresid || wcount <= 0)
                                        break;
                        }
@@ -156,10 +270,35 @@ copy_file(FTSENT *entp, int dne)
        } else
 #endif
        {
-               while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
+               if (buf == NULL) {
+                       /*
+                        * Note that buf and bufsize are static. If
+                        * malloc() fails, it will fail at the start
+                        * and not copy only some files. 
+                        */ 
+                       if (sysconf(_SC_PHYS_PAGES) > 
+                           PHYSPAGES_THRESHOLD)
+                               bufsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
+                       else
+                               bufsize = BUFSIZE_SMALL;
+                       buf = malloc(bufsize);
+                       if (buf == NULL)
+                               err(1, "Not enough memory");
+               }
+               wtotal = 0;
+               while ((rcount = read(from_fd, buf, bufsize)) > 0) {
                        for (bufp = buf, wresid = rcount; ;
                            bufp += wcount, wresid -= wcount) {
                                wcount = write(to_fd, bufp, wresid);
+                               wtotal += wcount;
+                               if (info) {
+                                       info = 0;
+                                       (void)fprintf(stderr,
+                                               "%s -> %s %3d%%\n",
+                                               entp->fts_path, to.p_path,
+                                               cp_pct(wtotal, fs->st_size));
+                                               
+                               }
                                if (wcount >= (ssize_t)wresid || wcount <= 0)
                                        break;
                        }
@@ -181,9 +320,28 @@ copy_file(FTSENT *entp, int dne)
         * or its contents might be irreplaceable.  It would only be safe
         * to remove it if we created it and its length is 0.
         */
-
+       if (mode != 0)
+               if (fchmod(to_fd, mode))
+                       warn("%s: fchmod failed", to.p_path);
+#ifdef __APPLE__
+       /* do these before setfile in case copyfile changes mtime */
+       if (!Xflag && S_ISREG(fs->st_mode)) { /* skip devices, etc */
+               if (fcopyfile(from_fd, to_fd, NULL, COPYFILE_XATTR) < 0)
+                       warn("%s: could not copy extended attributes to %s", entp->fts_path, to.p_path);
+       }
+       if (pflag && setfile(fs, to_fd))
+               rval = 1;
+       if (pflag) {
+               /* If this ACL denies writeattr then setfile will fail... */
+               if (fcopyfile(from_fd, to_fd, NULL, COPYFILE_ACL) < 0)
+                       warn("%s: could not copy ACL to %s", entp->fts_path, to.p_path);
+       }
+#else  /* !__APPLE__ */
        if (pflag && setfile(fs, to_fd))
                rval = 1;
+       if (pflag && preserve_fd_acls(from_fd, to_fd) != 0)
+               rval = 1;
+#endif /* __APPLE__ */
        (void)close(from_fd);
        if (close(to_fd)) {
                warn("%s", to.p_path);
@@ -193,9 +351,9 @@ copy_file(FTSENT *entp, int dne)
 }
 
 int
-copy_link(FTSENT *p, int exists)
+copy_link(const FTSENT *p, int exists)
 {
-       int len;
+       ssize_t len;
        char llink[PATH_MAX];
 
        if ((len = readlink(p->fts_path, llink, sizeof(llink) - 1)) == -1) {
@@ -211,7 +369,13 @@ copy_link(FTSENT *p, int exists)
                warn("symlink: %s", llink);
                return (1);
        }
-       return (0);
+#ifdef __APPLE__
+       if (!Xflag)
+               if (copyfile(p->fts_path, to.p_path, NULL, COPYFILE_XATTR | COPYFILE_NOFOLLOW_SRC) <0)
+                       warn("%s: could not copy extended attributes to %s",
+                            p->fts_path, to.p_path);
+#endif
+       return (pflag ? setfile(p->fts_statp, -1) : 0);
 }
 
 int
@@ -225,7 +389,7 @@ copy_fifo(struct stat *from_stat, int exists)
                warn("mkfifo: %s", to.p_path);
                return (1);
        }
-       return (pflag ? setfile(from_stat, 0) : 0);
+       return (pflag ? setfile(from_stat, -1) : 0);
 }
 
 int
@@ -239,33 +403,42 @@ copy_special(struct stat *from_stat, int exists)
                warn("mknod: %s", to.p_path);
                return (1);
        }
-       return (pflag ? setfile(from_stat, 0) : 0);
+       return (pflag ? setfile(from_stat, -1) : 0);
 }
 
 int
 setfile(struct stat *fs, int fd)
 {
-       static struct timeval tv[2];
+       struct attrlist ts_req = {};
        struct stat ts;
-       int rval;
-       int gotstat;
+       int rval, gotstat, islink, fdval;
+       struct {
+               struct timespec mtime;
+               struct timespec atime;
+       } set_ts;
 
        rval = 0;
-       fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX |
-                      S_IRWXU | S_IRWXG | S_IRWXO;
+       fdval = fd != -1;
+       islink = !fdval && S_ISLNK(fs->st_mode);
+       fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO;
+       unsigned int options = islink ? FSOPT_NOFOLLOW : 0;
 
-       TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
-       TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
-       if (utimes(to.p_path, tv)) {
-               warn("utimes: %s", to.p_path);
+       ts_req.bitmapcount = ATTR_BIT_MAP_COUNT;
+       ts_req.commonattr = ATTR_CMN_MODTIME | ATTR_CMN_ACCTIME;
+       set_ts.mtime = fs->st_mtimespec;
+       set_ts.atime = fs->st_atimespec;
+
+       if (fdval ? fsetattrlist(fd, &ts_req, &set_ts, sizeof(set_ts), options) :
+                   setattrlist(to.p_path, &ts_req, &set_ts, sizeof(set_ts), options)) {
+               warn("%ssetattrlist: %s", fdval ? "f" : "", to.p_path);
                rval = 1;
        }
-       if (fd ? fstat(fd, &ts) : stat(to.p_path, &ts))
+       if (fdval ? fstat(fd, &ts) : (islink ? lstat(to.p_path, &ts) :
+                                     stat(to.p_path, &ts))) {
                gotstat = 0;
-       else {
+       else {
                gotstat = 1;
-               ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX |
-                             S_IRWXU | S_IRWXG | S_IRWXO;
+               ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO;
        }
        /*
         * Changing the ownership probably won't succeed, unless we're root
@@ -273,38 +446,132 @@ setfile(struct stat *fs, int fd)
         * the mode; current BSD behavior is to remove all setuid bits on
         * chown.  If chown fails, lose setuid/setgid bits.
         */
-       if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid)
-               if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
-                   chown(to.p_path, fs->st_uid, fs->st_gid)) {
+       if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid) {
+               if (fdval ? fchown(fd, fs->st_uid, fs->st_gid) : (islink ?
+                                                                 lchown(to.p_path, fs->st_uid, fs->st_gid) :
+                                                                 chown(to.p_path, fs->st_uid, fs->st_gid))) {
+                           if (errno != EPERM) {
+                                   warn("%schown: %s", fdval ? "f" : (islink ? "l" : ""), to.p_path);
+                                   rval = 1;
+                           }
+                           fs->st_mode &= ~(S_ISUID | S_ISGID);
+                   }
+       }
+
+       if (!gotstat || fs->st_mode != ts.st_mode) {
+               if (fdval ? fchmod(fd, fs->st_mode) : (islink ?
+                                                      lchmod(to.p_path, fs->st_mode) :
+                                                      chmod(to.p_path, fs->st_mode))) {
+                       warn("%schmod: %s", fdval ? "f" : (islink ? "l" : ""), to.p_path);
+                       rval = 1;
+               }
+       }
+
+       if (!gotstat || fs->st_flags != ts.st_flags) {
+               if (fdval ? fchflags(fd, fs->st_flags) : (islink ?
+                                                         lchflags(to.p_path, fs->st_flags) :
+                                                         chflags(to.p_path, fs->st_flags))) {
                        if (errno != EPERM) {
-                               warn("chown: %s", to.p_path);
+                               warn("%schflags: %s", fdval ? "f" : (islink ? "l" : ""), to.p_path);
                                rval = 1;
                        }
-                       fs->st_mode &= ~(S_ISUID | S_ISGID);
                }
+       }
+       return (rval);
+}
 
-       if (!gotstat || fs->st_mode != ts.st_mode)
-               if (fd ? fchmod(fd, fs->st_mode) : chmod(to.p_path, fs->st_mode)) {
-                       warn("chmod: %s", to.p_path);
-                       rval = 1;
-               }
+#ifndef __APPLE__
+int
+preserve_fd_acls(int source_fd, int dest_fd)
+{
+       struct acl *aclp;
+       acl_t acl;
 
-       if (!gotstat || fs->st_flags != ts.st_flags)
-               if (fd ?
-                   fchflags(fd, fs->st_flags) : chflags(to.p_path, fs->st_flags)) {
-                       warn("chflags: %s", to.p_path);
-                       rval = 1;
-               }
+       if (fpathconf(source_fd, _PC_ACL_EXTENDED) != 1 ||
+           fpathconf(dest_fd, _PC_ACL_EXTENDED) != 1)
+               return (0);
+       acl = acl_get_fd(source_fd);
+       if (acl == NULL) {
+               warn("failed to get acl entries while setting %s", to.p_path);
+               return (1);
+       }
+       aclp = &acl->ats_acl;
+       if (aclp->acl_cnt == 3)
+               return (0);
+       if (acl_set_fd(dest_fd, acl) < 0) {
+               warn("failed to set acl entries for %s", to.p_path);
+               return (1);
+       }
+       return (0);
+}
 
-       return (rval);
+int
+preserve_dir_acls(struct stat *fs, char *source_dir, char *dest_dir)
+{
+       acl_t (*aclgetf)(const char *, acl_type_t);
+       int (*aclsetf)(const char *, acl_type_t, acl_t);
+       struct acl *aclp;
+       acl_t acl;
+
+       if (pathconf(source_dir, _PC_ACL_EXTENDED) != 1 ||
+           pathconf(dest_dir, _PC_ACL_EXTENDED) != 1)
+               return (0);
+       /*
+        * If the file is a link we will not follow it
+        */
+       if (S_ISLNK(fs->st_mode)) {
+               aclgetf = acl_get_link_np;
+               aclsetf = acl_set_link_np;
+       } else {
+               aclgetf = acl_get_file;
+               aclsetf = acl_set_file;
+       }
+       /*
+        * Even if there is no ACL_TYPE_DEFAULT entry here, a zero
+        * size ACL will be returned. So it is not safe to simply
+        * check the pointer to see if the default ACL is present.
+        */
+       acl = aclgetf(source_dir, ACL_TYPE_DEFAULT);
+       if (acl == NULL) {
+               warn("failed to get default acl entries on %s",
+                   source_dir);
+               return (1);
+       }
+       aclp = &acl->ats_acl;
+       if (aclp->acl_cnt != 0 && aclsetf(dest_dir,
+           ACL_TYPE_DEFAULT, acl) < 0) {
+               warn("failed to set default acl entries on %s",
+                   dest_dir);
+               return (1);
+       }
+       acl = aclgetf(source_dir, ACL_TYPE_ACCESS);
+       if (acl == NULL) {
+               warn("failed to get acl entries on %s", source_dir);
+               return (1);
+       }
+       aclp = &acl->ats_acl;
+       if (aclsetf(dest_dir, ACL_TYPE_ACCESS, acl) < 0) {
+               warn("failed to set acl entries on %s", dest_dir);
+               return (1);
+       }
+       return (0);
 }
+#endif /* !__APPLE__ */
 
 void
 usage(void)
 {
 
+       if (COMPAT_MODE("bin/cp", "unix2003")) {
        (void)fprintf(stderr, "%s\n%s\n",
-"usage: cp [-R [-H | -L | -P]] [-f | -i | -n] [-pv] src target",
-"       cp [-R [-H | -L | -P]] [-f | -i | -n] [-pv] src1 ... srcN directory");
+"usage: cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file target_file",
+"       cp [-R [-H | -L | -P]] [-fi | -n] [-apvXc] source_file ... "
+"target_directory");
+       } else {
+       (void)fprintf(stderr, "%s\n%s\n",
+"usage: cp [-R [-H | -L | -P]] [-f | -i | -n] [-apvXc] source_file target_file",
+"       cp [-R [-H | -L | -P]] [-f | -i | -n] [-apvXc] source_file ... "
+"target_directory");
+       }
        exit(EX_USAGE);
 }