]> arthur.barton.de Git - netatalk.git/commitdiff
Use FreeBSD cp as basis for ad cp
authorFrank Lahm <franklahm@googlemail.com>
Mon, 4 Oct 2010 14:01:41 +0000 (16:01 +0200)
committerFrank Lahm <franklahm@googlemail.com>
Mon, 4 Oct 2010 14:01:41 +0000 (16:01 +0200)
bin/cnid/ad.c
bin/cnid/ad.h
bin/cnid/ad_cp.c
bin/cnid/ad_util.c
include/atalk/adouble.h

index 01941ce17c713d48d7716e067e0a8a93cc382985..391a8465e180135027dc28af7a401f0bc8c32de1 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <atalk/cnid.h>
 #include <atalk/volinfo.h>
+#include <atalk/logger.h>
 #include "ad.h"
 
 static void usage_main(void)
index 14cc874dbd18a06178fb448688204133d65cb569..0f2c4ea3f96e0ae7b5b4e2481765290239a7ed2b 100644 (file)
 #ifndef AD_H
 #define AD_H
 
+#define _XOPEN_SOURCE 600
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <ftw.h>
+
 #include <atalk/volinfo.h>
 
 #define STRCMP(a,b,c) (strcmp(a,c) b 0)
@@ -29,6 +36,19 @@ typedef struct {
 //    int adflags;                /* file:0, dir:ADFLAGS_DIR */
 } afpvol_t;
 
+enum logtype {STD, DBG};
+
+#define SLOG(...)                             \
+    _log(STD, __VA_ARGS__)
+
+#define ERROR(...)                              \
+    do {                                        \
+        _log(STD, __VA_ARGS__);                 \
+        exit(1);                                \
+    } while (0)
+
+extern int log_verbose;             /* Logging flag */
+extern void _log(enum logtype lt, char *fmt, ...);
 
 extern int newvol(const char *path, afpvol_t *vol);
 extern void freevol(afpvol_t *vol);
@@ -36,4 +56,28 @@ extern void freevol(afpvol_t *vol);
 extern int ad_ls(int argc, char **argv);
 extern int ad_cp(int argc, char **argv);
 
+struct FTWELEM {
+    const struct FTW  *ftw;
+    const char        *ftw_path;
+    int               ftw_base_off;
+    int               ftw_tflag;
+    const struct stat *ftw_statp;
+};
+
+typedef struct {
+    char *p_end;/* pointer to NULL at end of path */
+    char *target_end;/* pointer to end of target base */
+    char p_path[PATH_MAX];/* pointer to the start of a path */
+} PATH_T;
+
+extern PATH_T to;
+extern int fflag, iflag, lflag, nflag, pflag, vflag;
+extern volatile sig_atomic_t info;
+
+extern int copy_file(const struct FTW *, const char *, const struct stat *, int);
+extern int copy_link(const struct FTW *, const char *, const struct stat *, int);
+extern int setfile(const struct stat *, int);
+extern int preserve_dir_acls(const struct stat *, char *, char *);
+extern int preserve_fd_acls(int, int);
+
 #endif /* AD_H */
index 531354788481b9b06eb67358705447fdc83c446d..10351467ecfaca571fdcbefea02bdb1cf1aa625e 100644 (file)
-/* 
-   Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
-   
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-*/
+/*
+ * Copyright (c) 2010, Frank Lahm <franklahm@googlemail.com>
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * David Hitz of Auspex Systems Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 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.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Cp copies source files to target files.
+ *
+ * The global PATH_T structure "to" always contains the path to the
+ * current target file.  Since fts(3) does not change directories,
+ * this path can be either absolute or dot-relative.
+ *
+ * The basic algorithm is to initialize "to" and use fts(3) to traverse
+ * the file hierarchy rooted in the argument list.  A trivial case is the
+ * case of 'cp file1 file2'.  The more interesting case is the case of
+ * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
+ * path (relative to the root of the traversal) is appended to dir (stored
+ * in "to") to form the final target path.
+ */
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif /* HAVE_CONFIG_H */
 
-#include <unistd.h>
 #include <sys/types.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdarg.h>
+#include <sys/stat.h>
+// #include <err.h>
+#include <errno.h>
+#include <ftw.h>
 #include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <errno.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <ctype.h>
-#include <pwd.h>
-#include <grp.h>
-#include <time.h>
-#include <libgen.h>
-
-#include <atalk/adouble.h>
-#include <atalk/cnid.h>
-#include <atalk/volinfo.h>
+#include <unistd.h>
+
 #include <atalk/util.h>
-#include <atalk/errchk.h>
-#include <atalk/bstrlib.h>
-#include <atalk/bstradd.h>
-#include <atalk/logger.h>
+
 #include "ad.h"
 
-#define ADv2_DIRNAME ".AppleDouble"
+#define STRIP_TRAILING_SLASH(p) {                                   \
+        while ((p).p_end > (p).p_path + 1 && (p).p_end[-1] == '/')  \
+            *--(p).p_end = 0;                                       \
+    }
+
+static char emptystring[] = "";
 
-/* options */
-static int cp_R;
-static int cp_L;
-static int cp_P;
-static int cp_n;
-static int cp_p;
-static int cp_v;
+PATH_T to = { to.p_path, emptystring, "" };
+
+int fflag, iflag, lflag, nflag, pflag, vflag;
+mode_t mask;
+static int Rflag, rflag;
+volatile sig_atomic_t sigint;
+static int type, badcp, rval;
+static int ftw_options = FTW_MOUNT | FTW_PHYS;
+
+enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
 
 static char           *netatalk_dirs[] = {
-    ADv2_DIRNAME,
+    ".AppleDouble",
     ".AppleDB",
     ".AppleDesktop",
     NULL
 };
 
+static int copy(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf);
+static void siginfo(int _U_);
+
+
 /*
   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
   Returns pointer to name or NULL.
@@ -78,250 +114,315 @@ static const char *check_netatalk_dirs(const char *name)
 static void usage_cp(void)
 {
     printf(
-        "Usage: ad cp [-R [-L | -P]] [-pv] <source_file> <target_file>\n"
-        "Usage: ad cp [-R [-L | -P]] [-pv] <source_file [source_file ...]> <target_directory>\n"
+        "Usage: ad cp [-R [-L | -P]] [-pvf] <source_file> <target_file>\n"
+        "Usage: ad cp [-R [-L | -P]] [-pvf] <source_file [source_file ...]> <target_directory>\n"
         );
 }
 
-static int ad_cp_copy(const afpvol_t *srcvol,
-                      const afpvol_t *dstvol,
-                      char *srcfile,
-                      char *dstfile,
-                      struct stat *st)
+int ad_cp(int argc, char *argv[])
 {
-    printf("copy: '%s' -> '%s'\n", srcfile, dstfile);
-    return 0;
-}
+    struct stat to_stat, tmp_stat;
+    enum op type;
+    int Pflag, ch, r, have_trailing_slash;
+    char *target;
+#if 0
+    afpvol_t srcvol;
+    afpvol_t dstvol;
+#endif
 
-static int ad_cp_r(const afpvol_t *srcvol, const afpvol_t *dstvol, char *srcdir, char *dstdir)
-{
-    int ret = 0, dirprinted = 0, dirempty;
-    static char srcpath[MAXPATHLEN+1];
-    static char dstpath[MAXPATHLEN+1];
-    char *tmp;
-    DIR *dp = NULL;
-    struct dirent *ep;
-    static struct stat st;      /* Save some stack space */
-
-    strlcat(srcpath, srcdir, sizeof(srcpath));
-    strlcat(dstpath, dstdir, sizeof(dstpath));
-
-    if ((dp = opendir (srcdir)) == NULL) {
-        perror("Couldn't opendir .");
-        return -1;
-    }
+    Pflag = 0;
 
-    /* First run: copy files */
-    while ((ep = readdir (dp))) {
-        /* Check if its "." or ".." */
-        if (DIR_DOT_OR_DOTDOT(ep->d_name))
-            continue;
+    while ((ch = getopt(argc, argv, "PRafilnprvx")) != -1)
+        switch (ch) {
+        case 'P':
+            Pflag = 1;
+            break;
+        case 'R':
+            Rflag = 1;
+            break;
+        case 'a':
+            Pflag = 1;
+            pflag = 1;
+            Rflag = 1;
+            break;
+        case 'f':
+            fflag = 1;
+            iflag = nflag = 0;
+            break;
+        case 'i':
+            iflag = 1;
+            fflag = nflag = 0;
+            break;
+        case 'l':
+            lflag = 1;
+            break;
+        case 'n':
+            nflag = 1;
+            fflag = iflag = 0;
+            break;
+        case 'p':
+            pflag = 1;
+            break;
+        case 'r':
+            rflag = 1;
+            Pflag = 0;
+            break;
+        case 'v':
+            vflag = 1;
+            break;
+        case 'x':
+            ftw_options |= FTW_MOUNT;
+            break;
+        default:
+            usage_cp();
+            break;
+        }
+    argc -= optind;
+    argv += optind;
 
-        /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
-        if ((check_netatalk_dirs(ep->d_name)) != NULL)
-            continue;
+    if (argc < 2)
+        usage_cp();
 
-        if (lstat(ep->d_name, &st) < 0) {
-            perror("Can't stat");
-            return -1;
-        }
+    if (Rflag && rflag)
+        ERROR("the -R and -r options may not be specified together");
 
-        /* Build paths, copy, strip name */
-        strlcat(srcpath, "/", sizeof(srcpath));
-        strlcat(dstpath, "/", sizeof(dstpath));
-        strlcat(srcpath, ep->d_name, sizeof(srcpath));
-        strlcat(dstpath, ep->d_name, sizeof(dstpath));
+    if (rflag)
+        Rflag = 1;
 
-        ret = ad_cp_copy(srcvol, dstvol, srcpath, dstpath, &st);
+    (void)signal(SIGINT, siginfo);
 
-        if ((tmp = strrchr(srcpath, '/')))
-            *tmp = 0;
-        if ((tmp = strrchr(dstpath, '/')))
-            *tmp = 0;
+    /* Save the target base in "to". */
+    target = argv[--argc];
+    if ((strlcpy(to.p_path, target, PATH_MAX)) >= PATH_MAX)
+        ERROR("%s: name too long", target);
 
-        if (ret != 0)
-            goto exit;
+    to.p_end = to.p_path + strlen(to.p_path);
+    if (to.p_path == to.p_end) {
+        *to.p_end++ = '.';
+        *to.p_end = 0;
     }
-
-    /* Second run: recurse to dirs */
-    rewinddir(dp);
-    while ((ep = readdir (dp))) {
-        /* Check if its "." or ".." */
-        if (DIR_DOT_OR_DOTDOT(ep->d_name))
-            continue;
-        
-        /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
-        if (check_netatalk_dirs(ep->d_name) != NULL)
-            continue;
-        
-        if (lstat(ep->d_name, &st) < 0) {
-            perror("Can't stat");
-            return -1;
+    have_trailing_slash = (to.p_end[-1] == '/');
+    if (have_trailing_slash)
+        STRIP_TRAILING_SLASH(to);
+    to.target_end = to.p_end;
+
+    /* Set end of argument list for fts(3). */
+    argv[argc] = NULL;
+
+    /*
+     * Cp has two distinct cases:
+     *
+     * cp [-R] source target
+     * cp [-R] source1 ... sourceN directory
+     *
+     * In both cases, source can be either a file or a directory.
+     *
+     * In (1), the target becomes a copy of the source. That is, if the
+     * source is a file, the target will be a file, and likewise for
+     * directories.
+     *
+     * In (2), the real target is not directory, but "directory/source".
+     */
+    r = stat(to.p_path, &to_stat);
+    if (r == -1 && errno != ENOENT)
+        ERROR("%s", to.p_path);
+    if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
+        /*
+         * Case (1).  Target is not a directory.
+         */
+        if (argc > 1)
+            ERROR("%s is not a directory", to.p_path);
+
+        /*
+         * Need to detect the case:
+         *cp -R dir foo
+         * Where dir is a directory and foo does not exist, where
+         * we want pathname concatenations turned on but not for
+         * the initial mkdir().
+         */
+        if (r == -1) {
+            lstat(*argv, &tmp_stat);
+
+            if (S_ISDIR(tmp_stat.st_mode) && Rflag)
+                type = DIR_TO_DNE;
+            else
+                type = FILE_TO_FILE;
+        } else
+            type = FILE_TO_FILE;
+
+        if (have_trailing_slash && type == FILE_TO_FILE) {
+            if (r == -1)
+                ERROR("directory %s does not exist", to.p_path);
+            else
+                ERROR("%s is not a directory", to.p_path);
         }
-        
-        /* Recursion */
-        if (S_ISDIR(st.st_mode)) {
-            strlcat(srcpath, "/", sizeof(srcpath));
-            strlcat(dstpath, "/", sizeof(dstpath));
-            ret = ad_cp_r(srcvol, dstvol, ep->d_name, ep->d_name);
+    } else
+        /*
+         * Case (2).  Target is a directory.
+         */
+        type = FILE_TO_DIR;
+
+    /*
+     * Keep an inverted copy of the umask, for use in correcting
+     * permissions on created directories when not using -p.
+     */
+    mask = ~umask(0777);
+    umask(~mask);
+
+    for (int i = 0; argv[i] != NULL; i++) { 
+        if (nftw(argv[i], copy, 20, ftw_options) == -1) {
+            ERROR("nftw: %s", strerror(errno));
+            exit(EXIT_FAILURE);
         }
-        if (ret != 0)
-            goto exit;
     }
-
-exit:
-    if (dp)
-        closedir(dp);
-    if ((tmp = strrchr(srcpath, '/')))
-        *tmp = 0;
-    if ((tmp = strrchr(dstpath, '/')))
-        *tmp = 0;
-
-    return ret;
+    return 0;
 }
 
-int ad_cp(int argc, char **argv)
+static int copy(const char *path,
+                const struct stat *statp,
+                int tflag,
+                struct FTW *ftw)
 {
-    EC_INIT;
-    int c, numpaths;
-    afpvol_t srcvvol;
-    struct stat sst;
-    struct stat dst;
-    afpvol_t srcvol;
-    afpvol_t dstvol;
-    char *srcfile = NULL;
-    char *srcdir = NULL;    
-    bstring dstfile;
-    char *dstdir = NULL;
-    char path[MAXPATHLEN+1];
-    char *basenametmp;
-
-    while ((c = getopt(argc, argv, ":npvLPR")) != -1) {
-        switch(c) {
-        case 'n':
-            cp_n = 1;
-            break;
-        case 'p':
-            cp_p = 1;
-            break;
-        case 'v':
-            cp_v = 1;
-            break;
-        case 'L':
-            cp_L = 1;
-            break;
-        case 'P':
-            cp_P = 1;
-            break;
-        case 'R':
-            cp_R = 1;
-            break;
-        case ':':
-        case '?':
-            usage_cp();
-            return -1;
-            break;
+    struct stat to_stat;
+    int base = 0, dne;
+    size_t nlen;
+    const char *p;
+    char *target_mid;
+
+    /*
+     * If we are in case (2) above, we need to append the
+     * source name to the target name.
+     */
+    if (type != FILE_TO_FILE) {
+        /*
+         * Need to remember the roots of traversals to create
+         * correct pathnames.  If there's a directory being
+         * copied to a non-existent directory, e.g.
+         *     cp -R a/dir noexist
+         * the resulting path name should be noexist/foo, not
+         * noexist/dir/foo (where foo is a file in dir), which
+         * is the case where the target exists.
+         *
+         * Also, check for "..".  This is for correct path
+         * concatenation for paths ending in "..", e.g.
+         *     cp -R .. /tmp
+         * Paths ending in ".." are changed to ".".  This is
+         * tricky, but seems the easiest way to fix the problem.
+         *
+         * XXX
+         * Since the first level MUST be FTS_ROOTLEVEL, base
+         * is always initialized.
+         */
+        if (ftw->level == 0) {
+            if (type != DIR_TO_DNE) {
+                base = ftw->base;
+
+                if (strcmp(&path[base], "..") == 0)
+                    base += 1;
+            } else
+                base = ftw->base;
+        }
+
+        p = &path[base];
+        nlen = strlen(path) - base;
+        target_mid = to.target_end;
+        if (*p != '/' && target_mid[-1] != '/')
+            *target_mid++ = '/';
+        *target_mid = 0;
+        if (target_mid - to.p_path + nlen >= PATH_MAX) {
+            SLOG("%s%s: name too long (not copied)", to.p_path, p);
+            badcp = rval = 1;
+            return 0;
         }
+        (void)strncat(target_mid, p, nlen);
+        to.p_end = target_mid + nlen;
+        *to.p_end = 0;
+        STRIP_TRAILING_SLASH(to);
     }
 
-    /* How many pathnames do we have */
-    numpaths = argc - optind;
-    printf("Number of paths: %u\n", numpaths);
-    if (numpaths < 2) {
-        usage_cp();
-        exit(EXIT_FAILURE);
+    /* Not an error but need to remember it happened */
+    if (stat(to.p_path, &to_stat) == -1)
+        dne = 1;
+    else {
+        if (to_stat.st_dev == statp->st_dev &&
+            to_stat.st_ino == statp->st_ino) {
+            SLOG("%s and %s are identical (not copied).",
+                to.p_path, path);
+            badcp = rval = 1;
+            if (S_ISDIR(statp->st_mode))
+                /* without using glibc extension FTW_ACTIONRETVAL cant handle this */
+                return -1;
+        }
+        if (!S_ISDIR(statp->st_mode) &&
+            S_ISDIR(to_stat.st_mode)) {
+            SLOG("cannot overwrite directory %s with "
+                "non-directory %s",
+                to.p_path, path);
+                badcp = rval = 1;
+                return 0;
+        }
+        dne = 0;
     }
 
-    while ( argv[argc-1][(strlen(argv[argc-1]) - 1)] == '/')
-        argv[argc-1][(strlen(argv[argc-1]) - 1)] = 0;
-
-    /* Create vol for destination */
-    newvol(argv[argc-1], &dstvol);
-
-    if (numpaths == 2) {
-        /* Case 1: 2 paths */
-
-        /* stat source */
-        EC_ZERO(stat(argv[optind], &sst));
-
-        if (S_ISREG(sst.st_mode)) {
-            /* source is just a file, thats easy */
-            /* Either file to file or file to dir copy */
-
-            /* stat destination */
-            if (stat(argv[argc-1], &dst) == 0) {
-                if (S_ISDIR(dst.st_mode)) {
-                    /* its a dir, build dest path: "dest" + "/" + basename("source") */
-                    EC_NULL(dstfile = bfromcstr(argv[argc-1]));
-                    EC_ZERO(bcatcstr(dstfile, "/"));
-                    EC_ZERO(bcatcstr(dstfile, basename(argv[optind])));
-                } else {
-                    /* its an existing file, truncate */
-                    EC_ZERO_LOG(truncate(argv[argc-1], 0));
-                    EC_NULL(dstfile = bfromcstr(argv[argc-1]));
-                }
-            } else {
-                EC_NULL(dstfile = bfromcstr(argv[argc-1]));
-            }
-            newvol(argv[optind], &srcvol);
-            printf("Source: %s, Destination: %s\n", argv[optind], cfrombstring(dstfile));
-            ad_cp_copy(&srcvol, &dstvol, argv[optind], cfrombstring(dstfile), &sst);
-            freevol(&srcvol);
-            freevol(&dstvol);
-            
-        } else if (S_ISDIR(sst.st_mode)) {
-            /* dir to dir copy. Check if -R is requested */
-            if (!cp_R) {
-                usage_cp();
-                exit(EXIT_FAILURE);
-            }
+    switch (statp->st_mode & S_IFMT) {
+    case S_IFLNK:
+        if (copy_link(ftw, path, statp, !dne))
+            badcp = rval = 1;
+        break;
+    case S_IFDIR:
+        if (!Rflag) {
+            SLOG("%s is a directory", path);
+            badcp = rval = 1;
+            return -1;
+        }
+        /*
+         * If the directory doesn't exist, create the new
+         * one with the from file mode plus owner RWX bits,
+         * modified by the umask.  Trade-off between being
+         * able to write the directory (if from directory is
+         * 555) and not causing a permissions race.  If the
+         * umask blocks owner writes, we fail..
+         */
+        if (dne) {
+            if (mkdir(to.p_path, statp->st_mode | S_IRWXU) < 0)
+                ERROR("%s", to.p_path);
+        } else if (!S_ISDIR(to_stat.st_mode)) {
+            errno = ENOTDIR;
+            ERROR("%s", to.p_path);
+        }
+
+        if (pflag) {
+            if (setfile(statp, -1))
+                rval = 1;
+#if 0
+            if (preserve_dir_acls(statp, curr->fts_accpath, to.p_path) != 0)
+                rval = 1;
+#endif
         }
+        break;
+
+    case S_IFBLK:
+    case S_IFCHR:
+        SLOG("%s is a device file (not copied).", path);
+        break;
+    case S_IFSOCK:
+        SLOG("%s is a socket (not copied).", path);
+        break;
+    case S_IFIFO:
+        SLOG("%s is a FIFO (not copied).", path);
+        break;
+    default:
+        if (copy_file(ftw, path, statp, dne))
+            badcp = rval = 1;
+        break;
+    }
+    if (vflag && !badcp)
+        (void)printf("%s -> %s\n", path, to.p_path);
 
-    } else {
-        /* Case 2: >2 paths */
-        while (optind < (argc-1)) {
-            printf("Source is: %s\n", argv[optind]);
-            newvol(argv[optind], &srcvol);
-            if (stat(argv[optind], &sst) != 0)
-                goto next;
-            if (S_ISDIR(sst.st_mode)) {
-                /* Source is a directory */
-                if (!cp_R) {
-                    printf("Source %s is a directory\n", argv[optind]);
-                    goto next;
-                }
-                srcdir = argv[optind];
-                dstdir = argv[argc-1];
-                
-                ad_cp_r(&srcvol, &dstvol, srcdir, dstdir);
-                freevol(&srcvol);
-            } else {
-                /* Source is a file */
-                srcfile = argv[optind];
-                if (numpaths != 2 && !dstdir) {
-                    usage_cp();
-                    exit(EXIT_FAILURE);
-                }
-                path[0] = 0;
-                strlcat(path, dstdir, sizeof(path));
-                basenametmp = strdup(srcfile);
-                strlcat(path, basename(basenametmp), sizeof(path));
-                free(basenametmp);
-                printf("%s %s\n", srcfile, path);
-                if (ad_cp_copy(&srcvol, &dstvol, srcfile, path, &sst) != 0) {
-                    freevol(&srcvol);
-                    freevol(&dstvol);
-                    exit(EXIT_FAILURE);
-                }
-            } /* else */
-        next:
-            optind++;
-        } /* while */
-    } /* else (numpath>2) */
-
-EC_CLEANUP:
-    freevol(&dstvol);
-
-    EC_EXIT;
+    return 0;
+}
+
+static void siginfo(int sig _U_)
+{
+    sigint = 1;
 }
index 5ba6471b686a0e8ab79f70c5f1b0a8205c5a39e1..1c9ff645ed9bc3bff98f1013a9b7cbc1c089a35b 100644 (file)
@@ -1,34 +1,89 @@
 /* 
-   Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
-   
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2 of the License, or
-   (at your option) any later version.
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-*/
+ * Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 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.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.   
+ */
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif /* HAVE_CONFIG_H */
 
-#include <unistd.h>
 #include <sys/types.h>
-#include <stdlib.h>
+#include <sys/acl.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <limits.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <unistd.h>
 #include <stdarg.h>
 #include <string.h>
-#include <errno.h>
-#include <libgen.h>
 
 #include <atalk/cnid.h>
 #include <atalk/volinfo.h>
 #include "ad.h"
 
+#define cp_pct(x, y)((y == 0) ? 0 : (int)(100.0 * (x) / (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 MAXPHYS (64 * 1024)
+#define BUFSIZE_SMALL (MAXPHYS)
+
+int log_verbose;             /* Logging flag */
+
+void _log(enum logtype lt, char *fmt, ...)
+{
+    int len;
+    static char logbuffer[1024];
+    va_list args;
+
+    if ( (lt == STD) || (log_verbose == 1)) {
+        va_start(args, fmt);
+        len = vsnprintf(logbuffer, 1023, fmt, args);
+        va_end(args);
+        logbuffer[1023] = 0;
+
+        printf("%s\n", logbuffer);
+    }
+}
 
 int newvol(const char *path, afpvol_t *vol)
 {
@@ -62,3 +117,405 @@ void freevol(afpvol_t *vol)
     }
 #endif
 }
+
+int copy_file(const struct FTW *entp,
+              const char *spath,
+              const struct stat *sp,
+              int dne)
+{
+    static char *buf = NULL;
+    static size_t bufsize;
+    ssize_t wcount;
+    size_t wresid;
+    off_t wtotal;
+    int ch, checkch, from_fd = 0, rcount, rval, to_fd = 0;
+    char *bufp;
+    char *p;
+
+    if ((from_fd = open(spath, O_RDONLY, 0)) == -1) {
+        SLOG("%s: %s", spath, strerror(errno));
+        return (1);
+    }
+
+    /*
+     * If the file exists and we're interactive, verify with the user.
+     * If the file DNE, set the mode to be the from file, minus setuid
+     * bits, modified by the umask; arguably wrong, but it makes copying
+     * executables work right and it's been that way forever.  (The
+     * other choice is 666 or'ed with the execute bits on the from file
+     * modified by the umask.)
+     */
+    if (!dne) {
+#define YESNO "(y/n [n]) "
+        if (nflag) {
+            if (vflag)
+                printf("%s not overwritten\n", to.p_path);
+            (void)close(from_fd);
+            return (0);
+        } else if (iflag) {
+            (void)fprintf(stderr, "overwrite %s? %s", 
+                          to.p_path, YESNO);
+            checkch = ch = getchar();
+            while (ch != '\n' && ch != EOF)
+                ch = getchar();
+            if (checkch != 'y' && checkch != 'Y') {
+                (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);
+            if (!lflag)
+                to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
+                             sp->st_mode & ~(S_ISUID | S_ISGID));
+        } else {
+            if (!lflag)
+                /* overwrite existing destination file name */
+                to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
+        }
+    } else {
+        if (!lflag)
+            to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
+                         sp->st_mode & ~(S_ISUID | S_ISGID));
+    }
+    
+    if (to_fd == -1) {
+        SLOG("%s: %s", to.p_path, strerror(errno));
+        (void)close(from_fd);
+        return (1);
+    }
+
+    rval = 0;
+
+    if (!lflag) {
+        /*
+         * 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.
+         * Some filesystems, such as smbnetfs, don't support mmap,
+         * so this is a best-effort attempt.
+         */
+
+        if (S_ISREG(sp->st_mode) && sp->st_size > 0 &&
+            sp->st_size <= 8 * 1024 * 1024 &&
+            (p = mmap(NULL, (size_t)sp->st_size, PROT_READ,
+                      MAP_SHARED, from_fd, (off_t)0)) != MAP_FAILED) {
+            wtotal = 0;
+            for (bufp = p, wresid = sp->st_size; ;
+                 bufp += wcount, wresid -= (size_t)wcount) {
+                wcount = write(to_fd, bufp, wresid);
+                if (wcount <= 0)
+                    break;
+                wtotal += wcount;
+                if (wcount >= (ssize_t)wresid)
+                    break;
+            }
+            if (wcount != (ssize_t)wresid) {
+                SLOG("%s: %s", to.p_path, strerror(errno));
+                rval = 1;
+            }
+            /* Some systems don't unmap on close(2). */
+            if (munmap(p, sp->st_size) < 0) {
+                SLOG("%s: %s", spath, strerror(errno));
+                rval = 1;
+            }
+        } else {
+            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)
+                    ERROR("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);
+                    if (wcount <= 0)
+                        break;
+                    wtotal += wcount;
+                    if (wcount >= (ssize_t)wresid)
+                        break;
+                }
+                if (wcount != (ssize_t)wresid) {
+                    SLOG("%s: %s", to.p_path, strerror(errno));
+                    rval = 1;
+                    break;
+                }
+            }
+            if (rcount < 0) {
+                SLOG("%s: %s", spath, strerror(errno));
+                rval = 1;
+            }
+        }
+    } else {
+        if (link(spath, to.p_path)) {
+            SLOG("%s", to.p_path);
+            rval = 1;
+        }
+    }
+    
+    /*
+     * Don't remove the target even after an error.  The target might
+     * not be a regular file, or its attributes might be important,
+     * or its contents might be irreplaceable.  It would only be safe
+     * to remove it if we created it and its length is 0.
+     */
+
+    if (!lflag) {
+        if (pflag && setfile(sp, to_fd))
+            rval = 1;
+        if (pflag && preserve_fd_acls(from_fd, to_fd) != 0)
+            rval = 1;
+        if (close(to_fd)) {
+            SLOG("%s: %s", to.p_path, strerror(errno));
+            rval = 1;
+        }
+    }
+
+    (void)close(from_fd);
+
+    return (rval);
+}
+
+int copy_link(const struct FTW *p,
+              const char *spath,
+              const struct stat *sstp,
+              int exists)
+{
+    int len;
+    char llink[PATH_MAX];
+
+    if ((len = readlink(spath, llink, sizeof(llink) - 1)) == -1) {
+        SLOG("readlink: %s: %s", spath, strerror(errno));
+        return (1);
+    }
+    llink[len] = '\0';
+    if (exists && unlink(to.p_path)) {
+        SLOG("unlink: %s: %s", to.p_path, strerror(errno));
+        return (1);
+    }
+    if (symlink(llink, to.p_path)) {
+        SLOG("symlink: %s: %s", llink, strerror(errno));
+        return (1);
+    }
+    return (pflag ? setfile(sstp, -1) : 0);
+}
+
+int setfile(const struct stat *fs, int fd)
+{
+    static struct timeval tv[2];
+    struct stat ts;
+    int rval, gotstat, islink, fdval;
+    mode_t mode;
+
+    rval = 0;
+    fdval = fd != -1;
+    islink = !fdval && S_ISLNK(fs->st_mode);
+    mode = fs->st_mode & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO);
+
+    TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atim);
+    TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtim);
+    if (islink ? lutimes(to.p_path, tv) : utimes(to.p_path, tv)) {
+        SLOG("%sutimes: %s", islink ? "l" : "", to.p_path);
+        rval = 1;
+    }
+    if (fdval ? fstat(fd, &ts) :
+        (islink ? lstat(to.p_path, &ts) : stat(to.p_path, &ts)))
+        gotstat = 0;
+    else {
+        gotstat = 1;
+        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
+     * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
+     * 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 (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) {
+                SLOG("chown: %s: %s", to.p_path, strerror(errno));
+                rval = 1;
+            }
+            mode &= ~(S_ISUID | S_ISGID);
+        }
+
+    if (!gotstat || mode != ts.st_mode)
+        if (fdval ? fchmod(fd, mode) : chmod(to.p_path, mode)) {
+            SLOG("chmod: %s: %s", to.p_path, strerror(errno));
+            rval = 1;
+        }
+
+#ifdef HAVE_ST_FLAGS
+    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))) {
+            SLOG("chflags: %s: %s", to.p_path, strerror(errno));
+            rval = 1;
+        }
+#endif
+
+    return (rval);
+}
+
+int preserve_fd_acls(int source_fd, int dest_fd)
+{
+#if 0
+    acl_t acl;
+    acl_type_t acl_type;
+    int acl_supported = 0, ret, trivial;
+
+    ret = fpathconf(source_fd, _PC_ACL_NFS4);
+    if (ret > 0 ) {
+        acl_supported = 1;
+        acl_type = ACL_TYPE_NFS4;
+    } else if (ret < 0 && errno != EINVAL) {
+        warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", to.p_path);
+        return (1);
+    }
+    if (acl_supported == 0) {
+        ret = fpathconf(source_fd, _PC_ACL_EXTENDED);
+        if (ret > 0 ) {
+            acl_supported = 1;
+            acl_type = ACL_TYPE_ACCESS;
+        } else if (ret < 0 && errno != EINVAL) {
+            warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
+                 to.p_path);
+            return (1);
+        }
+    }
+    if (acl_supported == 0)
+        return (0);
+
+    acl = acl_get_fd_np(source_fd, acl_type);
+    if (acl == NULL) {
+        warn("failed to get acl entries while setting %s", to.p_path);
+        return (1);
+    }
+    if (acl_is_trivial_np(acl, &trivial)) {
+        warn("acl_is_trivial() failed for %s", to.p_path);
+        acl_free(acl);
+        return (1);
+    }
+    if (trivial) {
+        acl_free(acl);
+        return (0);
+    }
+    if (acl_set_fd_np(dest_fd, acl, acl_type) < 0) {
+        warn("failed to set acl entries for %s", to.p_path);
+        acl_free(acl);
+        return (1);
+    }
+    acl_free(acl);
+#endif
+    return (0);
+}
+
+int preserve_dir_acls(const struct stat *fs, char *source_dir, char *dest_dir)
+{
+#if 0
+    acl_t (*aclgetf)(const char *, acl_type_t);
+    int (*aclsetf)(const char *, acl_type_t, acl_t);
+    struct acl *aclp;
+    acl_t acl;
+    acl_type_t acl_type;
+    int acl_supported = 0, ret, trivial;
+
+    ret = pathconf(source_dir, _PC_ACL_NFS4);
+    if (ret > 0) {
+        acl_supported = 1;
+        acl_type = ACL_TYPE_NFS4;
+    } else if (ret < 0 && errno != EINVAL) {
+        warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", source_dir);
+        return (1);
+    }
+    if (acl_supported == 0) {
+        ret = pathconf(source_dir, _PC_ACL_EXTENDED);
+        if (ret > 0) {
+            acl_supported = 1;
+            acl_type = ACL_TYPE_ACCESS;
+        } else if (ret < 0 && errno != EINVAL) {
+            warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
+                 source_dir);
+            return (1);
+        }
+    }
+    if (acl_supported == 0)
+        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;
+    }
+    if (acl_type == ACL_TYPE_ACCESS) {
+        /*
+         * 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);
+            acl_free(acl);
+            return (1);
+        }
+        acl_free(acl);
+    }
+    acl = aclgetf(source_dir, acl_type);
+    if (acl == NULL) {
+        warn("failed to get acl entries on %s", source_dir);
+        return (1);
+    }
+    if (acl_is_trivial_np(acl, &trivial)) {
+        warn("acl_is_trivial() failed on %s", source_dir);
+        acl_free(acl);
+        return (1);
+    }
+    if (trivial) {
+        acl_free(acl);
+        return (0);
+    }
+    if (aclsetf(dest_dir, acl_type, acl) < 0) {
+        warn("failed to set acl entries on %s", dest_dir);
+        acl_free(acl);
+        return (1);
+    }
+    acl_free(acl);
+#endif
+    return (0);
+}
index 1769c7b9612bb1a634ef4408718d64212f796c25..53337c789f113a27c25c6c3436affedc89b4019e 100644 (file)
   need _XOPEN_SOURCE defined for pread.
 */
 #if defined(HAVE_PREAD) && !defined(SOLARIS) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__FreeBSD__) && !defined(TRU64)
-#ifdef _XOPEN_SOURCE
-#undef _XOPEN_SOURCE
-#endif
+#ifndef _XOPEN_SOURCE
 #define _XOPEN_SOURCE 500
 #endif
+#endif
 
 #include <sys/types.h>
 #include <sys/stat.h>