[Nut-upsdev] [PATCH 30/36] Implement an Emergency Power Off (EPO) feature.

Greg A. Woods woods at planix.com
Thu Mar 8 23:21:41 UTC 2012


From: "Greg A. Woods" <woods at planix.com>

NOTE: This is different than a "FSD" command because with EPO the power
_must_ be cut and must _not_ return without direct human intervention,
and this must work while mains power is still being supplied.

To handle emergency situations you can set the emergency power off (EPO)
flag on your master upsmon system.  You can do this by calling upsmon
again to set the flag, i.e.:

	upsmon -c epo

After that, the master and the slaves will do their usual shutdown sequence
as if the battery had gone critical, and hopefully your UPS will be shut
down by the master in such a way that they will not restart without
manual intervention.

WARNING:  not yet tested in a live configuration, though multiple code
walk-throughs have been done.
---
 clients/upsmon.c        |   64 +++++++++++++++++--------
 clients/upsmon.h        |    3 +-
 docs/FAQ.txt            |   44 +++++++++++++++--
 docs/config-notes.txt   |  122 +++++++++++++++++++++++++++++++++--------------
 docs/man/apcsmart.txt   |    6 +--
 docs/man/snmp-ups.txt   |   14 +++---
 docs/man/upsdrvctl.txt  |   22 ++++++---
 docs/man/upsmon.txt     |   27 ++++++++++-
 docs/net-protocol.txt   |    3 ++
 docs/new-drivers.txt    |   21 ++++++--
 drivers/apcsmart-old.c  |    6 ++-
 drivers/apcsmart.c      |   73 +++++++++++++++++++---------
 drivers/apcsmart.h      |    1 +
 drivers/bcmxcp.c        |    4 +-
 drivers/belkin.c        |   12 +++--
 drivers/belkinunv.c     |    8 ++--
 drivers/bestfcom.c      |    8 +++-
 drivers/bestfortress.c  |    4 +-
 drivers/bestuferrups.c  |    8 +++-
 drivers/bestups.c       |   15 ++++--
 drivers/blazer.c        |    6 +--
 drivers/clone-outlet.c  |    2 +-
 drivers/clone.c         |    4 +-
 drivers/dummy-ups.c     |    4 +-
 drivers/etapro.c        |    6 +--
 drivers/everups.c       |    4 +-
 drivers/gamatronic.c    |    9 ++--
 drivers/genericups.c    |    6 ++-
 drivers/isbmex.c        |    7 ++-
 drivers/ivtscd.c        |    5 +-
 drivers/liebert-esp2.c  |    6 ++-
 drivers/liebert.c       |    4 +-
 drivers/main.c          |   17 ++++---
 drivers/main.h          |    2 +-
 drivers/masterguard.c   |   12 +++--
 drivers/metasys.c       |   27 +++++++----
 drivers/mge-shut.c      |   15 ++----
 drivers/mge-utalk.c     |   16 ++-----
 drivers/microdowell.c   |   16 +++++--
 drivers/netxml-ups.c    |    4 +-
 drivers/oneac.c         |    5 +-
 drivers/optiups.c       |   14 ++++--
 drivers/powercom.c      |   11 +++--
 drivers/powerpanel.c    |   30 ++++++++----
 drivers/rhino.c         |   23 +++------
 drivers/richcomm_usb.c  |    6 ++-
 drivers/safenet.c       |    5 +-
 drivers/skel.c          |   12 +++--
 drivers/snmp-ups.c      |   38 +++++++++++----
 drivers/solis.c         |   11 ++---
 drivers/tripplite.c     |    8 +++-
 drivers/tripplite_usb.c |    6 ++-
 drivers/tripplitesu.c   |   15 +++---
 drivers/upscode2.c      |   24 ++++++----
 drivers/upsdrvctl.c     |   19 ++++++--
 drivers/usbhid-ups.c    |   20 ++++++--
 drivers/victronups.c    |    6 ++-
 57 files changed, 608 insertions(+), 282 deletions(-)

diff --git a/clients/upsmon.c b/clients/upsmon.c
index 33d0a64..5116437 100644
--- a/clients/upsmon.c
+++ b/clients/upsmon.c
@@ -69,7 +69,7 @@ static	char	*certpath = NULL;
 static	int	certverify = 0;		/* don't verify by default */
 static	int	forcessl = 0;		/* don't require ssl by default */
 
-static	int	userfsd = 0, use_pipe = 1, pipefd[2];
+static	int	userfsd = 0, userepo = 0, use_pipe = 1, pipefd[2];
 
 static	utype_t	*firstups = NULL;
 
@@ -394,7 +394,7 @@ static void ups_on_line(utype_t *ups)
 }
 
 /* create the flag file if necessary */
-static void set_pdflag(void)
+static void set_pdflag(int epo)
 {
 	FILE	*pdf;
 
@@ -407,18 +407,18 @@ static void set_pdflag(void)
 		return;
 	}
 
-	fprintf(pdf, "%s", SDMAGIC);
+	fprintf(pdf, "%s%s", SDMAGIC, epo ? " epo" : "");
 	fclose(pdf);
 }
 
 /* the actual shutdown procedure */
-static void doshutdown(void)
+static void doshutdown(int epo)
 {
 	int	ret;
 
 	/* this should probably go away at some point */
-	upslogx(LOG_CRIT, "Executing automatic power-fail shutdown");
-	wall("Executing automatic power-fail shutdown\n");
+	upslogx(LOG_CRIT, "Executing automatic shutdown");
+	wall("Executing automatic shutdown\n");
 
 	do_notify(NULL, NOTIFY_SHUTDOWN);
 
@@ -428,7 +428,7 @@ static void doshutdown(void)
 	if (use_pipe) {
 		char	ch;
 
-		ch = 1;
+		ch = epo ? 2 : 1;
 		ret = write(pipefd[1], &ch, 1);
 	} else {
 		/* one process model = we do all the work here */
@@ -436,7 +436,7 @@ static void doshutdown(void)
 		if (geteuid() != 0)
 			upslogx(LOG_WARNING, "Not root, shutdown may fail");
 
-		set_pdflag();
+		set_pdflag(epo);
 
 		ret = system(shutdowncmd);
 
@@ -608,14 +608,26 @@ static void slavesync(void)
 	}
 }
 
-static void forceshutdown(void)
+static void forceshutdown(int epo)
 {
 	utype_t	*ups;
 	int	isamaster = 0;
 
-	upsdebugx(1, "Shutting down any UPSes in MASTER mode...");
+	upsdebugx(1, "Setting Forced Shutdown on any UPSes we are MASTER over...");
 
 	/* set FSD on any "master" UPS entries (forced shutdown in progress) */
+	/*
+	 * Note for now there is not yet an EPO state separate from FSD.
+	 *
+	 * Perhaps an EPO state would help somewhat as then slave systems could
+	 * use "halt -p" (if possible) to explictly power themselves down.
+	 * Doing this however would require either adding an option to the
+	 * shutdown command, or alternately possibly even adding a whole new
+	 * command for EPO separate from "SHUTDOWNCMD".  For the moment the
+	 * expection will be that anyone needing EPO capability will be using a
+	 * UPS smart enough to have a proper working driver with a proper
+	 * working "shutdown.stayoff" command.
+	 */
 	for (ups = firstups; ups != NULL; ups = ups->next)
 		if (flag_isset(ups->status, ST_MASTER)) {
 			isamaster = 1;
@@ -624,7 +636,7 @@ static void forceshutdown(void)
 
 	/* if we're not a master on anything, we should shut down now */
 	if (!isamaster)
-		doshutdown();
+		doshutdown(epo);
 
 	/* must be the master now */
 	upsdebugx(1, "This system is a master... waiting for slave logout...");
@@ -633,7 +645,7 @@ static void forceshutdown(void)
 	slavesync();
 
 	/* time expired or all the slaves are gone, so shutdown */
-	doshutdown();
+	doshutdown(epo);
 }
 
 static int is_ups_critical(utype_t *ups)
@@ -683,12 +695,12 @@ static void recalc(void)
 	ups = firstups;
 	while (ups != NULL) {
 		/* promote dead UPSes that were last known OB to OB+LB */
-		if ((now - ups->lastpoll) > deadtime)
+		if ((now - ups->lastpoll) > deadtime) {
 			if (flag_isset(ups->status, ST_ONBATT)) {
-				upsdebugx(1, "Promoting dead UPS: %s", ups->sys);
+				upsdebugx(1, "Promoting probably dead UPS from OnBattery to OnBattery+LowBattery: %s", ups->sys);
 				setflag(&ups->status, ST_LOWBATT);
 			}
-
+		}
 		/* note: we assume that a UPS that isn't critical must be OK *
 		 *							     *
 		 * this means a UPS we've never heard from is assumed OL     *
@@ -709,7 +721,7 @@ static void recalc(void)
 #endif
 
 	if (val_ol < minsupplies)
-		forceshutdown();
+		forceshutdown(0);	/* !epo */
 }		
 
 static void ups_low_batt(utype_t *ups)
@@ -1311,6 +1323,12 @@ static void user_fsd(int sig)
 	userfsd = 1;
 }
 
+static void user_epo(int sig)
+{
+	upslogx(LOG_INFO, "Signal %d: User requested EPO", sig);
+	userepo = 1;
+}
+
 static void set_reload_flag(int sig)
 {
 	reload_flag = 1;
@@ -1347,6 +1365,9 @@ static void setup_signals(void)
 	sa.sa_handler = user_fsd;
 	sigaction(SIGCMD_FSD, &sa, NULL);
 
+	sa.sa_handler = user_epo;
+	sigaction(SIGCMD_EPO, &sa, NULL);
+
 	sa.sa_handler = set_reload_flag;
 	sigaction(SIGCMD_RELOAD, &sa, NULL);
 }
@@ -1659,6 +1680,7 @@ static void help(const char *progname)
 	printf("  -c <cmd>	send command to running process\n");	
 	printf("		commands:\n");
 	printf("		 - fsd: shutdown all master UPSes (use with caution)\n");
+	printf("		 - epo: power off all master UPSes (use with caution)\n");
 	printf("		 - reload: reread configuration\n");
 	printf("		 - stop: stop monitoring and exit\n");
 	printf("  -D		raise debugging level\n");
@@ -1691,11 +1713,11 @@ static void runparent(int fd)
 		fatal_with_errno(EXIT_FAILURE, "upsmon parent: read");
 	}
 
-	if (ch != 1)
+	if (!(ch == 1) || (ch == 2))
 		fatalx(EXIT_FAILURE, "upsmon parent: got bogus pipe command %c", ch);
 
 	/* have to do this here - child is unprivileged */
-	set_pdflag();
+	set_pdflag(ch == 2 ? 1 : 0);
 
 	ret = system(shutdowncmd);
 
@@ -1892,6 +1914,8 @@ int main(int argc, char *argv[])
 			case 'c':
 				if (!strncmp(optarg, "fsd", strlen(optarg)))
 					cmd = SIGCMD_FSD;
+				if (!strncmp(optarg, "epo", strlen(optarg)))
+					cmd = SIGCMD_EPO;
 				if (!strncmp(optarg, "stop", strlen(optarg)))
 					cmd = SIGCMD_STOP;
 				if (!strncmp(optarg, "reload", strlen(optarg)))
@@ -2002,8 +2026,8 @@ int main(int argc, char *argv[])
 		utype_t	*ups;
 
 		/* check flags from signal handlers */
-		if (userfsd)
-			forceshutdown();
+		if (userfsd || userepo)
+			forceshutdown(userepo);
 
 		if (reload_flag)
 			reload_conf();
diff --git a/clients/upsmon.h b/clients/upsmon.h
index 1739912..9c5fe39 100644
--- a/clients/upsmon.h
+++ b/clients/upsmon.h
@@ -28,7 +28,7 @@
 #define ST_CONNECTED	(1 << 6)	/* upscli_connect returned OK		*/
 
 /* required contents of flag file */
-#define SDMAGIC "upsmon-shutdown-file"  
+#define SDMAGIC		"upsmon-shutdown-file" /* may be followed by " epo" */
 
 /* UPS tracking structure */
 
@@ -104,6 +104,7 @@ struct {
 /* values for signals passed between processes */
 
 #define SIGCMD_FSD	SIGUSR1
+#define SIGCMD_EPO	SIGUSR2
 #define SIGCMD_STOP	SIGTERM
 #define SIGCMD_RELOAD	SIGHUP
 
diff --git a/docs/FAQ.txt b/docs/FAQ.txt
index eb8115b..c1f594c 100644
--- a/docs/FAQ.txt
+++ b/docs/FAQ.txt
@@ -739,12 +739,46 @@ they won't be stuck in the halted state with the UPS running on line power.
 
 Implement this by modifying your shutdown script like this:
 
-	if (test -f /etc/killpower)
-	then
-		/usr/local/ups/bin/upsdrvctl shutdown
+	if [ -s /etc/killpower ]; then
+		if grep " epo" /etc/killpower >/dev/null 2>&1; then
+			echo "Killing all UPS power, bye!"
+			/usr/local/ups/bin/upsdrvctl epo
 
-		sleep 120
+			# Now we need to make sure this system shuts
+			# down and stays off even if the UPS fails to
+			# remove power.  This next command should halt
+			# the system and tell it to turn off its own
+			# power supplies if possible (normally possible
+			# on most modern systems)
+
+			halt -p		
+		fi
+
+		# if your system cannot power itself off at this point
+		# then you need to pause for at least the length of time
+		# it will take the UPS to power off.  This may depend on
+		# settings in the UPS and/or in the UPS driver.
+
+		sleep 240
+
+		# uh oh, we never lost power! (power race?)
+
+		# Unfortunately if the UPS is still on-battery and you
+		# do reboot then NUT will, or should, shut it down
+		# again, and then the cycle may continue until either
+		# mains power is restored, or the UPS batteries fail at
+		# an in-opportune time and your system crashes hard.
 
-		# uh oh, we never got shut down! (power race?)
 		reboot
 	fi
+
+You should run a manual forced shutdown test while mains power remains
+on to be sure that either your UPS units do remove and then, after some
+delay, restore power to your systems; or else the systems reboot
+themselves after the timeout.  If you want to be really careful to test
+the exact scenario of a race condition you could try pulling the plug on
+your UPS, waiting until the shutdown begins and immediately plugging it
+back in again.  Note that you may have to plug in the UPS a wee bit
+beforehand because it may not go back online right away.  Don't be
+surprised if you cannot simulate a race, but don't expect that this
+makes a race impossible in real life.
diff --git a/docs/config-notes.txt b/docs/config-notes.txt
index f7c40e4..0042e61 100644
--- a/docs/config-notes.txt
+++ b/docs/config-notes.txt
@@ -327,14 +327,16 @@ Edit your startup scripts, and make sure upsdrvctl and upsd are run every time
 your system starts.
 
 [[UPS_shutdown]]
-Configuring automatic shutdowns for low battery events
-------------------------------------------------------
+Configuring automatic shutdowns for critical events
+---------------------------------------------------
 
 The whole point of UPS software is to bring down the OS cleanly when you
-run out of battery power. Everything else is roughly eye candy.
+run out of battery power or the UPS notices some other critical
+condition.  Everything else is roughly eye candy.
 
-To make  sure your system shuts down properly, you will need to perform some
-additional configuration and run upsmon. Here are the basics.
+To make sure your system shuts down properly, you will need to perform
+some additional configuration and run "upsmon" on relevant systems.
+Here are the basics.
 
 [[Shutdown_design]]
 Shutdown design
@@ -349,12 +351,12 @@ Here are the steps that occur when a critical power event happens:
 1. The UPS goes on battery
 
 2. The UPS reaches low battery (a "critical" UPS), that is to say
-   upsc displays:
+   "upsc" will display:
 +
    ups.status: OB LB
 +
 The exact behavior depends on the specific device, and is related to:
-
++
    - battery.charge and battery.charge.low
    - battery.runtime and battery.runtime.low
 
@@ -383,11 +385,18 @@ The exact behavior depends on the specific device, and is related to:
    - calls the SHUTDOWNCMD
 
 7. On most systems, init takes over, kills your processes, syncs and
-   unmounts some filesystems, and remounts some read-only.
-
-8. init then runs your shutdown script.  This checks for the
-   POWERDOWNFLAG, finds it, and tells the UPS driver(s) to power off
-   the load.
+   unmounts some filesystems, and remounts some read-only.  Note that
+   for network-connected UPSes, such as those controlled by the
+   snmp-ups(8) driver, the network ports will have to stay up and
+   running, and hopefully all intervening devices will stay running too.
+
+8. init then runs your final UPS shutdown script.  This checks for the
+   POWERDOWNFLAG, finds it, and tells the UPS driver(s) to power off the
+   load by running "upsdrvctl shutdown".  At the moment the driver
+   decides how to power down the load and/or the UPS.  Usually this
+   means that if the UPS is still online, but is being shut off by force.
+   The driver will have to command it to shut off and stay off, thus
+   requiring human intervention to restore power.
 
 9. The system loses power.
 
@@ -396,6 +405,7 @@ The exact behavior depends on the specific device, and is related to:
 11. All systems reboot and go back to work.
 
 
+
 How you set it up
 ~~~~~~~~~~~~~~~~~
 
@@ -547,20 +557,51 @@ NOTE: This step is not need if you installed from packages.
 Edit your shutdown scripts, and add `upsdrvctl shutdown`.
 
 You should configure your system to power down the UPS after the filesystems are
-remounted read-only.  Have it look for the presence of the POWERDOWNFLAG (from
+remounted read-only.  This should be the very last thing your system
+does.  Have it look for the presence of the POWERDOWNFLAG (from
 linkman:upsmon.conf[5]), using this as an example:
 
 --------------------------------------------------------------------------------
 
-	if (test -f /etc/killpower)
-	then
-		echo "Killing the power, bye!"
+	if [ -s /etc/killpower ]; then
+		if grep " epo" /etc/killpower >/dev/null 2>&1; then
+			echo "Removing all UPS power permanently, bye!"
+			/usr/local/ups/bin/upsdrvctl epo
+
+			# Now we need to make sure this system shuts
+			# down and stays off even if the UPS fails to
+			# remove power.  This next command should halt
+			# the system and tell it to turn off its own
+			# power supplies if possible (normally possible
+			# on most modern systems)
+
+			halt -p
+		fi
+
+		echo "Removing all UPS power, bye!"
 		/usr/local/ups/bin/upsdrvctl shutdown
 
-		sleep 120
+		# if your system cannot power itself off at this point
+		# then you need to pause for at least the length of time
+		# it will take the UPS to power off.  This may depend on
+		# settings in the UPS and/or in the UPS driver.
+
+		#sleep 240
+
+		# if the system didn't power itself off and is still
+		# waiting for the UPS to power off then...  uh oh...
+
+		# the UPS power-off has failed and you may want to
+		# reboot here so you don't get stuck!
+
+		# Unfortunately if the UPS is still on-battery and you
+		# do reboot then NUT will, or should, shut it down
+		# again, and then the cycle may continue until either
+		# mains power is restored, or the UPS batteries fail at
+		# an in-opportune time and your system crashes hard.
+
+		#reboot
 
-		# uh oh... the UPS power-off failed
-		# you probably want to reboot here so you don't get stuck!
 		# *** see also the section on power races in the FAQ! ***
 	fi
 
@@ -572,9 +613,10 @@ linkman:upsmon.conf[5]), using this as an example:
 Don't use it unless your system is ready to be halted by force.
 If you run RAID, read the <<_raid_warning,RAID warning>> below!
 
-- Make sure the filesystem(s) containing upsdrvctl, ups.conf and your UPS
-driver(s) are mounted (possibly in read-only mode) when the system gets to this
-point.  Otherwise it won't be able to figure out what to do.
+- Make sure the filesystem(s) containing upsdrvctl, ups.conf and your
+UPS driver(s), etc., are all still mounted, but in read-only mode, when
+the system gets to this point.  Otherwise it won't be able to figure out
+what to do and do it.
 ================================================================================
 
 
@@ -604,15 +646,20 @@ This will execute a full shutdown sequence, as presented in
 <<Shutdown_design,Shutdown design>>, starting from the 3rd step.
 
 If everything works correctly, the computer will be forcibly powered
-off, may remain off for a few seconds to a few minutes (depending on
-the driver and UPS type), then will power on again.
+off, and it will remain off for a few seconds to a few minutes
+(depending on the driver and UPS type), then it should power on again.
 
-If your UPS just sits there and never resets the load, you are vulnerable
-to a power race and should add the "reboot after timeout" hack at the very
-least.
+If your UPS just sits there and never turns off the load, you are vulnerable
+to a power race and should add the "reboot after timeout" hack
+suggested in the example above at the very least.
 
 Also refer to the section on power races in the link:FAQ.html[FAQ].
 
+You can force a permanent power off by using the "epo" command:
+
+	/usr/local/ups/sbin/upsmon -c epo
+
+
 Using suspend to disk
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -637,21 +684,24 @@ the resume script.  Don't try to keep them running.  The upsd server
 will latch the FSD state (so it won't be useable after resuming) and so
 will the upsmon client.  Some drivers may work after resuming, but many
 don't and some UPS'es will require re-initialization, so it's best not
-to keep this running either.
-
-After stopping driver, server and client you'll have to send the UPS
-the command to shutdown only if the POWERDOWNFLAG is present.  Note
-that most likely you'll have to allow for a grace period after sending 
-'upsdrvctl shutdown' since the system will still have to take a
-snapshot of itself after that.  Not all drivers support this, so before
-going down this road, make sure that the one you're using does.
+to keep any UPS driver running either.
+
+After stopping the driver, server, and client you'll have to send the
+UPS the command to shutdown, but only if the POWERDOWNFLAG is present.
+You have to do this first because once the system starts the suspend
+process there's no way of doing anything else afterwards.  As a result
+you'll have to configure the UPS and/or driver to allow for a grace
+period after receiving the 'upsdrvctl shutdown' since the system will
+still have to write the snapshot of itself to disk after that.  Not all
+drivers or UPSes support this, so before going down this road, make sure
+that the one you're using does.
 
 RAID warning
 ~~~~~~~~~~~~
 
 If you run any sort of RAID equipment, make sure your arrays are either halted
 (if possible) or switched to "read-only" mode. Otherwise you may suffer a long
-resync once the system comes back up.
+parity resync once the system comes back up.
 
 The kernel may not ever run its final shutdown procedure, so you must take care
 of all array shutdowns in userspace before upsdrvctl runs.
diff --git a/docs/man/apcsmart.txt b/docs/man/apcsmart.txt
index 6523e47..fba4a28 100644
--- a/docs/man/apcsmart.txt
+++ b/docs/man/apcsmart.txt
@@ -191,10 +191,10 @@ The values permitted are from 0 to 5. Only one can be specified. Anything else
 will cause apcsmart to exit.
 
 0::
-issue soft hibernate (*S*) if the UPS is running on batteries, otherwise issue
-hard hibernate (*@*)
+issue hard hibernate (*@*) if the UPS is running on mains, otherwise issue
+soft hibernate (*S*)
 1::
-issue soft hibernate (*S*) (if on batteries), and if it fails (or on mains) -
+issue soft hibernate (*S*) if running on batteries, but if it fails, or running on mains -
 try hard hibernate (*@*)
 2::
 issue instant poweroff (*Z*)
diff --git a/docs/man/snmp-ups.txt b/docs/man/snmp-ups.txt
index e61f858..5512fa0 100644
--- a/docs/man/snmp-ups.txt
+++ b/docs/man/snmp-ups.txt
@@ -110,12 +110,14 @@ LIMITATION
 Shutdown
 ~~~~~~~~
 
-This driver does not provide a proper upsdrv_shutdown() function. There probably
-never will be one, since at the time this script should run (near the end of
-the system halt script), there will be no network capabilities anymore.
-Probably the only way to shutdown an SNMP UPS is by sending it a shutdown
-with delay command through linkman:upscmd[8] and hope for the best that the
-system will have finished shutting down before the power is cut.
+Care must be taken when configuring systems using this driver if this
+feature is to work properly.  In order to be safe with respect to the
+systems attached to it the UPS shutdown command must of course be given
+(assuming it is given by a system attached to it) near the end of the
+system halt script (hopefully after all filesystems have been unmounted
+or re-mounted read-only), but for it to actually get to the UPS and take
+effect the network will have to stay up in order to send the command.
+
 
 INSTALLATION
 ------------
diff --git a/docs/man/upsdrvctl.txt b/docs/man/upsdrvctl.txt
index f19fd77..fb3de37 100644
--- a/docs/man/upsdrvctl.txt
+++ b/docs/man/upsdrvctl.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 *upsdrvctl* -h
 
-*upsdrvctl* ['OPTIONS'] {start | stop | shutdown} ['ups']
+*upsdrvctl* ['OPTIONS'] {start | stop | shutdown | epo} ['UPSNAME']
 
 DESCRIPTION
 -----------
@@ -57,7 +57,7 @@ Raise the debug level.  Use this multiple times for additional details.
 COMMANDS
 --------
 
-upsdrvctl supports three commands - start, stop and shutdown.  They take
+upsdrvctl supports four commands - start, stop, shutdown, and epo.  They take
 an optional argument which is a UPS name from linkman:ups.conf[5].
 Without that argument, they operate on every UPS that is currently
 configured.
@@ -69,12 +69,22 @@ Start the UPS driver(s).
 Stop the UPS driver(s).
 
 *shutdown*::
-Command the UPS driver(s) to run their shutdown sequence.  Drivers are
+Command the UPS driver(s) to run their shutdown sequence and to restart
+when mains power returns.  Drivers are stopped according to their
+sdorder value - see linkman:ups.conf[5].
+
+WARNING: this will probably power off your computers even if mains power
+remains, so don't play around with this option.  Only use it when your
+systems are prepared to lose power.
+
+*epo*::
+Command the UPS driver(s) to power down the UPS device.  Drivers are
 stopped according to their sdorder value - see linkman:ups.conf[5].
 
-WARNING: this will probably power off your computers, so don't
-play around with this option.  Only use it when your systems are prepared
-to lose power.
+WARNING: this should power off your UPS and computers until you manually
+restart the UPS, so don't play around with this option.  Only use it
+when your systems are prepared to lose power.
+
 
 ENVIRONMENT VARIABLES
 ---------------------
diff --git a/docs/man/upsmon.txt b/docs/man/upsmon.txt
index 0f7805b..95d8c1b 100644
--- a/docs/man/upsmon.txt
+++ b/docs/man/upsmon.txt
@@ -39,6 +39,8 @@ commands are:
 
 *fsd*;; shutdown all master UPSes (use with caution)
 
+*epo*;; Emergency Power Off of all master UPSes (use with caution)
+
 *stop*;; stop monitoring and exit
 
 *reload*;; reread linkman:upsmon.conf[5] configuration file.  See
@@ -420,8 +422,29 @@ by calling upsmon again to set the flag, i.e.:
 +upsmon -c fsd+
 
 After that, the master and the slaves will do their usual shutdown sequence
-as if the battery had gone critical.  This is much easier on your UPS
-equipment, and it beats crawling under a desk to find the plug.
+as if the battery had gone critical.  This may be easier on your UPS
+equipment, and it beats crawling under a desk to find the plug.  However
+some UPS units may power back up automatically after a short delay if
+they still have mains power, so ready for a restart.
+
+EMERGENCY POWER OFF
+-------------------
+
+To handle emergency situations you can set the emergency power off (EPO)
+flag on your master upsmon system.  You can do this by calling upsmon
+again to set the flag, i.e.:
+
++upsmon -c epo+
+
+After that, the master and the slaves will do their usual shutdown sequence
+as if the battery had gone critical, and hopefully your UPS will be shut
+down by the master in such a way that they will not restart without
+manual intervention.
+
+*WARNING:*  Note that "epo" here is still a controlled shutdown and it
+is not in any way equivalent to a true hard-wired EPO switch which will
+physically and immediately remove all power to the server room.
+
 
 FILES
 -----
diff --git a/docs/net-protocol.txt b/docs/net-protocol.txt
index a9ba065..1420587 100644
--- a/docs/net-protocol.txt
+++ b/docs/net-protocol.txt
@@ -396,6 +396,9 @@ power disappears.
 
 Setting this flag makes "FSD" appear in a STATUS request for this UPS.
 Finding "FSD" in a status request should be treated just like a "OB LB".
+Note that since from the point of view of a slave system
+there is no difference between a forced shutdown and an emergency power
+off so there is no equivalent "EPO" function in the protocol.
 
 It should be noted that FSD is currently a latch - once set, there is  
 no way to clear it short of restarting upsd or dropping then re-adding
diff --git a/docs/new-drivers.txt b/docs/new-drivers.txt
index 91c3fb9..a59e389 100644
--- a/docs/new-drivers.txt
+++ b/docs/new-drivers.txt
@@ -91,7 +91,7 @@ to start using that port.  If you have to set DTR or RTS on a serial
 port, do it here.
 
 Don't do any sort of hardware detection here, since you may be going
-into upsdrv_shutdown next.
+directly into upsdrv_shutdown() next.
 
 upsdrv_initinfo
 ~~~~~~~~~~~~~~~
@@ -130,10 +130,21 @@ not allowed anymore.
 upsdrv_shutdown
 ~~~~~~~~~~~~~~~
 
-Do whatever you can to make the UPS power off the load but also return
-after the power comes back on.  You may use a different command that
-keeps the UPS off if the user has requested that with a configuration
-setting.
+The upsdrv_shutdown() function is passed a flag which specifies whether
+or not an Emergency Power Off (EPO) shutdown has been requested or not.
+
+In the case of an EPO shutdown you may need to use a different device
+command to shut down the UPS and say off if it is still online (as it
+most likely will be in the EPO shutdown scenario), e.g. being shutdown as a
+result of an Emergency Power Off (EPO) command from its master upsmon.
+Use the same method as you use for a "shutdown.stayoff" command.
+
+For normal (non-EPO) shutdowns do whatever you can to make the UPS power
+off the load but also return after the power comes back on.  Use the
+same method as you use for a "shutdown.return" command.  Note that you
+may support an option to keeps the UPS off via a user requested
+configuration setting.  In this case also use the same method you use
+for a "shutdown.stayoff" command.
 
 You should attempt the UPS shutdown command even if the UPS detection
 fails.  If the UPS does not shut down the load, then the user is
diff --git a/drivers/apcsmart-old.c b/drivers/apcsmart-old.c
index 5c06372..b3e3949 100644
--- a/drivers/apcsmart-old.c
+++ b/drivers/apcsmart-old.c
@@ -1023,11 +1023,15 @@ static void upsdrv_shutdown_advanced(int status)
 }
 
 /* power down the attached load immediately */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	char	temp[32];
 	int	ret, status;
 
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO shutdown requested, use the new apcsmart driver!");
+	}
+
 	if (!smartmode())
 		upsdebugx(1, "SM detection failed. Trying a shutdown command anyway");
 
diff --git a/drivers/apcsmart.c b/drivers/apcsmart.c
index 573983e..fed51b2 100644
--- a/drivers/apcsmart.c
+++ b/drivers/apcsmart.c
@@ -1462,19 +1462,25 @@ static int sdcmd_Z(const void *foo)
 	return sdok(1);
 }
 
-static void upsdrv_shutdown_simple(void)
+static void upsdrv_shutdown_simple(int epo)
 {
-	unsigned int sdtype = 0;
+	unsigned int sdcmd = epo ? 3 : 0;
 	const char *val;
 
-	if ((val = getval("sdtype")))
-		sdtype = strtol(val, NULL, 10);
+	if ((val = getval(epo ? "epotype" : "sdtype")))
+		sdcmd = strtol(val, NULL, 10);
 
-	switch (sdtype) {
+	/*
+	 * XXX It's stupid to be using number here -- we should use
+	 * human-readable, meaningful, identifiers!
+	 */
+
+	switch (sdcmd) {
 
 	case 5:		/* hard hibernate */
 		sdcmd_AT(getval("awd"));
 		break;
+
 	case 4:		/* special hack for CS 350 and similar models */
 		sdcmd_CS(0);
 		break;
@@ -1486,26 +1492,37 @@ static void upsdrv_shutdown_simple(void)
 	case 2:		/* instant poweroff */
 		sdcmd_Z(0);
 		break;
+
 	case 1:
 		/*
 		 * Send a combined set of shutdown commands which can work
-		 * better if the UPS gets power during shutdown process
-		 * Specifically it sends both the soft shutdown 'S' and the
-		 * hard hibernate '@nnn' commands
+		 * better if the UPS gets power during shutdown process.
+		 *
+		 * Specifically send the soft shutdown 'S' if OnBattery, but if
+		 * that doesn't work, or not OnBattery, send the hard hibernate
+		 * '@nnn' command.
 		 */
-		upsdebugx(1, "UPS - currently %s - sending soft/hard hibernate commands",
-			(ups_status & APC_STAT_OL) ? "on-line" : "on battery");
+		upsdebugx(1, "UPS - currently %s - sending %s",
+			  (ups_status & APC_STAT_OB) ? "on battery" : "on-line",
+			  (ups_status & APC_STAT_OB) ? "soft shutdown, then maybe hard hibernate command" :
+							  "hard hibernate command");
 
-		/* S works only when OB */
+		/* only try S if OB, and we're done if that worked */
 		if ((ups_status & APC_STAT_OB) && sdcmd_S(0) == STAT_INSTCMD_HANDLED)
 			break;
+
 		sdcmd_AT(getval("awd"));
 		break;
 
-	default:
+	default:			/* i.e. 0 */
 		/*
-		 * Send @nnn or S, depending on OB / OL status
+		 * Always send either hard hibernate "@nnn" if OnLine, or soft
+		 * shutdown "S" if not (i.e. if OnBattery).
 		 */
+		upsdebugx(1, "UPS - currently %s - sending %s command",
+			  (ups_status & APC_STAT_OL) ? "on-line" : "on battery",
+			  (ups_status & APC_STAT_OL) ? "hard hibernate" : "soft shutdown");
+
 		if (ups_status & APC_STAT_OL)		/* on line */
 			sdcmd_AT(getval("awd"));
 		else
@@ -1541,7 +1558,7 @@ static void upsdrv_shutdown_advanced(void)
 }
 
 /* power down the attached load immediately */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	char	temp[APC_LBUF];
 	int	ret;
@@ -1557,21 +1574,21 @@ void upsdrv_shutdown(void)
 		ret = apc_read(temp, sizeof(temp), SER_D1);
 
 		if (ret < 1) {
-			upsdebugx(1, "status read failed ! assuming on battery state");
+			upsdebugx(1, "status read failed ! assuming OnBattery+LowBattery state");
 			ups_status = APC_STAT_LB | APC_STAT_OB;
 		} else {
 			ups_status = strtol(temp, 0, 16);
 		}
 
 	} else {
-		upsdebugx(1, "status request failed; assuming on battery state");
+		upsdebugx(1, "status request failed; assuming OnBattery+LowBattery state");
 		ups_status = APC_STAT_LB | APC_STAT_OB;
 	}
 
-	if (testvar("advorder") && strcasecmp(getval("advorder"), "no"))
+	if (!epo && testvar("advorder") && strcasecmp(getval("advorder"), "no"))
 		upsdrv_shutdown_advanced();
 	else
-		upsdrv_shutdown_simple();
+		upsdrv_shutdown_simple(epo);
 }
 
 static void update_info_normal(void)
@@ -1971,9 +1988,10 @@ static void setuphandlers(void)
 void upsdrv_makevartable(void)
 {
 	addvar(VAR_VALUE, "cable", "specify alternate cable (940-0095B)");
-	addvar(VAR_VALUE, "awd", "hard hibernate's additional wakeup delay");
-	addvar(VAR_VALUE, "sdtype", "specify simple shutdown method (0 - " APC_SDMAX ")");
-	addvar(VAR_VALUE, "advorder", "enable advanced shutdown control");
+	addvar(VAR_VALUE, "awd", "hard hibernate's additional wakeup delay (units of 6 minutes)");
+	addvar(VAR_VALUE, "sdtype", "specify simple shutdown method (0 - " APC_SDMAX ", default=0)");
+	addvar(VAR_VALUE, "epotype", "specify emergency power off method (0 or 3, default=3)");
+	addvar(VAR_VALUE, "advorder", "enable advanced shutdown control (list of values 0-4)");
 }
 
 void upsdrv_initups(void)
@@ -1984,16 +2002,27 @@ void upsdrv_initups(void)
 	upsfd = extrafd = ser_open(device_path);
 	apc_ser_set();
 
-	/* sanitize awd (additional waekup delay of '@' command) */
+	/* sanitize awd (additional wake-up delay of '@' command) */
+	/* XXX using a regex to validate a numeric value seems odd here */
+	/* XXX using the raw protocol value here, instead of using a human-meaningful time format, is bogus */
 	if ((val = getval("awd")) && rexhlp(APC_AWDFMT, val)) {
 			fatalx(EXIT_FAILURE, "invalid value (%s) for option 'awd'", val);
 	}
 
 	/* sanitize sdtype */
+	/* XXX using a regex to validate a numeric value seems odd here */
+	/* XXX using arbitrary numbers here, instead of using a human-meaningful words, is bogus */
 	if ((val = getval("sdtype")) && rexhlp(APC_SDFMT, val)) {
 			fatalx(EXIT_FAILURE, "invalid value (%s) for option 'sdtype'", val);
 	}
 
+	/* sanitize epotype */
+	/* XXX using a regex to validate a numeric value seems odd here */
+	/* XXX using arbitrary numbers here, instead of using a human-meaningful words, is bogus */
+	if ((val = getval("epotype")) && rexhlp(APC_EDFMT, val)) {
+			fatalx(EXIT_FAILURE, "invalid value (%s) for option 'epotype'", val);
+	}
+
 	/* sanitize advorder */
 	if ((val = getval("advorder")) && strcasecmp(val, "no")) {
 		len = strlen(val);
diff --git a/drivers/apcsmart.h b/drivers/apcsmart.h
index d85c3b9..d06fb27 100644
--- a/drivers/apcsmart.h
+++ b/drivers/apcsmart.h
@@ -147,5 +147,6 @@
 /* maximum number of supported sdtype methods + regex format*/
 #define APC_SDMAX	"5"
 #define APC_SDFMT	"^[0-5]$"
+#define APC_EDFMT	"^[03]$"
 
 #endif
diff --git a/drivers/bcmxcp.c b/drivers/bcmxcp.c
index 707b55c..268b5cf 100644
--- a/drivers/bcmxcp.c
+++ b/drivers/bcmxcp.c
@@ -1483,7 +1483,7 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/* tell the UPS to shut down, then return - DO NOT SLEEP HERE */
 	unsigned char answer[5], cbuf[3];
@@ -1502,7 +1502,7 @@ void upsdrv_shutdown(void)
 
 	sleep(1);	/* Need to. Have to wait at least 0,25 sec max 16 sec */
 
-	cbuf[0] = PW_LOAD_OFF_RESTART;
+	cbuf[0] = epo ? PW_UPS_OFF : PW_LOAD_OFF_RESTART;
 	cbuf[1] = (unsigned char)(bcmxcp_status.shutdowndelay & 0x00ff);	/* "delay" sec delay for shutdown, */
 	cbuf[2] = (unsigned char)(bcmxcp_status.shutdowndelay >> 8);		/* hige byte sec. From ups.conf. */
 
diff --git a/drivers/belkin.c b/drivers/belkin.c
index ad5a658..4a909fe 100644
--- a/drivers/belkin.c
+++ b/drivers/belkin.c
@@ -346,7 +346,7 @@ void upsdrv_updateinfo(void)
 }
 
 /* power down the attached load immediately */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	int	res;
 
@@ -360,9 +360,10 @@ void upsdrv_shutdown(void)
 	/* shutdown type 2 (UPS system) */
 	send_belkin_command(CONTROL, "SDT", "2");
 
-	/* SDR means "do SDT and SDA, then reboot after n minutes" */
-	send_belkin_command(CONTROL, "SDR", "1");
-
+	if (!epo) {
+		/* SDR means "do SDT and SDA, then reboot after n minutes" */
+		send_belkin_command(CONTROL, "SDR", "1");
+	}
 	printf("UPS should power off load in 5 seconds\n");
 
 	/* shutdown in 5 seconds */
@@ -393,9 +394,10 @@ static void do_beeper_off(void) {
 /* handle the "load.off" with some paranoia */
 static void do_off(void)
 {
+#ifdef CONFIRM_DANGEROUS_COMMANDS
 	static	time_t lastcmd = 0;
 	time_t	now, elapsed;
-#ifdef CONFIRM_DANGEROUS_COMMANDS
+
 	time(&now);
 	elapsed = now - lastcmd;
 
diff --git a/drivers/belkinunv.c b/drivers/belkinunv.c
index 5698313..ff33990 100644
--- a/drivers/belkinunv.c
+++ b/drivers/belkinunv.c
@@ -1128,7 +1128,7 @@ void upsdrv_updateinfo(void)
 }
 
 /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/* Note: this UPS cannot (apparently) be put into "soft
 	   shutdown" mode; thus the -k option should not normally be
@@ -1146,10 +1146,12 @@ void upsdrv_shutdown(void)
 	   option instead, as suggested on the belkinunv(8) man
 	   page. */
 
-        upslogx(LOG_WARNING, "You are using the -k option, which is broken for this driver.\nShutting down for 10 minutes and hoping for the best");
+	if (!epo)
+		upslogx(LOG_WARNING, "You are using the -k option, which is broken for this driver.\nShutting down for 10 minutes and hoping for the best");
 
-	belkin_nut_write_int(REG_RESTARTTIMER, 10);  /* 10 minutes */
+	belkin_nut_write_int(REG_RESTARTTIMER, epo ? 0 : 10);  /* 10 minutes */
 	belkin_nut_write_int(REG_SHUTDOWNTIMER, 1);  /* 1 second */
+	/* XXX error checking? */
 }
 
 int instcmd(const char *cmdname, const char *extra)
diff --git a/drivers/bestfcom.c b/drivers/bestfcom.c
index 8a85e6d..8786751 100644
--- a/drivers/bestfcom.c
+++ b/drivers/bestfcom.c
@@ -440,11 +440,15 @@ static void ups_sync(void)
 }
 
 /* power down the attached load immediately */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/* NB: hard-wired password */
 	ser_send(upsfd, "pw377\r");
-	ser_send(upsfd, "off 1 a\r");	/* power off in 1 second and restart when line power returns */
+	if (epo) {
+		ser_send(upsfd, "off 1\r");	/* power off in 1 second and stay off */
+	} else {
+		ser_send(upsfd, "off 1 a\r");	/* power off in 1 second and restart when line power returns */
+	}
 }
 
 /* list flags and values that you want to receive via -x */
diff --git a/drivers/bestfortress.c b/drivers/bestfortress.c
index 973f461..c0f087b 100644
--- a/drivers/bestfortress.c
+++ b/drivers/bestfortress.c
@@ -358,7 +358,7 @@ static int upsdrv_setvar (const char *var, const char * data) {
 	return STAT_SET_HANDLED;
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	const	char	*grace;
 
@@ -369,7 +369,7 @@ void upsdrv_shutdown(void)
 
 	printf ("shutdown in %s seconds\n", grace);
 	/* make power return when utility power returns */
-	autorestart (1);
+	autorestart (epo ? 0 : 1);
 	upssend ("OFF%s\r", grace);
 	/* I'm nearly dead, Jim */
 	/* OFF will powercycle when line power is available again */
diff --git a/drivers/bestuferrups.c b/drivers/bestuferrups.c
index d60cb02..957bd32 100644
--- a/drivers/bestuferrups.c
+++ b/drivers/bestuferrups.c
@@ -339,11 +339,15 @@ static void ups_sync(void)
 }
 
 /* power down the attached load immediately */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 /* NB: hard-wired password */
   ser_send(upsfd, "pw377\r");
-  ser_send(upsfd, "off 1 a\r");	/* power off in 1 second and restart when line power returns */
+  if (epo) {
+    ser_send(upsfd, "off 1\r");		/* power off in 1 second and stay off */
+  } else {
+    ser_send(upsfd, "off 1 a\r");	/* power off in 1 second and restart when line power returns */
+  }
 }
 
 /* list flags and values that you want to receive via -x */
diff --git a/drivers/bestups.c b/drivers/bestups.c
index 854bddf..167f7d7 100644
--- a/drivers/bestups.c
+++ b/drivers/bestups.c
@@ -305,16 +305,23 @@ static int ups_on_line(void)
 	return 0;	/* on battery */
 }	
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	printf("The UPS will shut down in approximately one minute.\n");
 
-	if (ups_on_line())
+	if (epo) {
+		printf("The UPS will power off and stay off now.\n");
+	} else if (ups_on_line()) {
 		printf("The UPS will restart in about one minute.\n");
-	else
+	} else {
 		printf("The UPS will restart when power returns.\n");
+	}
 
-	ser_send_pace(upsfd, UPSDELAY, "S01R0001\r");
+	if (epo) {
+		ser_send_pace(upsfd, UPSDELAY, "S01R0000\r");
+	} else {
+		ser_send_pace(upsfd, UPSDELAY, "S01R0001\r");
+	}
 }
 
 void upsdrv_updateinfo(void)
diff --git a/drivers/blazer.c b/drivers/blazer.c
index b8c646a..3cd397e 100644
--- a/drivers/blazer.c
+++ b/drivers/blazer.c
@@ -777,7 +777,7 @@ void upsdrv_updateinfo(void)
 }
 
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	int	retry;
 
@@ -787,11 +787,11 @@ void upsdrv_shutdown(void)
 			continue;
 		}
 
-		if (blazer_instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) {
+		if (blazer_instcmd(epo ? "shutdown.stayoff" : "shutdown.return", NULL) != STAT_INSTCMD_HANDLED) {
 			continue;
 		}
 
-		fatalx(EXIT_SUCCESS, "Shutting down in %d seconds", offdelay);
+		fatalx(EXIT_SUCCESS, "%s in %d seconds", epo ? "Emergency power off" : "Shutting down", offdelay);
 	}
 
 	fatalx(EXIT_FAILURE, "Shutdown failed!");
diff --git a/drivers/clone-outlet.c b/drivers/clone-outlet.c
index 99d150c..441c546 100644
--- a/drivers/clone-outlet.c
+++ b/drivers/clone-outlet.c
@@ -369,7 +369,7 @@ void upsdrv_updateinfo(void)
 }
 
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 }
 
diff --git a/drivers/clone.c b/drivers/clone.c
index c8beb83..fb31a48 100644
--- a/drivers/clone.c
+++ b/drivers/clone.c
@@ -520,9 +520,9 @@ void upsdrv_updateinfo(void)
 }
 
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
-	fatalx(EXIT_FAILURE, "shutdown not supported");
+	fatalx(EXIT_FAILURE, "%s not supported", epo ? "epo" : "shutdown");
 }
 
 
diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c
index c2e9957..c7c90b5 100644
--- a/drivers/dummy-ups.c
+++ b/drivers/dummy-ups.c
@@ -188,9 +188,9 @@ void upsdrv_updateinfo(void)
 	}
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
-	fatalx(EXIT_FAILURE, "shutdown not supported");
+	fatalx(EXIT_FAILURE, "%s not supported", epo ? "epo" : "shutdown");
 }
 
 static int instcmd(const char *cmdname, const char *extra)
diff --git a/drivers/etapro.c b/drivers/etapro.c
index 6690348..d23827b 100644
--- a/drivers/etapro.c
+++ b/drivers/etapro.c
@@ -186,7 +186,7 @@ static int instcmd(const char *cmdname, const char *extra)
 	}
 
 	if (!strcasecmp(cmdname, "shutdown.return")) {
-		upsdrv_shutdown();
+		upsdrv_shutdown(0);
 		return STAT_INSTCMD_HANDLED;
 	}
 
@@ -334,9 +334,9 @@ upsdrv_updateinfo(void)
 #define SHUTDOWN_TO_RETURN_TIME 15
 
 void
-upsdrv_shutdown(void)
+upsdrv_shutdown(int epo)
 {
-	etapro_set_on_timer(SHUTDOWN_GRACE_TIME + SHUTDOWN_TO_RETURN_TIME);
+	etapro_set_on_timer(epo ? 0 : (SHUTDOWN_GRACE_TIME + SHUTDOWN_TO_RETURN_TIME));
 	etapro_set_off_timer(SHUTDOWN_GRACE_TIME);
 }
 
diff --git a/drivers/everups.c b/drivers/everups.c
index 7cd9328..416d0a6 100644
--- a/drivers/everups.c
+++ b/drivers/everups.c
@@ -161,8 +161,10 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
+	if (epo)
+		upslogx(LOG_NOTICE, "EPO shutdown requested, but may not work!");
 	if (!Code(2)) {
 		upslog_with_errno(LOG_INFO, "Code failed");
 		return;
diff --git a/drivers/gamatronic.c b/drivers/gamatronic.c
index acc56a3..7848ae7 100644
--- a/drivers/gamatronic.c
+++ b/drivers/gamatronic.c
@@ -293,7 +293,7 @@ void upsdrv_updateinfo(void)
 	
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	int msg_len;
 	char msgbuf[SMALLBUF];
@@ -301,9 +301,10 @@ void upsdrv_shutdown(void)
 	msg_len = snprintf(msgbuf, sizeof(msgbuf), "-1");
 	sec_cmd(SEC_SETCMD, SEC_SHUTDOWN, msgbuf, &msg_len);
 
-	msg_len = snprintf(msgbuf, sizeof(msgbuf), "1");
-	sec_cmd(SEC_SETCMD, SEC_AUTORESTART, msgbuf, &msg_len);
-
+	if (!epo) {
+		msg_len = snprintf(msgbuf, sizeof(msgbuf), "1");
+		sec_cmd(SEC_SETCMD, SEC_AUTORESTART, msgbuf, &msg_len);
+	}
 	msg_len = snprintf(msgbuf, sizeof(msgbuf), "2");
 	sec_cmd(SEC_SETCMD, SEC_SHUTTYPE,msgbuf, &msg_len);
 
diff --git a/drivers/genericups.c b/drivers/genericups.c
index bc8ddc4..767a59a 100644
--- a/drivers/genericups.c
+++ b/drivers/genericups.c
@@ -225,7 +225,7 @@ static void set_ups_type(void)
 }
 
 /* power down the attached load immediately */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	int	flags, ret;
 
@@ -233,6 +233,10 @@ void upsdrv_shutdown(void)
 		fatalx(EXIT_FAILURE, "No upstype set - see help text / man page!");
 	}
 
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO Shutdown requested, but may not stay off.");
+	}
+
 	flags = upstab[upstype].line_sd;
 
 	if (flags == -1) {
diff --git a/drivers/isbmex.c b/drivers/isbmex.c
index 17328b2..32f5550 100644
--- a/drivers/isbmex.c
+++ b/drivers/isbmex.c
@@ -310,7 +310,7 @@ void upsdrv_updateinfo(void)
    return;
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/* shutdown is supported on models with
 	 * contact closure. Some ISB models with serial
@@ -324,6 +324,11 @@ void upsdrv_shutdown(void)
 /*	fatalx(EXIT_FAILURE, "Shutdown only supported with the Generic Driver, type 6 and special cable");  */
 	/*fatalx(EXIT_FAILURE, "shutdown not supported");*/
 	int i;
+
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO Shutdown requested, but may not stay off.");
+	}
+
 	for(i=0;i<=5;i++)
 	{
 		ser_send_char(upsfd, '#');
diff --git a/drivers/ivtscd.c b/drivers/ivtscd.c
index d8ae507..b45307b 100644
--- a/drivers/ivtscd.c
+++ b/drivers/ivtscd.c
@@ -177,8 +177,11 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO shutdown requested, but this is not possible here!");
+	}
 	while (1) {
 
 		if (ivt_status() < 7) {
diff --git a/drivers/liebert-esp2.c b/drivers/liebert-esp2.c
index fca7c5e..0098f89 100644
--- a/drivers/liebert-esp2.c
+++ b/drivers/liebert-esp2.c
@@ -473,10 +473,14 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	char reply[8];
 	
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO Shutdown requested, but may not stay off.");
+	}
+
 	if(!(do_command(cmd_setOutOffMode, reply, 8) != -1) &&
 	(do_command(cmd_setOutOffDelay, reply, 8) != -1) &&
 	(do_command(cmd_sysLoadKey, reply, 6) != -1) &&
diff --git a/drivers/liebert.c b/drivers/liebert.c
index b2faa29..8d9e1a2 100644
--- a/drivers/liebert.c
+++ b/drivers/liebert.c
@@ -39,13 +39,13 @@ upsdrv_info_t upsdrv_info = {
 
 #define	ML_ONBATTERY	0x55
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/* XXX: replace with a proper shutdown function (raise DTR) */
 
 	/* worse yet: stock cables don't support shutdown at all */
 
-	fatalx(EXIT_FAILURE, "shutdown not supported");
+	fatalx(EXIT_FAILURE, "%s not supported", epo ? "epo" : "shutdown");
 }
 
 void upsdrv_initinfo(void)
diff --git a/drivers/main.c b/drivers/main.c
index 66caa36..e1eaef1 100644
--- a/drivers/main.c
+++ b/drivers/main.c
@@ -70,12 +70,13 @@ void upsdrv_banner (void)
 }
 
 /* power down the attached load immediately */
-static void forceshutdown(void)
+static void forceshutdown(int epo)
 {
-	upslogx(LOG_NOTICE, "Initiating UPS shutdown");
+	upslogx(LOG_NOTICE, "Initiating UPS %s", epo ? "emergency power off" : "shutdown");
 
 	/* the driver must not block in this function */
-	upsdrv_shutdown();
+	upsdrv_shutdown(epo);
+
 	exit(EXIT_SUCCESS);
 }
 
@@ -221,6 +222,7 @@ int testvar(const char *var)
 }
 
 /* callback from driver - create the table for -x/conf entries */
+/* XXX it would be nice if there were some way to include a value validation mechanism */
 void addvar(int vartype, const char *name, const char *desc)
 {
 	vartab_t	*tmp, *last;
@@ -468,7 +470,7 @@ static void setup_signals(void)
 int main(int argc, char **argv)
 {
 	struct	passwd	*new_uid = NULL;
-	int	i, do_forceshutdown = 0;
+	int	i, do_forceshutdown = 0, epo = 0;
 
 	atexit(exit_cleanup);
 
@@ -488,7 +490,7 @@ int main(int argc, char **argv)
 	/* build the driver's extra (-x) variable table */
 	upsdrv_makevartable();
 
-	while ((i = getopt(argc, argv, "+a:kDhx:Lqr:u:Vi:")) != -1) {
+	while ((i = getopt(argc, argv, "+a:KkDhx:Lqr:u:Vi:")) != -1) {
 		switch (i) {
 			case 'a':
 				upsname = optarg;
@@ -505,6 +507,9 @@ int main(int argc, char **argv)
 			case 'i':
 				poll_interval = atoi(optarg);
 				break;
+			case 'K':
+				epo = 1;
+				/* FALLTHROUGH */
 			case 'k':
 				do_lock_port = 0;
 				do_forceshutdown = 1;
@@ -617,7 +622,7 @@ int main(int argc, char **argv)
 	}
 
 	if (do_forceshutdown)
-		forceshutdown();
+		forceshutdown(epo);
 
 	/* note: device.type is set early to be overriden by the driver
 	 * when its a pdu! */
diff --git a/drivers/main.h b/drivers/main.h
index 3e3e84e..b0894fc 100644
--- a/drivers/main.h
+++ b/drivers/main.h
@@ -16,7 +16,7 @@ extern unsigned int	poll_interval;
 void upsdrv_initups(void);	/* open connection to UPS, fail if not found */
 void upsdrv_initinfo(void);	/* prep data, settings for UPS monitoring */
 void upsdrv_updateinfo(void);	/* update state data if possible */
-void upsdrv_shutdown(void);	/* make the UPS power off the load */
+void upsdrv_shutdown(int);	/* make the UPS power off the load, possibly permanently */
 void upsdrv_help(void);		/* tack on anything useful for the -h text */
 void upsdrv_banner(void);	/* print your version information */
 void upsdrv_cleanup(void);	/* free any resources before shutdown */
diff --git a/drivers/masterguard.c b/drivers/masterguard.c
index f480ad3..a5d4d43 100644
--- a/drivers/masterguard.c
+++ b/drivers/masterguard.c
@@ -539,16 +539,20 @@ void upsdrv_updateinfo(void)
 /********************************************************************
  *
  * Called if the driver wants to shutdown the UPS.
- * ( also used by the "-k" command line switch )
+ * ( also used by the "-k" and "-K" command line switches )
  * 
  * This cuts the utility from the UPS after 20 seconds and restores
  * the utility one minute _after_ the utility to the UPS has restored
  *
  ********************************************************************/
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
-	/* ups will come up within a minute if utility is restored */
-    ser_send_pace(upsfd, UPS_PACE, "%s", "S.2R0001\x0D" );
+	if (epo) {
+		ser_send_pace(upsfd, UPS_PACE, "%s", "S.2R0000\x0D" );
+	} else {
+		/* ups will come up within a minute if utility is restored */
+		ser_send_pace(upsfd, UPS_PACE, "%s", "S.2R0001\x0D" );
+	}
 }
 
 /********************************************************************
diff --git a/drivers/metasys.c b/drivers/metasys.c
index aec15e9..ddac1c0 100644
--- a/drivers/metasys.c
+++ b/drivers/metasys.c
@@ -810,7 +810,7 @@ void upsdrv_updateinfo(void)
 	return;
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	unsigned char command[10], answer[10];
 	
@@ -818,7 +818,7 @@ void upsdrv_shutdown(void)
 	/* Ensure that the ups is configured for automatically
 	   restart after a complete battery discharge 
 	   and when the power comes back after a shutdown */
-	if (! autorestart) {
+	if (/* XXX !epo || */ ! autorestart) {
 		command[0]=UPS_SET_TIMES_ON_BATTERY;
 		command[1]=0x00;					/* max time on  */ 
 		command[2]=0x00;					/* battery */
@@ -837,14 +837,21 @@ void upsdrv_shutdown(void)
 	command[3]=0x00;					/* to */
 	command[4]=0x00;					/* shutdown 150 secs */
 			
-	/* restart time has been set to 1 instead of 0 for avoiding
-		a bug in some ups firmware */
-	command[5]=0x01;					/* programmed */
-	command[6]=0x00;					/* time		 */
-	command[7]=0x00;					/* to */
-	command[8]=0x00;					/* restart 1 sec */
-	command_write_sequence(command, 9, answer);
-
+	if (epo) {
+		command[5]=0xff;					/* programmed */
+		command[6]=0xff;					/* time		 */
+		command[7]=0xff;					/* to */
+		command[8]=0xff;					/* restart -1 no restart*/
+		command_write_sequence(command, 9, answer);
+	} else {
+		/* restart time has been set to 1 instead of 0 for avoiding
+		   a bug in some ups firmware */
+		command[5]=0x01;					/* programmed */
+		command[6]=0x00;					/* time		 */
+		command[7]=0x00;					/* to */
+		command[8]=0x00;					/* restart 1 sec */
+		command_write_sequence(command, 9, answer);
+	}
 	/* you may have to check the line status since the commands
 	   for toggling power are frequently different for OL vs. OB */
 
diff --git a/drivers/mge-shut.c b/drivers/mge-shut.c
index 1f9e519..ec8c44e 100644
--- a/drivers/mge-shut.c
+++ b/drivers/mge-shut.c
@@ -57,11 +57,6 @@ int ondelay = DEFAULT_ONDELAY;
 int offdelay = DEFAULT_OFFDELAY;
 int notification = DEFAULT_NOTIFICATION;
 
-#define SD_RETURN	0
-#define SD_STAYOFF	1
-
-int sdtype = SD_RETURN;
-
 #define BYTESWAP(in) (((in & 0xFF) << 8) + ((in & 0xFF00) >> 8))
 
 /* realign packet data according to Endianess */
@@ -240,11 +235,11 @@ void upsdrv_updateinfo (void)
 
 /* --------------------------------------------------------------- */
 
-void upsdrv_shutdown (void)
+void upsdrv_shutdown (int epo)
 {
 	char val[5];
 
-	if (sdtype == SD_RETURN) {
+	if (!epo) {
 		/* set DelayBeforeStartup */
 		snprintf(val, sizeof(val), "%d", ondelay);
 		hid_set_value("ups.timer.start", val);
@@ -339,15 +334,13 @@ int instcmd(const char *cmdname, const char *extra)
 {
 	/* Shutdown UPS and return when power is restored */
 	if (!strcasecmp(cmdname, "shutdown.return")) {
-		sdtype = SD_RETURN;
-		upsdrv_shutdown();
+		upsdrv_shutdown(0);
 		return STAT_INSTCMD_HANDLED;
 	}
 
 	/* Shutdown UPS and stay off when power is restored */
 	if (!strcasecmp(cmdname, "shutdown.stayoff")) {
-		sdtype = SD_STAYOFF;
-		upsdrv_shutdown();
+		upsdrv_shutdown(1);
 		return STAT_INSTCMD_HANDLED;
 	}
 
diff --git a/drivers/mge-utalk.c b/drivers/mge-utalk.c
index 616e2ad..5663066 100644
--- a/drivers/mge-utalk.c
+++ b/drivers/mge-utalk.c
@@ -98,10 +98,6 @@ upsdrv_info_t upsdrv_info = {
 #define MAXTRIES    10 /* max number of connect tries              */
 #define BUFFLEN    256 
 
-#define SD_RETURN	0
-#define SD_STAYOFF	1
-
-int sdtype = SD_RETURN;
 static time_t lastpoll; /* Timestamp the last polling */
 
 /* --------------------------------------------------------------- */
@@ -461,13 +457,13 @@ void upsdrv_updateinfo(void)
 
 /* --------------------------------------------------------------- */
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	char buf[BUFFLEN];
 	/*  static time_t lastcmd = 0; */
 	memset(buf, 0, sizeof(buf));
 
-	if (sdtype == SD_RETURN) {
+	if (!epo) {
 		/* enable automatic restart */
 		mge_command(buf, sizeof(buf), "Sx 5");
 		
@@ -476,7 +472,7 @@ void upsdrv_shutdown(void)
 	
 	/* Only call the effective shutoff if restart is ok */
 	/* or if we need only a stayoff... */
-	if (!strcmp(buf, "OK") || (sdtype == SD_STAYOFF)) {
+	if (!strcmp(buf, "OK") || epo) {
 		/* shutdown UPS */
 		mge_command(buf, sizeof(buf), "Sx 0");
 
@@ -530,14 +526,12 @@ int instcmd(const char *cmdname, const char *extra)
 	/* Shutdown UPS */
 	if (!strcasecmp(cmdname, "shutdown.stayoff"))
 	{
-		sdtype = SD_STAYOFF;
-		upsdrv_shutdown();
+		upsdrv_shutdown(1);
 	}
 	
 	if (!strcasecmp(cmdname, "shutdown.return"))
 	{
-		sdtype = SD_RETURN;
-		upsdrv_shutdown();
+		upsdrv_shutdown(0);
 	}
 	
 	/* Power Off [all] plugs */
diff --git a/drivers/microdowell.c b/drivers/microdowell.c
index 82bcd43..b36b023 100644
--- a/drivers/microdowell.c
+++ b/drivers/microdowell.c
@@ -881,7 +881,7 @@ void upsdrv_initinfo(void)
 
 
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	unsigned char OutBuff[20] ;
 	unsigned char InpBuff[260] ;
@@ -928,10 +928,16 @@ void upsdrv_shutdown(void)
 
 	OutBuff[3] = (ups.ShutdownDelay >> 8) & 0xFF ;	/* SDDELAY (MSB)	Shutdown value (seconds) */
 	OutBuff[4] = (ups.ShutdownDelay & 0xFF) ;			/* SDDELAY (LSB) */
-	OutBuff[5] = (ups.WakeUpDelay >> 16) & 0xFF ;	/* WUDELAY (MSB)	Wakeup value (seconds) */
-	OutBuff[6] = (ups.WakeUpDelay >> 8) & 0xFF ;		/* WUDELAY (...) */
-	OutBuff[7] = (ups.WakeUpDelay & 0xFF ) ;			/* WUDELAY (LSB) */
-
+	/* XXX wow, holy duplicated code batman! */
+	if (epo) {
+		OutBuff[5] = 0 ;	/* WUDELAY (MSB)	Wakeup value (seconds) */
+		OutBuff[6] = 0 ;	/* WUDELAY (...) */
+		OutBuff[7] = 0 ;	/* WUDELAY (LSB) */
+	} else {
+		OutBuff[5] = (ups.WakeUpDelay >> 16) & 0xFF ;	/* WUDELAY (MSB)	Wakeup value (seconds) */
+		OutBuff[6] = (ups.WakeUpDelay >> 8) & 0xFF ;		/* WUDELAY (...) */
+		OutBuff[7] = (ups.WakeUpDelay & 0xFF ) ;			/* WUDELAY (LSB) */
+	}
 	if ((p = CmdSerial(OutBuff, LEN_SD_ONESHOT, InpBuff)) != NULL)
 		{
 		upsdebugx(2, "Shutdown command(TYPE=%02x, SD=%u, WU=%u): %s", OutBuff[2], ups.ShutdownDelay, ups.WakeUpDelay, PrintErr(ups.ErrCode));
diff --git a/drivers/netxml-ups.c b/drivers/netxml-ups.c
index 130d50f..71e151a 100644
--- a/drivers/netxml-ups.c
+++ b/drivers/netxml-ups.c
@@ -182,7 +182,7 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/* tell the UPS to shut down, then return - DO NOT SLEEP HERE */
 
@@ -190,7 +190,7 @@ void upsdrv_shutdown(void)
 	   it doesn't respond at first if possible */
 
 	/* replace with a proper shutdown function */
-	fatalx(EXIT_FAILURE, "shutdown not supported");
+	fatalx(EXIT_FAILURE, "%s not supported", epo ? "epo" : "shutdown");
 
 	/* you may have to check the line status since the commands
 	   for toggling power are frequently different for OL vs. OB */
diff --git a/drivers/oneac.c b/drivers/oneac.c
index 55035f7..297b2a8 100644
--- a/drivers/oneac.c
+++ b/drivers/oneac.c
@@ -785,8 +785,11 @@ void upsdrv_updateinfo(void)
 	}
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO Shutdown requested, but may not stay off.");
+	}
 	ser_send(upsfd,"%s",SHUTDOWN);
 }
 
diff --git a/drivers/optiups.c b/drivers/optiups.c
index 26b7f47..55f81a6 100644
--- a/drivers/optiups.c
+++ b/drivers/optiups.c
@@ -269,6 +269,7 @@ static int instcmd(const char *cmdname, const char *extra)
 		/* This actually stays off as long as the batteries hold,
 		 *   if the line power comes back before the batteries die,
 		 *   the UPS will never powerup its output stage!!! */
+		/* XXX this seems like the wrong thing to do!!! */
 		if ( optimodel == OPTIMODEL_ZINTO )
 		{
 			optiquery( "Ct1" );
@@ -478,7 +479,7 @@ void upsdrv_updateinfo(void)
 	}
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/* OL: this must power cycle the load if possible */
 	/* OB: the load must remain off until the power returns */
@@ -498,7 +499,11 @@ void upsdrv_shutdown(void)
 
 	/* Turn output stage back on if power returns - but really means
 	 *   turn off ups if on battery */
-	optiquery( "Ct1" );
+	if (epo) {
+		optiquery( "Ct0" );
+	} else {
+		optiquery( "Ct1" );
+	}
 
 	/* What happens, if the power comes back *after* reading the ups status and
 	 * before the shutdown command? For "Online-UPS Zinto D" *always* asking for
@@ -512,14 +517,15 @@ void upsdrv_shutdown(void)
 	{
 		/* On line power: Power up in 60 seconds (30 seconds after the following shutdown) */
 		/* On battery: Power up when the line power returns */
-		optiquery( "Cu0000060" );
+		if (!epo)
+			optiquery( "Cu0000060" );
 		/* Shutdown in 30 seconds */
 		optiquery( "Cs0000030" );
 		return;
 	}
 
 	/* Just cycling power, schedule output stage to come back on in 60 seconds */
-	if ( !(s&OPTISBIT_ON_BATTERY_POWER) )
+	if (!epo && !(s&OPTISBIT_ON_BATTERY_POWER) )
 		optiquery( "Cu00000600" );
 
 	/* Shutdown in 8 seconds */
diff --git a/drivers/powercom.c b/drivers/powercom.c
index 68165f8..fcc9c6c 100644
--- a/drivers/powercom.c
+++ b/drivers/powercom.c
@@ -736,11 +736,16 @@ void upsdrv_updateinfo(void)
 }
 
 /* shutdown UPS */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/* power down the attached load immediately */
-	printf("Forced UPS shutdown (and wait for power)...\n");
-	shutdown_ret();
+	if (epo) {
+		printf("Forced UPS emergency power off...\n");
+		shutdown_halt();
+	} else {
+		printf("Forced UPS shutdown (and wait for power)...\n");
+		shutdown_ret();
+	}
 }
 
 /* initialize UPS */
diff --git a/drivers/powerpanel.c b/drivers/powerpanel.c
index 2faf171..5318f60 100644
--- a/drivers/powerpanel.c
+++ b/drivers/powerpanel.c
@@ -99,19 +99,28 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	int	i, ret;
 
-	/*
-	 * Try to shutdown with delay and automatic reboot if the power
-	 * returned in the mean time (newer models support this).
-	 */
-	if (subdriver[mode]->instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) {
-		/* Shutdown successful */
-		return;
+	if (epo) {
+		/*
+		 * Try to shutdown with delay and stay off
+		 */
+		if (subdriver[mode]->instcmd("shutdown.stayoff", NULL) == STAT_INSTCMD_HANDLED) {
+			/* Shutdown successful */
+			return;
+		}
+	} else {
+		/*
+		 * Try to shutdown with delay and automatic reboot if the power
+		 * returned in the mean time (newer models support this).
+		 */
+		if (subdriver[mode]->instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) {
+			/* Shutdown successful */
+			return;
+		}
 	}
-
 	/*
 	 * Looks like an older type. Assume we're still on battery if
 	 * we can't read status or it is telling us we're on battery.
@@ -129,6 +138,9 @@ void upsdrv_shutdown(void)
 		 * When on battery, the 'shutdown.stayoff' command will make
 		 * the UPS switch back on when the power returns.
 		 */
+		if (epo) {
+			upslogx(LOG_NOTICE, "EPO shutdown requested, but this model UPS won't stay off!");
+		}
 		if (subdriver[mode]->instcmd("shutdown.stayoff", NULL) == STAT_INSTCMD_HANDLED) {
 			upslogx(LOG_INFO, "Waiting for power to return...");
 			return;
diff --git a/drivers/rhino.c b/drivers/rhino.c
index 707432b..2ea3756 100644
--- a/drivers/rhino.c
+++ b/drivers/rhino.c
@@ -745,24 +745,15 @@ void upsdrv_updateinfo(void)
 }
 
 /* power down the attached load immediately */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 
-	/* basic idea: find out line status and send appropriate command */
-	/* on line: send normal shutdown, ups will return by itself on utility */
-	/* on battery: send shutdown+return, ups will cycle and return soon */
-
-	if (!SourceFail)     /* on line */
-	  {
-	    printf("On line, forcing shutdown command...\n");
-	    send_command( CMD_SHUT );
-	  }
-	else
-	  {
-	    printf("On battery, sending normal shutdown command...\n");
-	    send_command( CMD_SHUT );
-	  }
-	
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO Shutdown requested, but may not stay off.");
+	} else {
+		upslogx(LOG_NOTICE, "Normal Shutdown requested, but may not restart.");
+	}
+	send_command( CMD_SHUT );
 }
 
 void upsdrv_help(void)
diff --git a/drivers/richcomm_usb.c b/drivers/richcomm_usb.c
index 489650a..313b7d7 100644
--- a/drivers/richcomm_usb.c
+++ b/drivers/richcomm_usb.c
@@ -473,7 +473,7 @@ void upsdrv_updateinfo(void)
  * Please note, this function doesn't power the UPS off if
  * line power is connected.
  */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/*
 	 * This packet shuts down the UPS, that is,
@@ -488,6 +488,10 @@ void upsdrv_shutdown(void)
 	char	restart[QUERY_PACKETSIZE] = { 0x02, 0x01, 0x00, 0x00 };
 	char	reply[REPLY_PACKETSIZE];
 
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO shutdown requested, but this is not possible here!");
+	}
+
 	execute_and_retrieve_query(prepare, reply);
 
 	/*
diff --git a/drivers/safenet.c b/drivers/safenet.c
index 392fa45..7bf2c7b 100644
--- a/drivers/safenet.c
+++ b/drivers/safenet.c
@@ -418,10 +418,13 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	int	retry = 3;
 
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO Shutdown requested, but may not stay off.");
+	}
 	/*
 	 * Since we may have arrived here before the hardware is initialized,
 	 * try to initialize it here.
diff --git a/drivers/skel.c b/drivers/skel.c
index 4957bcd..eab15b4 100644
--- a/drivers/skel.c
+++ b/drivers/skel.c
@@ -91,22 +91,24 @@ void upsdrv_updateinfo(void)
 	 */
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
-	/* tell the UPS to shut down, then return - DO NOT SLEEP HERE */
+	/* tell the UPS to shut down, and if !epo then return - DO NOT SLEEP HERE */
 
 	/* maybe try to detect the UPS here, but try a shutdown even if
 	   it doesn't respond at first if possible */
 
 	/* replace with a proper shutdown function */
-	fatalx(EXIT_FAILURE, "shutdown not supported");
+	fatalx(EXIT_FAILURE, "%s not supported", epo ? "epo" : "shutdown");
 
 	/* you may have to check the line status since the commands
 	   for toggling power are frequently different for OL vs. OB */
 
-	/* OL: this must power cycle the load if possible */
+	/* epo: this must power off permanently regardless of OL|OB (and shut down if possible) */
 
-	/* OB: the load must remain off until the power returns */
+	/* OL && !epo: this must power cycle the load if possible */
+
+	/* OB && !epo: the load must remain off until the power returns */
 }
 
 /*
diff --git a/drivers/snmp-ups.c b/drivers/snmp-ups.c
index 3d31f9c..ebf0cab 100644
--- a/drivers/snmp-ups.c
+++ b/drivers/snmp-ups.c
@@ -190,18 +190,36 @@ void upsdrv_updateinfo(void)
 	}
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	/*
-	This driver will probably never support this. In order to
-	be any use, the driver should be called near the end of
-	the system halt script. By that time we in all likelyhood
-	we won't have network capabilities anymore, so we could
-	never send this command to the UPS. This is not an error,
-	but a limitation of the interface used.
-	*/
-
-	upsdebugx(1, "upsdrv_shutdown...");
+	 * Care must be taken when configuring systems using this driver if
+	 * this feature is to work properly.  In order to be safe with respect
+	 * to the systems attached to it the UPS shutdown command must of
+	 * course be given (assuming it is given by a system attached to it)
+	 * near the end of the system halt script (hopefully after all
+	 * filesystems have been unmounted or re-mounted read-only), but for it
+	 * to actually get to the UPS and take effect the network will have to
+	 * stay up in order to send the command.
+	 */
+
+	upsdebugx(1, "upsdrv_shutdown(%s)...", epo ? "epo" : "normal");
+
+	/* XXX we should be calling su_shutdown_ups() to make use of sdtype */
+
+	if (epo) {
+		/* Try to shutdown and stay off */
+		if (su_instcmd("shutdown.stayoff", NULL) == STAT_INSTCMD_HANDLED) {
+			/* Shutdown successful */
+			return;
+		}
+		/* If the above doesn't work, try load.off.delay */
+		if (su_instcmd("load.off.delay", NULL) == STAT_INSTCMD_HANDLED) {
+			/* Shutdown successful */
+			return;
+		}
+		fatalx(EXIT_FAILURE, "EPO shutdown failed!");
+	} /* else */
 
 	/* Try to shutdown with delay */
 	if (su_instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) {
diff --git a/drivers/solis.c b/drivers/solis.c
index 4cc2c88..1baec83 100644
--- a/drivers/solis.c
+++ b/drivers/solis.c
@@ -1060,21 +1060,18 @@ void upsdrv_updateinfo(void)
 }
 
 /* power down the attached load immediately */
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
+	/* XXX EPO may not work right with this driver */
 
-	/* basic idea: find out line status and send appropriate command */
-	/* on battery: send normal shutdown, ups will return by itself on utility */
-	/* on line: send shutdown+return, ups will cycle and return soon */
-
-	if (!SourceFail) {     /* on line */
+	if (!epo && !SourceFail) {     /* on line */
 	
 		printf("On line, sending shutdown+return command...\n");
 		ser_send_char(upsfd, CMD_SHUTRET );
 	}
 	else
 	{
-		printf("On battery, sending normal shutdown command...\n");
+		printf("%s, sending normal shutdown command...\n", epo ? "Emergency power off" : "On battery");
 		ser_send_char(upsfd, CMD_SHUT);
 	}
 	
diff --git a/drivers/tripplite.c b/drivers/tripplite.c
index 78ef253..fd8abc7 100644
--- a/drivers/tripplite.c
+++ b/drivers/tripplite.c
@@ -336,9 +336,13 @@ void upsdrv_initinfo(void)
 	dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model"), device_path);
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
-	soft_shutdown();
+	if (epo) {
+		hard_shutdown();
+	} else {
+		soft_shutdown();
+	}
 }
 
 void upsdrv_updateinfo(void)
diff --git a/drivers/tripplite_usb.c b/drivers/tripplite_usb.c
index 187e773..9491f93 100644
--- a/drivers/tripplite_usb.c
+++ b/drivers/tripplite_usb.c
@@ -1171,8 +1171,12 @@ void upsdrv_initinfo(void)
 			dstate_getinfo("ups.mfr"), dstate_getinfo("ups.model"));
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO shutdown requested, but the UPS will not stay off!");
+	}
+
 	soft_shutdown();
 }
 
diff --git a/drivers/tripplitesu.c b/drivers/tripplitesu.c
index 9c9049b..7c932d1 100644
--- a/drivers/tripplitesu.c
+++ b/drivers/tripplitesu.c
@@ -821,17 +821,20 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
 	char parm[20];
 
 	if (!init_comm())
 		printf("Status failed.  Assuming it's on battery and trying a shutdown anyway.\n");
-	auto_reboot(1);
-	/* in case the power is on, tell it to automatically reboot.  if
-	   it is off, this has no effect. */
-	snprintf(parm, sizeof(parm), "%d", 1); /* delay before reboot, in minutes */
-	do_command(SET, SHUTDOWN_RESTART, parm, NULL);
+	auto_reboot(epo ? 0 : 1);
+	/* XXX not sure this is right for EPO, esp. given comment about shutdown.stayoff */
+	if (!epo) {
+		/* in case the power is on, tell it to automatically reboot.  if
+		   it is off, this has no effect. */
+		snprintf(parm, sizeof(parm), "%d", 1); /* delay before reboot, in minutes */
+		do_command(SET, SHUTDOWN_RESTART, parm, NULL);
+	}
 	snprintf(parm, sizeof(parm), "%d", 5); /* delay before shutdown, in seconds */
 	do_command(SET, SHUTDOWN_ACTION, parm, NULL);
 }
diff --git a/drivers/upscode2.c b/drivers/upscode2.c
index dcf050d..2e3e4fe 100644
--- a/drivers/upscode2.c
+++ b/drivers/upscode2.c
@@ -873,15 +873,23 @@ void upsdrv_updateinfo(void)
 }
 
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
-	upslogx(LOG_EMERG, "Shutting down...");
-
-	/* send shutdown command twice, just to be sure */
-	instcmd("shutdown.reboot", NULL);
-	sleep(1);
-	instcmd("shutdown.reboot", NULL);
-	sleep(1);
+	upslogx(LOG_EMERG, "%s...", epo ? "EPO shutdown" : "Shutting down");
+
+	if (epo) {
+		/* send shutdown command twice, just to be sure */
+		instcmd("shutdown.stayoff", NULL);
+		sleep(1);
+		instcmd("shutdown.stayoff", NULL);
+		sleep(1);
+	} else {
+		/* send shutdown command twice, just to be sure */
+		instcmd("shutdown.reboot", NULL);
+		sleep(1);
+		instcmd("shutdown.reboot", NULL);
+		sleep(1);
+	}
 }
 
 
diff --git a/drivers/upsdrvctl.c b/drivers/upsdrvctl.c
index 623f753..ea90560 100644
--- a/drivers/upsdrvctl.c
+++ b/drivers/upsdrvctl.c
@@ -40,7 +40,7 @@ typedef struct {
 
 static ups_t	*upstable = NULL;
 
-static int	maxsdorder = 0, testmode = 0, exec_error = 0;
+static int	maxsdorder = 0, testmode = 0, exec_error = 0, shutdown_epo = 0;
 
 	/* timer - keeps us from getting stuck if a driver hangs */
 static int	maxstartdelay = 45;
@@ -297,8 +297,10 @@ static void help(const char *progname)
 	printf("  start	<ups>		only start driver for UPS <ups>\n");
 	printf("  stop			stop all UPS drivers in ups.conf\n");
 	printf("  stop <ups>		only stop driver for UPS <ups>\n");
-	printf("  shutdown		shutdown all UPS drivers in ups.conf\n");
+	printf("  shutdown		shutdown all UPS units in ups.conf\n");
 	printf("  shutdown <ups>	only shutdown UPS <ups>\n");
+	printf("  epo			force off all UPS units in ups.conf\n");
+	printf("  epo <ups>		only force off UPS <ups>\n");
 
 	exit(EXIT_SUCCESS);
 }
@@ -316,7 +318,9 @@ static void shutdown_driver(const ups_t *ups)
 	argv[arg++] = dfn;
 	argv[arg++] = (char *)"-a";		/* FIXME: cast away const */
 	argv[arg++] = ups->upsname;
-	argv[arg++] = (char *)"-k";		/* FIXME: cast away const */
+
+	/* specify the driver agnostic "epo" or "killpower" option */
+	argv[arg++] = shutdown_epo ? (char *)"-K" : (char *)"-k"; /* FIXME: cast away const */
 
 	/* stick on the chroot / user args if given to us */
 	if (pt_root) {
@@ -476,9 +480,14 @@ int main(int argc, char **argv)
 	if (!strcmp(argv[0], "stop"))
 		command = &stop_driver;
 
-	if (!strcmp(argv[0], "shutdown"))
+	if (!strcmp(argv[0], "shutdown")) {
+		shutdown_epo = 0;
 		command = &shutdown_driver;
-
+	}
+	if (!strcmp(argv[0], "epo")) {
+		shutdown_epo = 1;
+		command = &shutdown_driver;
+	}
 	if (!command)
 		fatalx(EXIT_FAILURE, "Error: unrecognized command [%s]", argv[0]);
 
diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c
index 85c8134..6e23ac1 100644
--- a/drivers/usbhid-ups.c
+++ b/drivers/usbhid-ups.c
@@ -671,11 +671,25 @@ int setvar(const char *varname, const char *val)
 	return STAT_SET_UNKNOWN;
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
-	upsdebugx(1, "upsdrv_shutdown...");
+	upsdebugx(1, "upsdrv_shutdown(%s)...", epo ? "epo" : "normal");
 
-	/* Try to shutdown with delay */
+	if (epo) {
+		/* Try to shutdown and stay off */
+		if (instcmd("shutdown.stayoff", NULL) == STAT_INSTCMD_HANDLED) {
+			/* Shutdown successful */
+			return;
+		}
+		/* If the above doesn't work, try load.off.delay */
+		if (instcmd("load.off.delay", NULL) == STAT_INSTCMD_HANDLED) {
+			/* Shutdown successful */
+			return;
+		}
+		fatalx(EXIT_FAILURE, "EPO shutdown failed!");
+	} /* else */
+
+	/* Try to shutdown and reboot when power returns */
 	if (instcmd("shutdown.return", NULL) == STAT_INSTCMD_HANDLED) {
 		/* Shutdown successful */
 		return;
diff --git a/drivers/victronups.c b/drivers/victronups.c
index 5b6cf6d..2b8540e 100644
--- a/drivers/victronups.c
+++ b/drivers/victronups.c
@@ -495,8 +495,12 @@ void upsdrv_updateinfo(void)
 	dstate_dataok();
 }
 
-void upsdrv_shutdown(void)
+void upsdrv_shutdown(int epo)
 {
+	if (epo) {
+		upslogx(LOG_NOTICE, "EPO Shutdown requested, but UPS may not stay off");
+	}
+
 	ser_send(upsfd, "vCc0!%c", ENDCHAR);
 	usleep(UPS_DELAY);
 
-- 
1.7.9.2




More information about the Nut-upsdev mailing list