pcw-bsd / pcw.c

#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/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <libgen.h>
#include <fcntl.h>

#ifdef __linux__
#define HAVE_INOTIFY
#include <sys/inotify.h>
#include <sys/wait.h>
#else
#define HAVE_KQUEUE
#include <sys/event.h>
#endif

#include "config.h"

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

typedef struct watch_t watch_t;
struct watch_t {
	int wd;
	int pid;
	char *path;
	watch_t *next;
#ifdef HAVE_KQUEUE
	int outwd;
#endif
};

watch_t *watch = NULL;
char *nick = DEFAULT_NICK;
int color = DEFAULT_COLOR_SETTING;
int init;

#ifdef HAVE_INOTIFY
int evq;
#else
int kq;
#endif

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

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

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

watch_t *new_watch()
{
	watch_t *w = malloc(sizeof(watch_t));
	memset(w,0x00,sizeof(watch_t));
	return w;
}



void spawn_win(watch_t *w)
{
	char out[PATH_MAX], in[PATH_MAX], path[PATH_MAX], channel[32];
	char *cmd[] = CMD;
	char *cmd_color[] = CMD_COLOR;

	if(w->pid) return;

	strncpy(path,w->path,sizeof(path));
	snprintf(channel,sizeof(channel),"%s> ",basename(path));
	snprintf(out,sizeof(out),"%s/out",w->path);
	snprintf(in,sizeof(in),"%s/in",w->path);

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

watch_t *add_watch(int wd, const char *path)
{
	char out[PATH_MAX];
	struct stat st;
	watch_t *w;

	/* TODO: clean this up (once everything else works) */
	if (!watch) {
		watch = new_watch();
		watch->wd = wd;
	}
	for(w = watch; w; w = w->next) {
		if (w->wd == wd) {
			if(!w->path) {
				w->path = strdup(path);
				snprintf(out,sizeof(out),"%s/out",path);
				if(!init && stat(out, &st) == 0)
					spawn_win(w);
			}
			break;
		}
		if (w->next == NULL) {
			w->next = new_watch();
			w->next->wd = wd;
		}
	}
	return w;
}

#ifdef HAVE_INOTIFY
int add_dir_inotify(const char *fpath, const struct stat *sb, int typeflag)
{
	int wd;

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

	add_watch(wd,fpath);

	return 0;
}

void run_inotify(char *dir)
{
	char buf[4096];
	int len;
	struct inotify_event *cur;
	watch_t *w;

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

	init = 1;
	ftw(dir, add_dir_inotify, 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 = (struct inotify_event *)((char *)cur + sizeof(struct inotify_event) + cur->len)) {

			if (cur->mask & (IN_CREATE | IN_ISDIR))
				ftw(dir, add_dir_inotify, MAX_OPENFD);
			if (cur->mask & (IN_CREATE | IN_MODIFY) && !strcmp(cur->name, "out")) {
				for (w = watch; w; w = w->next) {
					if(w->wd == cur->wd) {
						spawn_win(w);
						break;
					}
				}
			}
		}
	}
}
#endif // HAVE_INOTIFY

#ifdef HAVE_KQUEUE
int add_dir_kqueue(const char *fpath, const struct stat *sb, int typeflag)
{
	char out[PATH_MAX];
	struct stat st;
	struct kevent kadd;
	watch_t *w;
	int fd;

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

	for (w = watch; w; w = w->next) {
		if (w->path && !strcmp(w->path, fpath)) {
			if(w->outwd)
				return 0;
			else
				break;
		}
	}

	if (!w) {
		if((fd = open(fpath, O_RDONLY)) == -1)
			err(1,"directory open");

		w = add_watch(fd,fpath);

		EV_SET(&kadd, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE, 0, w);
		if(kevent(kq, &kadd, 1, NULL, 0, NULL) == -1)
			err(1,"kevent set EVFILT_VNODE");
	}

	if(!w->outwd) {
		snprintf(out,sizeof(out),"%s/out",fpath);
		if(stat(out, &st) == 0) {
			fd = open(out, O_RDONLY);
			EV_SET(&kadd, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_WRITE, 0, w);
			if(kevent(kq, &kadd, 1, NULL, 0, NULL) == -1)
				err(1,"kevent set EVFILT_NODE");
			w->outwd = fd;
			if(!init)
				spawn_win(w); 
		}
	}

	return 0;
}

void run_kqueue(char *dir)
{
	struct kevent event;
	watch_t *w;
	int i;

	if ((kq = kqueue()) == -1)
		err(1,"kqueue failed");
	
	init = 1;
	ftw(dir, add_dir_kqueue, MAX_OPENFD);
	init = 0;

	for (;;) {
		i = kevent(kq, NULL, 0, &event, 1, NULL);
		if (i == -1 && errno != EINTR)
			err(1, "kevent read");
		if (i > 0) {
			w = (watch_t *)event.udata;
			if(event.ident == w->wd) {
				ftw(w->path, add_dir_kqueue, MAX_OPENFD);
			} else if (event.ident == w->outwd) {
				spawn_win(w);
			}
		}
	}
}
#endif // HAVE_KQUEUE

int main(int argc, char **argv)
{
	struct stat st;
	char *dir = NULL;
	int i;

	if (argc<2) usage();
	for (i = 1; i < argc; i++) {
		if (argv[i][0] != '-') {
			if(!dir) { dir = argv[i]; continue; }
			else usage();
		}
		if (argv[i][2] != '\0')
			usage();
		switch (argv[i][1]) {
			case 'c': color = 1; nick = argv[++i]; break;
			default: usage();
		}
	}
	if (!dir) usage();

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

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

#ifdef HAVE_INOTIFY
	run_inotify(dir);
#else
	run_kqueue(dir);
#endif

	return 0;
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.