Source

pcw / pcw.c

Full commit
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <ftw.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/inotify.h>
#include <sys/wait.h>

// fairly arbitray values...feel free to change
#define PATH_MAX    256
#define MAX_OPENFD  4

struct watch {
	int wd;
	pid_t pid;
	char *path;
	struct watch *next;
};

static int  add_dir(const char *fpath, const struct stat *sb, int typeflag);
static void sigchld(int unused);
static void sigusr(int unused);
static void usage(void);
static void win(struct watch *w);

static struct watch *watches = NULL;
static int evq;
static char *nick = "";

int add_dir(const char *fpath, const struct stat *sb, int typeflag)
{
	int wd;
	struct watch *w;

	if (!(typeflag & FTW_D))
		return 0;

	if ((wd = inotify_add_watch(evq, fpath, IN_CREATE | IN_MODIFY)) < 0) {
		warn("failed on inotify_add_watch on %s", fpath);
		return 0;
	}

	for (w = watches; w; w = w->next)
		if (w->wd == wd)
			return 0;

	if ((w = calloc(1, sizeof(struct watch))) == NULL)
		err(1, "error on calloc");

	w->next = watches;
	watches = w;

	w->wd   = wd;
	w->path = strdup(fpath);

	win(w);

	return 0;
}

void sigchld(int unused)
{
	pid_t pid;
	struct watch *w;

	while ((pid = waitpid(-1, NULL, WNOHANG)) > 0)
		for (w = watches; w; w = w->next)
			if (w->pid == pid)
				w->pid = 0;
}

void sigusr(int unused)
{
	struct watch *w;

	for (w = watches; w; w = w->next)
		win(w);
}

void usage(void)
{
	fprintf(stderr, "Usage: pcw [-n nick] [-v] dir\n");
	exit(1);
}

void win(struct watch *w)
{
	int fd = 0;
	char out[PATH_MAX], in[PATH_MAX];
	char *cmd[] = { "pcw_win.sh", w->path, out, in, nick, NULL };

	if (w->pid)
		return;

	sprintf(out, "%s/out", w->path);
	sprintf(in,  "%s/in",  w->path);

	// open() to see if ii has the fifo open for reading
	if (access(out, F_OK) < 0 || access(in, F_OK) < 0 || (fd = open(in, O_WRONLY | O_NONBLOCK)) < 0)
		return;

	close(fd);

	if ((w->pid = fork()) == 0) {
		execvp(cmd[0], cmd);
		err(1, "failed on execvp %s", cmd[0]);
	}
}

int main(int argc, char **argv)
{
	char buf[4096];
	int len, i;
	struct inotify_event *cur;
	struct stat st;
	struct watch *w;

	for (i = 1; i < argc; i++) {
		if (argv[i][0] != '-')
			break;
		if (argv[i][2] != '\0')
			usage();
		switch (argv[i][1]) {
			case 'n' : nick = argv[++i]; break;
			case 'v' : printf("pcw-"VERSION" © Evan Gates\n"); exit(1);
			default  : usage();
		}
	}

	if (i >= argc)
		usage();

	if (stat(argv[i], &st) < 0 || !S_ISDIR(st.st_mode))
		errx(1, "%s does not exist or is not a directory", argv[i]);

	if (signal(SIGCHLD, sigchld) == SIG_ERR)
		err(1, "failed installing SIGCHLD handler");

	if (signal(SIGUSR1, sigusr) == SIG_ERR)
		err(1, "failed installing SIGUSR1 handler");

	if ((evq = inotify_init()) < 0)
		err(1, "failed on inotify_init()");

	ftw(argv[i], add_dir, MAX_OPENFD);

	for (;;) {
		if ((len = read(evq, buf, sizeof(buf))) < 0 && errno != EINTR)
			warn("failed to read from inotify event queue");

		for (cur = (struct inotify_event *)buf;
			 (char *)cur - buf < len;
			 cur = (struct inotify_event *)((char *)cur + sizeof(struct inotify_event) + cur->len)) {

			if (cur->mask & (IN_CREATE | IN_ISDIR))
				ftw(argv[i], add_dir, MAX_OPENFD);
			if (cur->mask & (IN_CREATE | IN_MODIFY) && (!strcmp(cur->name, "out") || !strcmp(cur->name, "in"))) {
				for (w = watches; w; w = w->next) {
					if (w->wd == cur->wd) {
						win(w);
						break;
					}
				}
			}
		}
	}

	return 0;
}

/* vim: set ts=4 sw=4 noexpandtab: */