summaryrefslogtreecommitdiff
path: root/util
diff options
context:
space:
mode:
authorMichael Forney <mforney@mforney.org>2016-06-06 21:30:54 -0700
committerMichael Forney <mforney@mforney.org>2016-06-06 21:30:54 -0700
commit9297d221473959ea3be90de4ab5b22b8270450cf (patch)
tree0d9b00e9829dc8f7f4bb76b68f674a944ede4d75 /util
parentbf849d88ba0ee21245aa316fd435cb0cfe29faa6 (diff)
Add post-checkout hook to set special permissions
Diffstat (limited to 'util')
-rw-r--r--util/post-checkout.c250
1 files changed, 250 insertions, 0 deletions
diff --git a/util/post-checkout.c b/util/post-checkout.c
new file mode 100644
index 00000000..c8355474
--- /dev/null
+++ b/util/post-checkout.c
@@ -0,0 +1,250 @@
+/*
+See LICENSE file for copyright and license details.
+
+This program is a git post-checkout hook to fix the permissions of files based
+on a .perms file in the repository
+
+TODO: We should read the old .perms file, and remove any directories listed
+there that are not present in the new .perms file.
+
+TODO: Once we start processing the list of changed files, we should keep track
+of errors, but continue on to the next file.
+*/
+#define _POSIX_C_SOURCE 200809L
+#include <errno.h>
+#include <fcntl.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>
+
+struct perm {
+ mode_t mode;
+ char *name;
+ bool fixed;
+};
+
+extern char **environ;
+
+static int vflag;
+
+static dev_t rootdev;
+static struct perm *perms;
+static int perms_len;
+
+static noreturn void
+die(char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':')
+ fprintf(stderr, " %s", strerror(errno));
+ fputc('\n', stderr);
+
+ 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 (posix_spawn_file_actions_init(&actions) < 0)
+ die("posix_spawn_file_actions_init:");
+ if (posix_spawn_file_actions_addclose(&actions, fd[0]) < 0)
+ die("posix_spawn_file_actions_adddup2:");
+ if (posix_spawn_file_actions_adddup2(&actions, fd[1], 1) < 0)
+ die("posix_spawn_file_actions_adddup2:");
+ if (posix_spawnp(pid, argv[0], &actions, NULL, argv, environ) < 0)
+ die("posix_spawnp:");
+ posix_spawn_file_actions_destroy(&actions);
+ close(fd[1]);
+
+ return f;
+}
+
+static void
+readperms(void)
+{
+ static char *argv[] = { "git", "show", ":.perms", 0 };
+ FILE *f;
+ pid_t pid;
+ char *line = NULL, *s, *mode;
+ size_t size = 0;
+ ssize_t n;
+ int st;
+
+ f = spawn(argv, &pid);
+ 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 .perms file");
+ *s++ = '\0';
+ perms = realloc(perms, (perms_len + 1) * sizeof(*perms));
+ if (!perms)
+ die("realloc:");
+ perms[perms_len].name = strdup(s);
+ if (!perms[perms_len].name)
+ die("strdup:");
+ perms[perms_len].mode = strtoul(mode, &s, 8);
+ perms[perms_len].fixed = false;
+ if (*s)
+ die("invalid mode: %s\n", mode);
+ ++perms_len;
+ }
+ fclose(f);
+
+ if (waitpid(pid, &st, 0) < 0)
+ die("waitpid:");
+ if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0))
+ die("child process failed");
+}
+
+static int
+mkdir_v(const char *name, mode_t mode)
+{
+ if (vflag)
+ fprintf(stderr, "mkdir(\"%s\", %#o)\n", name, mode);
+ return mkdir(name, mode);
+}
+
+static int
+chmod_v(const char *name, mode_t mode)
+{
+ if (vflag)
+ fprintf(stderr, "chmod(\"%s\", %#o)\n", name, mode);
+ return chmod(name, mode);
+}
+
+static void
+fixperm(struct perm *p)
+{
+ struct stat st;
+
+ if (lstat(p->name, &st) < 0) {
+ if (errno != ENOENT)
+ die("lstat:");
+ if (!S_ISDIR(p->mode))
+ die("file missing and not a directory: %s", p->name);
+ if (mkdir_v(p->name, p->mode & ~S_IFMT) < 0)
+ die("mkdir:");
+ return;
+ }
+ if (st.st_dev != rootdev || st.st_mode == p->mode)
+ return;
+ if ((st.st_mode&S_IFMT) != (p->mode&S_IFMT))
+ die("conflicting modes: .perms=%#o, filesystem=%#o", p->mode, st.st_mode);
+ if (chmod_v(p->name, p->mode & ~S_IFMT) < 0)
+ die("chmod:");
+}
+
+static void
+fixperms(void)
+{
+ int i;
+
+ for (i = 0; i < perms_len; ++i) {
+ if (!perms[i].fixed)
+ fixperm(&perms[i]);
+ }
+}
+
+static void
+defperm(const char *name)
+{
+ struct stat st;
+ mode_t mode;
+
+ if (lstat(name, &st) < 0)
+ die("lstat:");
+ if (st.st_dev != rootdev)
+ return;
+ switch (st.st_mode & S_IFMT) {
+ case S_IFREG:
+ mode = st.st_mode&S_IXUSR ? 0755 : 0644;
+ if ((st.st_mode&~S_IFMT) == mode)
+ return;
+ if (chmod_v(name, mode) < 0)
+ die("chmod:");
+ break;
+ case S_IFLNK:
+ return;
+ default:
+ die("unexpected file type %#o: %s", st.st_mode, name);
+ }
+}
+
+static void
+readchanges(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;
+ char *line = NULL;
+ size_t size = 0;
+ int i, st;
+
+ f = spawn(old ? argv_diff : argv_new, &pid);
+nextline:
+ while (getdelim(&line, &size, '\0', f) >= 0) {
+ if (strcmp(line, ".perms") == 0) {
+ fixperms();
+ continue;
+ }
+ for (i = 0; i < perms_len; ++i) {
+ if (!perms[i].fixed && strcmp(line, perms[i].name) == 0) {
+ fixperm(&perms[i]);
+ goto nextline;
+ }
+ }
+ defperm(line);
+ }
+ fclose(f);
+
+ if (waitpid(pid, &st, 0) < 0)
+ die("waitpid:");
+ if (!(WIFEXITED(st) && WEXITSTATUS(st) == 0))
+ die("child process failed");
+}
+
+int main(int argc, char *argv[]) {
+ struct stat st;
+
+ if (strcmp(argv[1], "-v") == 0) {
+ vflag = 1;
+ --argc;
+ ++argv;
+ }
+
+ if (chdir("/") < 0)
+ die("chdir:");
+ if (stat(".", &st) < 0)
+ die("stat:");
+ rootdev = st.st_dev;
+
+ readperms();
+ readchanges(argc == 4 ? argv[1] : NULL, argc == 3 ? argv[2] : "HEAD");
+
+ return 0;
+}