/*
 * screenaction.c
 * (c) 2004 gophi
 * gophi@linux.net.pl
 *
 * 20:32:06 ::: Rozpoczęto rozmowę z kip
 * 20:32:06 ::: Wciśnij Alt-G by ignorować, Alt-K by zamknąć okno
 * 20:32:06 .-- kip/1460680 --- -- -
 * 20:32:06 | hej
 * 20:32:06 `----- ---- --- -- -
 * 20:32:18 .-- gophi/1234 --- -- -
 * 20:32:18 | kip.
 * 20:32:18 `----- ---- --- -- -
 * 20:32:18 .-- gophi/1234 --- -- -
 * 20:32:18 | :)
 * 20:32:18 `----- ---- --- -- -
 * 20:32:18 .-- kip/1460680 --- -- -
 * 20:32:18 | Stan: dostępny
 * 20:32:18 `----- ---- --- -- -
 * 20:32:19 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:32:19 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:32:19 .-- gophi/1234 --- -- -
 * 20:32:19 | elo
 * 20:32:19 `----- ---- --- -- -
 * 20:32:19 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:32:27 .-- kip/1460680 --- -- -
 * 20:32:27 | mam pytajnik :)
 * 20:32:27 `----- ---- --- -- -
 * 20:32:49 .-- gophi/1234 --- -- -
 * 20:32:49 | masz. ;)
 * 20:32:49 `----- ---- --- -- -
 * 20:32:49 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:32:59 .-- kip/1460680 --- -- -
 * 20:32:59 | czy wykonalne jest automagiczne wysylanie czegos do rurki 
 * 20:32:59 | ekg przy attachu i detachu screena?
 * 20:32:59 `----- ---- --- -- -
 * 20:33:14 .-- kip/1460680 --- -- -
 * 20:33:14 | np. /away i /back ? :)
 * 20:33:14 `----- ---- --- -- -
 * 20:33:16 .-- gophi/1234 --- -- -
 * 20:33:16 | hmmm
 * 20:33:16 `----- ---- --- -- -
 * 20:33:16 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:33:22 .-- gophi/1234 --- -- -
 * 20:33:22 | * gophi think think
 * 20:33:22 `----- ---- --- -- -
 * 20:33:23 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:33:29 .-- gophi/1234 --- -- -
 * 20:33:29 | da się.
 * 20:33:29 `----- ---- --- -- -
 * 20:33:29 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:33:35 .-- gophi/1234 --- -- -
 * 20:33:35 | nawet bez hakowania screena.
 * 20:33:35 `----- ---- --- -- -
 * 20:33:35 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:33:39 .-- kip/1460680 --- -- -
 * 20:33:39 | ladnie sie usmiecham :)
 * 20:33:39 `----- ---- --- -- -
 * 20:33:50 .-- gophi/1234 --- -- -
 * 20:33:50 | jak detachujesz screena to zmienia się tryb +x twojej rurki screenowej
 * 20:33:50 `----- ---- --- -- -
 * 20:33:50 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:33:57 .-- gophi/1234 --- -- -
 * 20:33:57 | tzn. jak detachujesz to się robi -x
 * 20:33:57 `----- ---- --- -- -
 * 20:33:57 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:34:00 .-- gophi/1234 --- -- -
 * 20:34:00 | a jak attachujesz +x
 * 20:34:00 `----- ---- --- -- -
 * 20:34:00 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:34:15 .-- gophi/1234 --- -- -
 * 20:34:15 | więc można np. co 5s sprawdzać tryb i jak się zmieni, wysyłać.
 * 20:34:15 `----- ---- --- -- -
 * 20:34:15 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:34:22 .-- gophi/1234 --- -- -
 * 20:34:22 | chcesz? :)
 * 20:34:22 `----- ---- --- -- -
 * 20:34:22 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:34:37 .-- kip/1460680 --- -- -
 * 20:34:37 | chce. Ale z odswiezaniem raczej rzedu 1 min
 * 20:34:37 `----- ---- --- -- -
 * 20:34:45 .-- gophi/1234 --- -- -
 * 20:34:45 | okej, wszystko configurable ;)
 * 20:34:45 `----- ---- --- -- -
 * 20:34:45 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:34:57 .-- kip/1460680 --- -- -
 * 20:34:57 | tylko ze nie /away a /invisible
 * 20:34:57 `----- ---- --- -- -
 * 20:34:58 .-- kip/1460680 --- -- -
 * 20:34:58 | :)
 * 20:34:58 `----- ---- --- -- -
 * 20:35:02 .-- gophi/1234 --- -- -
 * 20:35:02 | spoko, ustawisz sobie :)
 * 20:35:02 `----- ---- --- -- -
 * 20:35:03 ::: Wiadomość do kip/1460680 została dostarczona
 * 20:35:09 .-- kip/1460680 --- -- -
 * 20:35:09 | super :)
 * 20:35:09 `----- ---- --- -- -
 * 20:35:13 .-- kip/1460680 --- -- -
 * 20:35:13 | tylko mi powiesz gdzie :)
 * 20:35:13 `----- ---- --- -- -
 * 20:35:17 .-- gophi/1234 --- -- -
 * 20:35:17 | okej
 * 20:35:17 `----- ---- --- -- -
 * 20:35:17 ::: Wiadomość do kip/1460680 została dostarczona
 *
 * changelog:
 *
 *  * 09.05.2004 v1.0 wersja pierwsza, dziewicza.
 *
 * bugs:
 *
 *  * chyba nie ma ;)
 *
 */

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <dirent.h>
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>

#define PIPES_PATH "/var/run/screen/S-%"	/* % - user */
#define PID_FILE "/home/%/.screenaction.pid"	/* % - user */
#define CHECK_RATE 30	/* co ile sprawdzać stan attachnięcia */
#define ATTACH_CMD "/home/%/bin/on-attach"	/* % - jak wyżej */
#define DETACH_CMD "/home/%/bin/on-detach"
#define panic(...) _panic (__LINE__, __VA_ARGS__)

char *pipes_path = PIPES_PATH;
char *pid_file = PID_FILE;
int check_rate = CHECK_RATE;
char *attach_cmd = ATTACH_CMD;
char *detach_cmd = DETACH_CMD;
char foreground = 0;

extern int errno;
extern char *optarg;
extern int optind, opterr, optopt;

char *our_pipe;	/* aktualna rurka */
char attached;	/* czy screen jest attachnięty */
char *progname = "screenaction";
volatile int signo = 0;

void _panic (int line, char *fmt, ...)
{
	va_list args;
	char buf[1024];

	va_start (args, fmt);
	vsnprintf (buf, sizeof(buf), fmt, args);
	va_end (args);

	fprintf (stderr, "%s[%u]:%d: %s\n", progname, getpid(), line, buf);
	_exit (-1);
}

void set_progname (char **argv)
{
	if (!argv || !argv[0])
		return;

	progname = strrchr(argv[0], '/');
	if (progname)
		progname++;
	else
		progname = argv[0];
}

char *get_username (void)
{
	static struct passwd *pw;

	pw = getpwuid(getuid());
	return pw->pw_name;
}

void fasthelp (void)
{
	printf ("screenaction - program wywołujący określone zadania po\n");
	printf ("zattachowaniu / zdetachowaniu screena. by gophi@apcoh.org.\n\n");
	printf ("składnia: %s [opcje]\n", progname);
	printf ("opcje:\n");
	printf ("  -h: ten fasthelp\n");
	printf ("  -f: nie forkuje w tło (implikuje -p /dev/null)\n");
	printf ("  -p pipe[dir]: określa katalog bądź lokalizację screenrurki\n");
	printf ("  -r czas: określa, co ile sekund sprawdzany jest stan sesji\n");
	printf ("  -a program: program lub skrypt wywoływany po attachnięciu\n");
	printf ("  -d program: program lub skrypt wywoływany po detachnięciu\n");
	printf ("  -i plik: lokalizacja pidfile\n\n");
	printf ("jeśli -p wskazuje na katalog, w którym jest tylko jedna rurka,\n");
	printf ("to program uzna, że chodzi właśnie o tą rurkę. jeśli nie podano\n");
	printf ("opcji -f, to plik podany przy opcji -i jest przy starcie sprawdzany,\n");
	printf ("jeśli istnieje to czytany jest z niego pid, jeśli ten pid żyje, to\n");
	printf ("program jest kończony, a jeśli nie żyje, to pidfile jest usuwany\n");
	printf ("i program wykonuje się dalej. przy opcjach -p, -a, -d oraz -i można\n");
	printf ("podać %%, które zostanie zamienione na %s.\n", get_username());
}

char *expand_user (char *str)
{
	char *out_str = (char *) NULL;
	int in_index = 0, out_index = 0;
	char ch;

	while (!0) {
		ch = str[in_index++];
		if (!ch)
			break;
		else if (ch == '%') {
			int home_index = 0;
			char *home = get_username();
			while (home[home_index]) {
				out_str = (char *) realloc(out_str, out_index + 1);
				out_str[out_index++] = home[home_index++];
			}
		} else {
			out_str = (char *) realloc(out_str, out_index + 1);
			out_str[out_index++] = ch;
		}
	}

	out_str = (char *) realloc(out_str, out_index + 1);
	out_str[out_index] = (char) NULL;

	return out_str;
}

void parse_options (int argc, char **argv)
{
	char done = 0;

	while (!done) switch(getopt(argc, argv, "hfp:r:a:d:i:")) {
		case 'h':
			fasthelp();
			_exit (0);
		case 'f':
			foreground = 1;
			break;
		case 'p':
			pipes_path = optarg;
			break;
		case 'r':
			check_rate = atoi(optarg);
			break;
		case 'a':
			attach_cmd = optarg;
			break;
		case 'd':
			detach_cmd = optarg;
			break;
		case 'i':
			pid_file = optarg;
			break;
		case -1:
			done = 1;
			break;
		default:
			_exit (0);
	}

	pipes_path = expand_user(pipes_path);
	pid_file = expand_user(pid_file);
	attach_cmd = expand_user(attach_cmd);
	detach_cmd = expand_user(detach_cmd);
}

char is_dir (char *path)
{
	struct stat st;

	if (stat(path, &st) == -1) {
		if (foreground)
			fprintf (stderr, "Brak %s.\n", path);
		return -1;
	}

	if (S_ISDIR(st.st_mode)) {
		if (foreground)
			fprintf (stderr, "%s jest katalogiem.\n", path);
		return 1;
	}

	if (foreground)
		fprintf (stderr, "%s jest plikiem.\n", path);

	return 0;
}

char *get_pipe (char *path)
{
	char *p;
	DIR *d;
	struct dirent *dent;
	char done = 0;

	d = opendir(path);
	if (!d)
		panic ("opendir %s: %m", path);

	p = (char *) NULL;
	while (!p) {
		dent = readdir(d);
		if (!dent)
			panic ("readdir %s: %m", path);
		else if (dent->d_name[0] == '.')
			continue;
		else {
			p = (char *) malloc(strlen(path) + strlen(dent->d_name) + 2);
			snprintf (p, strlen(path) + strlen(dent->d_name) + 2, "%s/%s", path, dent->d_name);
		}
	}

	if (!p)
		panic ("readdir %s: %s", path, strerror(ENOENT));

	closedir (d);
	return p;
}

char can_exec (char *path)
{
	struct stat st;

	if (stat(path, &st) == -1 || !(st.st_mode & S_IXUSR))
		return 0;

	return 1;
}

int count_files (char *path)
{
	int i = 0;
	DIR *d;
	struct dirent *dent;
	char done = 0;

	d = opendir(path);
	if (!d)
		panic ("opendir %s: %m", path);

	while (!done) {
		dent = readdir(d);
		if (!dent)
			done = 1;
		else if (dent->d_name[0] == '.')
			continue;
		else
			i++;
	}

	closedir (d);
	return i;
}

void check_options (void)
{
	if (check_rate < 3)
		panic ("check_rate < 3");
	else if (check_rate > 86400)
		panic ("check_rate > 86400");

	/* tak, wiem, race condition, bla bla bla */

	switch (is_dir(pipes_path)) {
		case -1:
			panic ("%s: %s", pipes_path, strerror(ENOENT));
		case 0:
			our_pipe = pipes_path;
			break;
		case 1:
			switch (count_files(pipes_path)) {
				case 0:
					panic ("%s/*: %s", pipes_path, strerror(ENOENT));
				case 1:
					our_pipe = get_pipe(pipes_path);
					break;
				default:
					panic ("%s: Za dużo rurek, określ dokładnie.", pipes_path);
			}
	}

	if (foreground)
		fprintf (stderr, "Używam rurki %s.\n", our_pipe);
}

void sighnd (int _signo)
{
	signo = _signo;
	signal (_signo, sighnd);
}

char check_pid (void)
{
	struct stat st;
	FILE *fp;
	pid_t pid;

	if (stat(pid_file, &st) == -1)
		return;

	fp = fopen(pid_file, "r");
	if (!fp)
		panic ("fopen %s, \"r\": %m", pid_file);

	fscanf (fp, "%u", &pid);
	fclose (fp);

	if (kill(pid, 0) == -1)
		unlink (pid_file);
	else
		panic ("Już jest uruchomiony jeden screenaction.");
}

void do_action (char *cmd)
{
	if (foreground)
		fprintf (stderr, "Wykonywanie %s.\n", cmd);

	system (cmd);
}

int main (int argc, char **argv)
{
	char new_exec;

	set_progname (argv);
	parse_options (argc, argv);
	check_options();

	if (!foreground)
		check_pid();

	signal (SIGTERM, sighnd);
	signal (SIGINT, sighnd);

	if (!foreground) {
		FILE *fp;

		if (fork())
			return 0;
		setsid();
		fp = fopen(pid_file, "w");
		if (!fp)
			panic ("fopen %s, \"w\": %m", pid_file);
		fprintf (fp, "%u\n", getpid());
		fclose (fp);
	}

	attached = can_exec(our_pipe);
	while (!signo) {
		usleep (check_rate * 1000000);
		new_exec = can_exec(our_pipe);
		if (attached ^ new_exec) {
			attached = new_exec;
			if (new_exec)
				do_action (attach_cmd);
			else
				do_action (detach_cmd);
		}
	}

	if (!foreground)
		unlink (pid_file);

	signal (SIGTERM, SIG_DFL);
	signal (SIGINT, SIG_DFL);

	return 0;
}
