Luminated Software Group Presents HOWTO use Back-UPS (by APC) (to keep your linux box from frying) Document by: Christian G. Holtje Cabling info and help: Ben Galliart This document, under one condition, is placed in Public Domain. The one condition is that credit is given where credit is due. Modify this as much as you want, just give some credit to us who worked. ******************************************************************************* Warning! I, nor any of us who have written or helped with this document, make and guarantees or claims for this text/source/hints. If anything is damaged, we take NO RESPONSIBILITY! This works to the BEST OF OUR KNOWLEDGE, but we may have made mistakes. So be careful! ******************************************************************************* Al right, you just bought (or are going to buy) a Back-UPS from APC. (Other brands might be able to use this info, with little or no modification, but we don't know) You've looked at the price of the Power-Chute software/cabling, and just are not sure it's worth the price. Well, I made my own cable, and my own software and am using it to automatically shut off the power to my linux box when a power failure hits. Guess what? You can too! *** The Cable *** This was the hardest part to figure out (I know little about hardware, so Ben did the most work for this). To build one, you need to buy from your local radio shack (or other part supplier) this stuff: 1 male 9-pin rs-232 plug 1 female 9-pin rs-232 plug 2 casings for the above plugs (they are usually sold separately) Some wire You also need, but may be able to borrow: 1 soldering iron solder Okay...this is how you connect it up! These diagrams are looking into the REVERSE SIDE (the side where you solder the wire onto the plugs) The letters G, R, and B represent the colors of the wires I used, and help to distinguish one line from the next. (NOTE: I'm use standard rs-232 (as near as we can tell) numbering. The APC book uses different numbers. Ignore them! Use ours...I already changed the numbers for you!) --------------------- Male Side! (This goes into the UPS) \ B R * * * / \ * * * G / ------------ --------------------- Female Side! (This goes into your COM port) \ R * * * G / \ * B * * / ------------ For those who like the numbers better: Male Female --------------------------------------- 1 7 Black 2 1 Red 9 5 Green ---------Aside: What the rs-232 pins are for!----------- Since we had to dig this info up anyway: >From the REAR (the soldering side) the pins are numbered so: --------------------- \ 1 2 3 4 5 / \ 6 7 8 9 / ------------ The pins mean: Number Name Abbr. (Sometimes written with D prefix) 1 Carrier Detect CD 2 Receive Data RD 3 Transmit Data TD(?) 4 Data Terminal Ready DTR 5 Signal Ground Gnd 6 Data Set Ready DSR 7 Request to Send RTS(?) 8 Clear to Send CS 9 Ring Indicator RI What we did is connect the UPS's RS-232 Line Fail Output to the CD, the UPS's chassis to Gnd, and the UPS's RS-232 Shut Down Input to RTS. Easy now that we told you, no? I have no idea if the software below will work, if you purchase the cable from APC. It might, and it might not. *** The Software *** Okay, I use the SysVInit package by Miquel van Smoorenburg for Linux. (see end for file locations, credits, email addresses, etc.) I don't know what would have to be changed to use someone elses init, but I know this code (following) will work with Miquel's stuff. Just so I give credit where credit's due. I looked at Miquel's code to figure out how ioctl()'s worked. If I didn't have that example, I'd have been in trouble. I also used the powerfail() routine (verbatim, I think), since it must interact with his init, I thought that he should know best. The .c file is at the end of this document, and just needs to be clipped off. To clip the file, edit away and extra '.sigs' and junk. This document should end on the line /* End of File */.....cut the rest. Then type: tail -n 116 backups.HOWTO > backupsd.c This may need a little editing, but it works! This program can either be run as a daemon to check the status of the UPS and report it to init, or it can be run to send the kill-power command to the UPS. The power will only be killed if there is a power problem, and the UPS is running off the battery. Once the power is restored, it turns back on. To run as a daemon, just type: backupsd /dev/backups /dev/backups is a link to /dev/cua0 at the moment (COM 1, for you DOSers). The niceness of the link is that I can just re-link the device if I change to com 2 or 3. Then, if the power dies init will run the commands for the powerwait. An example (This is from my /etc/inittab): # Here are the actions for powerfailure. pf::powerwait:/etc/rc.d/rc.power start po::powerokwait:/etc/rc.d/rc.power stop The powerwait will run, if the power goes down, and powerokwait will run if the power comes back up. Here is my entire rc.power: ---------------------------------------------------------------------------- #! /bin/sh # # rc.power This file is executed by init when there is a powerfailure. # # Version: @(#)/etc/rc.d/rc.power 1.50 1994-08-10 # # Author: Christian Holtje, # # Set the path. PATH=/sbin:/etc:/bin:/usr/bin:/sbin/dangerous # Find out how we were called. case "$1" in start) echo "Warning there is Power problems." | wall # Save current Run Level ps | gawk '{ if (($5 == "init") && ($1 == "1")) print $6 }' \ | cut -f2 -d[ | cut -f1 -d] \ > /tmp/run.level.power /sbin/shutdown -h +1m ;; stop) echo "Power is back up. Attempting to halt shutdown." | wall shutdown -c ;; *) echo "Usage: $0 [start|stop]" exit 1 ;; esac #End of File ------------------------------------------------------------------------------ Pretty nifty, no? There is one little detail left, that is having the UPS turn off the power if it was halted with the power out. This is accomplished by adding this line into the end of your halt script: /sbin/backupsd /dev/backups killpower This will only kill the power if there is no power being supplied to your UPS. *** Testing the stuff *** This is just a short section saying this: BE CAREFUL! I recommend backing up your linux partitions, syncing several times before testing and just being careful in general. Of course, I'm just recommending this. I wasn't careful at all, and had to clean my partition several times testing my config. But it works. :) *** Where to Get It *** Miquel van Smoorenburg's SysVInit can be gotten at: sunsite.unc.edu:/pub/Linux/system/Daemons/SysVinit-2.50.tgz and a fix for some bash shells is right next-door as: sunsite.unc.edu:/pub/Linux/system/Daemons/SysVinit-2.50.patch1 As to getting this HOWTO, you can email me. docwhat@uiuc.edu with the subject saying 'request' and the keyword 'backups' in body of the letter. (I may automate this, and other stuff) *** Credit Where Credit's Due Dept. *** Thanks to Miquel van Smoorenburg for his wonderful SysVInit package and his powerd.c which helped me very much. Christian Holtje Documentation backupsd.c (what wasn't Miquel's) rc.power Ben Galliart The cable Information for the RS-232 standard Lousy Jokes (none quoted here) ------------------>8-------------CUT HERE--------8<--------------------------- /* backupsd.c -- Simple Daemon to catch power failure signals from a * Back-UPS (from APC). * * Parts of the code are from Miquel van Smoorenburg's powerd.c * Other parts are original from Christian Holtje * I believe that it is okay to say that this is Public Domain, just * give credit, where credit is due. * * Disclaimer: We make NO claims to this software, and take no * resposibility for it's use/misuse. */ #include #include #include #include #include #include #include #include /* This is the file needed by SysVInit */ #define PWRSTAT "/etc/powerstatus" void powerfail(int fail); /* Main program. */ int main(int argc, char **argv) { int fd; int killpwr_bit = TIOCM_RTS; int flags; int status, oldstat = -1; int count = 0; if (argc < 2) { fprintf(stderr, "Usage: %s [killpower]\n", argv[0]); exit(1); } /* Open the the device */ if ((fd = open(argv[1], O_RDWR | O_NDELAY)) < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], sys_errlist[errno]); exit(1); } if ( argc >= 3 && (strcmp(argv[2], "killpower")==0) ) { /* Let's kill the power! */ fprintf(stderr, "%s: Attempting to kill the power!\n",argv[0] ); ioctl(fd, TIOCMBIS, &killpwr_bit); /* Hmmm..... If you have a power outtage, you won't make it! */ exit(0); } else /* Since we don't want to kill the power, clear the RTS. (killpwr_bit) */ ioctl(fd, TIOCMBIC, &killpwr_bit); /* Become a daemon. */ switch(fork()) { case 0: /* I am the child. */ setsid(); break; case -1: /* Failed to become daemon. */ fprintf(stderr, "%s: can't fork.\n", argv[0]); exit(1); default: /* I am the parent. */ exit(0); } /* Now sample the DCD line. */ while(1) { ioctl(fd, TIOCMGET, &flags); status = (flags & TIOCM_CD); /* Did DCD jumps to high? Then the power has failed. */ if (oldstat == 0 && status != 0) { count++; if (count > 3) powerfail(0); else { sleep(1); continue; } } /* Did DCD go down again? Then the power is back. */ if (oldstat > 0 && status == 0) { count++; if (count > 3) powerfail(1); else { sleep(1); continue; } } /* Reset count, remember status and sleep 2 seconds. */ count = 0; oldstat = status; sleep(2); } /* Error! (shouldn't happen) */ return(1); } /* Tell init the power has either gone or is back. */ void powerfail(ok) int ok; { int fd; /* Create an info file needed by init to shutdown/cancel shutdown */ unlink(PWRSTAT); if ((fd = open(PWRSTAT, O_CREAT|O_WRONLY, 0644)) >= 0) { if (ok) write(fd, "OK\n", 3); else write(fd, "FAIL\n", 5); close(fd); } kill(1, SIGPWR); } /* End of File */ LocalWords: rc