diff options
Diffstat (limited to 'usr.sbin/pw/cpdir.c')
| -rw-r--r-- | usr.sbin/pw/cpdir.c | 154 | 
1 files changed, 154 insertions, 0 deletions
diff --git a/usr.sbin/pw/cpdir.c b/usr.sbin/pw/cpdir.c new file mode 100644 index 000000000000..979323d64342 --- /dev/null +++ b/usr.sbin/pw/cpdir.c @@ -0,0 +1,154 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (C) 1996 + *	David L. Nugent.  All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#include "pw.h" + +void +copymkdir(int rootfd, char const *dir, int skelfd, mode_t mode, uid_t uid, +    gid_t gid, int flags) +{ +	char		*p, lnk[MAXPATHLEN]; +	int		len, srcfd, destfd; +	ssize_t		sz; +	struct stat     st; +	struct dirent  *e; +	DIR		*d; +	mode_t		pumask; + +	if (*dir == '/') +		dir++; + +	pumask = umask(0); +	umask(pumask); + +	if (mkdirat(rootfd, dir, mode) != 0) { + +		if (errno != EEXIST) { +			warn("mkdir(%s)", dir); +			return; +		} + +		if (fchmodat(rootfd, dir, mode & ~pumask, +		    AT_SYMLINK_NOFOLLOW) == -1) +			warn("chmod(%s)", dir); +	} +	if (fchownat(rootfd, dir, uid, gid, AT_SYMLINK_NOFOLLOW) == -1) +		warn("chown(%s)", dir); +	if (flags > 0 && chflagsat(rootfd, dir, flags, +	    AT_SYMLINK_NOFOLLOW) == -1) +		warn("chflags(%s)", dir); +	metalog_emit(dir, (mode | S_IFDIR) & ~pumask, uid, gid, flags); + +	if (skelfd == -1) +		return; + +	if ((d = fdopendir(skelfd)) == NULL) { +		close(skelfd); +		return; +	} + +	while ((e = readdir(d)) != NULL) { +		char path[MAXPATHLEN]; + +		if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) +			continue; + +		p = e->d_name; +		if (fstatat(skelfd, p, &st, AT_SYMLINK_NOFOLLOW) == -1) +			continue; + +		if (strncmp(p, "dot.", 4) == 0)	/* Conversion */ +			p += 3; +		(void)snprintf(path, sizeof(path), "%s/%s", dir, p); + +		if (S_ISDIR(st.st_mode)) { +			int fd; + +			fd = openat(skelfd, e->d_name, O_DIRECTORY); +			if (fd == -1) { +				warn("openat(%s)", e->d_name); +				continue; +			} +			copymkdir(rootfd, path, fd, st.st_mode & _DEF_DIRMODE, +			    uid, gid, st.st_flags); +			continue; +		} + +		if (S_ISLNK(st.st_mode) && +		    (len = readlinkat(skelfd, e->d_name, lnk, sizeof(lnk) - 1)) +		    != -1) { +			lnk[len] = '\0'; +			if (symlinkat(lnk, rootfd, path) != 0) +				warn("symlink(%s)", path); +			else if (fchownat(rootfd, path, uid, gid, +			    AT_SYMLINK_NOFOLLOW) != 0) +				warn("chown(%s)", path); +			metalog_emit_symlink(path, lnk, st.st_mode & ~pumask, +			    uid, gid); +			continue; +		} + +		if (!S_ISREG(st.st_mode)) +			continue; + +		if ((srcfd = openat(skelfd, e->d_name, O_RDONLY)) == -1) +			continue; +		destfd = openat(rootfd, path, O_RDWR | O_CREAT | O_EXCL, +		    st.st_mode); +		if (destfd == -1) { +			close(srcfd); +			continue; +		} + +		do { +			sz = copy_file_range(srcfd, NULL, destfd, NULL, +			    SSIZE_MAX, 0); +		} while (sz > 0); +		if (sz < 0) +			warn("copy_file_range"); + +		close(srcfd); +		/* +		 * Propagate special filesystem flags +		 */ +		if (fchown(destfd, uid, gid) != 0) +			warn("chown(%s)", p); +		if (fchflags(destfd, st.st_flags) != 0) +			warn("chflags(%s)", p); +		metalog_emit(path, st.st_mode & ~pumask, uid, gid, st.st_flags); +		close(destfd); +	} +	closedir(d); +}  | 
