mv.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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;
}