#!mkcmd
# $Id: daemon.m,v 1.8 2009/01/15 20:29:15 ksb Exp $
from '<stdio.h>'
from '<unistd.h>'
from '<stdlib.h>'
from '<errno.h>'
from '<pwd.h>'
from '<grp.h>'
from '<string.h>'
from '<fcntl.h>'
from '<signal.h>'
from '<sys/stat.h>'
from '<sys/param.h>'
from '<sys/types.h>'

from '"config.h"'
from '"machine.h"'
from '"main.h"'

require "std_help.m" "std_version.m"

augment action 'V' {
	user "Version();"
}
%i
static const char rcsid[] =
	"$Id: daemon.m,v 1.8 2009/01/15 20:29:15 ksb Exp $";

static const char acDefFile[] =
	"/var/run/%s.pid";
%%

basename "daemon" ""

boolean 'c' {
	named "fIntoSlash"
	help 'change the current working directory to the root'
}
boolean 'f' {
	named "fClose"
	help 'redirect standard input, output and error to /dev/null'
}

char* 'p' {
	named "pcPidFile"
	param "pidfile"
	help "write new process ID to this file, delete on failure"
}
char* 'u' {
	named "pcUser"
	param "user"
	help "drop cerdentials to this login before utility execution"
}
char* named "pcCommand" {
	param "utility"
	help "the path to the new detached program"
}
left "pcCommand" {
}
list {
	named "List"
	param "arguments"
	help "any arguements for utility"
}

%c
/* Drop the lock on the given LockOn'd fd, called after LockOn below.	(ksb)
 * When using lockf(3) we need to be at the first byte of the fd (lseek 0).
 * Closing the descriptor (fclosing the (FILE*)) actually works as well.
 */
static int
LockOff(int fd)
{
#if USE_FCNTL
	auto struct flock Cntl;
#endif

#if USE_FLOCK
	if (-1 == flock(fd, LOCK_UN))
		return -1;
#else
#if USE_FCNTL
	memset(&Cntl, '\000', sizeof(Cntl));
	Cntl.l_start = Cntl.l_len = 0;
	Cntl.l_pid = -1;
	Cntl.l_type = F_UNLCK;
	Cntl.l_whence = SEEK_SET;
	if (-1 == fcntl(fd, F_SETLK, &Cntl))
		return -1;
#else /* use lockf */
	if (-1 == lockf(fd, F_ULOCK, 0))
		return -1;
#endif
#endif
	return 0;
}

/* Aquire a lock on the given fd (which should be RDWR on a plain file)	(ksb)
 * return -1 for system error, 0 for failed to lock, 1 for good aquisition,
 * we will always do a non-blocking lock request.  Also check that the file
 * has not rolled away (mv, rm, etc.) from us.  Using lockf -> 1st seek 0.
 */
static int
LockOn(int fd, char *pcPath)
{
	auto struct stat stFd, stPath;
#if USE_FCNTL
	auto struct flock Cntl;
#endif

	if (-1 == fstat(fd, &stFd))
		return -1;
#if USE_FLOCK
	if (-1 == flock(fd, LOCK_EX|LOCK_NB))
		return 0;
#else
#if USE_FCNTL
	memset(&Cntl, '\000', sizeof(Cntl));
	Cntl.l_start = Cntl.l_len = 0;
	Cntl.l_pid = -1;
	Cntl.l_type = F_WRLCK;
	Cntl.l_whence = SEEK_SET;
	if (-1 == fcntl(fd, F_SETLK, &Cntl))
		return 0;
#else /* use lockf */
	if (-1 == lockf(fd, F_TLOCK, 0))
		return (EAGAIN == errno) ? 0 : -1;
#endif
#endif
	/* We fail when someone else removed the file or made a new one.
	 */
	if (-1 == stat(pcPath, &stPath) || stPath.st_ino != stFd.st_ino || stPath.st_dev != stFd.st_dev) {
		(void)LockOff(fd);
		return 0;
	}
	return 1;
}

/* The common ksb requirement, show me your version			(ksb)
 */
static void
Version()
{
	printf("%s: default pid file template: %s\n", progname, acDefFile);
	printf("%s: lock type: %s\n", progname,
#if USE_FLOCK
		"flock"
#else
#if USE_FCNTL
		"fcntl"
#else
		"lockf"
#endif
#endif
		);
}

/* We know we are called from mkcmd, so don't change that.		(ksb)
 * We can write at least 1 slot backwards in argv, because mkcmd took both
 * progname and pcUtility from the original argv, which we index.
 */
static int
List(int argc, char **argv)
{
	register char *pcTail;
	register int i, fdDevNull, fdPid;
	auto struct passwd *pwWho;
	auto FILE *fpOldErr;
	auto int iCode, aiPipe[2];
	auto pid_t wChild;
	struct sigaction saWas, saNew;
	static const char acDevNull[] = "/dev/null";

	/* Make sure 0, 1, 2 are open (reopened) to start with, but save
	 * stderr if we need to close it.
	 */
	fdDevNull = -1;
	fpOldErr = stderr;
	for (i = 0; i < 3; ++i) {
		auto int iJunk;

		if (fClose && 2 == i) {
			if (-1 == (iJunk = dup(2))) {
				fprintf(stderr, "%s: dup: stderr: %s\n", progname, strerror(errno));
				exit(1);
			}
			if ((FILE *)0 == (fpOldErr = fdopen(iJunk, "w"))) {
				fprintf(stderr, "%s: fdopen: %ld: %s\n", progname, (long)iJunk, strerror(errno));
				exit(1);
			}
			(void)fcntl(iJunk, F_SETFD, 1);
		}
		if (fClose)
			close(i);
		else if (-1 != fcntl(i, F_GETFD, & iJunk))
			continue;

		if (-1 == fdDevNull)
			fdDevNull = open(acDevNull, O_RDWR, 0666);
		else
			dup(fdDevNull);
	}

	/* Act like a wrapper and set argv[0] to our name if we are not
	 * the default program name.
	 */
	--argv;
	if (0 == strcmp(progname, "daemon")) {
		argv[0] = pcCommand;
	} else {
		argv[0] = progname;
	}
	if ((char *)0 != (pcTail = strchr(argv[0], '/'))) {
		argv[0] = pcTail+1;
	}

	/* Map "-p -" to "-p /var/run/$(basename $utility).pid"
	 */
	if ((char *)0 != pcPidFile && '-' == pcPidFile[0] && '\000' == pcPidFile[1]) {
		register char *pcBasename;

		if ((char *)0 == (pcBasename = strrchr(pcCommand, '/')))
			pcBasename = pcCommand;
		else
			++pcBasename;
		if ('\000' == *pcBasename) {
			fprintf(stderr, "%s: -p: no value default name\n", progname);
			exit(2);
		}
		i = sizeof(acDefFile)+strlen(pcBasename)+1;
		if ((char *)0 == (pcPidFile = malloc(i))) {
			fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno));
			exit(2);
		}
		snprintf(pcPidFile, i, acDefFile, pcBasename);
	}

	/* Map "-u ''" -> "-u nobody", also allow a group specification.
	 */
	setpwent();
	pwWho = (struct passwd *)0;
	if ((char *)0 != pcUser) {
		register struct group *pwGroup;
		register char *pcGroup;
		register gid_t wDropGid;

		if ((char *)0 != (pcGroup = strchr(pcUser, ':'))) {
			*pcGroup++ = '\000';
		}
		if ('\000' == *pcUser) {
			pcUser = "nobody";
		}
		if ((struct passwd *)0 == (pwWho = getpwnam(pcUser))) {
			fprintf(fpOldErr, "%s: getpwname: %s: %s\n", progname, pcUser, strerror(errno));
			exit(1);
		}
		wDropGid = pwWho->pw_gid;
		if ((char *)0 != pcGroup && '\000' != pcGroup) {
			setgrent();
			if ((struct group *)0 == (pwGroup = getgrnam(pcGroup))) {
				fprintf(fpOldErr, "%s: getgrnam: %s: %s\n", progname, pcGroup, strerror(errno));
				exit(1);
			}
			wDropGid = pwGroup->gr_gid;
			endgrent();
		}
		if ( -1 == initgroups(pcUser, pwWho->pw_gid)) {
			fprintf(fpOldErr, "%s: initgroups: %s: %s\n", progname, pcUser, strerror(errno));
			exit(1);
		}
		if (-1 == setgid(wDropGid)) {
			fprintf(fpOldErr, "%s: setgid: %ld: %s\n", progname, (long)wDropGid, strerror(errno));
			exit(1);
		}
		pwWho->pw_gid = wDropGid;
	}

	/* Locate or build the pid file.
	 */
	fdPid = -1;
	if ((char *)0 != pcPidFile) {
		register pid_t wPeer;
		auto char *pcEnd;
		auto char acLiving[128]; /* %ld: peer pid read from file */

		if (-1 != (fdPid = open(pcPidFile, O_RDWR, 0644))) {
			/* We found an existing one to update */
		} else if (-1 == (fdPid = open(pcPidFile, O_RDWR|O_CREAT, 0666))) {
			fprintf(fpOldErr, "%s: open: %s: %s\n", progname, pcPidFile, strerror(errno));
			exit(1);
		} else if ((struct passwd *)0 != pwWho) {
			(void)fchown(fdPid, pwWho->pw_uid, pwWho->pw_gid);
		}
		switch (LockOn(fdPid, pcPidFile)) {
		case -1:
			/* failed to lock at all */
			exit(2);
		case 0:
			/* raced, or she is running already */
			exit(3);
		case 1:
			/* When we have a pid check it, non-numerics stop us,
			 * while zero-length file is always good.
			 */
			if (0 != read(fdPid, acLiving, sizeof(acLiving))) {
				wPeer = (pid_t)strtol(acLiving, &pcEnd, 0);
				if (pcEnd == acLiving || -1 != kill(wPeer, 0)) {
					exit(3);
				}
			}
			if (-1 == lseek(fdPid, 0, SEEK_SET)) {
				fprintf(fpOldErr, "%s: lseek: %s\n", progname, strerror(errno));
				exit(2);
			}
			if (-1 == ftruncate(fdPid, (off_t)0)) {
				fprintf(fpOldErr, "%s: ftruncate: %s: %s\n", progname, pcPidFile, strerror(errno));
				exit(2);
			}
			break;
		}
	}

	/* Drop to the user, as needed.
	 */
	if ((struct passwd *)0 != pwWho && -1 == setuid(pwWho->pw_uid)) {
		fprintf(fpOldErr, "%s: setuid: %s: %s\n", progname, pcUser, strerror(errno));
		exit(1);
	}
	endpwent();

	/* Used to be sure the child won on the execvp, so we exit correctly.
	 */
	if (-1 == pipe(aiPipe)) {
		fprintf(fpOldErr, "%s: pipe: %s\n", progname, strerror(errno));
		exit(1);
	}
	switch (wChild = fork()) {
		auto char acBust[64];	/* %ld\n for pid, or %c from child */
	case -1:
		fprintf(fpOldErr, "%s: fork: %s\n", progname, strerror(errno));
		exit(1);
	case 0:
		close(fdPid);
		break;
	default:
		close(aiPipe[1]);
		iCode = read(aiPipe[0], acBust, sizeof(acBust));
		if (0 != iCode) {
			if (-1 != fdPid)
				LockOff(fdPid);
			exit(1);
		}
		if (-1 != fdPid) {
			snprintf(acBust, sizeof(acBust), "%ld\n", (long)wChild);
			i = strlen(acBust);
			if (i != write(fdPid, acBust, i)) {
				fprintf(fpOldErr, "%s: write: %s: %s\n", progname, pcPidFile, strerror(errno));
			}
			LockOff(fdPid);
			close(fdPid);
		}
		exit(0);
	}

	/* We are the child, don't mess with fd's > 3 (we didn't instance)
	 */
	close(aiPipe[0]);
	(void)fcntl(aiPipe[1], F_SETFD, 1);
	if (fIntoSlash && -1 == chdir("/")) {
		fprintf(fpOldErr, "%s: chdir: /: %s\n", progname, strerror(errno));
		write(aiPipe[1], "/", 1);
		exit(0);
	}

	/* Ignore sighup if we can, not to worry if we can't
	 */
	sigemptyset(&saNew.sa_mask);
	saNew.sa_handler = SIG_IGN;
	saNew.sa_flags = 0;
	(void)sigaction(SIGHUP, &saNew, &saWas);
	if (-1 == setsid()) {
		fprintf(fpOldErr, "%s: setsid: %s\n", progname, strerror(errno));
		write(aiPipe[1], "~", 1);
		exit(0);
	}
	execvp(pcCommand, argv);
	fprintf(fpOldErr, "%s: execvp: %s: %s\n", progname, pcCommand, strerror(errno));
	write(aiPipe[1], "!", 1);
	fsync(aiPipe[1]);
	exit(0);
}
%%
