11556Srgrimes/*-
21556Srgrimes * Copyright (c) 1991, 1993, 1994
31556Srgrimes *	The Regents of the University of California.  All rights reserved.
41556Srgrimes *
51556Srgrimes * Redistribution and use in source and binary forms, with or without
61556Srgrimes * modification, are permitted provided that the following conditions
71556Srgrimes * are met:
81556Srgrimes * 1. Redistributions of source code must retain the above copyright
91556Srgrimes *    notice, this list of conditions and the following disclaimer.
101556Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111556Srgrimes *    notice, this list of conditions and the following disclaimer in the
121556Srgrimes *    documentation and/or other materials provided with the distribution.
131556Srgrimes * 4. Neither the name of the University nor the names of its contributors
141556Srgrimes *    may be used to endorse or promote products derived from this software
151556Srgrimes *    without specific prior written permission.
161556Srgrimes *
171556Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181556Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191556Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201556Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211556Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221556Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231556Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241556Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251556Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261556Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271556Srgrimes * SUCH DAMAGE.
281556Srgrimes */
291556Srgrimes
301556Srgrimes#ifndef lint
3135773Scharnier#if 0
3236003Scharnierstatic char sccsid[] = "@(#)utils.c	8.3 (Berkeley) 4/1/94";
3335773Scharnier#endif
341556Srgrimes#endif /* not lint */
3599109Sobrien#include <sys/cdefs.h>
3699109Sobrien__FBSDID("$FreeBSD$");
371556Srgrimes
38149790Scsjp#include <sys/types.h>
39149790Scsjp#include <sys/acl.h>
401556Srgrimes#include <sys/param.h>
411556Srgrimes#include <sys/stat.h>
4235773Scharnier#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
431556Srgrimes#include <sys/mman.h>
4435773Scharnier#endif
451556Srgrimes
461556Srgrimes#include <err.h>
471556Srgrimes#include <errno.h>
481556Srgrimes#include <fcntl.h>
491556Srgrimes#include <fts.h>
5076693Simp#include <limits.h>
511556Srgrimes#include <stdio.h>
5278469Sdes#include <stdlib.h>
5356420Smharo#include <sysexits.h>
541556Srgrimes#include <unistd.h>
551556Srgrimes
561556Srgrimes#include "extern.h"
571556Srgrimes
58163074Smaxim#define	cp_pct(x, y)	((y == 0) ? 0 : (int)(100.0 * (x) / (y)))
59163074Smaxim
60284163Sbdrewery/*
61284163Sbdrewery * Memory strategy threshold, in pages: if physmem is larger then this, use a
62284163Sbdrewery * large buffer.
63284163Sbdrewery */
64184471Sivoras#define PHYSPAGES_THRESHOLD (32*1024)
65184471Sivoras
66284163Sbdrewery/* Maximum buffer size in bytes - do not allow it to grow larger than this. */
67184471Sivoras#define BUFSIZE_MAX (2*1024*1024)
68184471Sivoras
69284163Sbdrewery/*
70284163Sbdrewery * Small (default) buffer size in bytes. It's inefficient for this to be
71284163Sbdrewery * smaller than MAXPHYS.
72284163Sbdrewery */
73184471Sivoras#define BUFSIZE_SMALL (MAXPHYS)
74184471Sivoras
751556Srgrimesint
76105395Smarkmcopy_file(const FTSENT *entp, int dne)
771556Srgrimes{
78184471Sivoras	static char *buf = NULL;
79184471Sivoras	static size_t bufsize;
8078070Sbde	struct stat *fs;
8199363Smarkm	ssize_t wcount;
8299363Smarkm	size_t wresid;
83163049Smaxim	off_t wtotal;
84284106Sbdrewery	int ch, checkch, from_fd, rcount, rval, to_fd;
8532540Sbde	char *bufp;
861556Srgrimes#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
871556Srgrimes	char *p;
881556Srgrimes#endif
898855Srgrimes
90284106Sbdrewery	from_fd = to_fd = -1;
91284106Sbdrewery	if (!lflag && !sflag &&
92284106Sbdrewery	    (from_fd = open(entp->fts_path, O_RDONLY, 0)) == -1) {
931556Srgrimes		warn("%s", entp->fts_path);
941556Srgrimes		return (1);
951556Srgrimes	}
961556Srgrimes
971556Srgrimes	fs = entp->fts_statp;
981556Srgrimes
991556Srgrimes	/*
1001556Srgrimes	 * If the file exists and we're interactive, verify with the user.
1011556Srgrimes	 * If the file DNE, set the mode to be the from file, minus setuid
1021556Srgrimes	 * bits, modified by the umask; arguably wrong, but it makes copying
1031556Srgrimes	 * executables work right and it's been that way forever.  (The
1041556Srgrimes	 * other choice is 666 or'ed with the execute bits on the from file
1051556Srgrimes	 * modified by the umask.)
1061556Srgrimes	 */
1071556Srgrimes	if (!dne) {
10830088Swosch#define YESNO "(y/n [n]) "
109100538Sjohan		if (nflag) {
110100538Sjohan			if (vflag)
111100538Sjohan				printf("%s not overwritten\n", to.p_path);
112284106Sbdrewery			rval = 1;
113284106Sbdrewery			goto done;
114100538Sjohan		} else if (iflag) {
11530088Swosch			(void)fprintf(stderr, "overwrite %s? %s",
116284163Sbdrewery			    to.p_path, YESNO);
1171556Srgrimes			checkch = ch = getchar();
1181556Srgrimes			while (ch != '\n' && ch != EOF)
1191556Srgrimes				ch = getchar();
12014416Swosch			if (checkch != 'y' && checkch != 'Y') {
12130088Swosch				(void)fprintf(stderr, "not overwritten\n");
122284106Sbdrewery				rval = 1;
123284106Sbdrewery				goto done;
1241556Srgrimes			}
1251556Srgrimes		}
126284163Sbdrewery
12714416Swosch		if (fflag) {
128284163Sbdrewery			/*
129284163Sbdrewery			 * Remove existing destination file name create a new
130284163Sbdrewery			 * file.
131284163Sbdrewery			 */
132284163Sbdrewery			(void)unlink(to.p_path);
133284163Sbdrewery			if (!lflag && !sflag) {
134284163Sbdrewery				to_fd = open(to.p_path,
135284163Sbdrewery				    O_WRONLY | O_TRUNC | O_CREAT,
136284163Sbdrewery				    fs->st_mode & ~(S_ISUID | S_ISGID));
137284163Sbdrewery			}
138284106Sbdrewery		} else if (!lflag && !sflag) {
139284163Sbdrewery			/* Overwrite existing destination file name. */
140284105Sbdrewery			to_fd = open(to.p_path, O_WRONLY | O_TRUNC, 0);
141161586Sjulian		}
142284106Sbdrewery	} else if (!lflag && !sflag) {
143284105Sbdrewery		to_fd = open(to.p_path, O_WRONLY | O_TRUNC | O_CREAT,
144284105Sbdrewery		    fs->st_mode & ~(S_ISUID | S_ISGID));
145161586Sjulian	}
146284163Sbdrewery
147284106Sbdrewery	if (!lflag && !sflag && to_fd == -1) {
1481556Srgrimes		warn("%s", to.p_path);
149284106Sbdrewery		rval = 1;
150284106Sbdrewery		goto done;
1511556Srgrimes	}
1521556Srgrimes
1531556Srgrimes	rval = 0;
1541556Srgrimes
155284106Sbdrewery	if (!lflag && !sflag) {
156161586Sjulian		/*
157284163Sbdrewery		 * Mmap and write if less than 8M (the limit is so we don't
158284163Sbdrewery		 * totally trash memory on big files.  This is really a minor
159284163Sbdrewery		 * hack, but it wins some CPU back.
160184342Sdds		 * Some filesystems, such as smbnetfs, don't support mmap,
161184342Sdds		 * so this is a best-effort attempt.
162161586Sjulian		 */
1631556Srgrimes#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
164161586Sjulian		if (S_ISREG(fs->st_mode) && fs->st_size > 0 &&
165284163Sbdrewery		    fs->st_size <= 8 * 1024 * 1024 &&
166184342Sdds		    (p = mmap(NULL, (size_t)fs->st_size, PROT_READ,
167184342Sdds		    MAP_SHARED, from_fd, (off_t)0)) != MAP_FAILED) {
168184342Sdds			wtotal = 0;
169184342Sdds			for (bufp = p, wresid = fs->st_size; ;
170284163Sbdrewery			    bufp += wcount, wresid -= (size_t)wcount) {
171184342Sdds				wcount = write(to_fd, bufp, wresid);
172184342Sdds				if (wcount <= 0)
173184342Sdds					break;
174184342Sdds				wtotal += wcount;
175184342Sdds				if (info) {
176184342Sdds					info = 0;
177184342Sdds					(void)fprintf(stderr,
178184342Sdds					    "%s -> %s %3d%%\n",
179184342Sdds					    entp->fts_path, to.p_path,
180184342Sdds					    cp_pct(wtotal, fs->st_size));
181184342Sdds				}
182184342Sdds				if (wcount >= (ssize_t)wresid)
183184342Sdds					break;
184184342Sdds			}
185184342Sdds			if (wcount != (ssize_t)wresid) {
186184342Sdds				warn("%s", to.p_path);
187184342Sdds				rval = 1;
188184342Sdds			}
189184342Sdds			/* Some systems don't unmap on close(2). */
190184342Sdds			if (munmap(p, fs->st_size) < 0) {
1911556Srgrimes				warn("%s", entp->fts_path);
1921556Srgrimes				rval = 1;
1931556Srgrimes			}
194161586Sjulian		} else
1951556Srgrimes#endif
196161586Sjulian		{
197184471Sivoras			if (buf == NULL) {
198184471Sivoras				/*
199184471Sivoras				 * Note that buf and bufsize are static. If
200184471Sivoras				 * malloc() fails, it will fail at the start
201184471Sivoras				 * and not copy only some files.
202184471Sivoras				 */
203184471Sivoras				if (sysconf(_SC_PHYS_PAGES) >
204184471Sivoras				    PHYSPAGES_THRESHOLD)
205184471Sivoras					bufsize = MIN(BUFSIZE_MAX, MAXPHYS * 8);
206184471Sivoras				else
207184471Sivoras					bufsize = BUFSIZE_SMALL;
208184471Sivoras				buf = malloc(bufsize);
209184471Sivoras				if (buf == NULL)
210184471Sivoras					err(1, "Not enough memory");
211184471Sivoras			}
212161586Sjulian			wtotal = 0;
213184471Sivoras			while ((rcount = read(from_fd, buf, bufsize)) > 0) {
214161586Sjulian				for (bufp = buf, wresid = rcount; ;
215284163Sbdrewery				    bufp += wcount, wresid -= wcount) {
216161586Sjulian					wcount = write(to_fd, bufp, wresid);
217163074Smaxim					if (wcount <= 0)
218163074Smaxim						break;
219161586Sjulian					wtotal += wcount;
220161586Sjulian					if (info) {
221161586Sjulian						info = 0;
222161586Sjulian						(void)fprintf(stderr,
223163075Smaxim						    "%s -> %s %3d%%\n",
224163075Smaxim						    entp->fts_path, to.p_path,
225163075Smaxim						    cp_pct(wtotal, fs->st_size));
226161586Sjulian					}
227163074Smaxim					if (wcount >= (ssize_t)wresid)
228161586Sjulian						break;
229113209Smdodd				}
230161586Sjulian				if (wcount != (ssize_t)wresid) {
231161586Sjulian					warn("%s", to.p_path);
232161586Sjulian					rval = 1;
23332540Sbde					break;
234161586Sjulian				}
23532540Sbde			}
236161586Sjulian			if (rcount < 0) {
237161586Sjulian				warn("%s", entp->fts_path);
2381556Srgrimes				rval = 1;
2391556Srgrimes			}
2401556Srgrimes		}
241284106Sbdrewery	} else if (lflag) {
242161586Sjulian		if (link(entp->fts_path, to.p_path)) {
243161586Sjulian			warn("%s", to.p_path);
2441556Srgrimes			rval = 1;
2451556Srgrimes		}
246284106Sbdrewery	} else if (sflag) {
247284106Sbdrewery		if (symlink(entp->fts_path, to.p_path)) {
248284106Sbdrewery			warn("%s", to.p_path);
249284106Sbdrewery			rval = 1;
250284106Sbdrewery		}
2511556Srgrimes	}
252284163Sbdrewery
2539220Sbde	/*
2549220Sbde	 * Don't remove the target even after an error.  The target might
2559220Sbde	 * not be a regular file, or its attributes might be important,
25646684Skris	 * or its contents might be irreplaceable.  It would only be safe
2579220Sbde	 * to remove it if we created it and its length is 0.
2589220Sbde	 */
2591556Srgrimes
260284106Sbdrewery	if (!lflag && !sflag) {
261161586Sjulian		if (pflag && setfile(fs, to_fd))
262161586Sjulian			rval = 1;
263161586Sjulian		if (pflag && preserve_fd_acls(from_fd, to_fd) != 0)
264161586Sjulian			rval = 1;
265161586Sjulian		if (close(to_fd)) {
266161586Sjulian			warn("%s", to.p_path);
267161586Sjulian			rval = 1;
268161586Sjulian		}
2691556Srgrimes	}
270181361Strasz
271284106Sbdrewerydone:
272284106Sbdrewery	if (from_fd != -1)
273284106Sbdrewery		(void)close(from_fd);
2741556Srgrimes	return (rval);
2751556Srgrimes}
2761556Srgrimes
2771556Srgrimesint
278105395Smarkmcopy_link(const FTSENT *p, int exists)
2791556Srgrimes{
2801556Srgrimes	int len;
28191087Smarkm	char llink[PATH_MAX];
2821556Srgrimes
283245960Smarkj	if (exists && nflag) {
284245960Smarkj		if (vflag)
285245960Smarkj			printf("%s not overwritten\n", to.p_path);
286245960Smarkj		return (1);
287245960Smarkj	}
28891087Smarkm	if ((len = readlink(p->fts_path, llink, sizeof(llink) - 1)) == -1) {
2891556Srgrimes		warn("readlink: %s", p->fts_path);
2901556Srgrimes		return (1);
2911556Srgrimes	}
29291087Smarkm	llink[len] = '\0';
2931556Srgrimes	if (exists && unlink(to.p_path)) {
2941556Srgrimes		warn("unlink: %s", to.p_path);
2951556Srgrimes		return (1);
2961556Srgrimes	}
29791087Smarkm	if (symlink(llink, to.p_path)) {
29891087Smarkm		warn("symlink: %s", llink);
2991556Srgrimes		return (1);
3001556Srgrimes	}
301116673Sjmg	return (pflag ? setfile(p->fts_statp, -1) : 0);
3021556Srgrimes}
3031556Srgrimes
3041556Srgrimesint
30590107Simpcopy_fifo(struct stat *from_stat, int exists)
3061556Srgrimes{
307245960Smarkj
308245960Smarkj	if (exists && nflag) {
309245960Smarkj		if (vflag)
310245960Smarkj			printf("%s not overwritten\n", to.p_path);
311245960Smarkj		return (1);
312245960Smarkj	}
3131556Srgrimes	if (exists && unlink(to.p_path)) {
3141556Srgrimes		warn("unlink: %s", to.p_path);
3151556Srgrimes		return (1);
3161556Srgrimes	}
3171556Srgrimes	if (mkfifo(to.p_path, from_stat->st_mode)) {
3181556Srgrimes		warn("mkfifo: %s", to.p_path);
3191556Srgrimes		return (1);
3201556Srgrimes	}
321116673Sjmg	return (pflag ? setfile(from_stat, -1) : 0);
3221556Srgrimes}
3231556Srgrimes
3241556Srgrimesint
32590107Simpcopy_special(struct stat *from_stat, int exists)
3261556Srgrimes{
327245960Smarkj
328245960Smarkj	if (exists && nflag) {
329245960Smarkj		if (vflag)
330245960Smarkj			printf("%s not overwritten\n", to.p_path);
331245960Smarkj		return (1);
332245960Smarkj	}
3331556Srgrimes	if (exists && unlink(to.p_path)) {
3341556Srgrimes		warn("unlink: %s", to.p_path);
3351556Srgrimes		return (1);
3361556Srgrimes	}
3371556Srgrimes	if (mknod(to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
3381556Srgrimes		warn("mknod: %s", to.p_path);
3391556Srgrimes		return (1);
3401556Srgrimes	}
341116673Sjmg	return (pflag ? setfile(from_stat, -1) : 0);
3421556Srgrimes}
3431556Srgrimes
3441556Srgrimesint
34590107Simpsetfile(struct stat *fs, int fd)
3461556Srgrimes{
347277645Sjilles	static struct timespec tspec[2];
34836838Speter	struct stat ts;
349116673Sjmg	int rval, gotstat, islink, fdval;
3501556Srgrimes
3511556Srgrimes	rval = 0;
352116673Sjmg	fdval = fd != -1;
353116673Sjmg	islink = !fdval && S_ISLNK(fs->st_mode);
35411146Sbde	fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX |
355284163Sbdrewery	    S_IRWXU | S_IRWXG | S_IRWXO;
3561556Srgrimes
357277645Sjilles	tspec[0] = fs->st_atim;
358277645Sjilles	tspec[1] = fs->st_mtim;
359280386Sjilles	if (fdval ? futimens(fd, tspec) : utimensat(AT_FDCWD, to.p_path, tspec,
360277645Sjilles	    islink ? AT_SYMLINK_NOFOLLOW : 0)) {
361277645Sjilles		warn("utimensat: %s", to.p_path);
3621556Srgrimes		rval = 1;
3631556Srgrimes	}
364116673Sjmg	if (fdval ? fstat(fd, &ts) :
365116673Sjmg	    (islink ? lstat(to.p_path, &ts) : stat(to.p_path, &ts)))
36636838Speter		gotstat = 0;
36736838Speter	else {
36836838Speter		gotstat = 1;
36936838Speter		ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX |
370284163Sbdrewery		    S_IRWXU | S_IRWXG | S_IRWXO;
37136838Speter	}
3721556Srgrimes	/*
3731556Srgrimes	 * Changing the ownership probably won't succeed, unless we're root
3741556Srgrimes	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
3751556Srgrimes	 * the mode; current BSD behavior is to remove all setuid bits on
3761556Srgrimes	 * chown.  If chown fails, lose setuid/setgid bits.
3771556Srgrimes	 */
37836838Speter	if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid)
379116673Sjmg		if (fdval ? fchown(fd, fs->st_uid, fs->st_gid) :
380116673Sjmg		    (islink ? lchown(to.p_path, fs->st_uid, fs->st_gid) :
381116673Sjmg		    chown(to.p_path, fs->st_uid, fs->st_gid))) {
38236838Speter			if (errno != EPERM) {
38336838Speter				warn("chown: %s", to.p_path);
38436838Speter				rval = 1;
38536838Speter			}
38636838Speter			fs->st_mode &= ~(S_ISUID | S_ISGID);
38736838Speter		}
38836838Speter
38936838Speter	if (!gotstat || fs->st_mode != ts.st_mode)
390116673Sjmg		if (fdval ? fchmod(fd, fs->st_mode) :
391116673Sjmg		    (islink ? lchmod(to.p_path, fs->st_mode) :
392116673Sjmg		    chmod(to.p_path, fs->st_mode))) {
39387652Smckay			warn("chmod: %s", to.p_path);
3941556Srgrimes			rval = 1;
3951556Srgrimes		}
3961556Srgrimes
39736838Speter	if (!gotstat || fs->st_flags != ts.st_flags)
398116673Sjmg		if (fdval ?
399116673Sjmg		    fchflags(fd, fs->st_flags) :
400193086Sjilles		    (islink ? lchflags(to.p_path, fs->st_flags) :
401116673Sjmg		    chflags(to.p_path, fs->st_flags))) {
40236838Speter			warn("chflags: %s", to.p_path);
40336838Speter			rval = 1;
40436838Speter		}
40536838Speter
4061556Srgrimes	return (rval);
4071556Srgrimes}
4081556Srgrimes
409149790Scsjpint
410149790Scsjppreserve_fd_acls(int source_fd, int dest_fd)
411149790Scsjp{
412149790Scsjp	acl_t acl;
413196754Strasz	acl_type_t acl_type;
414196754Strasz	int acl_supported = 0, ret, trivial;
415149790Scsjp
416196754Strasz	ret = fpathconf(source_fd, _PC_ACL_NFS4);
417196754Strasz	if (ret > 0 ) {
418196754Strasz		acl_supported = 1;
419196754Strasz		acl_type = ACL_TYPE_NFS4;
420196754Strasz	} else if (ret < 0 && errno != EINVAL) {
421196754Strasz		warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", to.p_path);
422196754Strasz		return (1);
423196754Strasz	}
424196754Strasz	if (acl_supported == 0) {
425196754Strasz		ret = fpathconf(source_fd, _PC_ACL_EXTENDED);
426196754Strasz		if (ret > 0 ) {
427196754Strasz			acl_supported = 1;
428196754Strasz			acl_type = ACL_TYPE_ACCESS;
429196754Strasz		} else if (ret < 0 && errno != EINVAL) {
430196754Strasz			warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
431196754Strasz			    to.p_path);
432196754Strasz			return (1);
433196754Strasz		}
434196754Strasz	}
435196754Strasz	if (acl_supported == 0)
436149790Scsjp		return (0);
437196754Strasz
438196754Strasz	acl = acl_get_fd_np(source_fd, acl_type);
439149790Scsjp	if (acl == NULL) {
440149790Scsjp		warn("failed to get acl entries while setting %s", to.p_path);
441149790Scsjp		return (1);
442149790Scsjp	}
443196754Strasz	if (acl_is_trivial_np(acl, &trivial)) {
444196754Strasz		warn("acl_is_trivial() failed for %s", to.p_path);
445196754Strasz		acl_free(acl);
446196754Strasz		return (1);
447196754Strasz	}
448196754Strasz	if (trivial) {
449196754Strasz		acl_free(acl);
450149790Scsjp		return (0);
451196754Strasz	}
452196754Strasz	if (acl_set_fd_np(dest_fd, acl, acl_type) < 0) {
453149790Scsjp		warn("failed to set acl entries for %s", to.p_path);
454196754Strasz		acl_free(acl);
455149790Scsjp		return (1);
456149790Scsjp	}
457196754Strasz	acl_free(acl);
458149790Scsjp	return (0);
459149790Scsjp}
460149790Scsjp
461149790Scsjpint
462149790Scsjppreserve_dir_acls(struct stat *fs, char *source_dir, char *dest_dir)
463149790Scsjp{
464149790Scsjp	acl_t (*aclgetf)(const char *, acl_type_t);
465149790Scsjp	int (*aclsetf)(const char *, acl_type_t, acl_t);
466149790Scsjp	struct acl *aclp;
467149790Scsjp	acl_t acl;
468196754Strasz	acl_type_t acl_type;
469196754Strasz	int acl_supported = 0, ret, trivial;
470149790Scsjp
471196754Strasz	ret = pathconf(source_dir, _PC_ACL_NFS4);
472196754Strasz	if (ret > 0) {
473196754Strasz		acl_supported = 1;
474196754Strasz		acl_type = ACL_TYPE_NFS4;
475196754Strasz	} else if (ret < 0 && errno != EINVAL) {
476196754Strasz		warn("fpathconf(..., _PC_ACL_NFS4) failed for %s", source_dir);
477196754Strasz		return (1);
478196754Strasz	}
479196754Strasz	if (acl_supported == 0) {
480196754Strasz		ret = pathconf(source_dir, _PC_ACL_EXTENDED);
481196754Strasz		if (ret > 0) {
482196754Strasz			acl_supported = 1;
483196754Strasz			acl_type = ACL_TYPE_ACCESS;
484196754Strasz		} else if (ret < 0 && errno != EINVAL) {
485196754Strasz			warn("fpathconf(..., _PC_ACL_EXTENDED) failed for %s",
486196754Strasz			    source_dir);
487196754Strasz			return (1);
488196754Strasz		}
489196754Strasz	}
490196754Strasz	if (acl_supported == 0)
491149790Scsjp		return (0);
492196754Strasz
493149790Scsjp	/*
494284163Sbdrewery	 * If the file is a link we will not follow it.
495149790Scsjp	 */
496149790Scsjp	if (S_ISLNK(fs->st_mode)) {
497149790Scsjp		aclgetf = acl_get_link_np;
498149790Scsjp		aclsetf = acl_set_link_np;
499149790Scsjp	} else {
500149790Scsjp		aclgetf = acl_get_file;
501149790Scsjp		aclsetf = acl_set_file;
502149790Scsjp	}
503196754Strasz	if (acl_type == ACL_TYPE_ACCESS) {
504196754Strasz		/*
505196754Strasz		 * Even if there is no ACL_TYPE_DEFAULT entry here, a zero
506196754Strasz		 * size ACL will be returned. So it is not safe to simply
507196754Strasz		 * check the pointer to see if the default ACL is present.
508196754Strasz		 */
509196754Strasz		acl = aclgetf(source_dir, ACL_TYPE_DEFAULT);
510196754Strasz		if (acl == NULL) {
511196754Strasz			warn("failed to get default acl entries on %s",
512196754Strasz			    source_dir);
513196754Strasz			return (1);
514196754Strasz		}
515196754Strasz		aclp = &acl->ats_acl;
516196754Strasz		if (aclp->acl_cnt != 0 && aclsetf(dest_dir,
517196754Strasz		    ACL_TYPE_DEFAULT, acl) < 0) {
518196754Strasz			warn("failed to set default acl entries on %s",
519196754Strasz			    dest_dir);
520196754Strasz			acl_free(acl);
521196754Strasz			return (1);
522196754Strasz		}
523196754Strasz		acl_free(acl);
524196754Strasz	}
525196754Strasz	acl = aclgetf(source_dir, acl_type);
526149790Scsjp	if (acl == NULL) {
527196754Strasz		warn("failed to get acl entries on %s", source_dir);
528149790Scsjp		return (1);
529149790Scsjp	}
530196754Strasz	if (acl_is_trivial_np(acl, &trivial)) {
531196754Strasz		warn("acl_is_trivial() failed on %s", source_dir);
532196754Strasz		acl_free(acl);
533149790Scsjp		return (1);
534149790Scsjp	}
535196754Strasz	if (trivial) {
536196754Strasz		acl_free(acl);
537196754Strasz		return (0);
538149790Scsjp	}
539196754Strasz	if (aclsetf(dest_dir, acl_type, acl) < 0) {
540149790Scsjp		warn("failed to set acl entries on %s", dest_dir);
541196754Strasz		acl_free(acl);
542149790Scsjp		return (1);
543149790Scsjp	}
544196754Strasz	acl_free(acl);
545149790Scsjp	return (0);
546149790Scsjp}
547149790Scsjp
5481556Srgrimesvoid
54990107Simpusage(void)
5501556Srgrimes{
55150543Smharo
5521556Srgrimes	(void)fprintf(stderr, "%s\n%s\n",
553284163Sbdrewery	    "usage: cp [-R [-H | -L | -P]] [-f | -i | -n] [-alpsvx] "
554284163Sbdrewery	    "source_file target_file",
555284163Sbdrewery	    "       cp [-R [-H | -L | -P]] [-f | -i | -n] [-alpsvx] "
556284163Sbdrewery	    "source_file ... "
557284163Sbdrewery	    "target_directory");
55856420Smharo	exit(EX_USAGE);
5591556Srgrimes}
560