]> arthur.barton.de Git - netatalk.git/blobdiff - bin/ad/ad_cp.c
Fix ad cp on appledouble = ea volumes
[netatalk.git] / bin / ad / ad_cp.c
index dfb2b7362d788010ea3bff61871ab10b5d8fd8b7..ef995ce8cf3f159c6346a5e32a4858e9cfad8948 100644 (file)
@@ -59,6 +59,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <libgen.h>
 
 #include <atalk/ftw.h>
 #include <atalk/adouble.h>
 #include <atalk/util.h>
 #include <atalk/unix.h>
 #include <atalk/volume.h>
-#include <atalk/volinfo.h>
 #include <atalk/bstrlib.h>
 #include <atalk/bstradd.h>
 #include <atalk/queue.h>
+
 #include "ad.h"
 
 #define STRIP_TRAILING_SLASH(p) {                                   \
@@ -83,7 +83,7 @@ static char emptystring[] = "";
 PATH_T to = { to.p_path, emptystring, "" };
 enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
 
-int fflag, iflag, lflag, nflag, pflag, vflag;
+int fflag, iflag, nflag, pflag, vflag;
 mode_t mask;
 
 cnid_t ppdid, pdid, did; /* current dir CNID and parent did*/
@@ -91,7 +91,7 @@ cnid_t ppdid, pdid, did; /* current dir CNID and parent did*/
 static afpvol_t svolume, dvolume;
 static enum op type;
 static int Rflag;
-volatile sig_atomic_t sigint;
+static volatile sig_atomic_t alarmed;
 static int badcp, rval;
 static int ftw_options = FTW_MOUNT | FTW_PHYS | FTW_ACTIONRETVAL;
 
@@ -104,7 +104,6 @@ static char           *netatalk_dirs[] = {
 
 /* Forward declarations */
 static int copy(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf);
-static void siginfo(int _U_);
 static int ftw_copy_file(const struct FTW *, const char *, const struct stat *, int);
 static int ftw_copy_link(const struct FTW *, const char *, const struct stat *, int);
 static int setfile(const struct stat *, int);
@@ -132,16 +131,87 @@ static void upfunc(void)
     pdid = ppdid;
 }
 
+/*
+  SIGNAL handling:
+  catch SIGINT and SIGTERM which cause clean exit. Ignore anything else.
+*/
+
+static void sig_handler(int signo)
+{
+    alarmed = 1;
+    return;
+}
+
+static void set_signal(void)
+{
+    struct sigaction sv;
+
+    sv.sa_handler = sig_handler;
+    sv.sa_flags = SA_RESTART;
+    sigemptyset(&sv.sa_mask);
+    if (sigaction(SIGTERM, &sv, NULL) < 0)
+        ERROR("error in sigaction(SIGTERM): %s", strerror(errno));
+
+    if (sigaction(SIGINT, &sv, NULL) < 0)
+        ERROR("error in sigaction(SIGINT): %s", strerror(errno));
+
+    memset(&sv, 0, sizeof(struct sigaction));
+    sv.sa_handler = SIG_IGN;
+    sigemptyset(&sv.sa_mask);
+
+    if (sigaction(SIGABRT, &sv, NULL) < 0)
+        ERROR("error in sigaction(SIGABRT): %s", strerror(errno));
+
+    if (sigaction(SIGHUP, &sv, NULL) < 0)
+        ERROR("error in sigaction(SIGHUP): %s", strerror(errno));
+
+    if (sigaction(SIGQUIT, &sv, NULL) < 0)
+        ERROR("error in sigaction(SIGQUIT): %s", strerror(errno));
+}
+
 static void usage_cp(void)
 {
     printf(
-        "Usage: ad cp [-R [-P]] [-pvf] <source_file> <target_file>\n"
-        "Usage: ad cp [-R [-P]] [-pvfx] <source_file [source_file ...]> <target_directory>\n"
+        "Usage: ad cp [-R] [-aipvf] <source_file> <target_file>\n"
+        "       ad cp [-R] [-aipvfx] <source_file [source_file ...]> <target_directory>\n\n"
+        "In the first synopsis form, the cp utility copies the contents of the source_file to the\n"
+        "target_file.  In the second synopsis form, the contents of each named source_file is copied to the\n"
+        "destination target_directory.  The names of the files themselves are not changed.  If cp detects an\n"
+        "attempt to copy a file to itself, the copy will fail.\n\n"
+        "Netatalk AFP volumes are detected by means of their \".AppleDesktop\" directory\n"
+        "which is located in their volume root. When a copy targetting an AFP volume\n"
+        "is detected, its CNID database daemon is connected and all copies will also\n"
+        "go through the CNID database.\n"
+        "AppleDouble files are also copied and created as needed when the target is\n"
+        "an AFP volume.\n\n"
+        "The following options are available:\n\n"
+        "     -a    Archive mode.  Same as -Rp.\n\n"
+        "     -f    For each existing destination pathname, remove it and create a new\n"
+        "           file, without prompting for confirmation regardless of its permis-\n"
+        "           sions.  (The -f option overrides any previous -i or -n options.)\n\n"
+        "     -i    Cause cp to write a prompt to the standard error output before\n"
+        "           copying a file that would overwrite an existing file.  If the\n"
+        "           response from the standard input begins with the character 'y' or\n"
+        "           'Y', the file copy is attempted.  (The -i option overrides any pre-\n"
+        "           vious -f or -n options.)\n\n"
+        "     -n    Do not overwrite an existing file.  (The -n option overrides any\n"
+        "           previous -f or -i options.)\n\n"
+        "     -p    Cause cp to preserve the following attributes of each source file\n"
+        "           in the copy: modification time, access time, file flags, file mode,\n"
+        "           user ID, and group ID, as allowed by permissions.\n"
+        "           If the user ID and group ID cannot be preserved, no error message\n"
+        "           is displayed and the exit value is not altered.\n\n"
+        "     -R    If source_file designates a directory, cp copies the directory and\n"
+        "           the entire subtree connected at that point.If the source_file\n"
+        "           ends in a /, the contents of the directory are copied rather than\n"
+        "           the directory itself.\n\n"
+        "     -v    Cause cp to be verbose, showing files as they are copied.\n\n"
+        "     -x    File system mount points are not traversed.\n\n"
         );
     exit(EXIT_FAILURE);
 }
 
-int ad_cp(int argc, char *argv[])
+int ad_cp(int argc, char *argv[], AFPObj *obj)
 {
     struct stat to_stat, tmp_stat;
     int r, ch, have_trailing_slash;
@@ -154,11 +224,8 @@ int ad_cp(int argc, char *argv[])
     ppdid = pdid = htonl(1);
     did = htonl(2);
 
-    while ((ch = getopt(argc, argv, "Rafilnpvx")) != -1)
+    while ((ch = getopt(argc, argv, "afinpRvx")) != -1)
         switch (ch) {
-        case 'R':
-            Rflag = 1;
-            break;
         case 'a':
             pflag = 1;
             Rflag = 1;
@@ -171,9 +238,6 @@ int ad_cp(int argc, char *argv[])
             iflag = 1;
             fflag = nflag = 0;
             break;
-        case 'l':
-            lflag = 1;
-            break;
         case 'n':
             nflag = 1;
             fflag = iflag = 0;
@@ -181,6 +245,9 @@ int ad_cp(int argc, char *argv[])
         case 'p':
             pflag = 1;
             break;
+        case 'R':
+            Rflag = 1;
+            break;
         case 'v':
             vflag = 1;
             break;
@@ -197,8 +264,7 @@ int ad_cp(int argc, char *argv[])
     if (argc < 2)
         usage_cp();
 
-    (void)signal(SIGINT, siginfo);
-
+    set_signal();
     cnid_init();
 
     /* Save the target base in "to". */
@@ -285,18 +351,21 @@ int ad_cp(int argc, char *argv[])
 #endif
 
     /* Load .volinfo file for destination*/
-    openvol(to.p_path, &dvolume);
+    openvol(obj, to.p_path, &dvolume);
 
-    for (int i = 0; argv[i] != NULL; i++) { 
+    for (int i = 0; argv[i] != NULL; i++) {
         /* Load .volinfo file for source */
-        openvol(argv[i], &svolume);
+        openvol(obj, argv[i], &svolume);
 
         if (nftw(argv[i], copy, upfunc, 20, ftw_options) == -1) {
-            ERROR("%s: %s", argv[i], strerror(errno));
-            exit(EXIT_FAILURE);
+            if (alarmed) {
+                SLOG("...break");
+            } else {
+                SLOG("Error: %s: %s", argv[i], strerror(errno));
+            }
+            closevol(&svolume);
+            closevol(&dvolume);
         }
-
-
     }
     return rval;
 }
@@ -314,12 +383,19 @@ static int copy(const char *path,
     const char *p;
     char *target_mid;
 
+    if (alarmed)
+        return -1;
+
+    /* This currently doesn't work with "." */
+    if (strcmp(path, ".") == 0) {
+        ERROR("\".\" not supported");
+    }
     const char *dir = strrchr(path, '/');
     if (dir == NULL)
         dir = path;
     else
         dir++;
-    if (check_netatalk_dirs(dir) != NULL)
+    if (!dvolume.vol->vfs->vfs_validupath(dvolume.vol, dir))
         return FTW_SKIP_SUBTREE;
 
     /*
@@ -379,24 +455,33 @@ static int copy(const char *path,
     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);
+            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;
+                return FTW_SKIP_SUBTREE;
+            return 0;
         }
         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;
+                 "non-directory %s",
+                 to.p_path, path);
+            badcp = rval = 1;
+            return 0;
         }
         dne = 0;
     }
 
+    /* Convert basename to appropiate volume encoding */
+    if (dvolume.vol->v_path) {
+        if ((convert_dots_encoding(&svolume, &dvolume, to.p_path, MAXPATHLEN)) == -1) {
+            SLOG("Error converting name for %s", to.p_path);
+            badcp = rval = 1;
+            return -1;
+        }
+    }
+
     switch (statp->st_mode & S_IFMT) {
     case S_IFLNK:
         if (ftw_copy_link(ftw, path, statp, !dne))
@@ -425,19 +510,19 @@ static int copy(const char *path,
         }
 
         /* Create ad dir and copy ".Parent" */
-        if (dvolume.volinfo.v_path && dvolume.volinfo.v_adouble == AD_VERSION2) {
-
-            /* Create ".AppleDouble" dir */
+        if (dvolume.vol->v_path && ADVOL_V2_OR_EA(dvolume.vol->v_adouble)) {
             mode_t omask = umask(0);
-            bstring addir = bfromcstr(to.p_path);
-            bcatcstr(addir, "/.AppleDouble");
-            mkdir(cfrombstr(addir), 02777);
-            bdestroy(addir);
-
-            if (svolume.volinfo.v_path && svolume.volinfo.v_adouble == AD_VERSION2) {
-                /* copy ".Parent" file */
-                SLOG("Copying adouble for %s -> %s", path, to.p_path);
-                if (dvolume.volume.vfs->vfs_copyfile(&dvolume.volume, -1, path, to.p_path)) {
+            if (dvolume.vol->v_adouble == AD_VERSION2) {
+                /* Create ".AppleDouble" dir */
+                bstring addir = bfromcstr(to.p_path);
+                bcatcstr(addir, "/.AppleDouble");
+                mkdir(cfrombstr(addir), 02777);
+                bdestroy(addir);
+            }
+
+            if (svolume.vol->v_path && ADVOL_V2_OR_EA(svolume.vol->v_adouble)) {
+                /* copy metadata file */
+                if (dvolume.vol->vfs->vfs_copyfile(dvolume.vol, -1, path, to.p_path)) {
                     SLOG("Error copying adouble for %s -> %s", path, to.p_path);
                     badcp = rval = 1;
                     break;
@@ -446,27 +531,31 @@ static int copy(const char *path,
 
             /* Get CNID of Parent and add new childir to CNID database */
             ppdid = pdid;
-            did = cnid_for_path(&dvolume.volinfo, &dvolume.volume, to.p_path, &pdid);
+            if ((did = cnid_for_path(&dvolume, to.p_path, &pdid)) == CNID_INVALID) {
+                SLOG("Error resolving CNID for %s", to.p_path);
+                badcp = rval = 1;
+                return -1;
+            }
 
             struct adouble ad;
             struct stat st;
-            if (stat(to.p_path, &st) != 0) {
+            if (lstat(to.p_path, &st) != 0) {
                 badcp = rval = 1;
                 break;
             }
-            ad_init(&ad, dvolume.volinfo.v_adouble, dvolume.volinfo.v_ad_options);
-            if (ad_open_metadata(to.p_path, ADFLAGS_DIR, O_RDWR | O_CREAT, &ad) != 0) {
+            ad_init(&ad, dvolume.vol);
+            if (ad_open(&ad, to.p_path, ADFLAGS_HF | ADFLAGS_DIR | ADFLAGS_RDWR | ADFLAGS_CREATE, 0666) != 0) {
                 ERROR("Error opening adouble for: %s", to.p_path);
             }
-            SLOG("Setting CNID %u for %s", ntohl(did), to.p_path);
             ad_setid( &ad, st.st_dev, st.st_ino, did, pdid, dvolume.db_stamp);
-            ad_setname(&ad, utompath(&dvolume.volinfo, basename(to.p_path)));
+            if (dvolume.vol->v_adouble == AD_VERSION2)
+                ad_setname(&ad, utompath(dvolume.vol, basename(to.p_path)));
             ad_setdate(&ad, AD_DATE_CREATE | AD_DATE_UNIX, st.st_mtime);
             ad_setdate(&ad, AD_DATE_MODIFY | AD_DATE_UNIX, st.st_mtime);
             ad_setdate(&ad, AD_DATE_ACCESS | AD_DATE_UNIX, st.st_mtime);
             ad_setdate(&ad, AD_DATE_BACKUP, AD_DATE_START);
             ad_flush(&ad);
-            ad_close_metadata(&ad);
+            ad_close(&ad, ADFLAGS_HF);
 
             umask(omask);
         }
@@ -495,12 +584,12 @@ static int copy(const char *path,
         if (ftw_copy_file(ftw, path, statp, dne))
             badcp = rval = 1;
 
-        if (dvolume.volinfo.v_path && dvolume.volinfo.v_adouble == AD_VERSION2) {
+        if (dvolume.vol->v_path && ADVOL_V2_OR_EA(dvolume.vol->v_adouble)) {
 
             mode_t omask = umask(0);
-            if (svolume.volinfo.v_path && svolume.volinfo.v_adouble == AD_VERSION2) {
+            if (svolume.vol->v_path && ADVOL_V2_OR_EA(svolume.vol->v_adouble)) {
                 /* copy ad-file */
-                if (dvolume.volume.vfs->vfs_copyfile(&dvolume.volume, -1, path, to.p_path)) {
+                if (dvolume.vol->vfs->vfs_copyfile(dvolume.vol, -1, path, to.p_path)) {
                     SLOG("Error copying adouble for %s -> %s", path, to.p_path);
                     badcp = rval = 1;
                     break;
@@ -509,27 +598,32 @@ static int copy(const char *path,
 
             /* Get CNID of Parent and add new childir to CNID database */
             pdid = did;
-            cnid_t cnid = cnid_for_path(&dvolume.volinfo, &dvolume.volume, to.p_path, &did);
+            cnid_t cnid;
+            if ((cnid = cnid_for_path(&dvolume, to.p_path, &did)) == CNID_INVALID) {
+                SLOG("Error resolving CNID for %s", to.p_path);
+                badcp = rval = 1;
+                return -1;
+            }
 
             struct adouble ad;
             struct stat st;
-            if (stat(to.p_path, &st) != 0) {
+            if (lstat(to.p_path, &st) != 0) {
                 badcp = rval = 1;
                 break;
             }
-            ad_init(&ad, dvolume.volinfo.v_adouble, dvolume.volinfo.v_ad_options);
-            if (ad_open_metadata(to.p_path, 0, O_RDWR | O_CREAT, &ad) != 0) {
+            ad_init(&ad, dvolume.vol);
+            if (ad_open(&ad, to.p_path, ADFLAGS_HF | ADFLAGS_RDWR | ADFLAGS_CREATE, 0666) != 0) {
                 ERROR("Error opening adouble for: %s", to.p_path);
             }
-            SLOG("setid: DID: %u, CNID: %u, %s", ntohl(did), ntohl(cnid), to.p_path);
             ad_setid( &ad, st.st_dev, st.st_ino, cnid, did, dvolume.db_stamp);
-            ad_setname(&ad, utompath(&dvolume.volinfo, basename(to.p_path)));
+            if (dvolume.vol->v_adouble == AD_VERSION2)
+                ad_setname(&ad, utompath(dvolume.vol, basename(to.p_path)));
             ad_setdate(&ad, AD_DATE_CREATE | AD_DATE_UNIX, st.st_mtime);
             ad_setdate(&ad, AD_DATE_MODIFY | AD_DATE_UNIX, st.st_mtime);
             ad_setdate(&ad, AD_DATE_ACCESS | AD_DATE_UNIX, st.st_mtime);
             ad_setdate(&ad, AD_DATE_BACKUP, AD_DATE_START);
             ad_flush(&ad);
-            ad_close_metadata(&ad);
+            ad_close(&ad, ADFLAGS_HF);
             umask(omask);
         }
         break;
@@ -540,11 +634,6 @@ static int copy(const char *path,
     return 0;
 }
 
-static void siginfo(int sig _U_)
-{
-    sigint = 1;
-}
-
 /* Memory strategy threshold, in pages: if physmem is larger then this, use a large buffer */
 #define PHYSPAGES_THRESHOLD (32*1024)
 
@@ -590,7 +679,7 @@ static int ftw_copy_file(const struct FTW *entp,
             (void)close(from_fd);
             return (0);
         } else if (iflag) {
-            (void)fprintf(stderr, "overwrite %s? %s", 
+            (void)fprintf(stderr, "overwrite %s? %s",
                           to.p_path, YESNO);
             checkch = ch = getchar();
             while (ch != '\n' && ch != EOF)
@@ -601,26 +690,23 @@ static int ftw_copy_file(const struct FTW *entp,
                 return (1);
             }
         }
-        
+
         if (fflag) {
-            /* remove existing destination file name, 
+            /* remove existing destination file name,
              * create a new file  */
             (void)unlink(to.p_path);
-            (void)dvolume.volume.vfs->vfs_deletefile(&dvolume.volume, -1, to.p_path);
-            if (!lflag)
-                to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
-                             sp->st_mode & ~(S_ISUID | S_ISGID));
+            (void)dvolume.vol->vfs->vfs_deletefile(dvolume.vol, -1, to.p_path);
+            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);
+            /* 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));
+        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);
@@ -629,22 +715,58 @@ static int ftw_copy_file(const struct FTW *entp,
 
     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.
-         */
+    /*
+     * 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) {
+    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;
@@ -655,58 +777,15 @@ static int ftw_copy_file(const struct FTW *entp,
             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;
+                break;
             }
         }
-    } else {
-        if (link(spath, to.p_path)) {
-            SLOG("%s", to.p_path);
+        if (rcount < 0) {
+            SLOG("%s: %s", spath, strerror(errno));
             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,
@@ -714,15 +793,13 @@ static int ftw_copy_file(const struct FTW *entp,
      * 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;
-        }
+    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);
@@ -766,10 +843,16 @@ static int setfile(const struct stat *fs, int fd)
     islink = !fdval && S_ISLNK(fs->st_mode);
     mode = fs->st_mode & (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO);
 
+#if defined(__FreeBSD__)
+    TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
+    TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
+#else
     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);
+#endif
+
+    if (utimes(to.p_path, tv)) {
+        SLOG("utimes: %s", to.p_path);
         rval = 1;
     }
     if (fdval ? fstat(fd, &ts) :