summaryrefslogtreecommitdiff
path: root/usr/src/cmd/mv/mv.c
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/mv/mv.c
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/mv/mv.c')
-rw-r--r--usr/src/cmd/mv/mv.c2279
1 files changed, 2279 insertions, 0 deletions
diff --git a/usr/src/cmd/mv/mv.c b/usr/src/cmd/mv/mv.c
new file mode 100644
index 0000000000..464e1fbcee
--- /dev/null
+++ b/usr/src/cmd/mv/mv.c
@@ -0,0 +1,2279 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License"). You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
+/* All Rights Reserved */
+
+/*
+ * University Copyright- Copyright (c) 1982, 1986, 1988
+ * The Regents of the University of California
+ * All Rights Reserved
+ *
+ * University Acknowledgment- Portions of this document are derived from
+ * software developed by the University of California, Berkeley, and its
+ * contributors.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * Combined mv/cp/ln command:
+ * mv file1 file2
+ * mv dir1 dir2
+ * mv file1 ... filen dir1
+ */
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/avl.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <errno.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/acl.h>
+#include <libcmdutils.h>
+
+#define FTYPE(A) (A.st_mode)
+#define FMODE(A) (A.st_mode)
+#define UID(A) (A.st_uid)
+#define GID(A) (A.st_gid)
+#define IDENTICAL(A, B) (A.st_dev == B.st_dev && A.st_ino == B.st_ino)
+#define ISBLK(A) ((A.st_mode & S_IFMT) == S_IFBLK)
+#define ISCHR(A) ((A.st_mode & S_IFMT) == S_IFCHR)
+#define ISDIR(A) ((A.st_mode & S_IFMT) == S_IFDIR)
+#define ISDOOR(A) ((A.st_mode & S_IFMT) == S_IFDOOR)
+#define ISFIFO(A) ((A.st_mode & S_IFMT) == S_IFIFO)
+#define ISLNK(A) ((A.st_mode & S_IFMT) == S_IFLNK)
+#define ISREG(A) (((A).st_mode & S_IFMT) == S_IFREG)
+#define ISDEV(A) ((A.st_mode & S_IFMT) == S_IFCHR || \
+ (A.st_mode & S_IFMT) == S_IFBLK || \
+ (A.st_mode & S_IFMT) == S_IFIFO)
+
+#define BLKSIZE 4096
+#define PATHSIZE 1024
+#define DOT "."
+#define DOTDOT ".."
+#define DELIM '/'
+#define EQ(x, y) (strcmp(x, y) == 0)
+#define FALSE 0
+#define MODEBITS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)
+#define TRUE 1
+#define MAXMAPSIZE (1024*1024*8) /* map at most 8MB */
+#define SMALLFILESIZE (32*1024) /* don't use mmap on little files */
+
+static char *dname(char *);
+static int getresp(void);
+static int lnkfil(char *, char *);
+static int cpymve(char *, char *);
+static int chkfiles(char *, char **);
+static int rcopy(char *, char *);
+static int chk_different(char *, char *);
+static int chg_time(char *, struct stat);
+static int chg_mode(char *, uid_t, gid_t, mode_t);
+static int copydir(char *, char *);
+static int copyspecial(char *);
+static int getrealpath(char *, char *);
+static void usage(void);
+static void Perror(char *);
+static void Perror2(char *, char *);
+static int writefile(int, int, char *, char *,
+ struct stat *, struct stat *);
+static int use_stdin(void);
+static int copyattributes(char *, char *);
+static void timestruc_to_timeval(timestruc_t *, struct timeval *);
+static tree_node_t *create_tnode(dev_t, ino_t);
+
+extern int errno;
+extern char *optarg;
+extern int optind, opterr;
+static struct stat s1, s2;
+static int cpy = FALSE;
+static int mve = FALSE;
+static int lnk = FALSE;
+static char *cmd;
+static int silent = 0;
+static int fflg = 0;
+static int iflg = 0;
+static int pflg = 0;
+static int Rflg = 0; /* recursive copy */
+static int rflg = 0; /* recursive copy */
+static int sflg = 0;
+static int Hflg = 0; /* follow cmd line arg symlink to dir */
+static int Lflg = 0; /* follow symlinks */
+static int Pflg = 0; /* do not follow symlinks */
+static int atflg = 0;
+static int attrsilent = 0;
+static int targetexists = 0;
+static char yeschr[SCHAR_MAX + 2];
+static char nochr[SCHAR_MAX + 2];
+static int s1aclcnt;
+static aclent_t *s1aclp = NULL;
+static int cmdarg; /* command line argument */
+static avl_tree_t *stree = NULL; /* source file inode search tree */
+
+
+void
+main(int argc, char *argv[])
+{
+ int c, i, r, errflg = 0;
+ char target[PATH_MAX];
+ int (*move)(char *, char *);
+
+ /*
+ * Determine command invoked (mv, cp, or ln)
+ */
+
+ if (cmd = strrchr(argv[0], '/'))
+ ++cmd;
+ else
+ cmd = argv[0];
+
+ /*
+ * Set flags based on command.
+ */
+
+ (void) setlocale(LC_ALL, "");
+#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
+#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
+#endif
+ (void) textdomain(TEXT_DOMAIN);
+
+ (void) strncpy(yeschr, nl_langinfo(YESSTR), SCHAR_MAX + 2);
+ (void) strncpy(nochr, nl_langinfo(NOSTR), SCHAR_MAX + 2);
+
+ if (EQ(cmd, "mv"))
+ mve = TRUE;
+ else if (EQ(cmd, "ln"))
+ lnk = TRUE;
+ else if (EQ(cmd, "cp"))
+ cpy = TRUE;
+ else {
+ (void) fprintf(stderr,
+ gettext("Invalid command name (%s); expecting "
+ "mv, cp, or ln.\n"), cmd);
+ exit(1);
+ }
+
+ /*
+ * Check for options:
+ * cp -r|-R [-H|-L|-P] [-fip@] file1 [file2 ...] target
+ * cp [-fiprR@] file1 [file2 ...] target
+ * ln [-f] [-n] [-s] file1 [file2 ...] target
+ * ln [-f] [-n] [-s] file1 [file2 ...]
+ * mv [-f|i] file1 [file2 ...] target
+ * mv [-f|i] dir1 target
+ */
+
+ if (cpy) {
+ while ((c = getopt(argc, argv, "fHiLpPrR@")) != EOF)
+ switch (c) {
+ case 'f':
+ fflg++;
+ break;
+ case 'i':
+ iflg++;
+ break;
+ case 'p':
+ pflg++;
+#ifdef XPG4
+ attrsilent = 1;
+ atflg = 0;
+#else
+ if (atflg == 0)
+ attrsilent = 1;
+#endif
+ break;
+ case 'H':
+ /*
+ * If more than one of -H, -L, or -P are
+ * specified, only the last option specified
+ * determines the behavior.
+ */
+ Lflg = Pflg = 0;
+ Hflg++;
+ break;
+ case 'L':
+ Hflg = Pflg = 0;
+ Lflg++;
+ break;
+ case 'P':
+ Lflg = Hflg = 0;
+ Pflg++;
+ break;
+ case 'R':
+ /*
+ * The default behavior of cp -R|-r
+ * when specified without -H|-L|-P
+ * is -L.
+ */
+ Rflg++;
+ /*FALLTHROUGH*/
+ case 'r':
+ rflg++;
+ break;
+ case '@':
+ atflg++;
+ attrsilent = 0;
+#ifdef XPG4
+ pflg = 0;
+#endif
+ break;
+ default:
+ errflg++;
+ }
+
+ /* -R or -r must be specified with -H, -L, or -P */
+ if ((Hflg || Lflg || Pflg) && !(Rflg || rflg)) {
+ errflg++;
+ }
+
+ } else if (mve) {
+ while ((c = getopt(argc, argv, "fis")) != EOF)
+ switch (c) {
+ case 'f':
+ silent++;
+#ifdef XPG4
+ iflg = 0;
+#endif
+ break;
+ case 'i':
+ iflg++;
+#ifdef XPG4
+ silent = 0;
+#endif
+ break;
+ default:
+ errflg++;
+ }
+ } else { /* ln */
+ while ((c = getopt(argc, argv, "fns")) != EOF)
+ switch (c) {
+ case 'f':
+ silent++;
+ break;
+ case 'n':
+ /* silently ignored; this is the default */
+ break;
+ case 's':
+ sflg++;
+ break;
+ default:
+ errflg++;
+ }
+ }
+
+ /*
+ * For BSD compatibility allow - to delimit the end of
+ * options for mv.
+ */
+ if (mve && optind < argc && (strcmp(argv[optind], "-") == 0))
+ optind++;
+
+ /*
+ * Check for sufficient arguments
+ * or a usage error.
+ */
+
+ argc -= optind;
+ argv = &argv[optind];
+
+ if ((argc < 2 && lnk != TRUE) || (argc < 1 && lnk == TRUE)) {
+ (void) fprintf(stderr,
+ gettext("%s: Insufficient arguments (%d)\n"),
+ cmd, argc);
+ usage();
+ }
+
+ if (errflg != 0)
+ usage();
+
+ /*
+ * If there is more than a source and target,
+ * the last argument (the target) must be a directory
+ * which really exists.
+ */
+
+ if (argc > 2) {
+ if (stat(argv[argc-1], &s2) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: %s not found\n"),
+ cmd, argv[argc-1]);
+ exit(2);
+ }
+
+ if (!ISDIR(s2)) {
+ (void) fprintf(stderr,
+ gettext("%s: Target %s must be a directory\n"),
+ cmd, argv[argc-1]);
+ usage();
+ }
+ }
+
+ if (strlen(argv[argc-1]) >= PATH_MAX) {
+ (void) fprintf(stderr,
+ gettext("%s: Target %s file name length exceeds PATH_MAX"
+ " %d\n"), cmd, argv[argc-1], PATH_MAX);
+ exit(78);
+ }
+
+ if (argc == 1) {
+ if (!lnk)
+ usage();
+ (void) strcpy(target, ".");
+ } else {
+ (void) strcpy(target, argv[--argc]);
+ }
+
+ /*
+ * Perform a multiple argument mv|cp|ln by
+ * multiple invocations of cpymve() or lnkfil().
+ */
+ if (lnk)
+ move = lnkfil;
+ else
+ move = cpymve;
+
+ r = 0;
+ for (i = 0; i < argc; i++) {
+ stree = NULL;
+ cmdarg = 1;
+ r += move(argv[i], target);
+ }
+
+ /*
+ * Show errors by nonzero exit code.
+ */
+
+ exit(r?2:0);
+}
+
+static int
+lnkfil(char *source, char *target)
+{
+ char *buf = NULL;
+
+ if (sflg) {
+
+ /*
+ * If target is a directory make complete
+ * name of the new symbolic link within that
+ * directory.
+ */
+
+ if ((stat(target, &s2) >= 0) && ISDIR(s2)) {
+ size_t len;
+
+ len = strlen(target) + strlen(dname(source)) + 4;
+ if ((buf = (char *)malloc(len)) == NULL) {
+ (void) fprintf(stderr,
+ gettext("%s: Insufficient memory "
+ "to %s %s\n"), cmd, cmd, source);
+ exit(3);
+ }
+ (void) snprintf(buf, len, "%s/%s",
+ target, dname(source));
+ target = buf;
+ }
+
+ /*
+ * Check to see if the file exists already
+ */
+
+ if ((stat(target, &s2) == 0)) {
+ /*
+ * Check if the silent flag is set ie. the -f option
+ * is used. If so, use unlink to remove the current
+ * target to replace with the new target, specified
+ * on the command line. Proceed with symlink.
+ */
+ if (silent) {
+ if (unlink(target) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot unlink %s: "),
+ cmd, target);
+ perror("");
+ return (1);
+ }
+ }
+ }
+
+
+ /*
+ * Create a symbolic link to the source.
+ */
+
+ if (symlink(source, target) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot create %s: "),
+ cmd, target);
+ perror("");
+ if (buf != NULL)
+ free(buf);
+ return (1);
+ }
+ if (buf != NULL)
+ free(buf);
+ return (0);
+ }
+
+ switch (chkfiles(source, &target)) {
+ case 1: return (1);
+ case 2: return (0);
+ /* default - fall through */
+ }
+
+ /*
+ * Make sure source file is not a directory,
+ * we can't link directories...
+ */
+
+ if (ISDIR(s1)) {
+ (void) fprintf(stderr,
+ gettext("%s: %s is a directory\n"), cmd, source);
+ return (1);
+ }
+
+ /*
+ * hard link, call link() and return.
+ */
+
+ if (link(source, target) < 0) {
+ if (errno == EXDEV)
+ (void) fprintf(stderr,
+ gettext("%s: %s is on a different file system\n"),
+ cmd, target);
+ else {
+ (void) fprintf(stderr,
+ gettext("%s: cannot create link %s: "),
+ cmd, target);
+ perror("");
+ }
+ if (buf != NULL)
+ free(buf);
+ return (1);
+ } else {
+ if (buf != NULL)
+ free(buf);
+ return (0);
+ }
+}
+
+static int
+cpymve(char *source, char *target)
+{
+ int n;
+ int fi, fo;
+ int ret = 0;
+ int attret = 0;
+ int errno_save;
+
+ switch (chkfiles(source, &target)) {
+ case 1: return (1);
+ case 2: return (0);
+ /* default - fall through */
+ }
+
+ /*
+ * If it's a recursive copy and source
+ * is a directory, then call rcopy (from copydir).
+ */
+ if (cpy) {
+ if (ISDIR(s1)) {
+ int rc;
+ avl_index_t where = 0;
+ tree_node_t *tnode;
+ tree_node_t *tptr;
+ dev_t save_dev = s1.st_dev;
+ ino_t save_ino = s1.st_ino;
+
+ /*
+ * We will be recursing into the directory so
+ * save the inode information to a search tree
+ * to avoid getting into an endless loop.
+ */
+ if ((rc = add_tnode(&stree, save_dev, save_ino)) != 1) {
+ if (rc == 0) {
+ /*
+ * We've already visited this directory.
+ * Don't remove the search tree entry
+ * to make sure we don't get into an
+ * endless loop if revisited from a
+ * different part of the hierarchy.
+ */
+ (void) fprintf(stderr, gettext(
+ "%s: cycle detected: %s\n"),
+ cmd, source);
+ } else {
+ Perror(source);
+ }
+ return (1);
+ }
+
+ cmdarg = 0;
+ rc = copydir(source, target);
+
+ /*
+ * Create a tnode to get an index to the matching
+ * node (same dev and inode) in the search tree,
+ * then use the index to remove the matching node
+ * so it we do not wrongly detect a cycle when
+ * revisiting this directory from another part of
+ * the hierarchy.
+ */
+ if ((tnode = create_tnode(save_dev,
+ save_ino)) == NULL) {
+ Perror(source);
+ return (1);
+ }
+ if ((tptr = avl_find(stree, tnode, &where)) != NULL) {
+ avl_remove(stree, tptr);
+ }
+ free(tptr);
+ free(tnode);
+ return (rc);
+
+ } else if (ISDEV(s1) && Rflg) {
+ return (copyspecial(target));
+ } else {
+ goto copy;
+ }
+ }
+
+ if (mve) {
+ if (rename(source, target) >= 0)
+ return (0);
+ if (errno != EXDEV) {
+ if (errno == ENOTDIR && ISDIR(s1)) {
+ (void) fprintf(stderr,
+ gettext("%s: %s is a directory\n"),
+ cmd, source);
+ return (1);
+ }
+ (void) fprintf(stderr,
+ gettext("%s: cannot rename %s to %s: "),
+ cmd, source, target);
+ perror("");
+ return (1);
+ }
+
+ /*
+ * cannot move a non-directory (source) onto an existing
+ * directory (target)
+ *
+ */
+ if (targetexists && ISDIR(s2) && (!ISDIR(s1))) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot mv a non directory %s "
+ "over existing directory"
+ " %s \n"), cmd, source, target);
+ return (1);
+ }
+ if (ISDIR(s1)) {
+#ifdef XPG4
+ if (targetexists && ISDIR(s2)) {
+ /* existing target dir must be empty */
+ if (rmdir(target) < 0) {
+ errno_save = errno;
+ (void) fprintf(stderr,
+ gettext("%s: cannot rmdir %s: "),
+ cmd, target);
+ errno = errno_save;
+ perror("");
+ return (1);
+ }
+ }
+#endif
+ if ((n = copydir(source, target)) == 0)
+ (void) rmdir(source);
+ return (n);
+ }
+
+ /* doors can't be moved across filesystems */
+ if (ISDOOR(s1)) {
+ (void) fprintf(stderr,
+ gettext("%s: %s: can't move door "
+ "across file systems\n"), cmd, source);
+ return (1);
+ }
+ /*
+ * File can't be renamed, try to recreate the symbolic
+ * link or special device, or copy the file wholesale
+ * between file systems.
+ */
+ if (ISLNK(s1)) {
+ register int m;
+ register mode_t md;
+ char symln[PATH_MAX + 1];
+
+ if (targetexists && unlink(target) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot unlink %s: "),
+ cmd, target);
+ perror("");
+ return (1);
+ }
+
+ if ((m = readlink(source, symln,
+ sizeof (symln) - 1)) < 0) {
+ Perror(source);
+ return (1);
+ }
+ symln[m] = '\0';
+
+ md = umask(~(s1.st_mode & MODEBITS));
+ if (symlink(symln, target) < 0) {
+ Perror(target);
+ return (1);
+ }
+ (void) umask(md);
+ m = lchown(target, UID(s1), GID(s1));
+#ifdef XPG4
+ if (m < 0) {
+ (void) fprintf(stderr, gettext("%s: cannot"
+ " change owner and group of"
+ " %s: "), cmd, target);
+ perror("");
+ }
+#endif
+ goto cleanup;
+ }
+ if (ISDEV(s1)) {
+
+ if (targetexists && unlink(target) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot unlink %s: "),
+ cmd, target);
+ perror("");
+ return (1);
+ }
+
+ if (mknod(target, s1.st_mode, s1.st_rdev) < 0) {
+ Perror(target);
+ return (1);
+ }
+
+ (void) chg_mode(target, UID(s1), GID(s1), FMODE(s1));
+ (void) chg_time(target, s1);
+ goto cleanup;
+ }
+
+ if (ISREG(s1)) {
+ if (ISDIR(s2)) {
+ if (targetexists && rmdir(target) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot rmdir %s: "),
+ cmd, target);
+ perror("");
+ return (1);
+ }
+ } else {
+ if (targetexists && unlink(target) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot unlink %s: "),
+ cmd, target);
+ perror("");
+ return (1);
+ }
+ }
+
+
+copy:
+ /*
+ * If the source file is a symlink, and either
+ * -P or -H flag (only if -H is specified and the
+ * source file is not a command line argument)
+ * were specified, then action is taken on the symlink
+ * itself, not the file referenced by the symlink.
+ * Note: this is executed for 'cp' only.
+ */
+ if (cpy && (Pflg || (Hflg && !cmdarg)) && (ISLNK(s1))) {
+ int m;
+ mode_t md;
+ char symln[PATH_MAX + 1];
+
+ m = readlink(source, symln, sizeof (symln) - 1);
+
+ if (m < 0) {
+ Perror(source);
+ return (1);
+ }
+ symln[m] = '\0';
+
+ /*
+ * Copy the sym link to the target.
+ * Note: If the target exists, write a
+ * diagnostic message, do nothing more
+ * with the source file, and return to
+ * process any remaining files.
+ */
+ md = umask(~(s1.st_mode & MODEBITS));
+ if (symlink(symln, target) < 0) {
+ Perror(target);
+ return (1);
+ }
+ (void) umask(md);
+ m = lchown(target, UID(s1), GID(s1));
+
+ if (m < 0) {
+ (void) fprintf(stderr, gettext(
+ "cp: cannot change owner and "
+ "group of %s:"), target);
+ perror("");
+ }
+ } else {
+ /*
+ * Copy the file. If it happens to be a
+ * symlink, copy the file referenced
+ * by the symlink.
+ */
+ fi = open(source, O_RDONLY);
+ if (fi < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot open %s: "),
+ cmd, source);
+ perror("");
+ return (1);
+ }
+
+ fo = creat(target, s1.st_mode & MODEBITS);
+ if (fo < 0) {
+ /*
+ * If -f and creat() failed, unlink
+ * and try again.
+ */
+ if (fflg) {
+ (void) unlink(target);
+ fo = creat(target,
+ s1.st_mode & MODEBITS);
+ }
+ }
+ if (fo < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot create %s: "),
+ cmd, target);
+ perror("");
+ (void) close(fi);
+ return (1);
+ } else {
+ /* stat the new file, its used below */
+ (void) stat(target, &s2);
+ }
+
+ /*
+ * Set target's permissions to the source
+ * before any copying so that any partially
+ * copied file will have the source's
+ * permissions (at most) or umask permissions
+ * whichever is the most restrictive.
+ *
+ * ACL for regular files
+ */
+
+ if (pflg || mve) {
+ (void) chmod(target, FMODE(s1));
+ if (s1aclp != NULL) {
+ if ((acl(target, SETACL,
+ s1aclcnt, s1aclp)) < 0) {
+ if (pflg || mve) {
+ (void) fprintf(
+ stderr,
+ "%s: failed to set acl entries on %s\n",
+ cmd,
+ target);
+ }
+ /*
+ * else: silent and
+ * continue
+ */
+ }
+ }
+ }
+
+ if (fstat(fi, &s1) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot access %s\n"),
+ cmd, source);
+ return (1);
+ }
+ if (IDENTICAL(s1, s2)) {
+ (void) fprintf(stderr,
+ gettext(
+ "%s: %s and %s are identical\n"),
+ cmd, source, target);
+ return (1);
+ }
+
+ if (writefile(fi, fo, source, target,
+ &s1, &s2) != 0) {
+ return (1);
+ }
+
+ (void) close(fi);
+ if (close(fo) < 0) {
+ Perror2(target, "write");
+ return (1);
+ }
+ }
+
+ if (pflg || atflg || mve) {
+ attret = copyattributes(source, target);
+ if (attret != 0 && !attrsilent) {
+ (void) fprintf(stderr, gettext(
+ "%s: Failed to preserve"
+ " extended attributes of file"
+ " %s\n"), cmd, source);
+ }
+
+ if (mve && attret != 0) {
+ (void) unlink(target);
+ return (1);
+ }
+
+ if (attrsilent)
+ attret = 0;
+ }
+
+ /*
+ * XPG4: the write system call will clear setgid
+ * and setuid bits, so set them again.
+ */
+ if (pflg || mve) {
+ if ((ret = chg_mode(target, UID(s1), GID(s1),
+ FMODE(s1))) > 0)
+ return (1);
+ if ((ret = chg_time(target, s1)) > 0)
+ return (1);
+ }
+ if (cpy) {
+ if (attret != 0)
+ return (1);
+ return (0);
+ }
+ goto cleanup;
+ }
+ (void) fprintf(stderr,
+ gettext("%s: %s: unknown file type 0x%x\n"), cmd,
+ source, (s1.st_mode & S_IFMT));
+ return (1);
+
+cleanup:
+ if (unlink(source) < 0) {
+ (void) unlink(target);
+ (void) fprintf(stderr,
+ gettext("%s: cannot unlink %s: "),
+ cmd, source);
+ perror("");
+ return (1);
+ }
+ if (attret != 0)
+ return (attret);
+ return (ret);
+ }
+ /*NOTREACHED*/
+}
+
+static int
+writefile(int fi, int fo, char *source, char *target,
+ struct stat *s1p, struct stat *s2p)
+{
+ int mapsize, munmapsize;
+ caddr_t cp;
+ off_t filesize = s1p->st_size;
+ off_t offset;
+ int nbytes;
+ int remains;
+ int n;
+
+ if (ISREG(*s1p) && s1p->st_size > SMALLFILESIZE) {
+ /*
+ * Determine size of initial mapping. This will determine the
+ * size of the address space chunk we work with. This initial
+ * mapping size will be used to perform munmap() in the future.
+ */
+ mapsize = MAXMAPSIZE;
+ if (s1p->st_size < mapsize) mapsize = s1p->st_size;
+ munmapsize = mapsize;
+
+ /*
+ * Mmap time!
+ */
+ if ((cp = mmap((caddr_t)NULL, mapsize, PROT_READ,
+ MAP_SHARED, fi, (off_t)0)) == MAP_FAILED)
+ mapsize = 0; /* can't mmap today */
+ } else
+ mapsize = 0;
+
+ if (mapsize != 0) {
+ offset = 0;
+
+ for (;;) {
+ nbytes = write(fo, cp, mapsize);
+ /*
+ * if we write less than the mmaped size it's due to a
+ * media error on the input file or out of space on
+ * the output file. So, try again, and look for errno.
+ */
+ if ((nbytes >= 0) && (nbytes != (int)mapsize)) {
+ remains = mapsize - nbytes;
+ while (remains > 0) {
+ nbytes = write(fo,
+ cp + mapsize - remains, remains);
+ if (nbytes < 0) {
+ if (errno == ENOSPC)
+ Perror(target);
+ else
+ Perror(source);
+ (void) close(fi);
+ (void) close(fo);
+ (void) munmap(cp, munmapsize);
+ if (ISREG(*s2p))
+ (void) unlink(target);
+ return (1);
+ }
+ remains -= nbytes;
+ if (remains == 0)
+ nbytes = mapsize;
+ }
+ }
+ /*
+ * although the write manual page doesn't specify this
+ * as a possible errno, it is set when the nfs read
+ * via the mmap'ed file is accessed, so report the
+ * problem as a source access problem, not a target file
+ * problem
+ */
+ if (nbytes < 0) {
+ if (errno == EACCES)
+ Perror(source);
+ else
+ Perror(target);
+ (void) close(fi);
+ (void) close(fo);
+ (void) munmap(cp, munmapsize);
+ if (ISREG(*s2p))
+ (void) unlink(target);
+ return (1);
+ }
+ filesize -= nbytes;
+ if (filesize == 0)
+ break;
+ offset += nbytes;
+ if (filesize < mapsize)
+ mapsize = filesize;
+ if (mmap(cp, mapsize, PROT_READ, MAP_SHARED | MAP_FIXED,
+ fi, offset) == MAP_FAILED) {
+ Perror(source);
+ (void) close(fi);
+ (void) close(fo);
+ (void) munmap(cp, munmapsize);
+ if (ISREG(*s2p))
+ (void) unlink(target);
+ return (1);
+ }
+ }
+ (void) munmap(cp, munmapsize);
+ } else {
+ char buf[SMALLFILESIZE];
+ for (;;) {
+ n = read(fi, buf, sizeof (buf));
+ if (n == 0) {
+ return (0);
+ } else if (n < 0) {
+ Perror2(source, "read");
+ (void) close(fi);
+ (void) close(fo);
+ if (ISREG(*s2p))
+ (void) unlink(target);
+ return (1);
+ } else if (write(fo, buf, n) != n) {
+ Perror2(target, "write");
+ (void) close(fi);
+ (void) close(fo);
+ if (ISREG(*s2p))
+ (void) unlink(target);
+ return (1);
+ }
+ }
+ }
+ return (0);
+}
+
+/*
+ * create_tnode()
+ *
+ * Create a node for use with the search tree which contains the
+ * inode information (device id and inode number).
+ *
+ * Input
+ * dev - device id
+ * ino - inode number
+ *
+ * Output
+ * tnode - NULL on error, otherwise returns a tnode structure
+ * which contains the input device id and inode number.
+ */
+static tree_node_t *
+create_tnode(dev_t dev, ino_t ino)
+{
+ tree_node_t *tnode;
+
+ if ((tnode = (tree_node_t *)malloc(sizeof (tree_node_t))) != NULL) {
+ tnode->node_dev = dev;
+ tnode->node_ino = ino;
+ }
+
+ return (tnode);
+}
+
+static int
+chkfiles(char *source, char **to)
+{
+ char *buf = (char *)NULL;
+ int (*statf)() = (cpy &&
+ !(Pflg || (Hflg && !cmdarg))) ? stat : lstat;
+ char *target = *to;
+
+ /*
+ * Make sure source file exists.
+ */
+ if ((*statf)(source, &s1) < 0) {
+ /*
+ * Keep the old error message except when someone tries to
+ * mv/cp/ln a symbolic link that has a trailing slash and
+ * points to a file.
+ */
+ if (errno == ENOTDIR)
+ (void) fprintf(stderr, "%s: %s: %s\n", cmd, source,
+ strerror(errno));
+ else
+ (void) fprintf(stderr,
+ gettext("%s: cannot access %s\n"), cmd, source);
+ return (1);
+ }
+
+ /*
+ * Get ACL info: don't bother with ln or mv'ing symlinks
+ */
+ if ((!lnk) && !(mve && ISLNK(s1))) {
+ if (s1aclp != NULL) {
+ free(s1aclp);
+ s1aclp = NULL;
+ }
+ if ((s1aclcnt = acl(source, GETACLCNT, 0, NULL)) < 0) {
+ (void) fprintf(stderr,
+ "%s: failed to get acl entries\n", source);
+ return (1);
+ }
+ if (s1aclcnt > MIN_ACL_ENTRIES) {
+ if ((s1aclp = (aclent_t *)malloc(
+ sizeof (aclent_t) * s1aclcnt)) == NULL) {
+ (void) fprintf(stderr, "Insufficient memory\n");
+ return (1);
+ }
+ if ((acl(source, GETACL, s1aclcnt, s1aclp)) < 0) {
+ (void) fprintf(stderr,
+ "%s: failed to get acl entries\n", source);
+ return (1);
+ }
+ }
+ /* else: just permission bits */
+ }
+
+ /*
+ * If stat fails, then the target doesn't exist,
+ * we will create a new target with default file type of regular.
+ */
+
+ FTYPE(s2) = S_IFREG;
+ targetexists = 0;
+ if ((*statf)(target, &s2) >= 0) {
+ if (ISLNK(s2))
+ (void) stat(target, &s2);
+ /*
+ * If target is a directory,
+ * make complete name of new file
+ * within that directory.
+ */
+ if (ISDIR(s2)) {
+ size_t len;
+
+ len = strlen(target) + strlen(dname(source)) + 4;
+ if ((buf = (char *)malloc(len)) == NULL) {
+ (void) fprintf(stderr,
+ gettext("%s: Insufficient memory to "
+ "%s %s\n "), cmd, cmd, source);
+ exit(3);
+ }
+ (void) snprintf(buf, len, "%s/%s",
+ target, dname(source));
+ *to = target = buf;
+ }
+
+ if ((*statf)(target, &s2) >= 0) {
+ int overwrite = FALSE;
+ int override = FALSE;
+
+ targetexists++;
+ if (cpy || mve) {
+ /*
+ * For cp and mv, it is an error if the
+ * source and target are the same file.
+ * Check for the same inode and file
+ * system, but don't check for the same
+ * absolute pathname because it is an
+ * error when the source and target are
+ * hard links to the same file.
+ */
+ if (IDENTICAL(s1, s2)) {
+ (void) fprintf(stderr,
+ gettext(
+ "%s: %s and %s are identical\n"),
+ cmd, source, target);
+ if (buf != NULL)
+ free(buf);
+ return (1);
+ }
+ }
+ if (lnk) {
+ /*
+ * For ln, it is an error if the source and
+ * target are identical files (same inode,
+ * same file system, and filenames resolve
+ * to same absolute pathname).
+ */
+ if (!chk_different(source, target)) {
+ if (buf != NULL)
+ free(buf);
+ return (1);
+ }
+ }
+ if (lnk && !silent) {
+ (void) fprintf(stderr,
+ gettext("%s: %s: File exists\n"),
+ cmd, target);
+ if (buf != NULL)
+ free(buf);
+ return (1);
+ }
+
+ /*
+ * overwrite:
+ * If the user does not have access to
+ * the target, ask ----if it is not
+ * silent and user invoked command
+ * interactively.
+ *
+ * override:
+ * If not silent, and stdin is a terminal, and
+ * there's no write access, and the file isn't a
+ * symbolic link, ask for permission.
+ *
+ * XPG4: both overwrite and override:
+ * ask only one question.
+ *
+ * TRANSLATION_NOTE - The following messages will
+ * contain the first character of the strings for
+ * "yes" and "no" defined in the file
+ * "nl_langinfo.po". After substitution, the
+ * message will appear as follows:
+ * <cmd>: overwrite <filename> (y/n)?
+ * where <cmd> is the name of the command
+ * (cp, mv) and <filename> is the destination file
+ */
+
+
+ overwrite = iflg && !silent && use_stdin();
+ override = !cpy && (access(target, 2) < 0) &&
+ !silent && use_stdin() && !ISLNK(s2);
+
+ if (overwrite && override)
+ (void) fprintf(stderr,
+ gettext("%s: overwrite %s and override "
+ "protection %o (%s/%s)? "), cmd, target,
+ FMODE(s2) & MODEBITS, yeschr, nochr);
+ else if (overwrite && ISREG(s2))
+ (void) fprintf(stderr,
+ gettext("%s: overwrite %s (%s/%s)? "),
+ cmd, target, yeschr, nochr);
+ else if (override)
+ (void) fprintf(stderr,
+ gettext("%s: %s: override protection "
+ /*CSTYLED*/
+ "%o (%s/%s)? "),
+ /*CSTYLED*/
+ cmd, target, FMODE(s2) & MODEBITS,
+ yeschr, nochr);
+ if (overwrite || override) {
+ if (ISREG(s2)) {
+ if (getresp()) {
+ if (buf != NULL)
+ free(buf);
+ return (2);
+ }
+ }
+ }
+ if (lnk && unlink(target) < 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot unlink %s: "),
+ cmd, target);
+ perror("");
+ return (1);
+ }
+ }
+ }
+ return (0);
+}
+
+/*
+ * check whether source and target are different
+ * return 1 when they are different
+ * return 0 when they are identical, or when unable to resolve a pathname
+ */
+static int
+chk_different(char *source, char *target)
+{
+ char rtarget[PATH_MAX], rsource[PATH_MAX];
+
+ if (IDENTICAL(s1, s2)) {
+ /*
+ * IDENTICAL will be true for hard links, therefore
+ * check whether the filenames are different
+ */
+ if ((getrealpath(source, rsource) == 0) ||
+ (getrealpath(target, rtarget) == 0)) {
+ return (0);
+ }
+ if (strncmp(rsource, rtarget, PATH_MAX) == 0) {
+ (void) fprintf(stderr, gettext(
+ "%s: %s and %s are identical\n"),
+ cmd, source, target);
+ return (0);
+ }
+ }
+ return (1);
+}
+
+/*
+ * get real path (resolved absolute pathname)
+ * return 1 on success, 0 on failure
+ */
+static int
+getrealpath(char *path, char *rpath)
+{
+ if (realpath(path, rpath) == NULL) {
+ int errno_save = errno;
+ (void) fprintf(stderr, gettext(
+ "%s: can't resolve path %s: "), cmd, path);
+ errno = errno_save;
+ perror("");
+ return (0);
+ }
+ return (1);
+}
+
+static int
+rcopy(char *from, char *to)
+{
+ DIR *fold = opendir(from);
+ struct dirent *dp;
+ struct stat statb, s1save;
+ int errs = 0;
+ char fromname[PATH_MAX];
+
+ if (fold == 0 || ((pflg || mve) && fstat(fold->dd_fd, &statb) < 0)) {
+ Perror(from);
+ return (1);
+ }
+ if (pflg || mve) {
+ /*
+ * Save s1 (stat information for source dir) so that
+ * mod and access times can be reserved during "cp -p"
+ * or mv, since s1 gets overwritten.
+ */
+ s1save = s1;
+ }
+ for (;;) {
+ dp = readdir(fold);
+ if (dp == 0) {
+ (void) closedir(fold);
+ if (pflg || mve)
+ return (chg_time(to, s1save) + errs);
+ return (errs);
+ }
+ if (dp->d_ino == 0)
+ continue;
+ if ((strcmp(dp->d_name, ".") == 0) ||
+ (strcmp(dp->d_name, "..") == 0))
+ continue;
+ if (strlen(from)+1+strlen(dp->d_name) >=
+ sizeof (fromname) - 1) {
+ (void) fprintf(stderr,
+ gettext("%s : %s/%s: Name too long\n"),
+ cmd, from, dp->d_name);
+ errs++;
+ continue;
+ }
+ (void) snprintf(fromname, sizeof (fromname),
+ "%s/%s", from, dp->d_name);
+ errs += cpymve(fromname, to);
+ }
+}
+
+static char *
+dname(char *name)
+{
+ register char *p;
+
+ /*
+ * Return just the file name given the complete path.
+ * Like basename(1).
+ */
+
+ p = name;
+
+ /*
+ * While there are characters left,
+ * set name to start after last
+ * delimiter.
+ */
+
+ while (*p)
+ if (*p++ == DELIM && *p)
+ name = p;
+ return (name);
+}
+
+static int
+getresp(void)
+{
+ register int c, i;
+ char ans_buf[SCHAR_MAX + 1];
+
+ /*
+ * Get response from user. Based on
+ * first character, make decision.
+ * Discard rest of line.
+ */
+ for (i = 0; ; i++) {
+ c = getchar();
+ if (c == '\n' || c == 0 || c == EOF) {
+ ans_buf[i] = 0;
+ break;
+ }
+ if (i < SCHAR_MAX)
+ ans_buf[i] = c;
+ }
+ if (i >= SCHAR_MAX) {
+ i = SCHAR_MAX;
+ ans_buf[SCHAR_MAX] = 0;
+ }
+ if ((i == 0) | (strncmp(yeschr, ans_buf, i)))
+ return (1);
+ return (0);
+}
+
+static void
+usage(void)
+{
+ /*
+ * Display usage message.
+ */
+
+ if (mve) {
+ (void) fprintf(stderr, gettext(
+ "Usage: mv [-f] [-i] f1 f2\n"
+ " mv [-f] [-i] f1 ... fn d1\n"
+ " mv [-f] [-i] d1 d2\n"));
+ } else if (lnk) {
+ (void) fprintf(stderr, gettext(
+#ifdef XPG4
+ "Usage: ln [-f] [-s] f1 [f2]\n"
+ " ln [-f] [-s] f1 ... fn d1\n"
+ " ln [-f] -s d1 d2\n"));
+#else
+ "Usage: ln [-f] [-n] [-s] f1 [f2]\n"
+ " ln [-f] [-n] [-s] f1 ... fn d1\n"
+ " ln [-f] [-n] -s d1 d2\n"));
+#endif
+ } else if (cpy) {
+ (void) fprintf(stderr, gettext(
+ "Usage: cp [-f] [-i] [-p] [-@] f1 f2\n"
+ " cp [-f] [-i] [-p] [-@] f1 ... fn d1\n"
+ " cp -r|-R [-H|-L|-P] [-f] [-i] [-p] [-@] "
+ "d1 ... dn-1 dn\n"));
+ }
+ exit(2);
+}
+
+/*
+ * chg_time()
+ *
+ * Try to preserve modification and access time.
+ * If 1) pflg is not set, or 2) pflg is set and this is the Solaris version,
+ * don't report a utime() failure.
+ * If this is the XPG4 version and utime fails, if 1) pflg is set (cp -p)
+ * or 2) we are doing a mv, print a diagnostic message; arrange for a non-zero
+ * exit status only if pflg is set.
+ * utimes(2) is being used to achieve granularity in
+ * microseconds while setting file times.
+ */
+static int
+chg_time(char *to, struct stat ss)
+{
+ struct timeval times[2];
+ int rc;
+
+ timestruc_to_timeval(&ss.st_atim, times);
+ timestruc_to_timeval(&ss.st_mtim, times + 1);
+
+ rc = utimes(to, times);
+#ifdef XPG4
+ if ((pflg || mve) && rc != 0) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot set times for %s: "), cmd, to);
+ perror("");
+ if (pflg)
+ return (1);
+ }
+#endif
+
+ return (0);
+
+}
+
+/*
+ * chg_mode()
+ *
+ * This function is called upon "cp -p" or mv across filesystems.
+ *
+ * Try to preserve the owner and group id. If chown() fails,
+ * only print a diagnostic message if doing a mv in the XPG4 version;
+ * try to clear S_ISUID and S_ISGID bits in the target. If unable to clear
+ * S_ISUID and S_ISGID bits, print a diagnostic message and arrange for a
+ * non-zero exit status because this is a security violation.
+ * Try to preserve permissions.
+ * If this is the XPG4 version and chmod() fails, print a diagnostic message
+ * and arrange for a non-zero exit status.
+ * If this is the Solaris version and chmod() fails, do not print a
+ * diagnostic message or exit with a non-zero value.
+ */
+static int
+chg_mode(char *target, uid_t uid, gid_t gid, mode_t mode)
+{
+ int clearflg = 0; /* controls message printed upon chown() error */
+
+ if (chown(target, uid, gid) != 0) {
+#ifdef XPG4
+ if (mve) {
+ (void) fprintf(stderr, gettext("%s: cannot change"
+ " owner and group of %s: "), cmd, target);
+ perror("");
+ }
+#endif
+ if (mode & (S_ISUID | S_ISGID)) {
+ /* try to clear S_ISUID and S_ISGID */
+ mode &= ~S_ISUID & ~S_ISGID;
+ ++clearflg;
+ }
+ }
+ if (chmod(target, mode) != 0) {
+ if (clearflg) {
+ (void) fprintf(stderr, gettext(
+ "%s: cannot clear S_ISUID and S_ISGID bits in"
+ " %s: "), cmd, target);
+ perror("");
+ /* cp -p should get non-zero exit; mv should not */
+ if (pflg)
+ return (1);
+ }
+#ifdef XPG4
+ else {
+ (void) fprintf(stderr, gettext(
+ "%s: cannot set permissions for %s: "), cmd, target);
+ perror("");
+ /* cp -p should get non-zero exit; mv should not */
+ if (pflg)
+ return (1);
+ }
+#endif
+ }
+ return (0);
+
+}
+
+static void
+Perror(char *s)
+{
+ char buf[PATH_MAX + 10];
+
+ (void) snprintf(buf, sizeof (buf), "%s: %s", cmd, s);
+ perror(buf);
+}
+
+static void
+Perror2(char *s1, char *s2)
+{
+ char buf[PATH_MAX + 20];
+
+ (void) snprintf(buf, sizeof (buf), "%s: %s: %s",
+ cmd, gettext(s1), gettext(s2));
+ perror(buf);
+}
+
+/*
+ * used for cp -R and for mv across file systems
+ */
+static int
+copydir(char *source, char *target)
+{
+ int ret, attret = 0;
+ int pret = 0; /* need separate flag if -p is specified */
+ mode_t fixmode = (mode_t)0; /* cleanup mode after copy */
+ struct stat s1save;
+ int s1aclcnt_save;
+ aclent_t *s1aclp_save = NULL;
+
+ if (cpy && !rflg) {
+ (void) fprintf(stderr,
+ gettext("%s: %s: is a directory\n"), cmd, source);
+ return (1);
+ }
+
+ if (stat(target, &s2) < 0) {
+ if (mkdir(target, (s1.st_mode & MODEBITS)) < 0) {
+ (void) fprintf(stderr, "%s: ", cmd);
+ perror(target);
+ return (1);
+ }
+ if (stat(target, &s2) == 0) {
+ fixmode = s2.st_mode;
+ } else {
+ fixmode = s1.st_mode;
+ }
+ (void) chmod(target, ((fixmode & MODEBITS) | S_IRWXU));
+ } else if (!(ISDIR(s2))) {
+ (void) fprintf(stderr,
+ gettext("%s: %s: not a directory.\n"), cmd, target);
+ return (1);
+ }
+ if (pflg || mve) {
+ /*
+ * Save s1 (stat information for source dir) and acl info,
+ * if any, so that ownership, modes, times, and acl's can
+ * be reserved during "cp -p" or mv.
+ * s1 gets overwritten when doing the recursive copy.
+ */
+ s1save = s1;
+ if (s1aclp != NULL) {
+ if ((s1aclp_save = (aclent_t *)malloc(sizeof (aclent_t)
+ * s1aclcnt)) != NULL) {
+ (void) memcpy(s1aclp_save, s1aclp,
+ sizeof (aclent_t) * s1aclcnt);
+ s1aclcnt_save = s1aclcnt;
+ }
+#ifdef XPG4
+ else {
+ (void) fprintf(stderr, gettext("%s: "
+ "Insufficient memory to save acl"
+ " entry\n"), cmd);
+ if (pflg)
+ return (1);
+ }
+#endif
+ }
+ }
+
+ ret = rcopy(source, target);
+
+ /*
+ * Once we created a directory, go ahead and set
+ * its attributes, e.g. acls and time. The info
+ * may get overwritten if we continue traversing
+ * down the tree.
+ *
+ * ACL for directory
+ */
+ if (pflg || mve) {
+ if (s1aclp_save != NULL) {
+ if ((acl(target, SETACL, s1aclcnt_save, s1aclp_save))
+ < 0) {
+#ifdef XPG4
+ if (pflg || mve) {
+#else
+ if (pflg) {
+#endif
+ (void) fprintf(stderr, gettext(
+ "%s: failed to set acl entries "
+ "on %s\n"), cmd, target);
+ if (pflg) {
+ free(s1aclp_save);
+ ret++;
+ }
+ }
+ /* else: silent and continue */
+ }
+ free(s1aclp_save);
+ }
+ if ((pret = chg_mode(target, UID(s1save), GID(s1save),
+ FMODE(s1save))) == 0)
+ pret = chg_time(target, s1save);
+ ret += pret;
+ } else if (fixmode != (mode_t)0)
+ (void) chmod(target, fixmode & MODEBITS);
+
+ if (pflg || atflg || mve) {
+ attret = copyattributes(source, target);
+ if (!attrsilent && attret != 0) {
+ (void) fprintf(stderr, gettext("%s: Failed to preserve"
+ " extended attributes of directory"
+ " %s\n"), cmd, source);
+ } else {
+ /*
+ * Otherwise ignore failure.
+ */
+ attret = 0;
+ }
+ }
+ if (attret != 0)
+ return (attret);
+ return (ret);
+}
+
+static int
+copyspecial(char *target)
+{
+ int ret = 0;
+
+ if (mknod(target, s1.st_mode, s1.st_rdev) != 0) {
+ (void) fprintf(stderr, gettext(
+ "cp: cannot create special file %s: "), target);
+ perror("");
+ return (1);
+ }
+
+ if (pflg) {
+ if ((ret = chg_mode(target, UID(s1), GID(s1), FMODE(s1))) == 0)
+ ret = chg_time(target, s1);
+ }
+
+ return (ret);
+}
+
+static int
+use_stdin(void)
+{
+#ifdef XPG4
+ return (1);
+#else
+ return (isatty(fileno(stdin)));
+#endif
+}
+
+static int
+copyattributes(char *source, char *target)
+{
+ int ret;
+ int sourcedirfd, targetdirfd;
+ int srcfd, targfd;
+ int tmpfd;
+ DIR *srcdirp;
+ int srcattrfd, targattrfd;
+ struct dirent *dp;
+ char *attrstr;
+ char *srcbuf, *targbuf;
+ size_t src_size, targ_size;
+ int error = 0;
+ mode_t mode;
+ int clearflg = 0;
+ int aclcnt;
+ int attrdiraclcnt;
+ aclent_t *aclp = NULL;
+ aclent_t *attrdiraclp = NULL;
+ struct stat attrdir, s3, s4;
+ struct timeval times[2];
+ mode_t targmode;
+
+ srcdirp = NULL;
+ srcfd = targfd = tmpfd = -1;
+ sourcedirfd = targetdirfd = srcattrfd = targattrfd = -1;
+ srcbuf = targbuf = NULL;
+
+ if (pathconf(source, _PC_XATTR_EXISTS) != 1)
+ return (0);
+
+ if (pathconf(target, _PC_XATTR_ENABLED) != 1) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext(
+ "%s: cannot preserve extended attributes, "
+ "operation not supported on file"
+ " %s\n"), cmd, target);
+ }
+ return (1);
+ }
+
+
+ if ((srcfd = open(source, O_RDONLY)) == -1) {
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot open file"
+ " %s: "), cmd, source);
+ perror("");
+ }
+ ++error;
+ goto out;
+ }
+ if ((targfd = open(target, O_RDONLY)) == -1) {
+
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot open file"
+ " %s: "), cmd, source);
+ perror("");
+ }
+ ++error;
+ goto out;
+ }
+
+ if ((sourcedirfd = openat(srcfd, ".", O_RDONLY|O_XATTR)) == -1) {
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot open attribute"
+ " directory for %s: "), cmd, source);
+ perror("");
+ ++error;
+ }
+ goto out;
+ }
+
+ if (fstat(sourcedirfd, &attrdir) == -1) {
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: could not retrieve stat"
+ " information for attribute directory"
+ "of file %s: "), cmd, source);
+ perror("");
+ ++error;
+ }
+ goto out;
+ }
+ if ((targetdirfd = openat(targfd, ".", O_RDONLY|O_XATTR)) == -1) {
+ /*
+ * We couldn't create the attribute directory
+ *
+ * Lets see if we can add write support to the mode
+ * and create the directory and then put the mode back
+ * to way it should be.
+ */
+
+ targmode = FMODE(s1) | S_IWUSR;
+ if (fchmod(targfd, targmode) == 0) {
+ targetdirfd = openat(targfd, ".", O_RDONLY|O_XATTR);
+ /*
+ * Put mode back to what it was
+ */
+ targmode = FMODE(s1) & MODEBITS;
+ if (fchmod(targfd, targmode) == -1) {
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: failed to set"
+ " mode correctly on file"
+ " %s: "), cmd, target);
+ perror("");
+ ++error;
+ goto out;
+ }
+ }
+ } else {
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot open attribute"
+ " directory for %s: "), cmd, target);
+ perror("");
+ ++error;
+ }
+ goto out;
+ }
+ }
+
+ if (targetdirfd == -1) {
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot open attribute directory"
+ " for %s: "), cmd, target);
+ perror("");
+ ++error;
+ }
+ goto out;
+ }
+
+ /*
+ * Set mode of attribute directory same as on source,
+ * if pflg set or this is a move.
+ */
+
+ if (pflg || mve) {
+ if (fchmod(targetdirfd, attrdir.st_mode) == -1) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: failed to set file mode"
+ " correctly on attribute directory of"
+ " file %s: "), cmd, target);
+ perror("");
+ ++error;
+ }
+ }
+
+ if (fchown(targetdirfd, attrdir.st_uid, attrdir.st_gid) == -1) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: failed to set file"
+ " ownership correctly on attribute"
+ " directory of file %s: "), cmd, target);
+ perror("");
+ ++error;
+ }
+ }
+ /*
+ * Now that we are the owner we can update st_ctime by calling
+ * futimesat.
+ */
+ times[0].tv_sec = attrdir.st_atime;
+ times[0].tv_usec = 0;
+ times[1].tv_sec = attrdir.st_mtime;
+ times[1].tv_usec = 0;
+ if (futimesat(targetdirfd, ".", times) < 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot set attribute times"
+ " for %s: "), cmd, target);
+ perror("");
+ ++error;
+ }
+ }
+
+ /*
+ * Now set owner and group of attribute directory, implies
+ * changing the ACL of the hidden attribute directory first.
+ */
+ if ((attrdiraclcnt = facl(sourcedirfd,
+ GETACLCNT, 0, NULL)) < 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr, gettext(
+ "%s: failed to get acl entries of"
+ " attribute directory for"
+ " %s\n"), cmd, source);
+ ++error;
+ }
+ }
+ if (attrdiraclcnt > MIN_ACL_ENTRIES) {
+ if ((attrdiraclp = (aclent_t *)malloc(
+ sizeof (aclent_t) * attrdiraclcnt)) == NULL) {
+ if (!attrsilent) {
+ (void) fprintf(stderr, gettext(
+ "insufficient memory"
+ " for acl\n"));
+ ++error;
+ }
+ } else {
+ if ((ret = facl(sourcedirfd, GETACL,
+ attrdiraclcnt, attrdiraclp)) == -1) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext(
+ "%s: failed to get acl"
+ " entries of attribute"
+ " directory for"
+ " %s\n"), cmd, target);
+ free(attrdiraclp);
+ attrdiraclp = NULL;
+ attrdiraclcnt = 0;
+ ++error;
+ }
+
+ }
+ if (ret != -1 && (facl(targetdirfd, SETACL,
+ attrdiraclcnt,
+ attrdiraclp) != 0)) {
+ if (!attrsilent) {
+ (void) fprintf(stderr, gettext(
+ "%s: failed to set acl entries"
+ " on attribute directory "
+ "for %s\n"), cmd, target);
+ ++error;
+ }
+ free(attrdiraclp);
+ attrdiraclp = NULL;
+ attrdiraclcnt = 0;
+ }
+ }
+
+ }
+ }
+
+ /*
+ * dup sourcedirfd for use by fdopendir().
+ * fdopendir will take ownership of given fd and will close
+ * it when closedir() is called.
+ */
+
+ if ((tmpfd = dup(sourcedirfd)) == -1) {
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext(
+ "%s: unable to dup attribute directory"
+ " file descriptor for %s: "), cmd, source);
+ perror("");
+ ++error;
+ }
+ goto out;
+ }
+ if ((srcdirp = fdopendir(tmpfd)) == NULL) {
+ if (pflg && attrsilent) {
+ error = 0;
+ goto out;
+ }
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: failed to open attribute"
+ " directory for %s: "), cmd, source);
+ perror("");
+ ++error;
+ }
+ goto out;
+ }
+
+ while (dp = readdir(srcdirp)) {
+ if ((dp->d_name[0] == '.' && dp->d_name[1] == '\0') ||
+ (dp->d_name[0] == '.' && dp->d_name[1] == '.' &&
+ dp->d_name[2] == '\0'))
+ continue;
+
+ if ((srcattrfd = openat(sourcedirfd, dp->d_name,
+ O_RDONLY)) == -1) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot open attribute %s on"
+ " file %s: "), cmd, dp->d_name, source);
+ perror("");
+ ++error;
+ goto next;
+ }
+ }
+
+ if (fstat(srcattrfd, &s3) < 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: could not stat attribute"
+ " %s on file"
+ " %s: "), cmd, dp->d_name, source);
+ perror("");
+ ++error;
+ }
+ goto next;
+ }
+
+ if (pflg || mve) {
+ if ((aclcnt = facl(srcattrfd,
+ GETACLCNT, 0, NULL)) < 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr, gettext(
+ "%s: failed to get acl entries of"
+ " attribute %s for"
+ " %s: "), cmd, dp->d_name, source);
+ perror("");
+ ++error;
+ }
+ }
+ if (aclcnt > MIN_ACL_ENTRIES) {
+ if ((aclp = (aclent_t *)malloc(
+ sizeof (aclent_t) * aclcnt)) ==
+ NULL) {
+ if (!attrsilent) {
+ (void) fprintf(stderr, gettext(
+ "insufficient memory"
+ " for acl: "));
+ perror("");
+ ++error;
+ }
+ } else {
+
+ if ((facl(srcattrfd, GETACL,
+ aclcnt, aclp)) < 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext(
+ "%s: failed to get"
+ " acl entries of"
+ " attribute %s for"
+ /*CSTYLED*/
+ " %s: "), cmd,
+ dp->d_name, target);
+ free(aclp);
+ aclp = NULL;
+ perror("");
+ ++error;
+ }
+
+ }
+
+ }
+ }
+
+ }
+
+ (void) unlinkat(targetdirfd, dp->d_name, 0);
+ if ((targattrfd = openat(targetdirfd, dp->d_name,
+ O_RDWR|O_CREAT|O_TRUNC, s3.st_mode & MODEBITS)) == -1) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: could not create attribute"
+ " %s on file"
+ " %s: "), cmd, dp->d_name, target);
+ perror("");
+ ++error;
+ }
+ goto next;
+ }
+
+ /*
+ * preserve ACL
+ */
+ if ((pflg || mve) && aclp != NULL) {
+ if ((facl(targattrfd, SETACL, aclcnt, aclp)) < 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr, gettext(
+ "%s: failed to set acl entries on"
+ " attribute %s for"
+ "%s\n"), cmd, dp->d_name, target);
+ ++error;
+ }
+ free(aclp);
+ aclp = NULL;
+ aclcnt = 0;
+ }
+ }
+
+ if (fstat(targattrfd, &s4) < 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: could not stat attribute"
+ " %s on file"
+ " %s: "), cmd, dp->d_name, source);
+ perror("");
+ ++error;
+ }
+ goto next;
+ }
+
+/*
+ * setup path string to be passed to writefile
+ *
+ * We need to include attribute in the string so that
+ * a useful error message can be printed in the case of a failure.
+ */
+ attrstr = gettext(" attribute ");
+ src_size = strlen(source) +
+ strlen(dp->d_name) + strlen(attrstr) + 1;
+ srcbuf = malloc(src_size);
+
+ if (srcbuf == NULL) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: could not allocate memory"
+ " for path buffer: "), cmd);
+ perror("");
+ ++error;
+ }
+ goto next;
+ }
+ targ_size = strlen(target) +
+ strlen(dp->d_name) + strlen(attrstr) + 1;
+ targbuf = malloc(targ_size);
+ if (targbuf == NULL) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: could not allocate memory"
+ " for path buffer: "), cmd);
+ perror("");
+ ++error;
+ }
+ goto next;
+ }
+
+ (void) snprintf(srcbuf, src_size, "%s%s%s",
+ source, attrstr, dp->d_name);
+ (void) snprintf(targbuf, targ_size, "%s%s%s",
+ target, attrstr, dp->d_name);
+
+ if (writefile(srcattrfd, targattrfd,
+ srcbuf, targbuf, &s3, &s4) != 0) {
+ if (!attrsilent) {
+ ++error;
+ }
+ goto next;
+ }
+
+ if (pflg || mve) {
+ mode = FMODE(s3);
+
+ if (fchown(targattrfd, UID(s3), GID(s3)) != 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot change"
+ " owner and group of"
+ " attribute %s for" " file"
+ " %s: "), cmd, dp->d_name, target);
+ perror("");
+ ++error;
+ }
+ if (mode & (S_ISUID | S_ISGID)) {
+ /* try to clear S_ISUID and S_ISGID */
+ mode &= ~S_ISUID & ~S_ISGID;
+ ++clearflg;
+ }
+ }
+ /* tv_usec were cleared above */
+ times[0].tv_sec = s3.st_atime;
+ times[1].tv_sec = s3.st_mtime;
+ if (futimesat(targetdirfd, dp->d_name, times) < 0) {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext("%s: cannot set attribute"
+ " times for %s: "), cmd, target);
+ perror("");
+ ++error;
+ }
+ }
+ if (fchmod(targattrfd, mode) != 0) {
+ if (clearflg) {
+ (void) fprintf(stderr, gettext(
+ "%s: cannot clear S_ISUID and "
+ "S_ISGID bits in attribute %s"
+ " for file"
+ " %s: "), cmd, dp->d_name, target);
+ } else {
+ if (!attrsilent) {
+ (void) fprintf(stderr,
+ gettext(
+ "%s: cannot set permissions of attribute"
+ " %s for %s: "), cmd, dp->d_name, target);
+ perror("");
+ ++error;
+ }
+ }
+ }
+ }
+next:
+ if (aclp != NULL) {
+ free(aclp);
+ aclp = NULL;
+ }
+ aclcnt = 0;
+ if (srcbuf != NULL)
+ free(srcbuf);
+ if (targbuf != NULL)
+ free(targbuf);
+ if (srcattrfd != -1)
+ (void) close(srcattrfd);
+ if (targattrfd != -1)
+ (void) close(targattrfd);
+ srcattrfd = targattrfd = -1;
+ srcbuf = targbuf = NULL;
+ }
+out:
+ if (aclp != NULL)
+ free(aclp);
+ if (attrdiraclp != NULL)
+ free(attrdiraclp);
+ if (srcbuf)
+ free(srcbuf);
+ if (targbuf)
+ free(targbuf);
+ if (sourcedirfd != -1)
+ (void) close(sourcedirfd);
+ if (targetdirfd != -1)
+ (void) close(targetdirfd);
+ if (srcdirp != NULL)
+ (void) closedir(srcdirp);
+ if (srcfd != -1)
+ (void) close(srcfd);
+ if (targfd != -1)
+ (void) close(targfd);
+ return (error == 0 ? 0 : 1);
+}
+
+/*
+ * nanoseconds are rounded off to microseconds by flooring.
+ */
+static void
+timestruc_to_timeval(timestruc_t *ts, struct timeval *tv)
+{
+ tv->tv_sec = ts->tv_sec;
+ tv->tv_usec = ts->tv_nsec / 1000;
+}