#!mkcmd # $Id: daemon.m,v 1.8 2009/01/15 20:29:15 ksb Exp $ from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' 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); } %%