#!mkcmd # $Id: generic.m,v 1.13 2011/10/13 19:38:25 ksb Exp $ # The generic wrapper template -- ksb # Find the 31 comments marked with double-percent (%%) below, do # what is implies you should then delete the comment. You should # end up with a wrapper. from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' from '' # %% add any mkcmd from directives or require directives near here %% from '"machine.h"' require "util_errno.m" require "util_tmp.m" "util_divstack.m" "util_divconnect.m" require "std_help.m" "std_version.m" # %% below with " to option t" change "t" to your direct option %% # %% access option leter %% key "divClient" 1 { "acMyDefName" 't' } # %% change your default basename, and option environment vaiable %% basename "%%prog%%" "" getenv "%%PROG%%" %i /* %% include anything you need here with a normal #include %% */ static char rcsid[] = "$Id: generic.m,v 1.13 2011/10/13 19:38:25 ksb Exp $", /* %% the name the doesn't change argv[0] in the inferior %% */ acMyDefName[] = "%% myself %%", /* %% change the mktemp afix "prog" to your favorite %% */ acMySuffix[] = "/%%prog%%XXXXXX"; /* mkdtemp suffix in $TMPDIR */ static char *pcMyTemp = (char *)0, /* if we had to build one */ *pcMySpace = (char *)0, /* where we hide to work */ *pcMySocket = (char *)0; /* my UNIX domain socket */ #if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER) static const char acAcceptFilter[] = "dataready"; #endif /* %% any required forward declaration, ignore the double-percent below %% */ %% augment action 'V' { user "Version();" } # %% this is the "direct" option from ptbw, pick your letter %% char* 't' { named "pcTags" param "tags" help "source which contains controlled resource tags" } boolean 'm' { once track named "fMaster" help "manage output for descendant processes" boolean 'd' { named "fPublish" init "1" help "do not publish this level in the linked environment" } char* 'N' { named "pcBindHere" param "path" help "force a UNIX domain name for this service" } exclude 'Vh' list { named "Master" param "utility" help "control process for task coordination" } } boolean 'Q' { named "fQuit" init '0' help "tell the enclosing persistent instance to finish" } list { named "Client" param "client" help "client command" } %c /* Build a temp directory for my space (ksb) */ static char * MyPlace() { register char *pcRet; extern char *mkdtemp(char *); if ((char *)0 == (pcRet = malloc(((strlen(pcTmpdir)+sizeof(acMySuffix))|7)+1))) { fprintf(stderr, "%s: malloc: %s\n", progname, strerror(errno)); exit(EX_OSERR); } (void)strcat(strcpy(pcRet, pcTmpdir), acMySuffix); return mkdtemp(pcRet); } /* When we are nested inside an xclate we can use the temp directory (ksb) * it built to hide our service socket. Otherwise we must build one * to hide us from the cold cruel world (eval dirname \$xcl_${xcl_link}). */ static void SafePlace(const char *pcInside) { register char *pcEnv, *pcTail; /* %% you might pick another friend, rather than xcl_ below %% */ static const char acXclGuess[] = "xcl_%s"; auto char acIndex[(sizeof(acXclGuess)|7)+(32+1)]; pcMyTemp = (char *)0; #if HAVE_SNPRINTF snprintf(acIndex, sizeof(acIndex), acXclGuess, "link"); #else sprintf(acIndex, acXclGuess, "link"); #endif if ((char *)0 != (pcEnv = divCurrent((char *)0))) { /* try to buddy with an enclosing instance */ } else if ((char *)0 != (pcEnv = getenv(acIndex)) && strlen(pcEnv) <= 32 && (isdigit(*pcEnv) || 'd' == *pcEnv)) { #if HAVE_SNPRINTF snprintf(acIndex, sizeof(acIndex), acXclGuess, pcEnv); #else sprintf(acIndex, acXclGuess, pcEnv); #endif pcEnv = getenv(acIndex); } if ((char *)0 == pcEnv || (char *)0 == (pcTail = strrchr(pcEnv, '/')) || pcEnv == pcTail) { pcMySpace = pcMyTemp = MyPlace(); return; } *pcTail = '\000'; pcMySpace = strdup(pcEnv); *pcTail = '/'; } /* Make the server port for this server to listen on (ksb) * stolen from ptyd, xclate, and others I've coded. */ static int MakeServerPort(pcName) char *pcName; { register int sCtl, iRet, iListen; auto struct sockaddr_un run; #if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER) auto struct accept_filter_arg FA; #endif if ((sCtl = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { fprintf(stderr, "%s: socket: %s\n", progname, strerror(errno)); exit(EX_OSERR); } #if HAVE_SUN_LEN run.sun_len = 0; /* hush valgrind */ #endif run.sun_family = AF_UNIX; (void) strcpy(run.sun_path, pcName); if (-1 == bind(sCtl, (struct sockaddr *)&run, EMU_SOCKUN_LEN(pcName))) { return -1; } /* On some UNIX-like systems we need a sockopt set first -- sigh */ /* %% you might need to prep the unix domain socket %% */ /* PidPrep(sCtl); */ iRet = -1; for (iListen = 20*64; iListen > 5 && -1 == (iRet = listen(sCtl, iListen)) ; iListen >>= 1) { /* try less */ } if (-1 == iRet) { fprintf(stderr, "%s: listen: %d: %s\n", progname, iListen, strerror(errno)); exit(EX_OSERR); } #if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER) (void)memset(&FA, '\000', sizeof(FA)); (void)strcpy(FA.af_name, acAcceptFilter); setsockopt(sCtl, SOL_SOCKET, SO_ACCEPTFILTER, &FA, sizeof(FA)); #endif (void)fcntl(sCtl, F_SETFD, 1); return sCtl; } /* Pass pcDirect = /tmp-dir/socket-name to contact * a ptbw-like service. */ int MakeClientPort(const char *pcDirect) { register int s, iFailed, iSlow; auto struct sockaddr_un run; for (iSlow = 0; ; iSlow ^= 1) { if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { return -1; } memset((void *)& run, '\000', sizeof(run)); run.sun_family = AF_UNIX; if (strlen(pcDirect) > sizeof(run.sun_path)-1) { errno = ENAMETOOLONG; goto failed; } (void)strcpy(run.sun_path, pcDirect); if (-1 != connect(s, (struct sockaddr *)&run, strlen(pcDirect)+2)) { (void)fcntl(s, F_SETFD, 1); break; } if (ECONNREFUSED != errno) { goto failed; } close(s); s = -1; /* try again */ sleep(iSlow); } /* %% any setup to get started here %% */ if (iFailed) { register int e; errno = EPFNOSUPPORT; failed: e = errno; (void)close(s); errno = e; return -1; } return s; } /* We don't want to leave trash around the filesystem (ksb) * Should be install with atexit before we call SafePlace or Bindings. */ static void CleanUp(void) { if ((char *)0 != pcMySocket) { (void)unlink(pcMySocket); pcMySocket = (char *)0; } if ((char *)0 != pcMyTemp) { (void)rmdir(pcMyTemp); pcMyTemp = (char *)0; } } /* Message passed as the (void *) target in calls to DiVersion (ksb) */ struct _WMnode { int iwidth; const char *pcnote; int fsawactive; /* %% you'll need to customize this, more than likely, see below %% */ const char *pcdirect; }; /* Format the info about a stacked (or diconnected) diversion (ksb) * (if we get to level 0 return true). */ static int DiVersion(const char *pcName, const char *pcTag, int fActive, void *pvMark) { auto struct stat stSock; register struct _WMnode *pWM = pvMark; register char *pcTail; if ((const char *)0 == pcName) { pcName = "--"; } if ((const char *)0 == pcTag) { printf("%s:%*s: missing from our environment", progname, pWM->iwidth, pcName); } else if (-1 != lstat(pcTag, & stSock)) { printf("%s:%*s: %s", progname, pWM->iwidth, pcName, pcTag); } else if (ENOTDIR != errno || (char *)0 == (pcTail = strrchr(pcTag, '/'))) { printf("%s:%*s: stat: %s: %s", progname, pWM->iwidth, pcName, pcTag, strerror(errno)); } else { *pcTail = '\000'; if (-1 == lstat(pcTag, & stSock) || S_IFSOCK != (S_IFMT & stSock.st_mode)) { printf("%s:%*s: lstat: %s: %s", progname, pWM->iwidth, pcName, pcTag, strerror(errno)); } else { printf("%s:%*s: %s/%s", progname, pWM->iwidth, pcName, pcTag, pcTail+1); } *pcTail = '/'; } if (fActive) { pWM->fsawactive = 1; printf("%s", pWM->pcnote); } /* %% if you don't have a pcTags direct option delete this if %% */ if ((const char *)0 != pWM->pcdirect && 0 == strcmp(pcTag, pWM->pcdirect)) { pWM->pcdirect = (const char *)0; printf(" [-t]"); } printf("\n"); if (fActive && !isdigit(*pcName)) { return 1; } return '0' == pcName[0] && '\000' == pcName[1]; } /* We output more than you'd want to know, good for debugging (ksb) */ static void Version() { register const char *pcLevel; auto unsigned uOuter; auto struct _WMnode WMParam; (void)divNumber(& uOuter); /* %% output any tunes or versions the Customer needs to see here %% */ divVersion(stdout); #if HAVE_ACCEPT_FILTER && defined(SO_ACCEPTFILTER) printf("%s: accept filter: %s\n", progname, acAcceptFilter); #endif printf("%s: safe directory template: %s\n", progname, acMySuffix+1); pcLevel = divCurrent((char *)0); WMParam.iwidth = (char *)0 == pcLevel ? 3 : strlen(pcLevel)+2; WMParam.pcnote = " [target]"; WMParam.fsawactive = 0; /* %% you might need more stuff here to setupt DiVersion %% * %% replace -t with your option letter below %% */ WMParam.pcdirect = pcTags; if (0 == divSearch(DiVersion, (void *)& WMParam) && iDevDepth >= uOuter) { if ((const char *)0 != WMParam.pcdirect) { DiVersion("-t", WMParam.pcdirect, !WMParam.fsawactive, (void *)&WMParam); } else if (0 == iDevDepth) { printf("%s: no current diversions\n", progname); } else { printf("%s: depth -%d is too great for current stack of %u\n", progname, iDevDepth, uOuter); } } else if ((const char *)0 != WMParam.pcdirect) { DiVersion("-t", WMParam.pcdirect, !WMParam.fsawactive, (void *)&WMParam); } else if (!WMParam.fsawactive) { printf("%s: never saw the active diversion\n", progname); } } /* %% put your support code here %% */ static void MyMaster(int fdCtl) { } static volatile pid_t wInferior = 0; static volatile int wInfExits = EX_SOFTWARE; static volatile int fInfOpts = WNOHANG|WUNTRACED; /* When a signal for a child arrives we burry the dead. (ksb) * If he is "our boy", then forget he's alive. */ static void Death(int _dummy) /*ARGSUSED*/ { register pid_t wReap; auto int wStatus; while (0 < (wReap = wait3(& wStatus, fInfOpts, (struct rusage *)0))) { if (WIFSTOPPED(wStatus)) { (void)kill(wReap, SIGCONT); continue; } if (wInferior == wReap) { wInferior = 0; wInfExits = wStatus; } } } /* Signal handler to remove our socket when we are signaled; (ksb) * we use atexit(3) to do that, by the way. */ static void DeadMan(int _dummy) /*ARGSUSED*/ { exit(0); } /* Build the service socket, put the mame in the environment (ksb) */ static int Bindings(unsigned uMake) { static const char acEnvRead[] = "PTBW";/* %% change this %% */ register char *pcTail; register unsigned int i; register int sRet; auto struct sigaction saWant; auto struct stat stLook; auto char acLevel[(sizeof(acEnvRead)|15)+(32+1)]; (void)memset((void *)& saWant, '\000', sizeof(saWant)); saWant.sa_handler = (void *)Death; #if HAVE_SIGACTION saWant.sa_sigaction = (void *)Death; #endif saWant.sa_flags = SA_RESTART; if (-1 == sigaction(SIGCHLD, & saWant, (struct sigaction *)0)) { fprintf(stderr, "%s: sigaction: CHLD: %s\n", progname, strerror(errno)); exit(EX_OSERR); } /* Accepting a -HUP signal to re-load the tableau data adds * uncertianty for no gain here, just install -TERM -- ksb */ (void)memset((void *)& saWant, '\000', sizeof(saWant)); saWant.sa_handler = (void *)DeadMan; #if HAVE_SIGACTION saWant.sa_sigaction = (void *)DeadMan; #endif saWant.sa_flags = SA_RESTART; if (-1 == sigaction(SIGINT, & saWant, (struct sigaction *)0)) { fprintf(stderr, "%s: sigaction: INT: %s\n", progname, strerror(errno)); exit(EX_OSERR); } (void)sigaction(SIGTERM, & saWant, (struct sigaction *)0); if ((char *)0 != pcBindHere) { if ('/' == *pcBindHere) { pcMySocket = pcBindHere; } else { register int iGuess; iGuess = (15|(strlen(pcMySpace)+strlen(pcBindHere)))+9; pcMySocket = malloc(iGuess); #if HAVE_SNPRINTF snprintf(pcMySocket, iGuess, "%s/%s", pcMySpace, pcBindHere); #else sprintf(pcMySocket, "%s/%s", pcMySpace, pcBindHere); #endif } if (-1 == (sRet = MakeServerPort(pcMySocket))) { fprintf(stderr, "%s: bind: %s: %s\n", progname, pcMySocket, strerror(errno)); exit(EX_OSERR); } } else { pcMySocket = malloc((strlen(pcMySpace)|63)+65); pcTail = strchr(strcpy(pcMySocket, pcMySpace), '\000'); if (pcTail > pcMySocket && '/' != pcTail[-1]) { *pcTail++ = '/'; } for (i = 0; 1; ++i) { /* %% socket mnemonic "dIv" can be any short word %% */ snprintf(pcTail, 64, "dIv%u", i); if (-1 != lstat(pcMySocket, & stLook)) continue; if (-1 != (sRet = MakeServerPort(pcMySocket))) break; /* We failed to get a bind in pcMySpace, if we * didn't make a space we still can, once. -- ksb * (Someone chmod'd our super-space ugo-w, sigh.) */ if ((char *)0 != pcMyTemp) { fprintf(stderr, "%s: bind: %s: %s\n", progname, pcMySocket, strerror(errno)); exit(EX_OSERR); } pcMySpace = pcMyTemp = MyPlace(); return Bindings(uMake); } } /* Notify the inferior processes of our IPC location */ if (fPublish) { divPush(pcMySocket); } else { divDetach(pcMySocket, (const char *)0); } /* Diddle the options variable $US_$level -> $US, I'm a wrapper */ (void)snprintf(acLevel, sizeof(acLevel), "%s_%u", acEnvRead, uMake); if ((char *)0 != (pcTail = getenv(acLevel))) { (void)setenv(acEnvRead, pcTail, 1); } else { (void)unsetenv(acEnvRead); } return sRet; } /* We are the client side, like xclate we include a client wrapper. (ksb) * %% fill in your client code %% */ static void Client(int argc, char **argv) { register int sHandle; register char *pcArgv0; register const char *pcOuter; register pid_t wReap; auto int wStatus; auto struct sigaction saWant; (void)memset((void *)& saWant, '\000', sizeof(saWant)); saWant.sa_handler = (void *)Death; #if HAVE_SIGACTION saWant.sa_sigaction = (void *)Death; #endif saWant.sa_flags = SA_RESTART; if (-1 == sigaction(SIGCHLD, & saWant, (struct sigaction *)0)) { fprintf(stderr, "%s: sigaction: %s\n", progname, strerror(errno)); exit(EX_OSERR); } pcOuter = divSelect(); if (-1 == (sHandle = MakeClientPort(pcOuter))) { fprintf(stderr, "%s: %s: cannot establish client connection\n", progname, pcOuter); exit(EX_DATAERR); } /* %% handle command line options %% */ switch (wInferior = fork()) { case -1: fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); exit(EX_OSERR); case 0: close(sHandle); /* %% we are about to become the inferior process %% * %% what do you need to setup here? %% */ if (0 != strcmp(acMyDefName, progname)) { argv[0] = progname; } (void)execvp(pcArgv0, argv); fprintf(stderr, "%s: execvp: %s: %s\n", progname, argv[0], strerror(errno)); exit(EX_OSERR); default: break; } fInfOpts = 0; Death('C'); /* Tell the master to abandon forever (:) mode, we get back the * number of client connected (ourself included) which we ignore. * If we got back "-No\n", then the master was not in : mode -- ksb */ if (fQuit) { /* %% tell the master to quit on sHandle %% */ } close(sHandle); exit(wInfExits); } /* Setup the master process and turn it loose (ksb) */ static void Master(int argc, char **argv) { register const char *pcArgv0, *pcOuterLevel; register unsigned uMasterLevel; auto int sCtl; static char *apcDefArgv[2]; if (0 == argc || (char **)0 == argv || (char *)0 == argv[0]) { argc = 1; argv = apcDefArgv; if ((char *)0 == (apcDefArgv[0] = getenv("SHELL"))) { apcDefArgv[0] = "sh"; } apcDefArgv[1] = (char *)0; } if ((char *)0 == (pcOuterLevel = divCurrent((char *)0))) { uMasterLevel = 1; } else { uMasterLevel = atoi(pcOuterLevel)+1; } atexit(CleanUp); SafePlace(pcOuterLevel); /* %% anyting we need to setup the Master's state machine %% */ sCtl = Bindings(uMasterLevel); if (':' == argv[0][0] && '\000' == argv[0][1] && (char *)0 == argv[1]) { wInferior = getpid(); } else switch (wInferior = fork()) { case -1: fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); (void)chdir("/"); exit(EX_OSERR); case 0: (void)close(sCtl); pcArgv0 = argv[0]; if (0 != strcmp(acMyDefName, progname)) { argv[0] = progname; } (void)execvp(pcArgv0, argv); fprintf(stderr, "%s: execvp: %s: %s\n", progname, argv[0], strerror(errno)); exit(EX_OSERR); default: break; } /* %% setup to the master process %% */ /* We are not really a daemon, but we don't want to be in the way, * so free us from stdin, stdout, and the current working dir. * We keep stderr for messages, but shouldn't spew any. */ (void)close(0); (void)open("/dev/null", O_RDONLY, 0666); (void)close(1); (void)open("/dev/null", O_WRONLY, 0666); (void)chdir(pcMySpace); MyMaster(sCtl /* other args */); /* %% if you have to tidy stuff (sCtl socket, files) do that here %% */ (void)chdir("/"); close(sCtl); /* wait for the wrapped process, if it hasn't finished yet */ fInfOpts = 0; Death('E'); exit(wInfExits); }