#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VERSION "V1.0" #define PIPE_NAME "/dev/IAManctl" #define BUTTON_HOLD_TIME 10 char * instructions = "IAMan " VERSION "\n" "----------\n" "A front-panel light-and-switch driver for Linux boxes that have no\n" "console.\n" "Just connect a couple of LEDS (or a bi-colour LED, green to RTS and red\n" "to DSR) to RTS and DTR on your chosen serial port, connect a switch\n" "between RTS and CTS, and you can monitor and control the machine!\n" "Note: Each LED should be connected in series with a suitable resistor\n" "(about 1kOhm) to limit the forward current, and a diode to protect the\n" "LED from reverse bias damage.\n" "LED signals\n" "-----------\n" "Here I assume that the green LED is connected to RTS, and the red to\n" "DTR.\n" "The green LED will blink while the system is in multi-user mode. It will\n" "spend longer on when the CPU utilisation goes up.\n" "Both flash together during booting and shutdown.\n" "The red LED flashes rapidly when the machine is fully shut down, and can\n" "be switched off.\n" "Switch\n" "------\n" "When the switch is ON for about 10 seconds, IAMan will initiate an\n" "innediate shutdown. Great for servers in racks which have no consoles,\n" "but you must move or switch off!\n" "\n" "Invoking:\n" " IAMan [-k] [-t] [-d ] [-r ] [-m] [-p\n" " ] [-s 0|1] [-c \"\"]\n" "\n" " -k kills other instances, then exits (used for testing)\n" " -t test mode\n" " -d debug level (0-2). if ommitted assumes 0.\n" " -r run state. One of:\n" " b booting\n" " r running\n" " s single-user\n" " h halted\n" " u unknown\n" " -m master mode. Won\'t try to talk to another instance, just kills\n" " it and goes daemon. Used when running from an inittab respawn\n" " line.\n" " -p specify port to use. Defaults to /dev/cua1.\n" " -s output sense. 0 = positive output=on, 1= negative output=on.\n" " Default 1.\n" " -c comment displayed on the console when it starts. Useful for\n" " tracing what init is up to.\n" ; typedef int bool; #define true (1) #define false (0) int debug = 0; const char *defaultDevice = "/dev/cua1"; /* default device if none specified on command line */ int fd; /* for serial device */ int outputSense = 1; /* active-1 or active 0, default 1 */ int pipeInFd; int pipeOutFd; char gRunLevel; /*-------------------------------------------------------------------------- fatal -------------------------------------------------------------------------- DESCRIPTION: Display an error message, copy it to a log file, then exit. PARAMETERS: [in] text Text to display RETURN: Doesn't return. ------------------------------------------------------------------------*/ void fatal(char * text) { FILE * fp; if((fp=fopen("IAMan.log", "a"))==0) { exit(1); } fprintf(fp, "Error: %s\r\n",text); fclose(fp); exit(1); } /*-------------------------------------------------------------------------- checkstat -------------------------------------------------------------------------- DESCRIPTION: Checks the return status from an OS command, and exits if the command failed. PARAMETERS: [in] cmd text of command that failed. RETURN: Doesn't ------------------------------------------------------------------------*/ void checkstat(int status, char * cmd) { char errorMessage[256]; if (status != 0) { sprintf(errorMessage,"IAMan: %s failed (err=%s)",cmd,strerror(errno)); printf("%s\r\n",errorMessage); fatal(errorMessage); exit(1); } } /*-------------------------------------------------------------------------- ShowPid -------------------------------------------------------------------------- DESCRIPTION: Displays the PID of the calling process, with a message. Really for me to check I'm fork()ing right. PARAMETERS: [in] msg Message to go with PID. ------------------------------------------------------------------------*/ void ShowPid(char * msg) { if (debug) { if (msg) printf("IAMan: %d at %s\r\n", getpid(), msg); else printf("IAMan: %d\r\n", getpid()); } } /*-------------------------------------------------------------------------- handleSignal -------------------------------------------------------------------------- DESCRIPTION: Simple "go away don't bother me" signal handler. ------------------------------------------------------------------------*/ void handleSignal(int signum) { if (debug) printf("IAMan: Got signal %d\r\n", signum); (void) signal(signum, handleSignal); } /*-------------------------------------------------------------------------- GetCPUUtilisation -------------------------------------------------------------------------- DESCRIPTION: Basic CPU utilisation reader. RETURN: The CPU utilisation as a value 0-100. IMPLEMENTATION: Does the usual thing with the /proc/stat file. Calling this too frequently will give coarse-grained results. ------------------------------------------------------------------------*/ int GetCPUUtilisation() { static int user0,nice0,system0,idle0; int user1,nice1,system1,idle1; int user,nice,system,idle; int tot; int utilisation = 0; FILE * f = fopen("/proc/stat","r"); if (f) { char b[255]; fgets(b,sizeof(b),f); if (sscanf(b,"cpu %d %d %d %d",&user1,&nice1,&system1,&idle1) ==4) { user = user1-user0; nice = nice1-nice0; system = system1-system0; idle = idle1-idle0; tot = user+nice+system+idle; if (tot > 0) { utilisation = ((user+nice+system) * 100)/tot; } if (debug >=2) printf("user %d, nice %d, system %d, idle %d\r\n", user,nice,system,idle); if (debug >=2) printf("cpu=%d\r\n",utilisation); user0 = user1; nice0 = nice1; system0 = system1; idle0 = idle1; } fclose(f); } return utilisation; } /*-------------------------------------------------------------------------- CreatePipe -------------------------------------------------------------------------- DESCRIPTION: Creates the named pipe that IAMan uses to try to communicate with a running instance of IAMan. If it exists, returns without doing anything. If it can't be created, exits the program. ------------------------------------------------------------------------*/ void CreatePipe() { /* Creates the pipe if it doesn't exist */ int ret; struct stat fileStat; ret = stat(PIPE_NAME, &fileStat); if (ret && (errno == ENOENT)) { /* Pipe doesn't exist */ if (debug) printf("IAMan:Create pipe\r\n"); ret = mkfifo(PIPE_NAME, S_IRUSR | S_IWUSR); checkstat(ret,"mkfifo(PIPE_NAME, S_IRUSR | S_IWUSR)"); } else { checkstat(ret,"stat(PIPE_NAME, &fileStat)"); } if (debug) printf("IAMan:Pipe exists\r\n"); } /*-------------------------------------------------------------------------- OpenPipeInput -------------------------------------------------------------------------- DESCRIPTION: Opens the named pipe for input. This call blocks. Exits the program if it can't be opened. RETURN: true. ------------------------------------------------------------------------*/ bool OpenPipeInput() { if (debug) printf("IAMan:Opening pipe for input\r\n"); pipeInFd = open(PIPE_NAME,O_RDONLY); checkstat(pipeInFd == -1, "open(PIPE_NAME,O_RDONLY)"); return true; } /*-------------------------------------------------------------------------- OpenPipeOutput -------------------------------------------------------------------------- DESCRIPTION: Attempts to open the named pipe for output. This will fail if either the pipe node has not been created, or there is no IAMan daemon to have it open for read. RETURN: true if the pipe was successfully opened. ------------------------------------------------------------------------*/ bool OpenPipeOutput() { if (debug) printf("IAMan:Opening pipe for output\r\n"); pipeOutFd = open(PIPE_NAME,O_WRONLY| O_NONBLOCK); if (pipeOutFd == -1) { if (debug) printf("IAMan:Can't open pipe for output\r\n"); return false; } return true; } /*-------------------------------------------------------------------------- PipeReaderThread -------------------------------------------------------------------------- DESCRIPTION: The daemon can optain information about the current run state from a transient instance writing to the named pipe. This thread parses any text that arrives on the pipe, and changes the current run state accordingly. PARAMETERS: [in] arg Unused argument (required for pthreads) ------------------------------------------------------------------------*/ void * PipeReaderThread(void * arg) { char buffer[2]; char command = ' '; int state = 0; setpriority(PRIO_PROCESS,getpid(),PRIO_MIN); if (debug) printf("IAMan:Thread started!\r\n"); ShowPid("PipReader"); if(!OpenPipeInput()) { fatal("Other instance jumped in!"); } while (true) { int n; n = read(pipeInFd,buffer, 1); if (n>0) { if (buffer[0] =='(') { state = 1; } else { switch (state) { case 1: command = buffer[0]; state = 2; break; case 2: if (buffer[0] == ')') { state = 0; if (strchr("0123456brush",command)) { gRunLevel = command; if (debug) printf("IAMan:Runlevel now %c\r\n", gRunLevel); } } } } } else if (n == 0) { sleep(1); } else if (n < 0) { checkstat(1,"read(pipeInFd,buffer, 1)"); } } } /*-------------------------------------------------------------------------- WaitLoop -------------------------------------------------------------------------- DESCRIPTION: The main wait loop. This blinks the leds on the serial port RTS and DTR lines, while waiting for CTS to go active. ------------------------------------------------------------------------*/ int WaitLoop() { int running = 1; int defaultState,status; int buttonCount = 0; int buttonLimit = 1; pthread_t pipeThread; if (debug) printf("IAMan: Starting reader thread\r\n"); ShowPid("WaitLoop"); pthread_create(&pipeThread, NULL, &PipeReaderThread, NULL); /* Get the default state. We must preserve the state of the OUT1 and OUT 2 lines. */ status = ioctl(fd, TIOCMGET, &defaultState); checkstat(status,"ioctl(fd, TIOCMGET, &arg)"); /* Switch the leds off */ if (outputSense) { defaultState &= ~(TIOCM_RTS | TIOCM_DTR ); } else { defaultState |= (TIOCM_RTS | TIOCM_DTR ); } status = ioctl(fd, TIOCMSET, &defaultState); checkstat(status,"ioctl(fd, TIOCMGET, &arg)"); while (running) { int state; int flashMask; int period; int onTime; int duty; bool canShutDown = false; bool ctsDetected = false; if (debug > 1) printf("IAMan:Loop\r\n"); /* Decide what flashpattern to use depending on the current run state We wait using usleep, so the period is set in microseconds */ switch(gRunLevel) { /* Booting */ case '0': case 'b': flashMask = TIOCM_RTS | TIOCM_DTR; period = 1000000; duty = 50; break; /* Running */ case 'r': case '2': case '3': /* The main running state. RTS is blinked on, with the mark/space determined by the current CPU utilisation. We fiddle the numbers to give a minimum of 5% and a maximum of 95% */ canShutDown = true; flashMask = TIOCM_RTS; period = 2000000; duty = 3 + (GetCPUUtilisation() * 94)/100; if (debug >= 2) printf("Duty=%d\r\n",duty); /* Number of periods that the button must be on */ buttonLimit = (BUTTON_HOLD_TIME * 1000000)/period; break; /* Single user mode */ case '1': case 's': flashMask = TIOCM_RTS; period = 500000; duty = 50; break; /* Halted */ case '6': case 'h': flashMask = TIOCM_DTR; period = 500000; duty = 50; break; /* Don't know */ default: case 'u': flashMask = TIOCM_RTS | TIOCM_DTR; period = 1000000; duty = 95; break; } ioctl(fd, TCSBRK, (period/100000)); onTime = (period * duty) /100; if (debug > 1) printf("IAMan:Period = %d, onTime = %d, delay1 = %d\r\n",period, onTime, period - onTime); usleep(period - onTime); /* Switch leds on */ if (debug > 1) printf("IAMan:ON "); if (outputSense) { state = defaultState | flashMask; } else { state = defaultState & ~flashMask; } status = ioctl(fd, TIOCMSET, &state); checkstat(status,"ioctl(fd, TIOCMSET, &arg)"); usleep(onTime); /* If RTS is active, sense CTS */ if (outputSense) { status = ioctl(fd, TIOCMGET, &state); checkstat(status,"ioctl(fd, TIOCMGET, &arg)"); ctsDetected = (state & TIOCM_CTS); } if (debug > 1) printf("OFF\r\n"); /* Switch leds off */ status = ioctl(fd, TIOCMSET, &defaultState); /* If RTS is active, sense CTS */ if (!outputSense) { status = ioctl(fd, TIOCMGET, &state); checkstat(status,"ioctl(fd, TIOCMGET, &arg)"); ctsDetected = (state & TIOCM_CTS); } if (ctsDetected && canShutDown) { /* Button is pressed, count down to kill time */ buttonCount++; if (debug) printf("IAMan: Going - %d of %d\r\n", buttonCount, buttonLimit); if(buttonCount >= buttonLimit) { int pid = fork(); if (pid == 0) { /* Run shutdown to shut us down */ if (debug) printf("IAMan:Shutdown detected!\r\n"); status = execl("/sbin/shutdown", "shutdown", "-h", "now", "Shutdown initiated from shutdown button", NULL); checkstat(status, "exec"); exit(0); } /* Poke the state to "booting" to make the flash pattern change immediately */ gRunLevel='b'; } } else { /* Use let go before timeout - back to DEFCON 5 */ buttonCount = 0; } } return 0; } /*-------------------------------------------------------------------------- GoDaemon -------------------------------------------------------------------------- DESCRIPTION: Do all the magic stuff required to be a daemon. Copied from "The LInux A-Z" by Phil Cornes ISBN 0- 13-234709-1 . Recommended. ------------------------------------------------------------------------*/ void GoDaemon() { int i; if (!debug) { for (i=0; i %s",psOutputName); system(commandLine); psFile = fopen(psOutputName,"r"); { if (psFile) { while (!feof(psFile)) { char line[80]; int pid, ppid; fgets(line,sizeof(line),psFile); if (sscanf(line,"%d %d",&ppid, &pid) == 2) { if ((ppid == 1) && (pid != getpid())) { if (debug) printf("IAMan:Killing PID:%d\r\n",pid); kill(pid,SIGKILL); } } } fclose(psFile); } } } int main(int argc, char * argv[]) { int argIndex; int stat; char device[255]; /* name of device to open */ bool testMode = false; bool masterMode = false; char * arg = NULL; char * param = NULL; setpriority(PRIO_PROCESS,getpid(),PRIO_MIN); gRunLevel = 2; strcpy(device,defaultDevice); /* Process arguments. */ if (argc < 2) { printf(instructions); return (0); } for (argIndex = 1; argIndex < argc; argIndex++) { arg = argv[argIndex]; param = ""; if ((argIndex +1) < argc) { param = argv[argIndex + 1]; } if ((param[0] != '-') && (param[0])) { argIndex++; } if (arg[0] == '-') { switch (arg[1]) { case 't': /* Run in test mode */ testMode = true; break; case 'd': /* debug level */ sscanf(param,"%d",&debug); break; case 'r': /* Set current runlevel */ gRunLevel = param[0]; break; case 'm': /* "Master" mode - kill all other instances */ masterMode = true; break; case 'k': /* Just kill any running instance, and exit */ KillOtherInstances(); return 0; break; case 'p': /* serial port name */ strcpy(device,param); break; case 's': if (param[0]=='0') { outputSense = 0; } break; case 'c': /* comment - ignored by program, just output. Useful for seeing why it started during system boot*/ printf("IAMan:-------------%s------------\r\n", param); break; default: printf("IAMan:Unknown arg %s\r\n",arg); exit(1); } } } if (testMode) { printf("IAMan:Test mode\r\n"); printf("IAMan:Dev=%s, runlevel=%c\r\n",device,gRunLevel); Start(device); } else { if (debug) printf("IAMan:Trying to be daemon\r\n"); ShowPid("1"); /* Make sure we're the only show in town */ if (masterMode) { KillOtherInstances(); } CreatePipe(); /* Try to open the pipe */ if (!OpenPipeOutput()) { if (debug) printf("IAMan:Pipe not openable - go daemon\r\n"); BeDaemon(device); } else { /* There's already a running one, so just send it a message */ char buffer[] = "( )\r\n"; buffer[1] = gRunLevel; stat = write(pipeOutFd,buffer,strlen(buffer)); checkstat(stat <= 0,"write(pipeFd,buffer,strlen(buffer))"); } } return 0; }