/*
 * inorun - Program for running a command when files in a directory change.
 * Copyright 2007  Ted Percival <ted@midg3t.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * For a copy of the GNU General Public License, write to the
 * Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 *
 * This program requires inotify. You will need glibc 2.4
 * or later and Linux 2.6.16 or later.
 *
 */

#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <unistd.h>


/**
 * Returns -1 on error, 0 on success.
 */
static int add_watches(int ino, int argc, char *argv[]) {
    char *pwd;
    int i;

    if (argc == 2) {
        /* Watch the current directory */
        pwd = getcwd(NULL, 0);

        /* No need to keep the watch descriptor that is returned. */
        if (inotify_add_watch(ino, pwd, IN_CLOSE_WRITE) == -1) {
            perror("inotify_add_watch()");
            return -1;
        }
        return 0;
    }

    /* Watch the files specified */
    for (i = 2; i < argc; ++i) {
        if (inotify_add_watch(ino, argv[i], IN_CLOSE_WRITE) == -1) {
            perror("inotify_add_watch()");
            return -1;
        }
    }

    return 0;
}

int main(int argc, char *argv[]) {
    int ino;
    struct {
        struct inotify_event event;
        char extra[512];
    } buf;
    ssize_t len;
    const char *cmd;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <cmd> [file [...]]\n", argv[0]);
        return 2;
    }

    cmd = argv[1];

    ino = inotify_init();
    if (ino == -1) {
        perror("inotify_init()");
        return 1;
    }

    if (add_watches(ino, argc, argv) == -1) {
        close(ino);
        return 1;
    }

    while ((len = read(ino, &buf, sizeof(buf))) > 0 || errno == EINTR) {
        pid_t pid;
        struct pollfd pfd;

        if ((buf.event.mask & IN_CLOSE_WRITE) == 0)
            continue;
        
        pid = vfork();
        if (pid == 0) {
            execlp(cmd, cmd, NULL);
            _exit(1);
        }
        if (pid == -1) {
            perror("vfork()");
            break;
        }

        pfd.fd = ino;
        pfd.events = POLLIN;

        /* Suck up any events that were triggered while the command was being
         * run. */
        while (poll(&pfd, 1, 0) > 0) {
            if (read(ino, &buf, sizeof(buf)) == -1)
                break;
        }
    }

    if (len == -1)
        perror("read()");

    close(ino);

    return 0;

}

/* vim: ts=4 sw=4 et tw=80
 */
