Source

pcw / pcw.c

Full commit
#include <err.h>
#include <errno.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>

#define PATH_MAX    1024
#define CMD_MAX     1024
#define MAX_ARGS    64
#define MAX_OPENFD  4
#define MAX_WATCH   512

int evq, init;
char paths[MAX_WATCH][PATH_MAX] = {{0}};
int wins[MAX_WATCH] = {0};

char cmd[CMD_MAX] = "urxvt -title %p -e srw -p %c cw %o %i";
char *cmd_argv[MAX_ARGS] = {NULL};

void usage(void)
{
	printf("Usage: pcw [-c command] [-v] dir\n");
	exit(1);
}

void win(int wd)
{
	char out[PATH_MAX], in[PATH_MAX], path[PATH_MAX], channel[32];
	int i;

	if (wins[wd])
		return;

	for (i = 0; cmd_argv[i]; i++) {
		if (cmd_argv[i][0] == '%' && cmd_argv[i][1] && cmd_argv[i][2] == '\0') {
			switch (cmd_argv[i][1]) {
				case 'c' : cmd_argv[i] = channel; break;
				case 'i' : cmd_argv[i] = in;      break;
				case 'o' : cmd_argv[i] = out;     break;
				case 'p' : cmd_argv[i] = path;    break;
			}
		}
	}

	strcpy(path, paths[wd]);
	sprintf(channel, "%s> ", basename(path));
	sprintf(out, "%s/out", paths[wd]);
	sprintf(in,  "%s/in",  paths[wd]);

	for (i = 0; cmd_argv[i]; i++)
		printf("%s ", cmd_argv[i]);
	printf("\n");

	if ((wins[wd] = fork()) == 0) {
		execvp(cmd_argv[0], cmd_argv);
		err(1, "failed on execvp %s", cmd);
	}
}

int add_dir(const char *fpath, const struct stat *sb, int typeflag)
{
	int wd;
	char out[PATH_MAX];
	struct stat st;

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

	if (paths[wd][0] == '\0') {
		strcpy(paths[wd], fpath);
		strcpy(out, fpath);
		strcat(out, "/out");
		if (!init && stat(out, &st) == 0)
			win(wd);
	}

	return 0;
}

int find(int *haystack, int needle, int len)
{
	int i;

	for (i = 0; i < len; i++)
		if (haystack[i] == needle)
			return i;

	return -1;
}

void sigchld(int unused) {
	pid_t pid;
	int wd;

	while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
		if ((wd = find(wins, pid, MAX_WATCH)) > -1)
			wins[wd] = 0;
	}
}

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

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

	if (i >= argc)
		usage();

	root = argv[i];

	for (i = 0, t = strtok(cmd, " "); i < MAX_ARGS - 1 && t; i++, t = strtok(NULL, " "))
		cmd_argv[i] = t;

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

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

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

	init = 1;
	ftw(root, add_dir, MAX_OPENFD);
	init = 0;

	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 += sizeof(struct inotify_event) + cur->len) {
			if (cur->mask & (IN_CREATE | IN_ISDIR))
				ftw(root, add_dir, MAX_OPENFD);
			if (cur->mask & (IN_CREATE | IN_MODIFY) && !strcmp(cur->name, "out"))
				win(cur->wd);
		}
	}

	return 0;
}