Scott M. Mcdermott

UNIX Systems & Network Administrator
available for contract or salaried positions

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;
}