xref: /netbsd-src/bin/cp/utils.c (revision b6787da4988b395b1429a4318518b8a78adafbd4)
1*b6787da4Schristos /* $NetBSD: utils.c,v 1.50 2024/01/15 17:41:06 christos Exp $ */
249f0ad86Scgd 
3e40b0636Smycroft /*-
4e40b0636Smycroft  * Copyright (c) 1991, 1993, 1994
5e40b0636Smycroft  *	The Regents of the University of California.  All rights reserved.
6e40b0636Smycroft  *
7e40b0636Smycroft  * Redistribution and use in source and binary forms, with or without
8e40b0636Smycroft  * modification, are permitted provided that the following conditions
9e40b0636Smycroft  * are met:
10e40b0636Smycroft  * 1. Redistributions of source code must retain the above copyright
11e40b0636Smycroft  *    notice, this list of conditions and the following disclaimer.
12e40b0636Smycroft  * 2. Redistributions in binary form must reproduce the above copyright
13e40b0636Smycroft  *    notice, this list of conditions and the following disclaimer in the
14e40b0636Smycroft  *    documentation and/or other materials provided with the distribution.
15b5b29542Sagc  * 3. Neither the name of the University nor the names of its contributors
16e40b0636Smycroft  *    may be used to endorse or promote products derived from this software
17e40b0636Smycroft  *    without specific prior written permission.
18e40b0636Smycroft  *
19e40b0636Smycroft  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20e40b0636Smycroft  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21e40b0636Smycroft  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22e40b0636Smycroft  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23e40b0636Smycroft  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24e40b0636Smycroft  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25e40b0636Smycroft  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26e40b0636Smycroft  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27e40b0636Smycroft  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28e40b0636Smycroft  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29e40b0636Smycroft  * SUCH DAMAGE.
30e40b0636Smycroft  */
31e40b0636Smycroft 
32efc20a30Sthorpej #include <sys/cdefs.h>
33e40b0636Smycroft #ifndef lint
3449f0ad86Scgd #if 0
3549f0ad86Scgd static char sccsid[] = "@(#)utils.c	8.3 (Berkeley) 4/1/94";
3649f0ad86Scgd #else
37*b6787da4Schristos __RCSID("$NetBSD: utils.c,v 1.50 2024/01/15 17:41:06 christos Exp $");
3849f0ad86Scgd #endif
39e40b0636Smycroft #endif /* not lint */
40e40b0636Smycroft 
419aa2a9c3Schristos #define _ACL_PRIVATE
42633b06feSwiz #include <sys/mman.h>
43e40b0636Smycroft #include <sys/param.h>
44e40b0636Smycroft #include <sys/stat.h>
45e40b0636Smycroft #include <sys/time.h>
46f6a91933Schristos #ifndef SMALL
479aa2a9c3Schristos #include <sys/acl.h>
48f6a91933Schristos #endif
4932c00d62Smanu #include <sys/extattr.h>
50e40b0636Smycroft 
51e40b0636Smycroft #include <err.h>
52e40b0636Smycroft #include <errno.h>
53e40b0636Smycroft #include <fcntl.h>
54e40b0636Smycroft #include <fts.h>
551181f404Stron #include <stdbool.h>
56e40b0636Smycroft #include <stdio.h>
57e40b0636Smycroft #include <stdlib.h>
58e40b0636Smycroft #include <string.h>
59e40b0636Smycroft #include <unistd.h>
60e40b0636Smycroft 
61e40b0636Smycroft #include "extern.h"
62e40b0636Smycroft 
631181f404Stron #define	MMAP_MAX_SIZE	(8 * 1048576)
641181f404Stron #define	MMAP_MAX_WRITE	(64 * 1024)
651181f404Stron 
66e40b0636Smycroft int
set_utimes(const char * file,struct stat * fs)67633b06feSwiz set_utimes(const char *file, struct stat *fs)
688a99d7cfSwsanchez {
69c1e351a1Senami     struct timespec ts[2];
708a99d7cfSwsanchez 
71c1e351a1Senami     ts[0] = fs->st_atimespec;
72c1e351a1Senami     ts[1] = fs->st_mtimespec;
738a99d7cfSwsanchez 
74c1e351a1Senami     if (lutimens(file, ts)) {
7580c6af72Senami 	warn("lutimens: %s", file);
768a99d7cfSwsanchez 	return (1);
778a99d7cfSwsanchez     }
788a99d7cfSwsanchez     return (0);
798a99d7cfSwsanchez }
808a99d7cfSwsanchez 
81eb8f42a5Schristos struct finfo {
82eb8f42a5Schristos 	const char *from;
83eb8f42a5Schristos 	const char *to;
849bb33dceSmrg 	off_t size;
85eb8f42a5Schristos };
86eb8f42a5Schristos 
87eb8f42a5Schristos static void
progress(const struct finfo * fi,off_t written)889bb33dceSmrg progress(const struct finfo *fi, off_t written)
89eb8f42a5Schristos {
90eb8f42a5Schristos 	int pcent = (int)((100.0 * written) / fi->size);
91eb8f42a5Schristos 
92eb8f42a5Schristos 	pinfo = 0;
939bb33dceSmrg 	(void)fprintf(stderr, "%s => %s %llu/%llu bytes %d%% written\n",
949bb33dceSmrg 	    fi->from, fi->to, (unsigned long long)written,
959bb33dceSmrg 	    (unsigned long long)fi->size, pcent);
96eb8f42a5Schristos }
97eb8f42a5Schristos 
988a99d7cfSwsanchez int
copy_file(FTSENT * entp,int dne)99633b06feSwiz copy_file(FTSENT *entp, int dne)
100e40b0636Smycroft {
101e40b0636Smycroft 	static char buf[MAXBSIZE];
102e40b0636Smycroft 	struct stat to_stat, *fs;
103471c8a8eSjschauma 	int ch, checkch, from_fd, rcount, rval, to_fd, tolnk, wcount;
1041181f404Stron 	char *p;
1059bb33dceSmrg 	off_t ptotal = 0;
106e40b0636Smycroft 
10722057652Sdarcy 	/* if hard linking then simply link and return */
10822057652Sdarcy 	if (lflag) {
10922057652Sdarcy 		(void)unlink(to.p_path);
11022057652Sdarcy 		if (link(entp->fts_path, to.p_path)) {
11122057652Sdarcy 			warn("%s", to.p_path);
11222057652Sdarcy 			return (1);
11322057652Sdarcy 		}
11422057652Sdarcy 		return (0);
11522057652Sdarcy 	}
11622057652Sdarcy 
117e40b0636Smycroft 	if ((from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
118e40b0636Smycroft 		warn("%s", entp->fts_path);
119e40b0636Smycroft 		return (1);
120e40b0636Smycroft 	}
121e40b0636Smycroft 
122471c8a8eSjschauma 	to_fd = -1;
123e40b0636Smycroft 	fs = entp->fts_statp;
124471c8a8eSjschauma 	tolnk = ((Rflag && !(Lflag || Hflag)) || Pflag);
125e40b0636Smycroft 
126e40b0636Smycroft 	/*
127e40b0636Smycroft 	 * If the file exists and we're interactive, verify with the user.
128e40b0636Smycroft 	 * If the file DNE, set the mode to be the from file, minus setuid
129e40b0636Smycroft 	 * bits, modified by the umask; arguably wrong, but it makes copying
130e40b0636Smycroft 	 * executables work right and it's been that way forever.  (The
131e40b0636Smycroft 	 * other choice is 666 or'ed with the execute bits on the from file
132e40b0636Smycroft 	 * modified by the umask.)
133e40b0636Smycroft 	 */
134e40b0636Smycroft 	if (!dne) {
135471c8a8eSjschauma 		struct stat sb;
136471c8a8eSjschauma 		int sval;
137471c8a8eSjschauma 
138e40b0636Smycroft 		if (iflag) {
139e40b0636Smycroft 			(void)fprintf(stderr, "overwrite %s? ", to.p_path);
140e40b0636Smycroft 			checkch = ch = getchar();
141e40b0636Smycroft 			while (ch != '\n' && ch != EOF)
142e40b0636Smycroft 				ch = getchar();
143051219a6Smycroft 			if (checkch != 'y' && checkch != 'Y') {
144e40b0636Smycroft 				(void)close(from_fd);
145e40b0636Smycroft 				return (0);
146e40b0636Smycroft 			}
147e40b0636Smycroft 		}
148471c8a8eSjschauma 
149471c8a8eSjschauma 		sval = tolnk ?
150471c8a8eSjschauma 			lstat(to.p_path, &sb) : stat(to.p_path, &sb);
151471c8a8eSjschauma 		if (sval == -1) {
152471c8a8eSjschauma 			warn("stat: %s", to.p_path);
15359dc922dSwiz 			(void)close(from_fd);
154471c8a8eSjschauma 			return (1);
155471c8a8eSjschauma 		}
156471c8a8eSjschauma 
157471c8a8eSjschauma 		if (!(tolnk && S_ISLNK(sb.st_mode)))
158e40b0636Smycroft 			to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
159e40b0636Smycroft 	} else
160e40b0636Smycroft 		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
161e40b0636Smycroft 		    fs->st_mode & ~(S_ISUID | S_ISGID));
162e40b0636Smycroft 
163471c8a8eSjschauma 	if (to_fd == -1 && (fflag || tolnk)) {
1648a99d7cfSwsanchez 		/*
1658a99d7cfSwsanchez 		 * attempt to remove existing destination file name and
1668a99d7cfSwsanchez 		 * create a new file
1678a99d7cfSwsanchez 		 */
1688a99d7cfSwsanchez 		(void)unlink(to.p_path);
1698a99d7cfSwsanchez 		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
1708a99d7cfSwsanchez 			     fs->st_mode & ~(S_ISUID | S_ISGID));
1718a99d7cfSwsanchez 	}
1728a99d7cfSwsanchez 
173e40b0636Smycroft 	if (to_fd == -1) {
174e40b0636Smycroft 		warn("%s", to.p_path);
175e40b0636Smycroft 		(void)close(from_fd);
176276fd166Ssimonb 		return (1);
177e40b0636Smycroft 	}
178e40b0636Smycroft 
179e40b0636Smycroft 	rval = 0;
180e40b0636Smycroft 
181e40b0636Smycroft 	/*
182*b6787da4Schristos 	 * We always copy regular files, even if they appear to be 0-sized
183*b6787da4Schristos 	 * because kernfs and procfs don't return file sizes.
1848a99d7cfSwsanchez 	 */
185*b6787da4Schristos 	bool need_copy = S_ISREG(fs->st_mode) || fs->st_size > 0;
1867790b321Schristos 
187eb8f42a5Schristos 	struct finfo fi;
188eb8f42a5Schristos 
189eb8f42a5Schristos 	fi.from = entp->fts_path;
190eb8f42a5Schristos 	fi.to = to.p_path;
1919bb33dceSmrg 	fi.size = fs->st_size;
192eb8f42a5Schristos 
193efab5715Spooka 	/*
194ec039eb9Schs 	 * Mmap and write if less than 8M (the limit is so
195ec039eb9Schs 	 * we don't totally trash memory on big files).
196ec039eb9Schs 	 * This is really a minor hack, but it wins some CPU back.
197e40b0636Smycroft 	 */
1987790b321Schristos 	if (S_ISREG(fs->st_mode) && fs->st_size && fs->st_size <= MMAP_MAX_SIZE) {
199509c72efSchristos 		size_t fsize = (size_t)fs->st_size;
200509c72efSchristos 		p = mmap(NULL, fsize, PROT_READ, MAP_FILE|MAP_SHARED,
201509c72efSchristos 		    from_fd, (off_t)0);
2021181f404Stron 		if (p != MAP_FAILED) {
2031181f404Stron 			size_t remainder;
2041181f404Stron 
2057790b321Schristos 			need_copy = false;
2061181f404Stron 
2077790b321Schristos 			(void) madvise(p, (size_t)fs->st_size, MADV_SEQUENTIAL);
2081181f404Stron 
2091181f404Stron 			/*
2101181f404Stron 			 * Write out the data in small chunks to
2111181f404Stron 			 * avoid locking the output file for a
2121181f404Stron 			 * long time if the reading the data from
2131181f404Stron 			 * the source is slow.
2141181f404Stron 			 */
2151181f404Stron 			remainder = fsize;
2161181f404Stron 			do {
2171181f404Stron 				ssize_t chunk;
2181181f404Stron 
2191181f404Stron 				chunk = (remainder > MMAP_MAX_WRITE) ?
2201181f404Stron 				    MMAP_MAX_WRITE : remainder;
2211181f404Stron 				if (write(to_fd, &p[fsize - remainder],
2221181f404Stron 				    chunk) != chunk) {
223e40b0636Smycroft 					warn("%s", to.p_path);
224e40b0636Smycroft 					rval = 1;
2251181f404Stron 					break;
226e40b0636Smycroft 				}
2271181f404Stron 				remainder -= chunk;
228eb8f42a5Schristos 				ptotal += chunk;
229eb8f42a5Schristos 				if (pinfo)
230eb8f42a5Schristos 					progress(&fi, ptotal);
2311181f404Stron 			} while (remainder > 0);
2321181f404Stron 
233509c72efSchristos 			if (munmap(p, fsize) < 0) {
234e40b0636Smycroft 				warn("%s", entp->fts_path);
235e40b0636Smycroft 				rval = 1;
236e40b0636Smycroft 			}
237e40b0636Smycroft 		}
2381181f404Stron 	}
2391181f404Stron 
2407790b321Schristos 	if (need_copy) {
241e40b0636Smycroft 		while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
242509c72efSchristos 			wcount = write(to_fd, buf, (size_t)rcount);
243e40b0636Smycroft 			if (rcount != wcount || wcount == -1) {
244e40b0636Smycroft 				warn("%s", to.p_path);
245e40b0636Smycroft 				rval = 1;
246e40b0636Smycroft 				break;
247e40b0636Smycroft 			}
248eb8f42a5Schristos 			ptotal += wcount;
249eb8f42a5Schristos 			if (pinfo)
250eb8f42a5Schristos 				progress(&fi, ptotal);
251e40b0636Smycroft 		}
252e40b0636Smycroft 		if (rcount < 0) {
253e40b0636Smycroft 			warn("%s", entp->fts_path);
254e40b0636Smycroft 			rval = 1;
255e40b0636Smycroft 		}
256e40b0636Smycroft 	}
257e40b0636Smycroft 
25832c00d62Smanu 	if (pflag && (fcpxattr(from_fd, to_fd) != 0))
25932c00d62Smanu 		warn("%s: error copying extended attributes", to.p_path);
26032c00d62Smanu 
261f6a91933Schristos #ifndef SMALL
2629aa2a9c3Schristos 	if (pflag && preserve_fd_acls(from_fd, to_fd) != 0)
2639aa2a9c3Schristos 		rval = 1;
264f6a91933Schristos #endif
2659aa2a9c3Schristos 
266e40b0636Smycroft 	(void)close(from_fd);
267d8781269Sdarcy 
268d8781269Sdarcy 	if (rval == 1) {
269e40b0636Smycroft 		(void)close(to_fd);
270e40b0636Smycroft 		return (1);
271e40b0636Smycroft 	}
272e40b0636Smycroft 
273e40b0636Smycroft 	if (pflag && setfile(fs, to_fd))
274e40b0636Smycroft 		rval = 1;
275e40b0636Smycroft 	/*
276e40b0636Smycroft 	 * If the source was setuid or setgid, lose the bits unless the
277e40b0636Smycroft 	 * copy is owned by the same user and group.
278e40b0636Smycroft 	 */
279e40b0636Smycroft #define	RETAINBITS \
280e40b0636Smycroft 	(S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
281eaef8d4aSjld 	if (!pflag && dne
282eaef8d4aSjld 	    && fs->st_mode & (S_ISUID | S_ISGID) && fs->st_uid == myuid) {
283e40b0636Smycroft 		if (fstat(to_fd, &to_stat)) {
284e40b0636Smycroft 			warn("%s", to.p_path);
285e40b0636Smycroft 			rval = 1;
286e40b0636Smycroft 		} else if (fs->st_gid == to_stat.st_gid &&
287e40b0636Smycroft 		    fchmod(to_fd, fs->st_mode & RETAINBITS & ~myumask)) {
288e40b0636Smycroft 			warn("%s", to.p_path);
289e40b0636Smycroft 			rval = 1;
290e40b0636Smycroft 		}
2918d68a9dcSthorpej 	}
292e40b0636Smycroft 	if (close(to_fd)) {
293e40b0636Smycroft 		warn("%s", to.p_path);
294e40b0636Smycroft 		rval = 1;
295e40b0636Smycroft 	}
2968a99d7cfSwsanchez 	/* set the mod/access times now after close of the fd */
2978a99d7cfSwsanchez 	if (pflag && set_utimes(to.p_path, fs)) {
2988a99d7cfSwsanchez 	    rval = 1;
2998a99d7cfSwsanchez 	}
300e40b0636Smycroft 	return (rval);
301e40b0636Smycroft }
302e40b0636Smycroft 
303e40b0636Smycroft int
copy_link(FTSENT * p,int exists)304633b06feSwiz copy_link(FTSENT *p, int exists)
305e40b0636Smycroft {
306e40b0636Smycroft 	int len;
307ce7d7575Smycroft 	char target[MAXPATHLEN];
308e40b0636Smycroft 
30932b88027Sprovos 	if ((len = readlink(p->fts_path, target, sizeof(target)-1)) == -1) {
310e40b0636Smycroft 		warn("readlink: %s", p->fts_path);
311e40b0636Smycroft 		return (1);
312e40b0636Smycroft 	}
313ce7d7575Smycroft 	target[len] = '\0';
314e40b0636Smycroft 	if (exists && unlink(to.p_path)) {
315e40b0636Smycroft 		warn("unlink: %s", to.p_path);
316e40b0636Smycroft 		return (1);
317e40b0636Smycroft 	}
318ce7d7575Smycroft 	if (symlink(target, to.p_path)) {
319ce7d7575Smycroft 		warn("symlink: %s", target);
320e40b0636Smycroft 		return (1);
321e40b0636Smycroft 	}
322fa580a82Senami 	return (pflag ? setfile(p->fts_statp, 0) : 0);
323e40b0636Smycroft }
324e40b0636Smycroft 
325e40b0636Smycroft int
copy_fifo(struct stat * from_stat,int exists)326633b06feSwiz copy_fifo(struct stat *from_stat, int exists)
327e40b0636Smycroft {
328e40b0636Smycroft 	if (exists && unlink(to.p_path)) {
329e40b0636Smycroft 		warn("unlink: %s", to.p_path);
330e40b0636Smycroft 		return (1);
331e40b0636Smycroft 	}
332e40b0636Smycroft 	if (mkfifo(to.p_path, from_stat->st_mode)) {
333e40b0636Smycroft 		warn("mkfifo: %s", to.p_path);
334e40b0636Smycroft 		return (1);
335e40b0636Smycroft 	}
336e40b0636Smycroft 	return (pflag ? setfile(from_stat, 0) : 0);
337e40b0636Smycroft }
338e40b0636Smycroft 
339e40b0636Smycroft int
copy_special(struct stat * from_stat,int exists)340633b06feSwiz copy_special(struct stat *from_stat, int exists)
341e40b0636Smycroft {
342e40b0636Smycroft 	if (exists && unlink(to.p_path)) {
343e40b0636Smycroft 		warn("unlink: %s", to.p_path);
344e40b0636Smycroft 		return (1);
345e40b0636Smycroft 	}
346e40b0636Smycroft 	if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
347e40b0636Smycroft 		warn("mknod: %s", to.p_path);
348e40b0636Smycroft 		return (1);
349e40b0636Smycroft 	}
350e40b0636Smycroft 	return (pflag ? setfile(from_stat, 0) : 0);
351e40b0636Smycroft }
352e40b0636Smycroft 
353e40b0636Smycroft 
3548a99d7cfSwsanchez /*
3558a99d7cfSwsanchez  * Function: setfile
3568a99d7cfSwsanchez  *
3578a99d7cfSwsanchez  * Purpose:
3588a99d7cfSwsanchez  *   Set the owner/group/permissions for the "to" file to the information
3598a99d7cfSwsanchez  *   in the stat structure.  If fd is zero, also call set_utimes() to set
3608a99d7cfSwsanchez  *   the mod/access times.  If fd is non-zero, the caller must do a utimes
3618a99d7cfSwsanchez  *   itself after close(fd).
3628a99d7cfSwsanchez  */
363e40b0636Smycroft int
setfile(struct stat * fs,int fd)364633b06feSwiz setfile(struct stat *fs, int fd)
365e40b0636Smycroft {
366fa580a82Senami 	int rval, islink;
367e40b0636Smycroft 
368e40b0636Smycroft 	rval = 0;
369fa580a82Senami 	islink = S_ISLNK(fs->st_mode);
370e40b0636Smycroft 	fs->st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
371e40b0636Smycroft 
372e40b0636Smycroft 	/*
373e40b0636Smycroft 	 * Changing the ownership probably won't succeed, unless we're root
374e40b0636Smycroft 	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
375e40b0636Smycroft 	 * the mode; current BSD behavior is to remove all setuid bits on
376e40b0636Smycroft 	 * chown.  If chown fails, lose setuid/setgid bits.
377e40b0636Smycroft 	 */
378e40b0636Smycroft 	if (fd ? fchown(fd, fs->st_uid, fs->st_gid) :
379fa580a82Senami 	    lchown(to.p_path, fs->st_uid, fs->st_gid)) {
380e40b0636Smycroft 		if (errno != EPERM) {
381e40b0636Smycroft 			warn("chown: %s", to.p_path);
382e40b0636Smycroft 			rval = 1;
383e40b0636Smycroft 		}
384e40b0636Smycroft 		fs->st_mode &= ~(S_ISUID | S_ISGID);
385e40b0636Smycroft 	}
386fa580a82Senami 	if (fd ? fchmod(fd, fs->st_mode) : lchmod(to.p_path, fs->st_mode)) {
387688ecf2aSmycroft 		warn("chmod: %s", to.p_path);
388e40b0636Smycroft 		rval = 1;
389e40b0636Smycroft 	}
390e40b0636Smycroft 
39176ed05e8Selad 	if (!islink && !Nflag) {
392509c72efSchristos 		unsigned long fflags = fs->st_flags;
3934f2bfe8bScgd 		/*
3944f2bfe8bScgd 		 * XXX
395fa580a82Senami 		 * NFS doesn't support chflags; ignore errors unless
396fa580a82Senami 		 * there's reason to believe we're losing bits.
397fa580a82Senami 		 * (Note, this still won't be right if the server
398fa580a82Senami 		 * supports flags and we were trying to *remove* flags
3994f2bfe8bScgd 		 * on a file that we copied, i.e., that we didn't create.)
4004f2bfe8bScgd 		 */
4014f2bfe8bScgd 		errno = 0;
402509c72efSchristos 		if ((fd ? fchflags(fd, fflags) :
403509c72efSchristos 		    chflags(to.p_path, fflags)) == -1)
4044f2bfe8bScgd 			if (errno != EOPNOTSUPP || fs->st_flags != 0) {
405e40b0636Smycroft 				warn("chflags: %s", to.p_path);
406e40b0636Smycroft 				rval = 1;
407e40b0636Smycroft 			}
408fa580a82Senami 	}
4098a99d7cfSwsanchez 	/* if fd is non-zero, caller must call set_utimes() after close() */
4108a99d7cfSwsanchez 	if (fd == 0 && set_utimes(to.p_path, fs))
4118a99d7cfSwsanchez 	    rval = 1;
412e40b0636Smycroft 	return (rval);
413e40b0636Smycroft }
414e40b0636Smycroft 
415f6a91933Schristos #ifndef SMALL
4169aa2a9c3Schristos int
preserve_fd_acls(int source_fd,int dest_fd)4179aa2a9c3Schristos preserve_fd_acls(int source_fd, int dest_fd)
4189aa2a9c3Schristos {
4199aa2a9c3Schristos 	acl_t acl;
4209aa2a9c3Schristos 	acl_type_t acl_type;
4219aa2a9c3Schristos 	int acl_supported = 0, ret, trivial;
4229aa2a9c3Schristos 
4239aa2a9c3Schristos 	ret = fpathconf(source_fd, _PC_ACL_NFS4);
4249aa2a9c3Schristos 	if (ret > 0 ) {
4259aa2a9c3Schristos 		acl_supported = 1;
4269aa2a9c3Schristos 		acl_type = ACL_TYPE_NFS4;
4279aa2a9c3Schristos 	} else if (ret < 0 && errno != EINVAL) {
4289aa2a9c3Schristos 		warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", to.p_path);
4299aa2a9c3Schristos 		return (1);
4309aa2a9c3Schristos 	}
4319aa2a9c3Schristos 	if (acl_supported == 0) {
4329aa2a9c3Schristos 		ret = fpathconf(source_fd, _PC_ACL_EXTENDED);
4339aa2a9c3Schristos 		if (ret > 0 ) {
4349aa2a9c3Schristos 			acl_supported = 1;
4359aa2a9c3Schristos 			acl_type = ACL_TYPE_ACCESS;
4369aa2a9c3Schristos 		} else if (ret < 0 && errno != EINVAL) {
4379aa2a9c3Schristos 			warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
4389aa2a9c3Schristos 			    to.p_path);
4399aa2a9c3Schristos 			return (1);
4409aa2a9c3Schristos 		}
4419aa2a9c3Schristos 	}
4429aa2a9c3Schristos 	if (acl_supported == 0)
4439aa2a9c3Schristos 		return (0);
4449aa2a9c3Schristos 
4459aa2a9c3Schristos 	acl = acl_get_fd_np(source_fd, acl_type);
4469aa2a9c3Schristos 	if (acl == NULL) {
4479aa2a9c3Schristos 		warn("failed to get acl entries while setting %s", to.p_path);
4489aa2a9c3Schristos 		return (1);
4499aa2a9c3Schristos 	}
4509aa2a9c3Schristos 	if (acl_is_trivial_np(acl, &trivial)) {
4519aa2a9c3Schristos 		warn("acl_is_trivial() failed for %s", to.p_path);
4529aa2a9c3Schristos 		acl_free(acl);
4539aa2a9c3Schristos 		return (1);
4549aa2a9c3Schristos 	}
4559aa2a9c3Schristos 	if (trivial) {
4569aa2a9c3Schristos 		acl_free(acl);
4579aa2a9c3Schristos 		return (0);
4589aa2a9c3Schristos 	}
4599aa2a9c3Schristos 	if (acl_set_fd_np(dest_fd, acl, acl_type) < 0) {
4609aa2a9c3Schristos 		warn("failed to set acl entries for %s", to.p_path);
4619aa2a9c3Schristos 		acl_free(acl);
4629aa2a9c3Schristos 		return (1);
4639aa2a9c3Schristos 	}
4649aa2a9c3Schristos 	acl_free(acl);
4659aa2a9c3Schristos 	return (0);
4669aa2a9c3Schristos }
4679aa2a9c3Schristos 
4689aa2a9c3Schristos int
preserve_dir_acls(struct stat * fs,char * source_dir,char * dest_dir)4699aa2a9c3Schristos preserve_dir_acls(struct stat *fs, char *source_dir, char *dest_dir)
4709aa2a9c3Schristos {
4719aa2a9c3Schristos 	acl_t (*aclgetf)(const char *, acl_type_t);
4729aa2a9c3Schristos 	int (*aclsetf)(const char *, acl_type_t, acl_t);
4739aa2a9c3Schristos 	struct acl *aclp;
4749aa2a9c3Schristos 	acl_t acl;
4759aa2a9c3Schristos 	acl_type_t acl_type;
4769aa2a9c3Schristos 	int acl_supported = 0, ret, trivial;
4779aa2a9c3Schristos 
4789aa2a9c3Schristos 	ret = pathconf(source_dir, _PC_ACL_NFS4);
4799aa2a9c3Schristos 	if (ret > 0) {
4809aa2a9c3Schristos 		acl_supported = 1;
4819aa2a9c3Schristos 		acl_type = ACL_TYPE_NFS4;
4829aa2a9c3Schristos 	} else if (ret < 0 && errno != EINVAL) {
4839aa2a9c3Schristos 		warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", source_dir);
4849aa2a9c3Schristos 		return (1);
4859aa2a9c3Schristos 	}
4869aa2a9c3Schristos 	if (acl_supported == 0) {
4879aa2a9c3Schristos 		ret = pathconf(source_dir, _PC_ACL_EXTENDED);
4889aa2a9c3Schristos 		if (ret > 0) {
4899aa2a9c3Schristos 			acl_supported = 1;
4909aa2a9c3Schristos 			acl_type = ACL_TYPE_ACCESS;
4919aa2a9c3Schristos 		} else if (ret < 0 && errno != EINVAL) {
4929aa2a9c3Schristos 			warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
4939aa2a9c3Schristos 			    source_dir);
4949aa2a9c3Schristos 			return (1);
4959aa2a9c3Schristos 		}
4969aa2a9c3Schristos 	}
4979aa2a9c3Schristos 	if (acl_supported == 0)
4989aa2a9c3Schristos 		return (0);
4999aa2a9c3Schristos 
5009aa2a9c3Schristos 	/*
5019aa2a9c3Schristos 	 * If the file is a link we will not follow it.
5029aa2a9c3Schristos 	 */
5039aa2a9c3Schristos 	if (S_ISLNK(fs->st_mode)) {
5049aa2a9c3Schristos 		aclgetf = acl_get_link_np;
5059aa2a9c3Schristos 		aclsetf = acl_set_link_np;
5069aa2a9c3Schristos 	} else {
5079aa2a9c3Schristos 		aclgetf = acl_get_file;
5089aa2a9c3Schristos 		aclsetf = acl_set_file;
5099aa2a9c3Schristos 	}
5109aa2a9c3Schristos 	if (acl_type == ACL_TYPE_ACCESS) {
5119aa2a9c3Schristos 		/*
5129aa2a9c3Schristos 		 * Even if there is no ACL_TYPE_DEFAULT entry here, a zero
5139aa2a9c3Schristos 		 * size ACL will be returned. So it is not safe to simply
5149aa2a9c3Schristos 		 * check the pointer to see if the default ACL is present.
5159aa2a9c3Schristos 		 */
5169aa2a9c3Schristos 		acl = aclgetf(source_dir, ACL_TYPE_DEFAULT);
5179aa2a9c3Schristos 		if (acl == NULL) {
5189aa2a9c3Schristos 			warn("failed to get default acl entries on %s",
5199aa2a9c3Schristos 			    source_dir);
5209aa2a9c3Schristos 			return (1);
5219aa2a9c3Schristos 		}
5229aa2a9c3Schristos 		aclp = &acl->ats_acl;
5239aa2a9c3Schristos 		if (aclp->acl_cnt != 0 && aclsetf(dest_dir,
5249aa2a9c3Schristos 		    ACL_TYPE_DEFAULT, acl) < 0) {
5259aa2a9c3Schristos 			warn("failed to set default acl entries on %s",
5269aa2a9c3Schristos 			    dest_dir);
5279aa2a9c3Schristos 			acl_free(acl);
5289aa2a9c3Schristos 			return (1);
5299aa2a9c3Schristos 		}
5309aa2a9c3Schristos 		acl_free(acl);
5319aa2a9c3Schristos 	}
5329aa2a9c3Schristos 	acl = aclgetf(source_dir, acl_type);
5339aa2a9c3Schristos 	if (acl == NULL) {
5349aa2a9c3Schristos 		warn("failed to get acl entries on %s", source_dir);
5359aa2a9c3Schristos 		return (1);
5369aa2a9c3Schristos 	}
5379aa2a9c3Schristos 	if (acl_is_trivial_np(acl, &trivial)) {
5389aa2a9c3Schristos 		warn("acl_is_trivial() failed on %s", source_dir);
5399aa2a9c3Schristos 		acl_free(acl);
5409aa2a9c3Schristos 		return (1);
5419aa2a9c3Schristos 	}
5429aa2a9c3Schristos 	if (trivial) {
5439aa2a9c3Schristos 		acl_free(acl);
5449aa2a9c3Schristos 		return (0);
5459aa2a9c3Schristos 	}
5469aa2a9c3Schristos 	if (aclsetf(dest_dir, acl_type, acl) < 0) {
5479aa2a9c3Schristos 		warn("failed to set acl entries on %s", dest_dir);
5489aa2a9c3Schristos 		acl_free(acl);
5499aa2a9c3Schristos 		return (1);
5509aa2a9c3Schristos 	}
5519aa2a9c3Schristos 	acl_free(acl);
5529aa2a9c3Schristos 	return (0);
5539aa2a9c3Schristos }
554f6a91933Schristos #endif
5559aa2a9c3Schristos 
556e40b0636Smycroft void
usage(void)557633b06feSwiz usage(void)
558e40b0636Smycroft {
559633b06feSwiz 	(void)fprintf(stderr,
560d8781269Sdarcy 	    "usage: %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src target\n"
561d8781269Sdarcy 	    "       %s [-R [-H | -L | -P]] [-f | -i] [-alNpv] src1 ... srcN directory\n",
562633b06feSwiz 	    getprogname(), getprogname());
563e40b0636Smycroft 	exit(1);
5649dc385beSmycroft 	/* NOTREACHED */
565e40b0636Smycroft }
566