#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <sys/wait.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/errno.h>
#include <sys/signal.h>
#include <string.h>
#include <time.h>
#include <getopt.h>
#include <err.h>
#include <pwd.h>

#ifndef MAXPATHLEN
#define MAXPATHLEN 512
#endif
#define DEFAULT_PID_FMT "/var/run/%s.pid"
int failcount = 5;
int failtime = 10;
char *pidfile = NULL;
char *userid = NULL;
char *ch_dir = NULL;
char *output = NULL;

void unlink_pid(void){
    if (pidfile)
	unlink(pidfile);
}

void
write_pid(const char *p)
{
    FILE *pf;
    if (NULL == pidfile) {
	static char pid_path[MAXPATHLEN];
	const char *t = strrchr(p, '/');
	if (t)
	    p = t;
	snprintf(pid_path, MAXPATHLEN, DEFAULT_PID_FMT, p);
	pidfile = pid_path;
    }
    pf = fopen(pidfile, "w");
    if (NULL != pf) {
	fprintf(pf, "%d\n", (int)getpid());
	atexit(unlink_pid);
	fclose(pf);
    }
}

void
usage(const char *me)
{
    fprintf(stderr, "usage: %s --user=login --pidfile=path --fail-count=N --fail-time=N\n", me);
    exit(1);
}

static struct option longopts[] = {
    {"user", required_argument, NULL, 'u'},
    {"pidfile", required_argument, NULL, 'p'},
    {"chdir", required_argument, NULL, 'c'},
    {"stdouterr", required_argument, NULL, 's'},
    {"fail-count", required_argument, &failcount, 1001},
    {"fail-time", required_argument, &failtime, 1002},
    {NULL, 0, NULL, 0}
};

int
main(int argc, char *argv[])
{
    pid_t pid = -1;
    int fd = -1;
    time_t start = 0;
    time_t stop = 0;
    int fails = 0;
    int status;
    char *appname;
    char *t;
    uid_t uid = 0;
    int ch;

    if ((t = strrchr(argv[0], '/')))
	appname = t + 1;
    else
	appname = argv[0];
    openlog(appname, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_DAEMON);

    while ((ch = getopt_long(argc, argv, "", longopts, NULL)) != -1)
	switch (ch) {
	case 'u':
	    userid = strdup(optarg);
	    break;
	case 'p':
	    pidfile = strdup(optarg);
	    break;
	case 'c':
	    ch_dir = strdup(optarg);
	    break;
	case 's':
	    output = strdup(optarg);
	    break;
	default:
	    usage(appname);
	    break;
	}
    argc -= optind;
    argv += optind;

    if (userid) {
	struct passwd *p = getpwnam(userid);
	if (NULL == p)
	    errx(1, "Unknown user '%s'", userid);
	uid = p->pw_uid;
	setenv("HOME", p->pw_dir, 1);
    }
    if ((pid = fork()) < 0)
	syslog(LOG_ALERT, "fork failed: %s", strerror(errno));
    else if (pid > 0)
	exit(0);
    if (setsid() < 0)
	syslog(LOG_ALERT, "setsid failed: %s", strerror(errno));
    closelog();
#ifdef TIOCNOTTY
    if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
	ioctl(fd, TIOCNOTTY, NULL);
	close(fd);
    }
#endif

    fd = open("/dev/null", O_RDWR);
    if (fd < 0)
	syslog(LOG_ERR, "/dev/null: %s\n", strerror(errno));
    else {
	dup2(fd, 0);
	dup2(fd, 1);
	dup2(fd, 2);
	close(fd);
    }
    if (output) {
	fd = open(output, O_WRONLY|O_APPEND);
	if (fd < 0)
	    syslog(LOG_ERR, "output: %s\n", strerror(errno));
	else {
	    dup2(fd, 1);
	    dup2(fd, 2);
	    close(fd);
	}
    }
    openlog(appname, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_DAEMON);

    for (;;) {
	if ((pid = fork()) == 0) {
	    /* child */
	    write_pid(argv[0]);
	    if (uid > 0) {
		syslog(LOG_NOTICE, "changing to user %s/%d", userid, uid);
		setuid(uid);
	    }
	    if (ch_dir)
		chdir(ch_dir);
	    syslog(LOG_NOTICE, "running '%s'", argv[0]);
	    execvp(argv[0], &argv[0]);
	    syslog(LOG_ALERT, "execvp '%s' failed: %s", argv[0], strerror(errno));
	}
	/* parent */
	openlog(appname, LOG_PID | LOG_NDELAY | LOG_CONS, LOG_DAEMON);
	time(&start);
	pid = waitpid(-1, &status, 0);
	time(&stop);

	if (WIFEXITED(status)) {
	    syslog(LOG_NOTICE, "child process %d exited with status %d",
		pid, WEXITSTATUS(status));
	} else if (WIFSIGNALED(status)) {
	    syslog(LOG_NOTICE, "child process %d exited due to signal %d",
		pid, WTERMSIG(status));
	} else {
	    syslog(LOG_NOTICE, "child process %d exited", pid);
	}

	if (stop - start < failtime)
	    fails++;
	else
	    fails = 0;
	if (fails == failcount) {
	    syslog(LOG_ALERT, "Exiting due to repeated, frequent failures");
	    exit(1);
	}
	if (WIFEXITED(status))
	    if (WEXITSTATUS(status) == 0)
		exit(0);
	if (WIFSIGNALED(status)) {
	    switch (WTERMSIG(status)) {
	    case SIGKILL:
		exit(0);
		break;
	    default:
		break;
	    }
	}
    }
}
