Dumbupsd is a daemon that sits in the background and monitors the dumb UPS (\fBUninterruptible Power Supply) so that the computer can shut down itself (or do anything you want) in case of power cut.
There are three main types of ups's. Very dumb ups's cannot talk to the computer. Of course there's not much to do with them.
The so-called DUMB UPS's (such as APC BACK 650 -- this is the one I use with this software) use the flow control lines of a serial port for communication. They have a line where they tell if power goes down (we'll call this line LINEFAIL). Typically they have another line to tell if battery is low (let's call this line BATTLOW). Latter one means there are about 2 minutes left, but see this in the manual of your ups. Optionally there is a line where the computer can tell the ups to shut down the power.
Of course it is possible that these options are not all supported by your ups. It's also possible that only 3 combinations are used by the ups out of the 4, for example if line is okay then it does not report the state of its battery. Many ups's can only be shut down when they operate from the battery (ie. linefail is true).
Smart ups's talk to the computer via normal serial line, telling many garbage such as fluctuation of voltage. Not all the manufacturers do publish how the communication goes, for example APC does not. This dumbupsd cannot communicate in smart mode. Smart ups's usually can operate in dumb mode.
Usually if the ups daemon recognises the change of one of the lines, it puts the word FAIL, LOW or OK into /etc/powerstatus and sends a SIGPWR to the first process, init(8). Then init reads and removes this file, and invokes one of the three corresponding entries found in inittab(5).
As I see, there are two major problems with this method. First, since the ups daemon only sends a signal, it has no information on script's finish. This way if power goes down and returns in very short intervals then you may find more scripts running at the same time, one trying to shut down the the system and another one trying to restore everything. This leads to big chaos. (Okay, maybe init waits for the script, but I didn't find this clearly documented.) It's also possible that init had no time to read the file and the ups daemon has already put a new word there. (Okay, you're right again, it's possible that the ups daemon first checks for the presence of this file. Who knows...)
The other drawback is that not everybody wants to simply shut down the option not to kill given processes, since you may want dumbupsd not to be killed. So this dumbupsd really lets you do whatever you want...
When dumbupsd starts, it opens and locks the device, and unless it fails it forks itself to the background and becomes a daemon. Then first checks if the connection is okay. If it is not then starts the script (see the -s option) with an argument of "cable" and with an environment variable CONNECTION containing the value 0. Then this script does anything it wants. If the cable is still not okay, the script is invoked again after every 60 seconds with the CONNECTION variable containing 1. When connection becomes okay, the script is invoked with CONNECTION=2. In these cases the first argument is always "cable". If the connection is okay at startup then the script is not invoked at all with "cable" argument.
When dumbupsd has successfully started and the connection is okay, it invokes the script with an argument "init" and tells the initial state of the two lines in the environment variables LINEFAIL and BATTLOW. Value 0 means that there is no linefail and/or battery is okay, value 1 means that something is not okay. This can be used for example to immediately shut down the power if the ups operates from its battery. Of course, to do this, you need to start dumbupsd before filesystems are mounted read-write.
Then dumbupsd keeps on monitoring, checks the two lines every second. If the status of linefail line changes, then the script is invoked with an argument "linechange" and environment variables LINEFAIL and BATTLOW telling the new state of the two lines. If the status of battlow line changes then the script is invoked with "battchange" argument and the same two variables. Note that dumbupsd never sees that the two lines have changed at the same time, it always notices at most one change. For example if both the line and battery go out at the same time, then there are two possibilities. It's possible that first the script is invoked with "linechange" argument, LINEFAIL=1 and BATTLOW=0 (yes, that's zero) and then the script is invoked with "battchange" argument, LINEFAIL=1 and BATTLOW=1. But it's also possible that we first notice the "battchange" and then the "linechange" event.
While monitoring the lines, dumbupsd always checks the connection. If it fails, then the script is invoked as described above, and dumbupsd waits till connection becomes okay.
When receiving a SIGUSR1 signal, dumbupsd reports the state of the three lines via the CONNECTION, LINEFAIL and BATTLOW variables, launching the script with the argument "status". CONNECTION can be 0 meaning troube and 2 meaning okay. As always, LINEFAIL and BATTLOW are not the real state of the physical lines, they are the values dumbupsd remembers to be last reported. I hope you understand what I mean...
Receiving a SIGPWR causes dumbupsd to kill the ups's power. About 10 seconds later it restores the original state of the line, reports the unsuccessful kill and continues operating as if nothing had happened. suggested that you terminate all the commands like "echo foobar >/dev/some_tty", "wall", "write" with an ampersand ('&') sign in your script because of the same reason, or use the noblock(1) program. (Fortunately init also uses this flag when writing to the console.) By the way, the standard output and the error of the launched script are connected to /dev/console (in non-blocking mode), and the standard input is connected to /dev/null.
Inside the computer TTL logic is used where 0V represents the logical 0 and 5V represents the logical 1. Inside the cable, on the RS-232 port, logical 0 is represented by +12V and logical 1 is represented by -12V. The receiver IC has to accept -15V..-3V as 1 and +3V..+15V as 0. Whenever I say "high" or "low", "up" or "down" etc. in this man page, I'll think of the logical value (or the TTL voltage value), and not the RS-232 voltage.
The following pin numbers are denoted according to the 9-pin Cannon connectors.
The ups has two relays, one closes when line fails, the other closes when the battery is low. (Closing means to let the current flow.) In an APC, these relays are between pins 4-3 and 4-5, respectively. You should connect the common pin in the ups to the ground in the pc (pin 5) (ground means 0V inside the cable), connect the linefail line of your ups to the DCD line on your pc (pin 1), and connect the battlow line of your ups to the CTS line on your pc (pin 8). Also you should pull up these two lines to DTR (pin 4) with two 10Kohm resistors (DTR is set high by software). (My friend, Nagy Dániel says he'd rather use 4.7Kohm. But I made the cable using 10K's.) So normally, when the relay is open, we see high level in the DCD and CTS inputs. If the ups closes a relay, then the input becomes 0V in the RS-232 cable, and (very ugly, ain't it?) we expect that our hardware in the pc says it's logical 0, just as if there was +12V instead of the 0V inside the cable.
The shutdown line is normally low, and goes high when we turn off the power. The software uses the RTS line (pin 7) as the shutdown line. APC ups's have pin 1 as the shutdown line. These should be simply connected.
PC end of the cable (female) DSR 6 o------+ | DTR 4 o------+---+--------+ | | +-+-+ +-+-+ | | | | |10K| |10K| | | | | +-+-+ +-+-+ | | DCD 1 o----------+------- | ------> ... linefail | CTS 8 o-------------------+-------> ... battlow GND 5 o---------------------------> ... ground RTS 7 o---------------------------> ... shutdown UPS end of the cable (male) # Numbers are according to APC. # See the manual of your ups. # This is inside the ups. linefail ... <----o 3 3 o--------------o linefail / relay battlow ... <----o 5 5 o--o battlow o / relay | +-----+-----+------+-------+ | 1 | 8 | DCD | in | | 2 | 3 | RxD | in | | 3 | 2 | TxD | out | | 4 | 20 | DTR | out | | 5 | 7 | SGND | - | | 6 | 6 | DSR | in | | 7 | 4 | RTS | out | | 8 | 5 | CTS | in | | 9 | 22 | RI | in | +-----+-----+------+-------+
The cable described above is only one of the several solutions. For example APC's also have an active (not relay) output for linefail (pin 2). Some people rather use this.
There are some ups's that need the shutdown line to be high for about 5 seconds. Marek Michalkiewicz says in the FAQ that a few milliseconds is engouh for his APC Back 600. This is also true for the APC Back 650 I'm working with. Marek says this is a real problem since opening the device pulls RTS high for a moment. So I was very careful writing the code. Anyway Marek suggests to start the ups daemon before mounting any filesystem read-write. I think it's a very good idea. He also suggests using TxD instead of RTS. If you want to do so, modify the source code. Is it possible that all this depends on whether you're using /dev/cua? or /dev/ttyS?, or that it depends on whether you have rts/cts handshaking enabled or disabled (see stty(1) and termios(3))? If you know the answer, please tell me.
Switching to another runlevel when power is gone is a very good idea, I think. Init scripts are very nice, easy to maintain and this situation is exactly what runlevels are for. (In case you don't want to shut down your computer immediately, of course.) But I faced a serious problem using init-2.65f. That comes from the sysvinit_2.65 package, the original version of Miquel van Smoorenburg nearly completely rewritten by Florian La Roche at S.u.S.E. GmbH. This version of init hangs if you wish to switch runlevel but /var is not writable. So if power restores, I have to mount /var read-write first, and change runlevel afterwards. I don't know how other versions of init behave. If you don't have an /etc/shutdown.allow file (see shutdown(8)) then anyone pressing Ctrl-Alt-Del can also hang init. (Shutdown needs to read a file in /var)