mirror of
https://github.com/oasislinux/oasis.git
synced 2026-05-12 21:34:47 +02:00
444 lines
8.5 KiB
C
444 lines
8.5 KiB
C
/*
|
|
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;
|
|
}
|