cvs-permfix-tag.c
/* * CVS permissions fix: tags * * SUMMARY * * Run as taginfo script for all CVS tag operations. * Propagates ACL entries of parent/container * directories of any tagged files. NOTE: does NOT * handle commits. See the companion program for that. * * EXECUTION * * Run as a CVS taginfo filter using regexp ALL. * * DETAILS * * Please see the program that's run on commit for an * explanation of what we're used for. The only * difference for us is that we're run on tag, not * commit. It was really unfortunate for us to have * discovered that the loginfo script is not run on * tags, which we do every single night. Not only that, * but taginfo has a completely different invocation * format which is totally dissimilar from the way * loginfo runs. * * ASSUMPTIONS * * Not too much care was taken to check for input. * Since it's coming from CVS and we aren't running * privileged, this shouldn't be too big a danger. * * Does not attempt to handle allocation failures. If * we enter real OOM state on our CVS server and have * run out of swap, we have much bigger problems. * * BUGS * * Both bugs I mention in the loginfo filter run on * commits are actually fixed for taginfo, strangely. * * AUTHOR: * * Scott Mcdermott */ #define _GNU_SOURCE #include <sys/acl.h> #include <unistd.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <string.h> #include "util.h" #include "acl.h" ^L static void wait_for_program_exit (pid_t pid); ^L int main (int argc, char **argv) { int dirfd; pid_t ppid; /* must have: progname, tagname, operation, * repository; at least one: file, revision */ if (argc < 6) { fprintf(stderr, "%s: %d: not enough args\n", progname, argc); for (++argv; *argv; argv++) fprintf(stderr, "%s: arg: %s\n", progname, *argv); exit(EXIT_FAILURE); } /* skip progname, tagname, and operation */ argv += 3; argc -= 3; /* our program is invoked exactly once per * repository directory that has tagged files */ xchdir (*argv); dirfd = xopen(".", O_RDONLY); /* since taginfo is run *before* tagging, we have to * fool it into thinking we are done, and wait on * its (our current parent's) completion before * continuing. */ ppid = getppid(); switch (fork()) { case 0: wait_for_program_exit(ppid); break; case -1: fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); default: /* XXX TODO sleep first? */ exit(EXIT_SUCCESS); } /* every remaining argument is a filename, followed * by another argument saying what the revision is */ //for (argv++; argv; argv += 2) for (argv++, argc--; argc > 0; argv += 2, argc -= 2) set_repoacl_from_parent(*argv, dirfd); /* if there are additional files in other * directories, we will be invoked again */ exit(EXIT_SUCCESS); } ^L /* * This is an abhorrent hack but there's no other way to do * it on Linux. Solaris pwait(1) does a poll(2) on the * procfile with infinite timeout, which returns as soon as * the process dies. However, on Linux, the procfile stays * open until its last reference closes it. So instead we * just keep trying to reopen the procfile every second * until it fails, which is crude but should work. * Fortunately, we only have to do this for every CVS * directory with files in it being tagged, not every CVS * file; otherwise, it would be ridiculous and we'd have * thousands of processes doing this. */ static void wait_for_program_exit (pid_t pid) { char *procfile; int fd; if (asprintf(&procfile, "/proc/%u/stat", pid) == -1) { fprintf(stderr, "%s: %s: asprintf: %s\n", progname, __FUNCTION__, strerror(errno)); exit(EXIT_FAILURE); } while ((fd = open(procfile, O_RDONLY)) > 0) { sleep(1); close(fd); } if (errno != ENOENT) { fprintf(stderr, "%s: %s: open: %s\n", progname, __FUNCTION__, strerror(errno)); exit(EXIT_FAILURE); } } static char * get_cvsroot_or_exit (void) { char *val; if (!(val = getenv(ENV_CVSROOT))) { fprintf(stderr, "%s: getenv: %s: %s\n", progname, ENV_CVSROOT, strerror(errno)); exit(EXIT_FAILURE); } return val; } static int goto_repodir_and_open (char *dir) { char *dirname; int dirfd; asprintf(&dirname, "%s/%s", get_cvsroot_or_exit(), dir); xchdir(dirname); dirfd = xopen(".", O_RDONLY); return dirfd; }