Commit: 6a79250a77e2c7e6bc8fff1a18e72374e2e2381f
Parent: e63628561896018d2275ba2e24edfe16868fd32b
Author: Randy Palamar
Date: Sun, 24 Sep 2023 14:34:52 -0600
skeleton pkg is needed for {merge,apply}perms
Diffstat:
5 files changed, 614 insertions(+), 2 deletions(-)
diff --git a/pkg/admin/gen.lua b/pkg/admin/gen.lua
@@ -1 +1,2 @@
-subgen('pwgen')
-\ No newline at end of file
+subgen('pwgen')
+subgen('skeleton')
diff --git a/pkg/admin/skeleton/applyperms.c b/pkg/admin/skeleton/applyperms.c
@@ -0,0 +1,443 @@
+/*
+See LICENSE file for copyright and license details.
+
+This program is meant to be run by a git hook to fix the permissions of files
+based on a .perms file in the repository
+
+It can also be run with the -d flag on any directory containing .perms in order
+to apply the permissions specified by that file.
+
+Security considerations:
+If the repository previously contained a world or group readable directory which
+has become secret, the names of the new files in that directory will become
+temporarily visible because git checks out the files before this program is run.
+*/
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <spawn.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define PERMS_FILE ".perms"
+
+struct perm {
+ char *name;
+ mode_t mode;
+ bool attempted; /* whether we attempted to set permissions for this file */
+ bool delete; /* marked for directories that only appear in old .perms */
+};
+
+struct special {
+ struct perm *perms;
+ int len;
+};
+
+extern char **environ;
+
+static char *prog;
+static dev_t rootdev;
+static int rootfd = AT_FDCWD;
+static struct special oldsp, newsp;
+static int status;
+
+static void
+verror(char *fmt, va_list ap)
+{
+ status = 1;
+ vfprintf(stderr, fmt, ap);
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':')
+ fprintf(stderr, " %s", strerror(errno));
+ fputc('\n', stderr);
+}
+
+static void
+error(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ verror(fmt, ap);
+ va_end(ap);
+}
+
+static noreturn void
+die(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ verror(fmt, ap);
+ va_end(ap);
+
+ exit(1);
+}
+
+static FILE *
+spawn(char **argv, pid_t *pid)
+{
+ FILE *f;
+ int fd[2];
+ posix_spawn_file_actions_t actions;
+
+ if (pipe(fd) < 0)
+ die("pipe:");
+ f = fdopen(fd[0], "r");
+ if (!f)
+ die("fdopen:");
+ if ((errno = posix_spawn_file_actions_init(&actions)) > 0)
+ die("posix_spawn_file_actions_init:");
+ if ((errno = posix_spawn_file_actions_addclose(&actions, fd[0])) > 0)
+ die("posix_spawn_file_actions_adddup2:");
+ if ((errno = posix_spawn_file_actions_adddup2(&actions, fd[1], 1)) > 0)
+ die("posix_spawn_file_actions_adddup2:");
+ if ((errno = posix_spawnp(pid, argv[0], &actions, NULL, argv, environ)) > 0)
+ die("posix_spawnp %s:", argv[0]);
+ posix_spawn_file_actions_destroy(&actions);
+ close(fd[1]);
+
+ return f;
+}
+
+static void
+readspecial(struct special *sp, FILE *f)
+{
+ char *line = NULL, *s, *mode;
+ size_t size = 0;
+ ssize_t n;
+
+ while ((n = getline(&line, &size, f)) >= 0) {
+ if (line[n-1] == '\n')
+ line[--n] = '\0';
+ mode = s = line;
+ s = strchr(s, ' ');
+ if (!s || s == mode)
+ die("malformed permissions file: %s", PERMS_FILE);
+ *s++ = '\0';
+ sp->perms = realloc(sp->perms, (sp->len + 1) * sizeof(*sp->perms));
+ if (!sp->perms)
+ die("realloc:");
+ sp->perms[sp->len] = (struct perm){
+ .name = strdup(s),
+ .mode = strtoul(mode, &s, 8),
+ };
+ if (!sp->perms[sp->len].name)
+ die("strdup:");
+ if (*s)
+ die("invalid mode: %s", mode);
+ ++sp->len;
+ }
+}
+
+static void
+gitspecial(struct special *sp, const char *rev)
+{
+ char object[41 + sizeof(PERMS_FILE)];
+ char *argv[] = {"git", "show", object, 0};
+ FILE *f;
+ pid_t pid;
+ int st, n;
+
+ n = snprintf(object, sizeof(object), "%s:%s", rev, PERMS_FILE);
+ if (n < 0 || n >= (int)sizeof(object))
+ die("revision is too large: %s", rev);
+ f = spawn(argv, &pid);
+ readspecial(sp, f);
+ fclose(f);
+
+ if (waitpid(pid, &st, 0) < 0)
+ die("waitpid:");
+ if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0))
+ die("child process failed");
+}
+
+static int
+chmod_v(const char *path, mode_t mode)
+{
+ printf("chmod(\"%s\", %#o)\n", path, mode);
+ return fchmodat(rootfd, path, mode, 0);
+}
+
+static int
+mkdir_v(const char *path, mode_t mode)
+{
+ printf("mkdir(\"%s\", %#o)\n", path, mode);
+ return mkdirat(rootfd, path, mode);
+}
+
+static int
+rmdir_v(const char *path)
+{
+ printf("rmdir(\"%s\")\n", path);
+ return unlinkat(rootfd, path, AT_REMOVEDIR);
+}
+
+static int
+defperm(const char *name)
+{
+ struct stat st;
+ mode_t mode;
+
+ if (fstatat(rootfd, name, &st, AT_SYMLINK_NOFOLLOW) < 0)
+ return -1;
+ if (st.st_dev != rootdev) {
+ errno = EXDEV;
+ return -1;
+ }
+ switch (st.st_mode & S_IFMT) {
+ case S_IFREG:
+ mode = st.st_mode & S_IXUSR ? 0755 : 0644;
+ break;
+ case S_IFDIR:
+ mode = 0755;
+ break;
+ case S_IFLNK:
+ return 0;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ if ((st.st_mode & ~S_IFMT) == mode)
+ return 0;
+ return chmod_v(name, mode);
+}
+
+static int
+specialperm(struct perm *p)
+{
+ struct stat st;
+
+ if (p->attempted)
+ return 0;
+ p->attempted = true;
+ if (fstatat(rootfd, p->name, &st, AT_SYMLINK_NOFOLLOW) < 0) {
+ if (errno != ENOENT || !S_ISDIR(p->mode))
+ return -1;
+ return mkdir_v(p->name, p->mode & ~S_IFMT);
+ }
+ if (st.st_dev != rootdev) {
+ errno = EXDEV;
+ return -1;
+ }
+ if (st.st_mode == p->mode)
+ return 0;
+ if ((st.st_mode & S_IFMT) != (p->mode & S_IFMT)) {
+ errno = EINVAL;
+ return -1;
+ }
+ return chmod_v(p->name, p->mode & ~S_IFMT);
+}
+
+static void
+specialperms(void)
+{
+ int i = 0, j = 0, n;
+
+ while (i < oldsp.len || j < newsp.len) {
+ if (i == oldsp.len)
+ n = 1;
+ else if (j == newsp.len)
+ n = -1;
+ else
+ n = strcmp(oldsp.perms[i].name, newsp.perms[j].name);
+ if (n >= 0) {
+ if (specialperm(&newsp.perms[j]) < 0 && errno != EXDEV)
+ error("specialperm:");
+ ++j;
+ if (n == 0)
+ ++i;
+ continue;
+ }
+ if ((oldsp.perms[i].mode & S_IFMT) == S_IFDIR) {
+ oldsp.perms[i].delete = true;
+ } else if (defperm(oldsp.perms[i].name) < 0) {
+ switch (errno) {
+ case ENOENT:
+ case EXDEV:
+ break;
+ default:
+ error("defperm:");
+ }
+ }
+ ++i;
+ }
+ /* delete directories in reverse order */
+ while (i > 0) {
+ --i;
+ if (oldsp.perms[i].delete && rmdir_v(oldsp.perms[i].name) < 0) {
+ switch (errno) {
+ case ENOENT:
+ case ENOTEMPTY:
+ break;
+ default:
+ error("rmdir:");
+ }
+ }
+ }
+}
+
+static int
+setperm(const char *name)
+{
+ int i;
+
+ for (i = 0; i < newsp.len; ++i) {
+ if (strcmp(name, newsp.perms[i].name) == 0)
+ return specialperm(&newsp.perms[i]);
+ }
+ return defperm(name);
+}
+
+static void
+setroot(const char *root)
+{
+ struct stat st;
+
+ rootfd = open(root, O_RDONLY);
+ if (rootfd < 0)
+ die("open %s:", root);
+ if (fstat(rootfd, &st) < 0)
+ die("fstat:", root);
+ rootdev = st.st_dev;
+}
+
+static void
+gitupdate(char *old, char *new)
+{
+ char *argv_diff[] = {"git", "diff", "--name-only", "-z", old, new, 0};
+ char *argv_new[] = {"git", "ls-tree", "--name-only", "--full-tree", "-z", "-r", new, 0};
+ FILE *f;
+ pid_t pid;
+ struct {
+ char *buf;
+ size_t size;
+ } lines[2] = {0};
+ ssize_t n;
+ int cur = 0, st;
+ char *root, *path, *diff, *s;
+
+ root = getenv("GIT_WORK_TREE");
+ setroot(root ? root : ".");
+ if (old)
+ gitspecial(&oldsp, old);
+ gitspecial(&newsp, new);
+
+ f = spawn(old ? argv_diff : argv_new, &pid);
+ umask(0);
+ while ((n = getdelim(&lines[cur].buf, &lines[cur].size, '\0', f)) >= 0) {
+ path = lines[cur].buf;
+ if (strcmp(path, PERMS_FILE) == 0) {
+ specialperms();
+ continue;
+ }
+ if (setperm(path) < 0) switch (errno) {
+ case ENOENT:
+ continue;
+ case EXDEV:
+ break;
+ default:
+ error("setperm %s:", path);
+ }
+ /* find the first difference from the previous path */
+ diff = path;
+ if (lines[!cur].buf)
+ for (s = lines[!cur].buf; *s && *s == *diff; ++s, ++diff);
+ /* set permissions on each parent directory after that difference */
+ for (s = path + n; s >= diff; --s) {
+ if (*s != '/')
+ continue;
+ *s = '\0';
+ if (setperm(path) < 0)
+ error("setperm %s:", path);
+ *s = '/';
+ }
+ cur = !cur;
+ }
+ fclose(f);
+
+ if (waitpid(pid, &st, 0) < 0)
+ die("waitpid:");
+ if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0))
+ die("child process failed");
+}
+
+static void
+applyspecial(void)
+{
+ int fd;
+ FILE *f;
+
+ fd = openat(rootfd, ".perms", O_RDONLY);
+ if (fd < 0)
+ die("open .perms:");
+ f = fdopen(fd, "r");
+ if (!f)
+ die("fdopen:");
+ readspecial(&newsp, f);
+ fclose(f);
+ umask(0);
+ specialperms();
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: %s [[old] new] | %s -d dir\n", prog, prog);
+ exit(2);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int dflag = 0;
+ char *old, *new;
+
+ prog = basename(argv[0]);
+ for (++argv, --argc; argc && (*argv)[0] == '-' && (*argv)[1]; ++argv, --argc) {
+ switch ((*argv)[1]) {
+ case 'd':
+ if (!*++argv)
+ usage();
+ --argc;
+ setroot(*argv);
+ dflag = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ if (dflag) {
+ if (argc)
+ usage();
+ applyspecial();
+ return status;
+ }
+
+ switch (argc) {
+ case 0:
+ old = NULL;
+ new = "HEAD";
+ break;
+ case 1:
+ old = NULL;
+ new = argv[0];
+ break;
+ case 2:
+ old = argv[0];
+ new = argv[1];
+ break;
+ default:
+ usage();
+ }
+ gitupdate(old, new);
+ return status;
+}
diff --git a/pkg/admin/skeleton/gen.lua b/pkg/admin/skeleton/gen.lua
@@ -0,0 +1,4 @@
+set('srcdir', '$dir')
+
+file('libexec/applyperms', '755', exe('applyperms', {'applyperms.c'}))
+file('libexec/mergeperms', '755', exe('mergeperms', {'mergeperms.c'}))
diff --git a/pkg/admin/skeleton/mergeperms.c b/pkg/admin/skeleton/mergeperms.c
@@ -0,0 +1,164 @@
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdnoreturn.h>
+#include <string.h>
+#include <sys/stat.h>
+
+struct perm {
+ char *name;
+ mode_t mode;
+};
+
+static struct perm *perms;
+static size_t permslen, permscap;
+
+static noreturn void
+fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
+ fputc(' ', stderr);
+ perror(NULL);
+ } else {
+ fputc('\n', stderr);
+ }
+ exit(1);
+}
+
+static void
+addperm(struct perm *p)
+{
+ if (permslen == permscap) {
+ permscap = permscap ? permscap * 2 : 64;
+ if (permscap > SIZE_MAX / sizeof(perms[0])) {
+ errno = ENOMEM;
+ fatal("realloc:");
+ }
+ perms = realloc(perms, permscap * sizeof(perms[0]));
+ if (!perms)
+ fatal("realloc:");
+ }
+ perms[permslen++] = *p;
+}
+
+static void
+readperm(FILE *file, struct perm *perm)
+{
+ static char *line;
+ static size_t size;
+ ssize_t n;
+ char *s, *mode;
+
+ n = getline(&line, &size, file);
+ if (n < 0) {
+ if (ferror(file))
+ fatal("getline:");
+ perm->name = NULL;
+ return;
+ }
+ if (n && line[n - 1] == '\n')
+ line[n - 1] = '\0';
+ mode = s = line;
+ s = strchr(s, ' ');
+ if (!s || s == mode)
+ fatal("invalid permissions file");
+ *s++ = '\0';
+ perm->name = strdup(s);
+ if (!perm->name)
+ fatal("strdup:");
+ perm->mode = strtoul(mode, &s, 8);
+ if (*s)
+ fatal("invalid mode '%s'", mode);
+}
+
+static int
+permcmp(struct perm *a, struct perm *b)
+{
+ return a->name ? b->name ? strcmp(a->name, b->name) : -1 : !!b->name;
+}
+
+static noreturn void
+usage(void)
+{
+ fprintf(stderr, "usage: mergeperms old cur new\n");
+ exit(2);
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *oldf, *curf, *newf;
+ struct perm old, cur, new;
+ int ij, ik, jk;
+ int ret;
+
+ if (argc != 4)
+ usage();
+
+ ret = 0;
+ oldf = fopen(argv[1], "r");
+ if (!oldf)
+ fatal("open %s:", argv[1]);
+ curf = fopen(argv[2], "r");
+ if (!curf)
+ fatal("open %s:", argv[2]);
+ newf = fopen(argv[3], "r");
+ if (!newf)
+ fatal("open %s:", argv[3]);
+
+ readperm(oldf, &old);
+ readperm(curf, &cur);
+ readperm(newf, &new);
+ for (;;) {
+ ij = permcmp(&old, &cur);
+ ik = permcmp(&old, &new);
+ if (ij < 0 && ik < 0 && old.name) {
+ readperm(oldf, &old);
+ continue;
+ }
+ if (!old.name && !cur.name && !new.name)
+ break;
+ jk = permcmp(&cur, &new);
+ if ((jk < 0 && ij == 0 && old.mode == cur.mode) || (jk > 0 && ik == 0 && old.mode == new.mode)) {
+ /* deleted in cur or new and unchanged in the other */
+ } else if (jk < 0) {
+ if (ij == 0)
+ ret = 3;
+ addperm(&cur);
+ } else if (jk > 0) {
+ if (ik == 0)
+ ret = 3;
+ addperm(&new);
+ } else if (ij == 0 && old.mode == cur.mode) {
+ addperm(&new);
+ } else {
+ if (cur.mode != new.mode && (ik != 0 || old.mode != new.mode))
+ ret = 3;
+ addperm(&cur);
+ }
+ if (jk <= 0)
+ readperm(curf, &cur);
+ if (jk >= 0)
+ readperm(newf, &new);
+ }
+
+ fclose(curf);
+ curf = fopen(argv[2], "w");
+ if (!curf)
+ fatal("open %s:", argv[1]);
+ for (; permslen > 0; --permslen, ++perms)
+ fprintf(curf, "%#o %s\n", perms->mode, perms->name);
+ fflush(curf);
+ if (ferror(curf))
+ fatal("write error");
+
+ return ret;
+}
diff --git a/sets.lua b/sets.lua
@@ -4,6 +4,7 @@ S.bin = {
'awk',
'pwgen',
'samurai',
+ 'skeleton',
'strace',
'vis',
}