head 1.3; access; symbols; locks; strict; comment @ * @; 1.3 date 99.01.07.13.32.03; author alexis; state Exp; branches; next 1.2; 1.2 date 98.10.17.18.56.27; author alexis; state Exp; branches; next 1.1; 1.1 date 98.10.17.18.52.31; author alexis; state Exp; branches; next ; desc @UPSD DAEMON SOURCE CODE @ 1.3 log @removed message functions - they are now in standard utils.c locking mechanism standarised a lot of symbols moved to Makefile.defs symbol name changes @ text @/***************************************************************************** * * * Program: UPSD (UPS Daemon) * * * * Date: Mon Dec 11 14:17:31 WET 1995 * * * * Author: Alexis Huxley * * * * Description: Daemon program to monitor the state of DCD in a * * specified serial port. This program is compatible with * * any UPS that has a relay that closes contacts in the event * * of loss of mains power, or a UPS that displays relay-like * * behaviour. * * * * It is known to work with the following UPSs with the * * following cables: * * * * Victron Micro 106 * * 25 female 9 female * * 8 <----------------------------------------> 5 * * 20 <----------------------------------------> 9 * * * *****************************************************************************/ /* * Include files and reasons for inclusion */ #include /* principally for NULL */ #include /* for errno */ #include /* for strrchr(), strtok() */ #include /* for ioctl() */ #include /* for O_RDWR in open() */ #include /* for time_t ret'd by time() */ #include /* for stat.h, wait.h */ #include /* for stat(), umask() */ #include /* for sleep(), getpid() */ #include /* for messaging funcs */ #include /* for wait() */ #include /* for signal() */ #include /* for syslog() */ #if linux #include /* for setenv() */ #endif #include /* for getpwnam() */ #include "utils.h" /* for local stuff */ /* * Non-configurable symbols */ #define NULLCP ((char *) NULL) /* I pine for K&R C! */ #define NULLFP ((FILE *) NULL) /* I pine for K&R C! */ #if linux #define WALL_CMD "/usr/bin/wall" /* Linux wall */ #define WRITE_CMD "/usr/bin/write" /* Linux write */ #define SHUTDOWN_CMD "/sbin/shutdown" /* Linux shutdown */ #else #define WALL_CMD "/bin/wall" /* Ultrix wall */ #define WRITE_CMD "/bin/write" /* Ultrix write */ #define SHUTDOWN_CMD "/etc/shutdown" /* Ultrix shutdown */ #endif #define UPSD_ENV_INTERVAL "UPSD_INTERVAL" /* name of env variable */ #define UPSD_ENV_STATE "UPSD_STATE" /* name of env variable */ /* * Configurable symbols */ #define DFLT_DEBUG_LEVEL 2 /* default debug level */ #define DFLT_SHUTDOWN_INTERVAL 240 /* default 4 minute warning */ #define DFLT_WARNING_INTERVAL 30 /* wall every 10 secs */ #define DFLT_CHECKING_INTERVAL 1 /* check every 5 seconds */ #define RESET_FLOW_CONTROL TRUE /* if badly behaved set this */ #define TEST_FAKE_DCD FALSE /* dcd or file /tmp/powerfail */ #define TEST_DONT_WALL FALSE /* wall or write */ #define TEST_DONT_HALT FALSE /* shutdown or pretend */ /* * Forward references for internal functons */ static void usage(void); /* tell user what to do */ static void shutdown_now(char *); /* do an immediate shutdown */ static int shutdown_warning(char *, int); /* warn of impending shutdown */ static int shutdown_cancelled(char *); /* tell shutdown cancelled */ static int shutdown_powerfail(void); /* call as power fails */ static int callprog(char *, char *, char *, int); /* call external prog/dflt */ static int gen_lockfile_name(char *, char *); /* write filename into buffer */ /* * Forward references for standard library functions not mentioned in headers */ #if !linux /* linux declares all these */ extern void openlog(char *, int); /* not mentioned in syslog.h */ extern void syslog(int, char *, ...); /* not mentioned in syslog.h */ extern int ioctl(int, int, char *); /* not mentioned in ioctl.h */ extern void setenv(char *, char *, int); /* not mentioned in anything! */ #endif /* * Global variables */ /* * Static Variables */ /* RCS version control id */ static char *rcs_id_upsd_c = "$Header: /diskb/home/alexis/dev/upsd/bin/RCS/upsd.c,v 1.2 1998/10/17 18:56:27 alexis Exp alexis $"; static char *version = "MARKER_PATCHLEVEL"; /* version of this file */ /**/ /****************************************************************************** * * Function: main() * * Parameters: argc - count of command line arguments * * Return value: argv - char vectort for all command line arguments * * Description: this is the entry point for the UPS daemon. It enters * a loop where is monitors changes in the state of the * DCD line and, interpreting these as changes in the * state of the mains power supply relayed by the UPS, * in the event of mains power loss it informs the users * at regular intervals of an impending shutdown and * finally shuts down the machine. * Of course it handles option processing, locking, signal * processing, logging, debugging, verbose and quiet * behaviour, etc. * ******************************************************************************/ int main( int argc, /* argument count */ char *argv[]) /* argument list */ { char *cp; /* handy char pointer */ time_t tm; /* handy time var */ char *devcp; /* pointer to device name */ int shutdown; /* flag meaning now shutdown */ int pwrstat; /* mains status */ int lastpwrstat; /* previous mains status */ #if !TEST_FAKE_DCD #if linux unsigned int ioctl_flags; /* linux ioctl uses 32 bits */ #else unsigned int ioctl_flags; /* ultrix ioctl uses 32 bits */ #endif #endif int devfd; /* to open port */ time_t shutdown_time; /* time to shutdown at */ time_t warnmsg_time; /* time to warn users */ struct stat devstat; /* to check dev is serial */ int shutdown_interval; /* from powerloss to shutdown */ unsigned int checking_interval; /* between power checks */ int warning_interval; /* between shutdown warnings */ char *warning_prog; /* to run at warning time */ char *shutdown_prog; /* to run at shutdown time */ char *cancelled_prog; /* to run at cancelled time */ pid_t lockingpid; /* active pid holding lock */ #if RESET_FLOW_CONTROL char killpwr_bit = TIOCM_RTS; /* may be superflous */ #endif progname = mybasename(argv[0], ""); /* get basename of this prog */ shutdown_interval = DFLT_SHUTDOWN_INTERVAL; /* set default shutdown int */ checking_interval = DFLT_CHECKING_INTERVAL; /* set default check interval */ warning_interval = DFLT_WARNING_INTERVAL; /* set default warning int */ shutdown_prog = NULLCP; /* action handled internally */ warning_prog = NULLCP; /* action handled internally */ cancelled_prog = NULLCP; /* action handled internally */ /************************************************************************** * * Process options * **************************************************************************/ while (argc > 1 && argv[1][0] == '-') { /* deal with options */ if (!strcmp(argv[1], "-V")) { /* -V prints version info */ printf("%s version %s\n", progname, version); return(0); } else if (!strcmp(argv[1], "-v")) { /* verbose */ debug_level = 3; } else if (!strcmp(argv[1], "-is")) { /* shutdown interval */ if (argc == 2) usage(); if (sscanf(argv[2], "%d", &shutdown_interval) != 1) usage(); argc--; argv++; } else if (!strcmp(argv[1], "-iw")) { /* warning interval */ if (argc == 2) usage(); if (sscanf(argv[2], "%d", &warning_interval) != 1) usage(); argc--; argv++; } else if (!strcmp(argv[1], "-ic")) { /* checking interval */ if (argc == 2) usage(); if (sscanf(argv[2], "%d", &checking_interval) != 1) usage(); argc--; argv++; } else if (!strcmp(argv[1], "-ps")) { /* program to run at shutdown */ if (argc == 2) usage(); shutdown_prog = argv[2]; argc--; argv++; } else if (!strcmp(argv[1], "-pw")) { /* program to run at warning */ if (argc == 2) usage(); warning_prog = argv[2]; argc--; argv++; } else if (!strcmp(argv[1], "-pc")) { /* program to run at cancel */ if (argc == 2) usage(); cancelled_prog = argv[2]; argc--; argv++; } else if (!strcmp(argv[1], "-d")) { /* debugging (very verbose) */ if (argc == 2) usage(); if (sscanf(argv[2], "%d", &debug_level) != 1) usage(); argc--; argv++; } else /* any other option is error */ usage(); argc--; /* ready for next option */ argv++; } /************************************************************************** * * Process required paramters * **************************************************************************/ if (argc != 2) usage(); /* required par is port name */ devcp = argv[1]; /* save it */ /************************************************************************** * * Sanity checks * **************************************************************************/ debug(50, "stat()ing %s ...", devcp); /* does port exist */ if (stat(devcp, &devstat)) /* is it a char device */ error("couldn't stat %s", devcp); if (!(devstat.st_mode & S_IFCHR)) error("%s is not a character device", devcp); if (shutdown_prog != NULLCP) { /* check shutdown prog */ if (stat(shutdown_prog, &devstat)) error("couldn't stat %s", shutdown_prog); if (!(devstat.st_mode & S_IFREG)) error("%s is not a regular file", shutdown_prog); if (!(devstat.st_mode & S_IEXEC)) error("%s is not an executable program", shutdown_prog); } if (warning_prog != NULLCP) { /* check warning prog */ if (stat(warning_prog, &devstat)) error("couldn't stat %s", warning_prog); if (!(devstat.st_mode & S_IFREG)) error("%s is not a regular file", warning_prog); if (!(devstat.st_mode & S_IEXEC)) error("%s is not an executable program", warning_prog); } if (cancelled_prog != NULLCP) { /* check cancelled prog */ if (stat(cancelled_prog, &devstat)) error("couldn't stat %s", cancelled_prog); if (!(devstat.st_mode & S_IFREG)) error("%s is not a regular file", cancelled_prog); if (!(devstat.st_mode & S_IEXEC)) error("%s is not an executable program", cancelled_prog); } /************************************************************************** * * Initialise interrupt handling * **************************************************************************/ signal(SIGTERM, genericsighandler); signal(SIGINT, genericsighandler); /************************************************************************** * * Lock port and open * **************************************************************************/ /* lock the device */ if ((lockingpid=my_lock(devcp, gen_lockfile_name)) > 0) error("%s is in use by process %d", devcp, lockingpid); else if (lockingpid < 0) error("can't create lockfile: %s", strerror(errno)); debug(50, "open()ing %s ...", devcp); /* open for ioctl()ing */ if ((devfd = open(devcp, O_RDWR | O_NDELAY)) < 0) error("can't open %s", devcp); #if RESET_FLOW_CONTROL /* clear flow control */ ioctl(devfd, TIOCMBIC, (char *) &killpwr_bit); #endif /************************************************************************** * * Loop monitoring DCD * **************************************************************************/ info("version %s started on %s", version, devcp); i_am_a_daemon = TRUE; for (shutdown=FALSE,lastpwrstat=TRUE;!shutdown;lastpwrstat=pwrstat) { sleep(checking_interval); /* pause between loops */ #if TEST_FAKE_DCD pwrstat = !(!stat("/tmp/powerfail", &devstat)); #else ioctl(devfd, TIOCMGET, (char *) &ioctl_flags); /* get DCD and more! */ pwrstat = !(ioctl_flags & TIOCM_CD); /* filter out all except DCD */ #endif debug(20, "powerstat: %d, lastpwrstat: %d", pwrstat, lastpwrstat); /* * If the power is on and it was on before then don't do anything. */ if (pwrstat == TRUE && pwrstat == lastpwrstat) continue; /* * If the power is on, but it wasn't before then cancel the * impending shutdown. */ if (pwrstat == TRUE) { shutdown_cancelled(cancelled_prog); continue; } /* * If the power is off but it was on before, then reset shutdown * counters and ... */ if (pwrstat != lastpwrstat) { shutdown_time = (tm=time((time_t *) NULL)) + shutdown_interval; warnmsg_time = tm; shutdown_powerfail(); /* logs only, warning below */ } /* * ... if the power is off, regardless of what it was like before, * and if shutdown time has not been reached yet, then give the * warning message if it's time for the next one and continue with * with the next check. */ if ((tm=time((time_t *) NULL)) < shutdown_time) { debug(100, "time is: %ld", tm); debug(100, "warning time is: %ld", warnmsg_time); if (tm >= warnmsg_time) { /* don't warn each time */ shutdown_warning(warning_prog, (int) (shutdown_time - tm)); /* schedule next warning */ warnmsg_time = tm + warning_interval; debug(100, "new wrn time is: %ld", warnmsg_time); } continue; } /* * If we get to here then the power is off and the time interval * between a power down and the actual shutdown has elapsed. So we * just set a flag to get out of this loop. (The first line after * the loop will shutdown the machine.) */ shutdown = TRUE; /* to break out of loop */ } shutdown_now(shutdown_prog); /* "the time has come ..." */ return(0); /* not reached */ } int gen_lockfile_name( char *lockfile, char *lockwhat) { #if UUCP_STYLE_LOCKS sprintf(lockfile, "%s/LCK..%s", LOCK_DIR, mybasename(lockwhat, "")); #else sprintf(lockfile, "%s/%s.pid", LOCK_DIR, mybasename(lockwhat, "")); #endif return(0); } /****************************************************************************** * * Function: usage() * * Parameters: none * * Return value: doesn't return * * Description: tells the user how to use this command * ******************************************************************************/ static void usage() { fprintf(stderr, "Usage: %s -V\n", progname); fprintf(stderr, " %s [ -v | -d level ] [ -i[wcs] secs ... ] [ -p[wcs] prog ... ] port\n", progname); fprintf(stderr, "\n"); exit(1); } /****************************************************************************** * * Function: shutdown_powerfail() * * Parameters: none * * Return value: 0 * * Description: as the power fails there is no action to be taken other * then logging the situation. Warning the users takes * place at regular intervals after the power has failed, * but there is no specific action associated with the * failure itself. * ******************************************************************************/ static int shutdown_powerfail() { warning("power has failed"); /* just log it, no action */ return(0); } /****************************************************************************** * * Function: shutdown_warning() * * Parameters: warning_prog - name of program to execute or NULL * * Return value: 0 for success * non-zero of unsuccessful * * Description: if the user has specified a command on the command line * to execute when a warning should be issued then this * is executed at this time, otherwise a message is sent * to all users alerting them that a shutdown will follow * shortly. * ******************************************************************************/ static int shutdown_warning( char *warning_prog, int interval) { warning("shutdown in %d seconds", interval); return(callprog(warning_prog, "\n\n*** POWER FAILURE! ***\n*** SHUTDOWN IN %3d SECONDS! ***\n\n", "impending", interval)); } /****************************************************************************** * * Function: shutdown_cancelled() * * Parameters: cancelled_prog - name of program to execute or NULL * * Return value: 0 for success * non-zero of unsuccessful * * Description: if the user has specified a command on the command line * to execute when a shutdown has been cancelled then this * is executed at this time, otherwise a message is sent * to all users alerting them that a scheduled shutdown has * been cancelled. * ******************************************************************************/ static int shutdown_cancelled( char *cancelled_prog) { warning("power restored"); return(callprog(cancelled_prog, "\n\n*** POWER RESTORED! ***\n*** SHUTDOWN CANCELLED! ***\n\n", "cancelled", 0)); } /****************************************************************************** * * Function: shutdown_now() * * Parameters: shutdown_prog - path of program to execute or NULL * * Return value: doesn't return * * Description: this program is responsible for actually performing an * immediate shutdown. If prog is specified then it is called, * otherwise "shutdown -h now" is called. * ******************************************************************************/ static void shutdown_now( char *shutdown_prog) { warning("shutting down now!"); if (shutdown_prog == NULL) { /* if not spec'd, do shutdown */ #if TEST_DONT_HALT error("shutdown not enabled, exiting"); #else execl(SHUTDOWN_CMD, SHUTDOWN_CMD, "-h", "now", NULLCP); #endif } else { /* exec user def'd action */ setenv("UPSD_INTERVAL", "0", 1); setenv("UPSD_STATE", "immediate", 1); execl(shutdown_prog, shutdown_prog, NULLCP); } } /****************************************************************************** * * Function: callprog() * * Parameters: prog - path of program to execute or NULL * fmtstr - message written to all users if prog was NULL * state - word written to environment if prog not NULL * interval - number written to enviroment if prog not NULL * * Return value: 0 for success * exits via error() otherwise * * Description: If prog is NULL then this function sends the specified * message to all users. Otherwise it calls prog, first * writing the passed word and number into the enviroment * available to prog. * ******************************************************************************/ static int callprog( char *prog, /* user def'd program or NULL */ char *fmtstr, /* if no user def'd, say this */ char *state, /* to user def'd prog in env */ int interval) /* to user def'd prog in env */ { int pipefds[2]; /* to talk to wall */ char msgbuf[256]; /* handy char buffer */ int pid; /* handy pid */ char numbuf[16]; /* handy chat buffer */ if (prog == NULL && pipe(pipefds) < 0) /* no user def'd & can't pipe */ error("Can't open pipe"); if ((pid=fork()) < 0) /* fork to wall */ error("Can't fork"); /************************************************************************** * * Child * **************************************************************************/ /* * Child process takes input from parent and sends it to users or simply * calls a user defined program if one was supplied on the command line. */ else if (pid == 0) { /* fork ok, this is child */ debug(5, "child running ..."); if (prog == NULL) { /* no user def'd program */ debug(5, "child doesn't need to write pipe"); close(pipefds[1]); /* no need to write to parent */ debug(5, "child setting stdin to read from pipe"); close(0); /* stdin comes from parent */ dup(pipefds[0]); close(pipefds[0]); #if TEST_DONT_WALL /* wall or write, for testing */ debug(5, "child execing write ..."); execl(WRITE_CMD, WRITE_CMD, "alexis", NULLCP); error("can't exec %s", WRITE_CMD); #else debug(5, "child execing wall ..."); execl(WALL_CMD, WALL_CMD, NULLCP); error("can't exec %s", WALL_CMD); #endif } else { /* user def'd program to call */ sprintf(numbuf, "%d", interval); /* set up env for user def'd */ setenv(UPSD_ENV_INTERVAL, numbuf, 1); setenv(UPSD_ENV_STATE, state, 1); execl(prog, prog, NULLCP); /* exec user defined program */ error("can't exec %s", prog); } } /************************************************************************** * * Parent * **************************************************************************/ /* * Meanwhile the parent either feeds a message to the child to be sent * to users or does nothing while the child calls a user defined program. * In either case it waits until the child has finished. */ if (prog == NULL) { /* no user spec'd prog to run */ debug(5, "parent doesn't need to read from pipe"); close(pipefds[0]); /* no need to read from child */ debug(5, "parent writing message to pipe ..."); sprintf(msgbuf, fmtstr, interval); /* put interval into message */ write(pipefds[1], msgbuf, (int) strlen(msgbuf)); /* send to child */ debug(5, "parent closing write side of pipe ..."); close(pipefds[1]); /* nothing more to send */ } debug(5, "parent waiting for child to exit"); while (wait((int *) 0) != pid) /* wait for child to exit */ ; debug(5, "parent done"); return(0); } @ 1.2 log @half standardised version number processing @ text @d30 1 d47 2 a54 10 #define TRUE (0 == 0) /* boolean flags */ #define FALSE (0 != 0) /* boolean flags */ #define LOCK_NAME_UUCP 1 /* /usr/lib/uucp/LCK.device */ #define LOCK_NAME_TEST 2 /* /usr/lib/uucp/LCK.device */ #define LOCK_CONT_ASCII 1 /* ascii pid in lock file */ #define LOCK_CONT_BINARY 2 /* binary pid in lock file */ #define EXITACTION_NONE 0x00 /* no action on intr */ #define EXITACTION_EXIT 0x01 /* no action on intr */ #define EXITACTION_DTLK 0x02 /* no action on intr */ #define EXITACTION_DPLK 0x04 /* no action on intr */ d76 1 a76 3 #define LOCK_NAME LOCK_NAME_UUCP /* choose from above */ #define LOCK_CONT LOCK_CONT_ASCII /* choose from above */ #define TEST_DUMMY_DCD TRUE /* dcd or file /tmp/powerfail */ d78 1 a78 2 #define TEST_DONT_HALT FALSE /* shutdown or pretend */ #define TEST_NOT_ROOT FALSE /* is root running this */ d84 7 a90 14 void debug(int, char *, ...); /* for debugging */ void info(char *, ...); /* verbose messages */ void warning(char *, ...); /* warning messages */ void error(char *, ...); /* error messages */ void internal(char *, ...); /* internal errors */ void usage(void); /* tell user what to do */ void shutdown_now(char *); /* do an immediate shutdown */ int shutdown_warning(char *, int); /* warn of impending shutdown */ int shutdown_cancelled(char *); /* tell shutdown cancelled */ int shutdown_powerfail(void); /* call as power fails */ int lock_device(char *, int); /* lock a serial device */ int pidrunning(int); /* test if a pid is running */ void sighandler(int); /* catches INT and TERM */ int callprog(char *, char *, char *, int); /* call external prog or dflt */ d99 1 a99 1 extern int ioctl(int, int, char *); /* not mentioned in ioctl.h */ a106 3 unsigned int debug_level = DFLT_DEBUG_LEVEL; /* debug level */ char *progname; /* basename of this program */ d112 2 a113 5 static char *rcs_id_upsd_c = "$Header: upsd.c,v 1.1 1998/10/17 18:52:31 alexis Exp alexis $"; static char *version = "PATCHLEVEL_MARKER"; /* version of this file */ static exitactions; /* what do do on signal */ static char lockbuf[64]; /* name of lock file */ static char tmplockbuf[64]; /* name of tmp lock file */ d148 1 a148 1 #if !TEST_DUMMY_DCD d170 1 a170 15 /************************************************************************** * * Important things to do at the beginning * **************************************************************************/ /* get basename of this prog */ progname = ((cp=strrchr(argv[0], '/')) == NULLCP) ? argv[0] : cp+1; /************************************************************************** * * Set default values for options * **************************************************************************/ d222 1 a222 1 } else if (!strcmp(argv[1], "-pc")) { /* program to run at cancelled */ d257 5 a261 3 if (stat(devcp, &devstat)) error("couldn't stat %s", devcp); /* is it a char device */ if (!(devstat.st_mode & S_IFCHR)) error("%s is not a character device", devcp); d270 1 d279 1 d295 2 a296 9 exitactions = EXITACTION_EXIT; signal(SIGTERM, sighandler); signal(SIGINT, sighandler); /************************************************************************** * * Initialise logging * **************************************************************************/ a297 7 #if linux openlog(progname, LOG_PID, LOG_DAEMON); /* all syslog calls use pid */ #else openlog(progname, LOG_PID); /* all syslog calls use pid */ #endif /* note that we have started */ syslog(LOG_NOTICE, "%s version %s started on %s", progname, version, devcp); d305 2 a306 1 if ((lockingpid=lock_device(devcp, 0)) > 0) /* lock the device */ d308 2 a309 2 else if (lockingpid < 0) /* competing locks */ internal("lock collision"); d323 2 a324 2 info("Monitoring %s ...", devcp); debug(50, "entering loop ..."); /* loop looking at DCD */ d327 1 a327 1 #if TEST_DUMMY_DCD a392 8 /* * We're done monitoring, we can unlock the device (not that there's * much point, nobody else wants info from the UPS) and shutdown. */ unlink(lockbuf); /* done using port */ exitactions &= ~EXITACTION_DPLK; debug(20, "exitactions at exit is %d", exitactions); d397 12 d421 1 a421 1 void usage() a425 2 fprintf(stderr, "See upsd(8) for more details\n"); fprintf(stderr, "\n"); a430 147 * Function: info() * * Parameters: as per printf() * * Return value: none * * Description: this function formats and outputs (if debug_level is * set to a sufficiently high level) a nicely formatted * informational message. * ******************************************************************************/ void info( char *fmt, /* like printf() */ ...) /* parameters to format str */ { va_list argp; /* to grab var list */ if (3 > debug_level) return; /* verbose only if 3 or more */ printf("%s: INFO: ", progname); va_start(argp, fmt); vprintf(fmt, argp); va_end(argp); printf("\n"); } /****************************************************************************** * * Function: warning() * * Parameters: as per printf() * * Return value: none * * Description: this function formats and outputs (if debug_level is * set to a sufficiently high level) a nicely formatted * warning message. * ******************************************************************************/ void warning( char *fmt, /* like printf() */ ...) /* parameters to format str */ { va_list argp; /* to grab var list */ if (2 > debug_level) return; /* warnings only if 2 or more */ printf("%s: WARNING: ", progname); va_start(argp, fmt); vprintf(fmt, argp); va_end(argp); printf("\n"); } /****************************************************************************** * * Function: debug() * * Parameters: level - debug level at which this message should be sent * remaing parameters as per printf() * * Return value: none * * Description: this function formats and outputs (if debug_level is * set to a sufficiently high level) a nicely formatted * debug message. * ******************************************************************************/ void debug( int level, /* level of this message */ char *fmt, /* like printf() */ ...) { va_list argp; /* to grab var list */ if (level > debug_level) return; /* debug only suff. level */ fprintf(stderr, "%s: DEBUG[%d]: ", progname, level); va_start(argp, fmt); vfprintf(stderr, fmt, argp); va_end(argp); fprintf(stderr, "\n"); } /****************************************************************************** * * Function: error() * * Parameters: as per printf() * * Return value: none * * Description: this function formats and outputs (if debug_level is * set to a sufficiently high level) a nicely formatted * error message. * ******************************************************************************/ void error( char *fmt, /* like printf() */ ...) { va_list argp; /* to grab var list */ char msgbuf[256]; if (2 > debug_level) return; /* errors only if 2 or more */ va_start(argp, fmt); vsprintf(msgbuf, fmt, argp); va_end(argp); printf("%s: ERROR: %s\n", progname, msgbuf); syslog(LOG_NOTICE, msgbuf); /* do things to do at exit */ if (exitactions && EXITACTION_DTLK) unlink(tmplockbuf); if (exitactions && EXITACTION_DPLK) unlink(lockbuf); exit(2); } /****************************************************************************** * * Function: internal() * * Parameters: as per printf() * * Return value: none * * Description: this function formats an internal error nicely! * ******************************************************************************/ void internal( char *fmt, /* like printf() */ ...) { va_list argp; /* to grab var list */ char msgbuf[256]; va_start(argp, fmt); vsprintf(msgbuf, fmt, argp); va_end(argp); printf("%s: INTERNAL ERROR: %s\n", progname, msgbuf); syslog(LOG_NOTICE, msgbuf); /* don't tidy up */ exit(3); } /****************************************************************************** * d445 1 a445 1 int shutdown_powerfail() d447 1 a447 1 syslog(LOG_NOTICE, "power has failed"); /* just log it, no action */ d468 1 a468 1 int shutdown_warning( d472 1 a472 1 info("SHUTDOWN IN %d SECONDS", interval); d493 1 a493 1 int shutdown_cancelled( d496 1 a496 2 info("SHUTDOWN CANCELLED"); syslog(LOG_NOTICE, "power has returned"); d514 1 a514 1 void shutdown_now( d517 1 a517 2 info("SHUTTING DOWN NOW!"); syslog(LOG_NOTICE, "shutting down now"); d536 1 a536 1 * fmtstr - message written to all users of prog was NULL d550 1 a550 1 int callprog( a629 239 } /****************************************************************************** * * Function: lock_device() * * Parameters: devcp - name of device to lock * attempt - number of attempt, this should be 0 * * Return value: 0 if successful * pid of process owning lock if unsuccessful * * Description: this function uses a standard locking mechanism to lock * the specified device. It honours existing locks and * can detect stale lock files. * ******************************************************************************/ int lock_device( char *devcp, /* name of device to lock */ int attempt) /* this is attempt number */ { char *basecp; /* dev name base e.g. tty00 */ char *cp; /* handy char pointer */ pid_t mypid; /* pid of this process */ pid_t lockingpid; /* pid of lock owning process */ FILE *tmplockfp; /* name of temp lock file */ FILE *lockfp; /* name of lock file */ #if !TEST_NOT_ROOT struct passwd *pwentp; /* to get uucp's uid */ #endif /* get basename of device */ basecp = ((cp=strrchr(devcp, '/')) == NULLCP) ? devcp : cp+1; mypid = getpid(); /* get pid of this process */ umask(022); /* readable lock files */ /************************************************************************** * * Set lockfile and temporary lockfile names * **************************************************************************/ #if (LOCK_NAME == LOCK_NAME_UUCP) sprintf(lockbuf, "/var/spool/uucp/LCK..%s", basecp); sprintf(tmplockbuf, "/var/spool/uucp/LCK..%s.%d", basecp, mypid); #elif (LOCK_NAME == LOCK_NAME_TEST) sprintf(lockbuf, "/tmp/LCK..%s", basecp); sprintf(tmplockbuf, "/tmp/LCK..%s.%d", basecp, mypid); #else internal("unsupported lock style %d", LOCK_STYLE); #endif debug(10, "lockfile is %s, tmplockfile is %s", lockbuf, tmplockbuf); /************************************************************************** * * Create a temporary lock * **************************************************************************/ if (attempt == 0) { /* 1st time create tmp lock */ exitactions |= EXITACTION_DTLK; if ((tmplockfp=fopen(tmplockbuf, "w")) == NULLFP) error("problem opening temporary lock %s", tmplockbuf); #if (LOCK_CONT == LOCK_CONT_ASCII) fprintf(tmplockfp, "%d", mypid); #else internal("unsupported lock contents %d", LOCK_CONT); #endif fclose(tmplockfp); #if !TEST_NOT_ROOT if ((pwentp=getpwnam("uucp")) == (struct passwd *) NULL) error("can't find user 'uucp'"); if (chown(tmplockbuf, pwentp->pw_uid, (gid_t) -1) != 0) error("can't change ownership to 'uucp'"); #endif } else if (attempt == 2) { /* if too many times ... */ unlink(tmplockbuf); /* remove tmp lock */ exitactions &= ~EXITACTION_DTLK; /* tmplock won't need rm'ing */ return(-1); /* give up */ } /************************************************************************** * * Create a permanent lock * **************************************************************************/ debug(50, "linking %s to %s ...", lockbuf, tmplockbuf); if (link(tmplockbuf, lockbuf) == 0) { /* slide lock into place */ exitactions |= EXITACTION_DPLK; /* lock will need removing */ unlink(tmplockbuf); /* remove temporary lock */ exitactions &= ~EXITACTION_DTLK; /* templock won't need rm'ing */ debug(50, "successful, %s should exist", lockbuf); return(0); /* ok! */ } debug(50, "lock file already exists"); /************************************************************************** * * Examine existing lock * **************************************************************************/ if ((lockfp=fopen(lockbuf, "r")) == NULLFP) /* open existing lock file */ error("problem opening existing lock file"); #if (LOCK_CONT == LOCK_CONT_ASCII) fscanf(lockfp, "%d", &lockingpid); /* get owning pid */ #else internal("unsupported lock contents %d", LOCK_CONT); #endif debug(10, "lockfile %s contains pid %d", lockbuf, lockingpid); if (pidrunning(lockingpid) != 0) { /* if owning pid is running */ unlink(tmplockbuf); /* remove temp lock */ exitactions &= ~EXITACTION_DTLK; /* templock won't need rm'ing */ return(lockingpid); /* tell call who locked it */ } /************************************************************************** * * Remove stale lock and try again * **************************************************************************/ debug(10, "stale lock file"); info("stale lock file %s removed", lockbuf); /* remove stale lock */ if (unlink(lockbuf) != 0) error("can't unlink %s", lockbuf); exitactions &= ~EXITACTION_DPLK; /* lock won't need rm'ing */ debug(10, "trying again"); return(lock_device(devcp, attempt+1)); /* try again */ } /****************************************************************************** * * Function: pidrunning() * * Parameters: runningpid - number of process to test if running * * Return value: 0 if not running * runningpid if runningpid is running * * Description: this function forks a process. The child calls ps and * sends all output to the parent. The parent scans this * data looking for the sequence NEWLINE,SPACE,PID,SPACE * which is sufficient to ascertain if the process is running. * ******************************************************************************/ int pidrunning( int runningpid) /* pid to test if running */ { int pipefds[2]; /* to read what ps says */ int thiscnt; /* bytes read from ps */ int totalcnt; /* total bytes read from ps */ char pslinebuf[16384]; /* total ps output */ char expectbuf[16]; /* string we're looking for */ int pid; /* handy pid (used with fork) */ int i; /* handy loop counter */ if (pipe(pipefds) < 0) /* make a pipe */ error("Can't open pipe"); if ((pid=fork()) < 0) /* fork for ps */ error("Can't fork"); /************************************************************************** * * Child process execs ps * **************************************************************************/ else if (pid == 0) { debug(5, "child running ..."); /* close the read side of the pipe */ close(pipefds[0]); /* dont need stdin from pipe */ close(1); /* make stdout to pipe */ dup(pipefds[1]); close(pipefds[1]); /* exec ps */ execl("/bin/ps", "/bin/ps", "-ax", NULLCP); error("can't exec /bin/ps"); } /************************************************************************** * * Parent hunts for specified process in ps output * **************************************************************************/ close(pipefds[1]); /* dont need stdout to pipe */ /* need multiple reads, why ? */ for (totalcnt=0; (thiscnt=read(pipefds[0], pslinebuf+totalcnt, (int) (sizeof(pslinebuf)-totalcnt))) != 0; totalcnt+=thiscnt) ; if (totalcnt == sizeof(pslinebuf)) /* some still to read ? */ internal("ps output exceeded %d bytes", sizeof(pslinebuf)); debug(100, "total read: %d", totalcnt); pslinebuf[totalcnt] = '\0'; /* so we can use strcmp() */ while (wait((int *) 0) != pid) /* wait for child to exit */ ; close(pipefds[0]); /* close from pipe */ debug(500, "ps exited and returned: [%s]", pslinebuf); sprintf(expectbuf, "\n%5d ", runningpid); /* pattern we're hunting for */ debug(100, "searching ps output for [%s]", expectbuf); for (i=0; i