diff --git a/usr.sbin/pw/cpdir.c b/usr.sbin/pw/cpdir.c
--- a/usr.sbin/pw/cpdir.c
+++ b/usr.sbin/pw/cpdir.c
@@ -40,49 +40,48 @@
gid_t gid, int flags)
{
char *p, lnk[MAXPATHLEN];
- int len, homefd, srcfd, destfd;
+ 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) {
- mode_t pumask;
if (errno != EEXIST) {
warn("mkdir(%s)", dir);
return;
}
- pumask = umask(0);
- umask(pumask);
-
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;
- homefd = openat(rootfd, dir, O_DIRECTORY);
if ((d = fdopendir(skelfd)) == NULL) {
close(skelfd);
- close(homefd);
return;
}
while ((e = readdir(d)) != NULL) {
+ char path[MAXPATHLEN];
+
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0)
continue;
@@ -92,19 +91,32 @@
if (strncmp(p, "dot.", 4) == 0) /* Conversion */
p += 3;
+ (void)snprintf(path, sizeof(path), "%s/%s", dir, p);
if (S_ISDIR(st.st_mode)) {
- copymkdir(homefd, p, openat(skelfd, e->d_name, O_DIRECTORY),
- st.st_mode & _DEF_DIRMODE, uid, gid, st.st_flags);
+ 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))
+ (len = readlinkat(skelfd, e->d_name, lnk, sizeof(lnk) - 1))
!= -1) {
lnk[len] = '\0';
- symlinkat(lnk, homefd, p);
- fchownat(homefd, p, uid, gid, AT_SYMLINK_NOFOLLOW);
+ 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;
}
@@ -113,7 +125,7 @@
if ((srcfd = openat(skelfd, e->d_name, O_RDONLY)) == -1)
continue;
- destfd = openat(homefd, p, O_RDWR | O_CREAT | O_EXCL,
+ destfd = openat(rootfd, path, O_RDWR | O_CREAT | O_EXCL,
st.st_mode);
if (destfd == -1) {
close(srcfd);
@@ -135,6 +147,7 @@
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);
diff --git a/usr.sbin/pw/pw.h b/usr.sbin/pw/pw.h
--- a/usr.sbin/pw/pw.h
+++ b/usr.sbin/pw/pw.h
@@ -70,6 +70,11 @@
struct userconf *read_userconfig(char const * file);
int write_userconfig(struct userconf *cnf, char const * file);
+void metalog_emit(const char *path, mode_t mode, uid_t uid, gid_t gid,
+ int flags);
+void metalog_emit_symlink(const char *path, const char *target, mode_t mode,
+ uid_t uid, gid_t gid);
+
int pw_group_add(int argc, char **argv, char *name);
int pw_group_del(int argc, char **argv, char *name);
int pw_group_mod(int argc, char **argv, char *name);
diff --git a/usr.sbin/pw/pw.8 b/usr.sbin/pw/pw.8
--- a/usr.sbin/pw/pw.8
+++ b/usr.sbin/pw/pw.8
@@ -30,6 +30,7 @@
.Nd create, remove, modify & display system users and groups
.Sh SYNOPSIS
.Nm
+.Op Fl M Ar metalog
.Op Fl R Ar rootdir
.Op Fl V Ar etcdir
.Cm useradd
@@ -464,6 +465,21 @@
This can be overridden by the
.Fl d
option on the command line, if desired.
+.It Fl M Ar metalog
+Specify a path to a
+.Xr mtree 5
+metalog file.
+.Nm
+will add entries for all files added to a user's home directory.
+This is useful when building images as a non-root user, as the
+metalog can be used as input to
+.Xr tar 1
+or
+.Xr makefs 8 .
+Note that this option must precede the
+.Ql useradd
+string on the command line, otherwise it will be interpreted as the mode
+option.
.It Fl M Ar mode
Create the user's home directory with the specified
.Ar mode ,
diff --git a/usr.sbin/pw/pw.c b/usr.sbin/pw/pw.c
--- a/usr.sbin/pw/pw.c
+++ b/usr.sbin/pw/pw.c
@@ -132,7 +132,11 @@
while (argc > 1) {
if (*argv[1] == '-') {
/*
- * Special case, allow pw -V
[args] for scripts etc.
+ * Special case, allow pw -V [args] for
+ * scripts etc.
+ *
+ * The -M option before the keyword is handled
+ * differently from -M after a keyword.
*/
arg = argv[1][1];
if (arg == 'V' || arg == 'R') {
@@ -164,6 +168,23 @@
"%s%s", optarg, arg == 'R' ?
_PATH_PWD : "");
conf.altroot = true;
+ } else if (mode == -1 && which == -1 && arg == 'M') {
+ int fd;
+
+ optarg = &argv[1][2];
+ if (*optarg == '\0') {
+ optarg = argv[2];
+ ++argv;
+ --argc;
+ }
+ fd = open(optarg,
+ O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC,
+ 0644);
+ if (fd == -1)
+ errx(EX_OSERR,
+ "Cannot open metalog `%s'",
+ optarg);
+ conf.metalog = fdopen(fd, "ae");
} else
break;
} else if (mode == -1 && (tmp = getindex(Modes, argv[1])) != -1)
@@ -195,6 +216,10 @@
if (conf.rootfd == -1)
errx(EXIT_FAILURE, "Unable to open '%s'", conf.rootdir);
+ if (conf.metalog != NULL && (which != W_USER || mode != M_ADD))
+ errx(EXIT_FAILURE,
+ "metalog can only be specified with 'useradd'");
+
return (cmdfunc[which][mode](argc, argv, arg1));
}
@@ -233,10 +258,11 @@
static const char *help[W_NUM][M_NUM] =
{
{
- "usage: pw useradd [name] [switches]\n"
+ "usage: pw [-M metalog] useradd [name] [switches]\n"
"\t-V etcdir alternate /etc location\n"
"\t-R rootdir alternate root directory\n"
"\t-C config configuration file\n"
+ "\t-M metalog mtree file, must precede 'useradd'\n"
"\t-q quiet operation\n"
" Adding users:\n"
"\t-n name login name\n"
diff --git a/usr.sbin/pw/pw_user.c b/usr.sbin/pw/pw_user.c
--- a/usr.sbin/pw/pw_user.c
+++ b/usr.sbin/pw/pw_user.c
@@ -86,10 +86,13 @@
{
struct stat st;
char *dirs, *tmp;
+ mode_t pumask;
+
+ pumask = umask(0);
+ umask(pumask);
if (*dir != '/')
errx(EX_DATAERR, "invalid base directory for home '%s'", dir);
-
dir++;
if (fstatat(dfd, dir, &st, 0) != -1) {
@@ -120,6 +123,9 @@
dirs);
if (fchownat(dfd, dirs, 0, 0, 0) != 0)
warn("chown(%s)", dirs);
+ metalog_emit(dir,
+ (_DEF_DIRMODE | S_IFDIR) & ~pumask, 0, 0,
+ 0);
}
*tmp = '/';
}
@@ -129,6 +135,7 @@
err(EX_OSFILE, "'%s' (home parent) is not a directory", dirs);
if (fchownat(dfd, dirs, 0, 0, 0) != 0)
warn("chown(%s)", dirs);
+ metalog_emit(dirs, (_DEF_DIRMODE | S_IFDIR) & ~pumask, 0, 0, 0);
}
free(dirs);
diff --git a/usr.sbin/pw/pw_utils.c b/usr.sbin/pw/pw_utils.c
--- a/usr.sbin/pw/pw_utils.c
+++ b/usr.sbin/pw/pw_utils.c
@@ -92,3 +92,49 @@
errx(i, "make exited with status %d", i);
return (i);
}
+
+static void
+metalog_emit_record(const char *path, const char *target, mode_t mode,
+ uid_t uid, gid_t gid, int flags)
+{
+ const char *flagstr, *type;
+ int error;
+
+ if (conf.metalog == NULL)
+ return;
+
+ if (target != NULL)
+ type = "link";
+ else if (S_ISDIR(mode))
+ type = "dir";
+ else if (S_ISREG(mode))
+ type = "file";
+ else
+ errx(1, "metalog_emit: unhandled file type for %s", path);
+
+ flagstr = fflagstostr(flags &
+ (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND));
+ if (flagstr == NULL)
+ errx(1, "metalog_emit: fflagstostr failed");
+
+ error = fprintf(conf.metalog,
+ "./%s type=%s mode=0%03o uid=%u gid=%u%s%s%s%s\n",
+ path, type, mode & ACCESSPERMS, uid, gid,
+ target != NULL ? " link=" : "", target != NULL ? target : "",
+ *flagstr != '\0' ? " flags=" : "", *flagstr != '\0' ? flagstr : "");
+ if (error < 0)
+ errx(1, "metalog_emit: write error");
+}
+
+void
+metalog_emit(const char *path, mode_t mode, uid_t uid, gid_t gid, int flags)
+{
+ metalog_emit_record(path, NULL, mode, uid, gid, flags);
+}
+
+void
+metalog_emit_symlink(const char *path, const char *target, mode_t mode,
+ uid_t uid, gid_t gid)
+{
+ metalog_emit_record(path, target, mode, uid, gid, 0);
+}
diff --git a/usr.sbin/pw/pwupd.h b/usr.sbin/pw/pwupd.h
--- a/usr.sbin/pw/pwupd.h
+++ b/usr.sbin/pw/pwupd.h
@@ -76,6 +76,7 @@
struct pwconf {
char rootdir[MAXPATHLEN];
char etcpath[MAXPATHLEN];
+ FILE *metalog;
int fd;
int rootfd;
bool altroot;
diff --git a/usr.sbin/pw/tests/pw_useradd_test.sh b/usr.sbin/pw/tests/pw_useradd_test.sh
--- a/usr.sbin/pw/tests/pw_useradd_test.sh
+++ b/usr.sbin/pw/tests/pw_useradd_test.sh
@@ -1,4 +1,3 @@
-
# Import helper functions
. $(atf_get_srcdir)/helper_functions.shin
@@ -357,15 +356,28 @@
echo "c" > ${HOME}/skel/c/d/dot.c
mkdir ${HOME}/home
ln -sf /nonexistent ${HOME}/skel/c/foo
- atf_check -s exit:0 ${RPW} useradd foo -k /skel -m
+ atf_check -s exit:0 ${RPW} -M METALOG useradd foo -k /skel -m
test -d ${HOME}/home/foo || atf_fail "Directory not created"
test -f ${HOME}/home/foo/.a || atf_fail "File not created"
atf_check -o file:${HOME}/skel/.a -s exit:0 cat ${HOME}/home/foo/.a
atf_check -o file:${HOME}/skel/b -s exit:0 cat ${HOME}/home/foo/b
- test -d ${HOME}/home/foo/c || atf_fail "Dotted directory in skel not copied"
- test -d ${HOME}/home/foo/.plop || atf_fail "Directory in skell not created"
+ test -d ${HOME}/home/foo/c || atf_fail "Directory in skel not copied"
+ test -d ${HOME}/home/foo/.plop || atf_fail "Dotted directory in skel not created"
atf_check -o inline:"/nonexistent\n" -s ignore readlink -f ${HOME}/home/foo/c/foo
atf_check -o file:${HOME}/skel/c/d/dot.c -s exit:0 cat ${HOME}/home/foo/c/d/.c
+
+ cat <<__EOF__ >METALOG.expected
+./home/foo type=dir mode=0755 uid=1001 gid=1001
+./home/foo/.a type=file mode=0644 uid=1001 gid=1001
+./home/foo/.plop type=dir mode=0755 uid=1001 gid=1001
+./home/foo/b type=file mode=0644 uid=1001 gid=1001
+./home/foo/c type=dir mode=0755 uid=1001 gid=1001
+./home/foo/c/d type=dir mode=0755 uid=1001 gid=1001
+./home/foo/c/d/.c type=file mode=0644 uid=1001 gid=1001
+./home/foo/c/foo type=link mode=0755 uid=1001 gid=1001 link=/nonexistent
+__EOF__
+ atf_check -o save:METALOG.out sort METALOG
+ atf_check diff METALOG.out METALOG.expected
}
atf_test_case user_add_uid0