[comp.sources.misc] v18i005: upsd - UPS monitor daemon, Part01/01

art@pilikia.pegasus.com (Authur W. Neilson III) (04/09/91)

Submitted-by: Authur W. Neilson III <art@pilikia.pegasus.com>
Posting-number: Volume 18, Issue 5
Archive-name: upsd/part01

The UPS monitor daemon or "upsd"  watches the serial port connected to 
an UPS and will perform an unattended shutdown of the system if the UPS 
is on battery longer than a specified number of minutes.  Upsd needs to 
watch a tty with modem control properties, and expects the UPS to raise 
DCD when it switches to battery backup and drop DCD when it goes back to 
online.  Upsd was developed and tested under ISC with the FAS 2.08 driver
and an American Power Conversion SmartUPS 600, your milage may
vary on other OSes and UPSes.

Art
-----8<----- cut here -----8<----- cut here -----8<----- cut here -----8<-----
#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 1)."
# Contents:  MANIFEST Makefile README common.h funcs.c main.c ups
# Wrapped by root@pilikia on Sat Apr  6 21:22:27 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
echo shar: Extracting \"'MANIFEST'\" \(422 characters\)
sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X MANIFEST                   1	This shipping list
X Makefile                   1	Upsd makefile	
X README                     1	Documentation
X common.h                   1	Common program header
X funcs.c                    1	Functions 
X main.c                     1	Main program
X ups                        1	Init.d startup script
END_OF_FILE
if test 422 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
fi
# end of 'MANIFEST'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(942 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
X##
X#	M a k e f i l e
X#
X#	makefile for UPS monitor daemon
X#
X#	Arthur W. Neilson III
X#	art@pilikia.pegasus.com
X#	Sat Mar 30 1991
X#
X
XCC = cc
XDEFS =
XCFLAGS = -O $(DEFS)
XCFILES = main.c funcs.c
XOFILES = main.o funcs.o
XHFILES = common.h
XLIBES =
XDESTDIR = /etc
XINITDIR = /etc/init.d
XRC2DIR = /etc/rc2.d
XRC2NUM = 22
X
Xupsd: $(OFILES)
X	$(CC) $(CFLAGS) $(OFILES) -o $@ $(LIBES)
X	@strip $@
X	@mcs -d $@
X
Xinstall: upsd
X	cp upsd $(DESTDIR)/upsd
X	chown root $(DESTDIR)/upsd
X	chgrp sys $(DESTDIR)/upsd
X	chmod 550 $(DESTDIR)/upsd
X
Xinstall_rc: install
X	cp ups $(INITDIR)/ups
X	chown root $(INITDIR)/ups
X	chgrp sys $(INITDIR)/ups
X	chmod 744 $(INITDIR)/ups
X	ln $(INITDIR)/ups $(RC2DIR)/S$(RC2NUM)ups
X
Xindent:
X	@for f in $(CFILES); do \
X		indent $$f; \
X	done
X
Xkit:
X	makekit -m
X
Xclean:
X	rm -f upsd core *.o *.BAK Part*
X
Xclobber: clean
X	rm -f $(DESTDIR)/upsd $(INITDIR)/ups $(RC2DIR)/S$(RC2NUM)ups
X
X# dependencies
Xmain.o:		main.c $(HFILES)
Xfuncs.o:	funcs.c $(HFILES)
END_OF_FILE
if test 942 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'README' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'README'\"
else
echo shar: Extracting \"'README'\" \(3910 characters\)
sed "s/^X//" >'README' <<'END_OF_FILE'
X	Tue Apr 02 23:24:20 HST 1991	art@pilikia.pegasus.com		Pg. 1
X 
X
X	INTRODUCTION
X
X	The UPS monitor daemon or "upsd"  watches the serial port
X	connected to an UPS and will perform an unattended shutdown
X	of the system if the UPS is on battery longer than a specified
X	number of minutes.  Upsd needs to watch a tty with modem control
X	properties, and expects the UPS to raise DCD when it switches
X	to battery backup and drop DCD when it goes back to online.
X	Upsd was developed and tested under ISC with the FAS 2.08 driver
X	and an American Power Conversion SmartUPS 600, your milage may
X	vary on other OSes and UPSes.
X
X	The C source was written for system V and hence will require 
X	some work to get it working under BSD or other UNIXes, the
X	program is built via the Makefile.  A number of configurable
X	defaults are in common.h, you may want to hack that file before
X	making upsd.  Although the program can be run manually from the
X	command line, users will most likely want the program to start
X	automatically from their /etc/rc script (SYS5R2) or a script
X	in /etc/rc2.d (SYS5R3).  The Makefile by default installs upsd
X	in the /etc directory when the install target is made.  
X
X
X	Tue Apr 02 23:24:20 HST 1991	art@pilikia.pegasus.com		Pg. 2
X
X
X	COMMAND LINE OPTIONS
X
X	Upsd runtime behavior can be configured either on the command
X	line or via environment variables.  The command line options
X	take precedence to the environment variable settings, and are
X	as follows:
X
X	usage: upsd [-d tty][-c cmd][-l log][-t min]
X		-d tty		pathname of UPS device
X		-c cmd		pathname of shutdown command
X		-l log		pathname of UPS log file
X		-t min		delay time in minutes
X
X	The -d tty option must specify the full pathname (including the
X	/dev/ prefix) to the tty device the UPS is on.
X
X	Example:
X
X	upsd -d /dev/ttyFM00
X
X	The -c cmd option specifies the full pathname of the command
X	to be executed to shut down the system.  This command must
X	be enclosed in quotes if it consists of 2 or more words.
X
X	Example:
X
X	upsd -d /dev/ttyFM00 -c "/etc/shutdown -y -g1 -i0"
X
X	The -l option specified the logfile upsd will write it's event
X	messages to, these messages give the date and time that the UPS
X	daemon started, switched to battery, switched back to online,
X	executed the shutdown command, or was terminated via SIGTERM.
X
X	Example:
X
X	upsd -d /dev/ttyFM00 -c "/etc/shutdown -y -g1 -i0" -l /etc/upslog
X
X	Finally, the -t option specifies the number of minutes to allow
X	the UPS to be on battery backup before executing the shutdown
X	sequence.  This number must be between 1 and 30.  Be careful not
X	to choose a value greater than the number of minutes of battery
X	time your UPS supports with your current load.  A value in the
X	5 to 10 minute range is probably sufficient.
X
X	Example:
X
X	upsd -d /dev/ttyFM00 -c "/etc/shutdown -y -g1 -i0" -l /etc/upslog -t 10
X	
X	Tue Apr 02 23:24:20 HST 1991	art@pilikia.pegasus.com		Pg. 3
X
X
X	ENVIRONMENT VARIABLES
X
X	The following environment variables can be set if that interface
X	is preferred to the command line options.  Note again that command
X	line options override the environment variable settings.
X
X
X	Environment	Equivalent		Default
X	Variable	Command line option	Value
X	UPSPORT			-p		/dev/ttyFM00
X	UPSSHUT			-c		"/etc/shutdown -y -g1 -i0"
X	UPSLOG			-l		/etc/upslog
X	UPSTIME			-t		10
X
X	Note that the compiled in default values can be altered in common.h
X	and the program can be recompiled.  If no command line options or
X	environment variables exist, the defaults will be used.  The table
X	above gives the environment variables looked for by the program and
X	their command line option counterparts.  All the rules applying to
X	the command line options apply to the environment variables as well.
X
X	If you have any comments or suggestions regarding my program,
X	send email to the following address:
X
X	Arthur W. Neilson III
X	INET: art@pilikia.pegasus.com
X	UUCP: uunet!ucsd!nosc!pilikia!art
X
END_OF_FILE
if test 3910 -ne `wc -c <'README'`; then
    echo shar: \"'README'\" unpacked with wrong size!
fi
# end of 'README'
fi
if test -f 'common.h' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'common.h'\"
else
echo shar: Extracting \"'common.h'\" \(1885 characters\)
sed "s/^X//" >'common.h' <<'END_OF_FILE'
X/*
X**	c o m m o n . h
X**
X**	common header file for UPS monitor daemon
X**
X**	Arthur W. Neilson III
X**	art@pilikia.pegasus.com
X**	Sat Mar 30 1991
X*/
X
X#include <stdio.h>
X#include <string.h>
X#include <fcntl.h>
X#include <time.h>
X#include <sys/types.h>
X#include <sys/termio.h>
X#include <sys/signal.h>
X#include <sys/stat.h>
X
X#define ERR		-1		/* error return value */
X#define SH		"/bin/sh"	/* shell to exec shutdown command */
X#define ROOT		"/"		/* root directory for chdir */
X#define CONSOLE		"/dev/console"	/* console device */
X#define SECS_PER_MIN	60		/* number of secs in a minute */
X#define MAX_TIME	30		/* maximum delay time allowed */
X#define SHUT_PERMS	0500		/* shutdown command permissions mask */
X#define LOG_PERMS	0600		/* logfile permissions on create */
X#define LOG_FLAGS	O_WRONLY | O_CREAT | O_APPEND	/* logfile open flags */
X
X/* default tuneable parameter values */
X#define UPS_PORT	"/dev/ttyFM00"	/* UPS port device name */
X#define UPS_SHUT	"/etc/shutdown -y -g0"	/* shutdown command */
X#define UPS_LOG		"/etc/upslog"	/* UPS log file pathname */
X#define UPS_TIME	10		/* default delay time (minutes) */
X
X/* environment variable names */
X#define UPSPORT		"UPSPORT"
X#define UPSSHUT		"UPSSHUT"
X#define UPSLOG		"UPSLOG"
X#define UPSTIME		"UPSTIME"
X
X/* UPS log messages */
X#define START_MSG	"UPS daemon started"
X#define OPEN_MSG	"Error opening UPS port"
X#define BATTERY_MSG	"UPS switch to battery backup"
X#define ONLINE_MSG	"UPS switch to online power"
X#define SHUTDOWN_MSG	"Shutdown command executed"
X#define TERM_MSG	"Caught termination signal"
X
X/* common globals */
Xextern char   *ups_port;		/* UPS device name */
Xextern char   *ups_shut;		/* system shutdown command */
Xextern char   *ups_log;			/* UPS log file name */
Xextern int     ups_time;		/* delay time before shutdown */
Xextern int     ups_fd;			/* UPS port descriptor */
Xextern int     log_fd;			/* log file descriptor */
END_OF_FILE
if test 1885 -ne `wc -c <'common.h'`; then
    echo shar: \"'common.h'\" unpacked with wrong size!
fi
# end of 'common.h'
fi
if test -f 'funcs.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'funcs.c'\"
else
echo shar: Extracting \"'funcs.c'\" \(4629 characters\)
sed "s/^X//" >'funcs.c' <<'END_OF_FILE'
X/*
X**	f u n c s . c
X**
X**	functions for UPS monitor daemon
X**
X**	Arthur W. Neilson III
X**	art@pilikia.pegasus.com
X**	Sat Mar 30 1991
X*/
X
X#include "common.h"
X
X/*
X**	g e t v a r s
X**
X**	retrieve environment variables
X*/
Xvoid
Xgetvars()
X{
X	char   *s, *getenv();
X
X	if ((s = getenv(UPSPORT)) != NULL)
X		ups_port = s;
X	if ((s = getenv(UPSSHUT)) != NULL)
X		ups_shut = s;
X	if ((s = getenv(UPSLOG)) != NULL)
X		ups_log = s;
X	if ((s = getenv(UPSTIME)) != NULL)
X		ups_time = atoi(s) * SECS_PER_MIN;
X}
X
X/*
X**	g e t o p t i o n s
X**
X**	retrieve and process command line options
X*/
Xvoid
Xgetoptions(ac, av)
Xint     ac;
Xchar   *av[];
X{
X	int     c;
X
X	void    usage();
X
X	extern char *optarg;
X	extern int optind, opterr;
X
X	/* parse the command line */
X	while ((c = getopt(ac, av, "d:c:l:t:")) != EOF)
X		switch (c) {
X		case 'd':	/* device option */
X			ups_port = optarg;
X			break;
X		case 'c':	/* command option */
X			ups_shut = optarg;
X			break;
X		case 'l':	/* logfile option */
X			ups_log = optarg;
X			break;
X		case 't':	/* time option */
X			ups_time = atoi(optarg) * SECS_PER_MIN;
X			break;
X		case '\?':	/* illegal option */
X			usage(av[0]);
X			exit(1);
X		}
X}
X
X/*
X**	c h k o p t i o n s
X**
X**	check runtime options
X*/
Xvoid
Xchkoptions()
X{
X	struct stat st;
X	char   *p, buf[64];
X
X	/* UPS port must exist */
X	if (stat(ups_port, &st) == ERR) {
X		perror(ups_port);
X		exit(1);
X	}
X	/* and must be character special */
X	if ((st.st_mode & S_IFMT) != S_IFCHR) {
X		fprintf(stderr, "%s not character special\n", ups_port);
X		exit(1);
X	}
X	/* get command name out of shutdown command */
X	strcpy(buf, ups_shut);
X	if ((p = strtok(buf, " ")) == NULL)
X		p = buf;
X
X	/* shutdown command must exist */
X	if (stat(p, &st) == ERR) {
X		perror(ups_shut);
X		exit(1);
X	}
X	/* and must be readable/executable by owner */
X	if (!(st.st_mode & SHUT_PERMS)) {
X		fprintf(stderr, "%s must be readable/executable by owner\n", ups_port);
X		exit(1);
X	}
X	/* delay time must be > 0 and <= MAX_TIME */
X	if (ups_time < 1 || ups_time > MAX_TIME) {
X		fprintf(stderr, "time must be between 1 and %d\n", MAX_TIME);
X		exit(1);
X	}
X}
X
X/*
X**	m k d a e m o n
X**
X**	create daemon process
X*/
Xvoid
Xmkdaemon()
X{
X	char    c;
X
X	void    sigcatch();
X	void    writelog();
X	void    shutdown();
X
X	if (!fork()) {
X
X		/* close standard files */
X		close(0);	/* stdin */
X		close(1);	/* stdout */
X		close(2);	/* stderr */
X
X		setpgrp();	/* disassociate from terminal */
X
X		/* ignore interrupts */
X		signal(SIGHUP, SIG_IGN);
X		signal(SIGINT, SIG_IGN);
X		signal(SIGQUIT, SIG_IGN);
X
X		/* catch termination signal */
X		signal(SIGTERM, sigcatch);
X
X		/* and shutdown on alarm */
X		signal(SIGALRM, shutdown);
X
X		/* open log file for append */
X		if ((log_fd = open(ups_log, LOG_FLAGS, LOG_PERMS)) == ERR)
X			exit(1);
X
X		writelog(START_MSG);
X
X		/* open blocks on UPS switch to battery */
X		if ((ups_fd = open(ups_port, O_RDWR)) == ERR) {
X			writelog(OPEN_MSG);
X			exit(1);
X		}
X		writelog(BATTERY_MSG);
X
X		alarm(ups_time);/* set the alarm clock */
X
X		/* read blocks on UPS switch to online */
X		if (read(ups_fd, &c, 1) == ERR)
X			exit(1);
X
X		writelog(ONLINE_MSG);
X
X		close(log_fd);
X		close(ups_fd);
X
X		mkdaemon();
X	}
X}
X
X/*
X**	s i g c a t c h
X**
X**	catch termination signal
X*/
Xvoid
Xsigcatch()
X{
X	writelog(TERM_MSG);
X	close(log_fd);
X	close(ups_fd);
X	exit(1);
X}
X
X/*
X**	w r i t e l o g
X**
X**	write message to the UPS log file
X*/
Xvoid
Xwritelog(msg)
Xchar   *msg;
X{
X	time_t  ticks;
X	char   *p, *ct;
X	char    msg_buf[80];
X
X	time(&ticks);
X	ct = ctime(&ticks);
X
X	/* find newline in buffer */
X	if ((p = strrchr(ct, '\n')) != NULL)
X		*p = NULL;	/* and zap it */
X
X	sprintf(msg_buf, "%s -- %s\n", ct, msg);
X	write(log_fd, msg_buf, strlen(msg_buf));
X}
X
X/*
X**	s h u t d o w n
X**
X**	shutdown the system
X*/
Xvoid
Xshutdown()
X{
X	void    attach();
X
X	writelog(SHUTDOWN_MSG);
X
X	close(log_fd);
X	close(ups_fd);
X
X	attach(CONSOLE);
X
X	chdir(ROOT);
X
X	/* execute shutdown command */
X	execlp(SH, SH, "-c", ups_shut, NULL);
X}
X
X/*
X**	a t t a c h
X**
X**	attach standard i/o to a device
X*/
Xvoid
Xattach(dev)
Xchar   *dev;
X{
X	int     fd;
X
X	/* close standard files */
X	close(0);		/* stdin */
X	close(1);		/* stdout */
X	close(2);		/* stderr */
X
X	/* attach stdin to named device */
X	if ((fd = open(dev, O_RDWR)) == ERR)
X		exit(1);
X	dup(fd);		/* and stdout */
X	dup(fd);		/* and stderr */
X}
X
X/*
X**	u s a g e
X**
X**	display program usage
X*/
Xvoid
Xusage(s)
Xchar   *s;
X{
X	fprintf(stderr, "usage: %s [-d tty][-c cmd][-l log][-t min]\n", s);
X	fprintf(stderr, "\t-d tty\t\tpathname of UPS device\n");
X	fprintf(stderr, "\t-c cmd\t\tpathname of shutdown command\n");
X	fprintf(stderr, "\t-l log\t\tpathname of UPS log file\n");
X	fprintf(stderr, "\t-t min\t\tdelay time in minutes\n");
X}
END_OF_FILE
if test 4629 -ne `wc -c <'funcs.c'`; then
    echo shar: \"'funcs.c'\" unpacked with wrong size!
fi
# end of 'funcs.c'
fi
if test -f 'main.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'main.c'\"
else
echo shar: Extracting \"'main.c'\" \(692 characters\)
sed "s/^X//" >'main.c' <<'END_OF_FILE'
X/*
X**	m a i n . c
X**
X**	main program for UPS monitor daemon
X**
X**	Arthur W. Neilson III
X**	art@pilikia.pegasus.com
X**	Sat Mar 30 1991
X*/
X
X#include "common.h"
X
X/* default tuneables */
Xchar   *ups_port = UPS_PORT;
Xchar   *ups_shut = UPS_SHUT;
Xchar   *ups_log = UPS_LOG;
Xint     ups_time = UPS_TIME;
X
X/* global descriptors */
Xint     ups_fd;
Xint     log_fd;
X
Xmain(argc, argv)
Xint     argc;
Xchar   *argv[];
X{
X	void    getvars();
X	void    getoptions();
X	void    chkoptions();
X	void    mkdaemon();
X
X	getvars();		/* retrieve environment vars */
X	getoptions(argc, argv);	/* process command line options */
X	chkoptions();		/* validate command line options */
X	mkdaemon();		/* fork daemon process */
X}
END_OF_FILE
if test 692 -ne `wc -c <'main.c'`; then
    echo shar: \"'main.c'\" unpacked with wrong size!
fi
# end of 'main.c'
fi
if test -f 'ups' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'ups'\"
else
echo shar: Extracting \"'ups'\" \(296 characters\)
sed "s/^X//" >'ups' <<'END_OF_FILE'
X#ident	"@(#)ups	1.0 - 91/03/30"
X#	UPS monitor daemon
X
X# configure UPS daemon runtime
Xexport UPSPORT; UPSPORT=/dev/ttyFM00
Xexport UPSSHUT; UPSSHUT="/etc/shutdown -y -i0"
Xexport UPSLOG;  UPSLOG=/etc/upslog
Xexport UPSTIME; UPSTIME=10
X
Xset `who -r`
Xif [ $9 = "S" -a -x /etc/upsd ]
Xthen
X	/etc/upsd
Xfi
END_OF_FILE
if test 296 -ne `wc -c <'ups'`; then
    echo shar: \"'ups'\" unpacked with wrong size!
fi
chmod +x 'ups'
# end of 'ups'
fi
echo shar: End of archive 1 \(of 1\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have the archive.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
-----8<----- cut here -----8<----- cut here -----8<----- cut here -----8<-----

-- 
Arthur W. Neilson III		| INET: art@pilikia.pegasus.com
Bank of Hawaii Tech Support	| UUCP: uunet!ucsd!nosc!pilikia!art

exit 0 # Just in case...
-- 
Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
Sterling Software, IMD           UUCP:     uunet!sparky!kent
Phone:    (402) 291-8300         FAX:      (402) 291-4362
Please send comp.sources.misc-related mail to kent@uunet.uu.net.