head 1.3; access; symbols; locks; strict; comment @ * @; 1.3 date 99.05.07.13.39.48; author alexis; state Exp; branches; next 1.2; 1.2 date 99.05.07.08.44.50; author alexis; state Exp; branches; next 1.1; 1.1 date 99.04.28.09.44.34; author alexis; state Exp; branches; next ; desc @@ 1.3 log @fork_and_submit_ipc_return_code() renamed to smf_fkwsmrc() smf_fkwsmrc() now uses waitpid() instead of wait() - this was to fix an error whereby killing the sendmail that ppplcd launched after the connection had come down with a SIGKILL gave an internal error lookup_msginfo() renamed smf_msginfo() smf_fkwsmrc()'s execer now uses execvp() instead of execv() - this was in preparation for allowing config commands to be of the form sh -c "cmd1; cmd2" smf_fkwsmrc()'s message submission now uses smf_submit(SMS_SUBMIT_QUEUE) instead of ipc_queue_submit() - this is effectively only a cosmetic change. @ text @/*@@unused@@*/ static char *sm_c_rcs_id = "$Header: /diskb/home/alexis/dev/supported/ppplc/bin/RCS/sm.c,v 1.2 1999/05/07 08:44:50 alexis Exp alexis $"; /******************************************************************************* * * System Includes * *******************************************************************************/ #include #include /* for SIGTERM, ... */ #include /* for strerror() */ #include /* for EXIT_* */ #include /* for NULL */ #include /* for calloc() */ #include /* for pwd.h */ #include /* for wait() macros */ #include /* for getpwuid() */ #include /* for getgrgid() */ #include /* for msgget(), ... */ #include /* for msgget(), ... */ #include /* for time_t ? (see time(2)) */ /******************************************************************************* * * Application Includes * *******************************************************************************/ #include "utils.h" /* for mybasename() and error fncs */ #include "sm.h" /******************************************************************************* * * Symbol Definitions * *******************************************************************************/ #define MSGSZ 128 #define C_QUEUE_SIZE 8 #define MAX_MSG_SIZ 1024 /******************************************************************************* * * Structure Definitions * *******************************************************************************/ struct c_queue_rec { time_t time; /* dequeue time */ int id; /* message id */ int *parp; /* parameter block pointer */ }; struct msgrec { long mtype; char mtext[MAX_MSG_SIZ]; }; /******************************************************************************* * * Static Function Forward Declarations * *******************************************************************************/ static void c_queue_process(struct msginfrec *, struct mhflkuprec *); static int ipc_queue_submit(struct msginfrec *, int, time_t, int, int *); static int c_queue_submit(time_t, int, int *); static int c_queue_del_msgid(int); static int lookup_msgmhf(struct mhflkuprec *, int); static int ipc_server_init(uid_t, gid_t, int); static int ipc_client_init(void); static int ipc_server_shutdown(void); static int ipc_queue_process(struct msginfrec *, int, int (*)(time_t, int, int *)); static void sigalrm_handler(int); /******************************************************************************* * * Static Data Definitions * *******************************************************************************/ static uid_t smd_interface_owner; static gid_t smd_interface_group; static int smd_interface_mode; static int c_queue_top; /* how many in that array? */ static int state_quit; static struct c_queue_rec c_queue[C_QUEUE_SIZE];/* for delayed messages */ static int key = 1234; static int msqid; /******************************************************************************* * * Global Data Definitions * *******************************************************************************/ /******************************************************************************* * * Functions: State Machine Interface (not called internally) * *******************************************************************************/ int smf_init( int init_mode, int (*assmf_init_p)(char *, uid_t *, gid_t *, int *), char *cp) { int rc; if (init_mode & SMS_MCMODE_SERVER) { state_quit = FALSE; c_queue_top = 0; if (rc=(*assmf_init_p)(cp, &smd_interface_owner, &smd_interface_group, &smd_interface_mode)) { debug(DBG_SM, "smf_init: assmf_init failed (errcode=%d)", rc); return(rc); } if (rc=ipc_server_shutdown()) { debug(DBG_SM, "smf_init: ipc_server_shutdown failed (errcode=%d)", rc); return(rc); } if (rc=ipc_server_init(smd_interface_owner, smd_interface_group, smd_interface_mode)) { debug(DBG_SM, "smf_init: ipc_server_init failed (errcode=%d)", rc); return(rc); } /* * IMPORTANT NOTE: * In this program SIGALRM is a way to say "a job that was already * present on the C job queue is now due for servicing". * However, processing the queue in the signal handler can * get *very* sticky, because the processing can *itself* modify the * queue. Far safer is to let the SIGALRM cause the wait on the IPC * queue to break out, and the main loop to simply cycle round to * checking the C queue. This way we don't get terribly recursive. * So, the handler does *nothing*. And this is the critical bit: * IGNORING SIGALRM IS NOT THE SAME AS HAVING A HANDLER WHICH DOES * NOTHING! THIS IS CRITICAL TO THE FUNCTIONING OF THIS LOOP! If * SIGALRM is ignored, then msgrcv() simply goes on waiting forever * and the (possibly overdue) message in the C queue is never * processed. If SIGALRM is left at its default behaviour then the * whole program aborts. However, if any handler is specified, then * msgrcv() exits with error code EINTR. We can test for this. So the * alarm handler *really* does nothing. It is just there so that this * signal() call can ensure that msgrcv() will exit when the SIGALRM * is generated. */ signal(SIGALRM, sigalrm_handler); } else if (init_mode & SMS_MCMODE_CLIENT) if (rc=ipc_client_init()) { debug(DBG_SM, "smf_init: ipc_client_init failed (errcode=%d)", rc); return(rc); } return(0); } int smf_setquit( int flg_write) { if (flg_write == SMS_VAR_TOGGLE) state_quit = !state_quit; return(state_quit); } int smf_submit( struct msginfrec *msginfo_datap, int rcrecipientid, time_t kickoff_time, int msgid, int *par_block_p, int submit_method) { if (submit_method == SMS_SUBMIT_DIRECT) return(c_queue_submit(kickoff_time, msgid, par_block_p)); else return(ipc_queue_submit(msginfo_datap, rcrecipientid, kickoff_time, msgid, par_block_p)); } int smf_engine( int smd_id, struct msginfrec *msginfo_datap, struct mhflkuprec *msghdlr_datap) { int rc; while (!state_quit) { /* * Try to read the IPC message queue. If an error occurs then abort. * If we get an interrupted system call then this should be becuase * an alarm has gone off, but since we can't be sure it's best to * to cancel any pending alarm anyway. (It will get set in the queue * processing code anyway.) */ rc = ipc_queue_process(msginfo_datap, smd_id, c_queue_submit); if (rc < 0) error("read error on ipc msg"); else if (rc == EINTR) (void) alarm(0); /* * Process the C queue. This may or may not set an alarm which * *should* interrupt the IPC read after we loop round. Note that * this should come *after* the IPC read in the loop, in order * that state_quit gets tested before the IPC read, otherwise * we can send a quit message, and the system doesn't quit until * any other message is recieved. */ c_queue_process(msginfo_datap, msghdlr_datap); } return(0); } int smf_shutdown() { ipc_server_shutdown(); } int smf_msginfo( struct msginfrec *msginfo_datap, int msgid) { int msgidx; debug(DBG_FUNCS, "smf_msginfo: sof"); for (msgidx=0; (msginfo_datap+msgidx)->id != msgid && (msginfo_datap+msgidx)->id != MSGID_PSEUDO_LAST; msgidx++) ; if ((msginfo_datap+msgidx)->id == MSGID_PSEUDO_LAST) { internal("(%s:%d): couldn't locate msgid %d in table", __FILE__, __LINE__, msgid); return(-1); } debug(DBG_FUNCS, "smf_msginfo: eof"); return(msgidx); } /* * Fork With State Machine Return Code */ int smf_fkwsmrc( char *cmdstr, int rcrecipientid, int okmsgid, int nokmsgid, struct msginfrec *msginfo_datap) { pid_t pid; /* used to hold process ids */ int cmdrc; /* return code of the exec'd command */ int statsup; /* for ascertaining rc of exec'd prg */ int old_statsup; /* for ascertaining rc of exec'd prg */ int inquotes, inword, moveon; char *argv[64]; int i; char *cp; int rc; debug(DBG_FUNCS, "smf_fkwsmrc: sof"); /* * do some very very quick tests on the string to check there's * nothing really daft going on. */ if (cmdstr == NULL || *cmdstr == '\0') { debug(DBG_SPECIAL1, "smf_fkwsmrc: invalid cmdstr"); return(-1); } /* * Let the server go on with what it was doing - possibly dealing with * more messages and signals. */ if ((pid=fork()) < 0) error(strerror(errno)); else if (pid > 0) { debug(DBG_SIGEXEC, "smf_fkwsmrc: returning after fork"); return(0); } /* * Wrapper signal handlers are a bit different to normal. INT and QUIT * are ignored in order to ensure that the OK/NOK message will get issued * signalling that the wrapped program has exited. And CHLD is waited * for. Can't remember why I can't just ignore it - maybe because I do * want to wait for the child. */ signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGCHLD, SIG_DFL); /* smf_fkwsmrc-rcr is the 'return code reporter' part of smf_fkwsmrc() */ debug(DBG_SIGEXEC, "smf_fkwsmrc-rcr: about to fork execer and wait"); if ((pid=fork()) < 0) error(strerror(errno)); else if (pid == 0) { /* * The execer process should use default handlers, as it it were * being run from the shell. */ signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); inword=0; inquotes=0; moveon=1; for (i=0,cp=cmdstr; *cp != '\0'; moveon && cp++) { moveon = 1; /* *after* quotes we're always in a word */ if (!inword && inquotes) { argv[i++] = cp; inword = 1; /* start of normal word */ } else if (!inword && *cp != ' ' && *cp != '\t' && *cp != '"') { argv[i++] = cp; inword = 1; /* encountering quotes while not in a word, means the next char is the start of a word - left to be picked up next time round */ } else if (!inquotes && *cp == '"') { inquotes = 1; /* but we do need to shuffle everything after this up one */ (void) strmove(cp, cp+1); moveon = 0; /* normal end of word */ } else if (!inquotes && (*cp == ' ' || *cp == '\t')) { *cp = '\0'; inword = 0; /* encountering end quotes does not mean end of word ("hell"o) */ } else if (inquotes && *cp == '"') { inquotes = 0; /* *cp = '\0'; */ inword = 0; /* but we do need to shuffle everything after this up one */ (void) strmove(cp, cp+1); moveon = 0; } } argv[i] = NULL; /*@@-onlytrans@@*/ argv[0] = mybasename(argv[0], ""); /*@@=onlytrans@@*/ (void) execvp(cmdstr, argv); error(strerror(errno)); } /* * Evaluate child process's exit code. */ debug(DBG_SIGEXEC, "smf_fkwsmrc-rcr: waiting for child (pid=%d) to exit", pid); switch (waitpid(pid, &statsup, 0)) { case -1: case 0: internal("%s:%s: smf_fkwsmrc-rcr: waitpid() error (%s)", __FILE__, __LINE__, strerror(errno)); default: debug(DBG_SIGEXEC, "smf_fkwsmrc-rcr: waitpid() returned ok"); } if (WIFEXITED(statsup)) { cmdrc = WEXITSTATUS(statsup); debug(DBG_SIGEXEC, "smf_fkwsmrc-rcr: child exited with rc %d", cmdrc); } else if (WIFSIGNALED(statsup)) { debug(DBG_SIGEXEC, "smf_fkwsmrc-rcr: child died on sig %d", WTERMSIG(statsup)); cmdrc = -1; } else if (WIFSTOPPED(statsup)) internal("%s:%s: smf_fkwsmrc-rcr: waitpid() says child stopped on sig %d", __FILE__, __LINE__, WSTOPSIG(statsup)); /* * Note we cannot use 'SMS_DIRECT' queuing method - since that would * send to the return-code-reporter's C queue, not the *real* C queue. * (Remember we did a fork()!) */ debug(DBG_SIGEXEC, "smf_fkwsmrc-rcr: sending rc"); /*@@-evalorder@@*/ if ((rc=smf_submit(msginfo_datap, rcrecipientid, time(NULL), (cmdrc == 0) ? okmsgid : nokmsgid, NULL, SMS_SUBMIT_QUEUE)) != 0) /*@@=evalorder@@*/ error("can't queue message (%d)", rc); exit(EXIT_SUCCESS); } /******************************************************************************* * * Functions: State Engine Internals (C queue transposition) * *******************************************************************************/ static int c_queue_submit( time_t dotime, int msgid, int *parp) { int i, j; time_t tmptime; int *tmpparp; int tmpid; time_t timenow; debug(DBG_FUNCS, "c_queue_submit: sof"); if (c_queue_top == C_QUEUE_SIZE) { warning("c queue is full, submit ignored"); return(1); } c_queue[c_queue_top].time = dotime; c_queue[c_queue_top].id = msgid; /*@@-temptrans@@*/ c_queue[c_queue_top].parp = parp; /*@@=temptrans@@*/ c_queue_top++; debug(DBG_MSGQ, "c_queue_submit: submitted: time %ld, id %d", dotime, msgid); for (i=0; i c_queue[j+1].time) { tmptime = c_queue[j].time; c_queue[j].time = c_queue[j+1].time; c_queue[j+1].time = tmptime; /*@@-kepttrans -mustfree@@*/ tmpparp = c_queue[j].parp; c_queue[j].parp = c_queue[j+1].parp; c_queue[j+1].parp = tmpparp; /*@@=kepttrans =mustfree@@*/ tmpid = c_queue[j].id; c_queue[j].id = c_queue[j+1].id; c_queue[j+1].id = tmpid; } } } for (timenow=time(NULL), i=0; i 0 && (timenow=time(NULL)) >= c_queue[0].time) { debug(DBG_MSGQ, "c_queue_process: dequeuing message %d", c_queue[0].id); /* * Under the new system it is vital to dequeue the entry *before* * executing it. Otherwise we can easily get into infinite loops. */ tmp_id = c_queue[0].id; tmp_parp = c_queue[0].parp; for (i=0; icmt); c_queue_del_msgid(*tmp_parp); } else { debug(DBG_SPECIAL1, "c_queue_process: processing msg %d (%s)", tmp_id, (msginfo_datap + smf_msginfo(msginfo_datap,tmp_id))->cmt); msgidx = lookup_msgmhf(msghdlr_datap, tmp_id); (void) ((msghdlr_datap+msgidx)->fnc)(tmp_parp); debug(DBG_MSGQ, "c_queue_process: done mhf for %d", tmp_id); } free(tmp_parp); } /* * Show the queue in debug. */ for (i=0; i 0) { debug(DBG_FUNCS, "c_queue_process: setting c queue process time"); (void) alarm((unsigned) c_queue[0].time - timenow); } else debug(DBG_FUNCS, "c_queue_process: no jobs on c queue, no alarm"); debug(DBG_FUNCS, "c_queue_process: eof"); } static int c_queue_del_msgid( int msgid) { int s, d; debug(DBG_FUNCS, "c_queue_del_msgid: sof"); for (s=0,d=0; sid != msgid && (mhf_datap+msgidx)->id != MSGID_PSEUDO_LAST; msgidx++) ; if ((mhf_datap+msgidx)->id == MSGID_PSEUDO_LAST) { internal("(%s,%d): failed to locate msgid %d in table", __FILE__, __LINE__, msgid); return(-1); } return(msgidx); } static int ipc_queue_submit( struct msginfrec *msginfo_datap, int towhome, time_t dotime, int msgid, int *mp) { struct msgrec sbuf; int msgidx; int i; debug(DBG_FUNCS, "ipc_queue_submit: sof"); /* * Mark who the message is for, and put the message id in the message * text. */ sbuf.mtype = towhome; sprintf(sbuf.mtext, "%ld %d ", dotime, msgid); /* * Look up how many parameters this message type has and copy them out * of the passed memory block into the message text. */ if ((msgidx=smf_msginfo(msginfo_datap, msgid)) < 0) return(1); for (i=0; i<(msginfo_datap+msgidx)->argc; i++) sprintf(sbuf.mtext+strlen(sbuf.mtext), "%d ", *(mp+i)); /* * Append the message text (not important for programs, but looks nice * if we ever get to making this use AF_INET sockets instead of IPC. */ sprintf(sbuf.mtext+strlen(sbuf.mtext), "(%s)", (msginfo_datap+msgidx)->cmt); /* * Dispatch the message. */ debug(DBG_MSGQ, "ipc_queue_submit: sending to: %d msg: %s", towhome, sbuf.mtext); if (msgsnd(msqid, (struct msgbuf *) &sbuf, strlen(sbuf.mtext) + 1, 0) < 0) error("msgsnd: %s", strerror(errno)); /* * It's up to the caller to free the memory block if it wants. * Though if this is being called by the delayed message queue then it * will free it up after this function gets called, having taken a * copy when it itself was called. */ debug(DBG_FUNCS, "ipc_queue_submit: eof"); return(0); } static ipc_server_init( uid_t ipc_owner, gid_t ipc_group, int ipc_mode) { struct msqid_ds mspars; struct passwd *tpwp; struct group *tgrp; debug(DBG_FUNCS, "ipc_server_init: sof"); /* * Create the message queue */ if ((msqid=msgget(key, IPC_CREAT | ipc_mode)) < 0) { debug(DBG_MSGQ, "msgget(%d, IPC_CREAT | %o): %s", key, ipc_mode, strerror(errno)); return(-1); } /* * Read current owner and group */ if (msgctl(msqid, IPC_STAT, &mspars) < 0) { debug(DBG_MSGQ, "msgctl(%d, IPC_STAT, ): %s", msqid, strerror(errno)); return(-1); } tpwp = getpwuid(mspars.msg_perm.uid); tgrp = getgrgid(mspars.msg_perm.gid); debug(DBG_MSGQ, "queue has owner: %s, group %s", tpwp->pw_name, tgrp->gr_name); if (mspars.msg_perm.uid == ipc_owner && mspars.msg_perm.gid == ipc_group) { debug(DBG_MSGQ, "owner and group are ok already"); return(0); } /* * If necessary, set owner and group */ mspars.msg_perm.uid = ipc_owner; mspars.msg_perm.gid = ipc_group; if (msgctl(msqid, IPC_SET, &mspars) < 0) { debug(DBG_MSGQ, "msgctl(%d, IPC_SET, ): %d (%s)", msqid, errno, strerror(errno)); return(-1); } return(0); } static int ipc_client_init() { debug(DBG_FUNCS, "ipc_client_init: sof"); return(((msqid=msgget(key, 0666)) < 0) ? -1 : 0); } static int ipc_server_shutdown() { debug(DBG_FUNCS, "ipc_server_shutdown: sof"); msgctl(msgget(key, 0666), IPC_RMID, NULL); return(0); } static int ipc_queue_process( struct msginfrec *msginfo_datap, int towhome, int (*c_queue_submit_fnc)(time_t, int, int *)) { struct msgrec rbuf; int msgid; int rc; int parcnt; int *mp; int i; char *cp; int msgidx; time_t dotime; debug(DBG_FUNCS, "ipc_queue_process: sof"); debug(DBG_MSGQ, "ipc_queue_process: awaiting msg to %d", towhome); rc = msgrcv(msqid, (struct msgbuf *) &rbuf, MAX_MSG_SIZ, towhome, 0); if (rc < 0 && errno == EINTR) { debug(DBG_MSGQ, "ipc_queue_process: interrupted system call"); return(EINTR); } else if (rc < 0) error("msgrcv: %s", strerror(errno)); debug(DBG_MSGQ, "ipc_queue_process: to: %d, msg: %s", towhome, rbuf.mtext); /* * at this point we would parse the next word as a activation time */ dotime = atoi(strtok(rbuf.mtext, " ")); /* * Get the message id, and allocate enough memory for the number of * parameters that this message should have. Then read the parameters * checking that there are the right number. */ msgid = atoi(strtok(NULL, " ")); parcnt = (msginfo_datap+smf_msginfo(msginfo_datap, msgid))->argc; if ((mp=((int *) calloc(parcnt, sizeof(int)))) == NULL) error("calloc: ", strerror(errno)); for (i=0; (cp=strtok(NULL, " ")) != NULL && i> 8, __FILE__, __LINE__); else if (old_statsup & 0x7f) internal("don't know how to handle child exiting on signal %d (%s,%d)", old_statsup & 0x7f, __FILE__, __LINE__); cmdrc = (int) ((unsigned) old_statsup >> 8); debug(DBG_SIGEXEC, "fork_and_submit_rc_ipc_msg-rc_rptr: child exited with exit %d", cmdrc); d386 3 a388 3 * Note we can't use c_queue_submit here because it would go to the * return-code-reporter's C queue - NOT - the C-queue-processor's * C queue. We *must* use IPC. d391 1 a391 1 debug(DBG_SIGEXEC, "fork_and_submit_rc_ipc_msg-rc_rptr: sending rc"); d393 1 a393 1 if ((rc=ipc_queue_submit(msginfo_datap, rcrecipientid, time(NULL), (cmdrc == 0) ? okmsgid : nokmsgid, NULL)) != 0) d500 1 a500 1 debug(DBG_SPECIAL1, "c_queue_process: processing msg %d (%s) internally", tmp_id, (msginfo_datap + lookup_msginfo(msginfo_datap,tmp_id))->cmt); d503 1 a503 1 debug(DBG_SPECIAL1, "c_queue_process: processing msg %d (%s)", tmp_id, (msginfo_datap + lookup_msginfo(msginfo_datap,tmp_id))->cmt); d595 1 a595 1 if ((msgidx=lookup_msginfo(msginfo_datap, msgid)) < 0) d730 1 a730 1 parcnt = (msginfo_datap+lookup_msginfo(msginfo_datap, msgid))->argc; @ 1.1 log @Initial revision @ text @d2 1 a2 1 static char *ppplcd_c_rcs_id = "$Header$"; d70 3 a72 3 static ppplcd_ipc_server_init(uid_t, gid_t, int); static ppplcd_ipc_client_init(void); static ppplcd_ipc_server_shutdown(void); d74 1 d108 2 d113 14 d128 21 a148 1 (*assmf_init_p)(cp, &smd_interface_owner, &smd_interface_group, &smd_interface_mode); d150 1 a150 3 ppplcd_ipc_server_shutdown(); if (ppplcd_ipc_server_init(smd_interface_owner, smd_interface_group, smd_interface_mode) != 0) error("failed to locate message queue"); a151 2 return(0); d153 6 a158 1 return(ppplcd_ipc_client_init()); d223 1 a223 1 ppplcd_ipc_server_shutdown(); d617 1 a617 1 static ppplcd_ipc_server_init( d626 1 a626 1 debug(DBG_FUNCS, "ppplcd_ipc_server_init: sof"); d667 1 a667 1 static int ppplcd_ipc_client_init() d669 1 a669 1 debug(DBG_FUNCS, "ppplcd_ipc_client_init: sof"); d674 1 a674 1 static ppplcd_ipc_server_shutdown() d676 1 a676 1 debug(DBG_FUNCS, "ppplcd_ipc_server_shutdown: sof"); d679 1 d745 7 @