/* * UPS daemon for MGE Pulsar * At least on my ES5+ works fine * Copyright (c) Stanislav Voronyi * Version 0.3 28.12.1998 */ #include #include #include #include #include #include #include #include #include #include #include #include /* some defaults */ #define DEFAULT_INTERVAL 10 #define DEFAULT_L_INTERVAL 30 int RTS = TIOCM_RTS, use_syslog = 0; /* only bits I need for */ #define SS_BATTERY_LOW 4 #define SS_POWER_FAIL 5 #define SS_OVERLOAD 6 #define BS_POWER_FAIL 0 #define BS_POWER_OK 1 #define BS_BAT 2 #define BS_PWS 11 #define BS_TDP 12 /* status file for init */ #define PWRFILE "/var/run/powerstatus" /* Linux usual */ #define LOCKDIR "/var/run" #define RUNDIR "/var/run" char lockfile[40] = {0,}; char pidfile[40] = {0,}; char progname[40] = {0,}; void sigexit (int ignore) { if (*lockfile) unlink (lockfile); if (*pidfile) unlink (pidfile); if (use_syslog) syslog (LOG_NOTICE, "UPS daemon terminated"); exit (EX_OK); } int main (int argc, char **argv) { int tty, fd; FILE *trace = NULL; char iline[40], Status[20], *av[3], *tfile = NULL, *p; char portname[40], *pwrfile = PWRFILE, *run_path = NULL; struct termios ts; struct timeval timeout; fd_set fds; int interval = DEFAULT_INTERVAL, lowinterval = DEFAULT_L_INTERVAL; int c, i, mode = 0, nr_count = 0, start = 1, prev = '0', ckbat = 0; int tdp = 0, pws = 0; /* don't touch this flags by default */ int before_pwroff = 0, pwr_on_wait = 0; time_t last_low_rep = 0, overload_time = 0; pid_t init_pid = 1; if ((p = strrchr (argv[0], '/')) != NULL) strcpy (progname, p + 1); else strcpy (progname, argv[0]); /* now parsing argumets We may have mgeupsd [-i check_interval] [-q] port port may be as /dev/ttyxxx or ttyxx or you can do ln -s /dev/ttyXxx /dev/mgeups and use /dev/mgeups */ for (i = 1; i < argc; i++) { if (strcmp (argv[i], "-pwroff") == 0) { mode = 1; continue; } else if (strcmp (argv[i], "-q") == 0) { /* for comatibility with standart powerd */ mode = 1; continue; } else if (strcmp (argv[i], "-l") == 0) /* use syslog for reporting */ { use_syslog = 1; continue; } else if (strcmp (argv[i], "-i") == 0) { if ((interval = atoi (argv[++i])) == 0) interval = DEFAULT_INTERVAL; /* my default - check ups every 10 seconds */ continue; } else if (strcmp (argv[i], "-li") == 0) { if ((lowinterval = atoi (argv[++i])) == 0) lowinterval = DEFAULT_L_INTERVAL; /* my default - report twice a minute */ continue; } else if (strcmp (argv[i], "-t") == 0) /* trace commands, huge output - only for debbuging */ { tfile = strdup (argv[++i]); continue; } else if (strcmp (argv[i], "-pon") == 0) /* total discharge protection on */ { tdp_on: tdp = '0'; continue; } else if (strcmp (argv[i], "-poff") == 0) /* total discharge protection off */ { tdp_off: tdp = '1'; continue; } else if (strcmp (argv[i], "-p") == 0) { if (strcmp (argv[++i], "on") == 0) goto tdp_on; else if (strcmp (argv[i], "off") == 0) goto tdp_off; else { fprintf (stderr, "Invalid option -p %s\n", argv[i]); break; } } else if (strcmp (argv[i], "-son") == 0) /* power saving mode on */ { pws_on: pws = '1'; continue; } else if (strcmp (argv[i], "-soff") == 0) /* power saving mode off */ { pws_off: pws = '0'; continue; } else if (strcmp (argv[i], "-s") == 0) { if (strcmp (argv[++i], "on") == 0) goto pws_on; else if (strcmp (argv[i], "off") == 0) goto pws_off; else { fprintf (stderr, "Invalid option -s %s\n", argv[i]); break; } } /* -pwrfile, -process & -run three options that allow monitoring not only machine but any other equipment as well */ else if (strcmp (argv[i], "-pwrfile") == 0) /* change name of power status file */ { pwrfile = argv[++i]; /* make some verification */ if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0) { close (fd); unlink (pwrfile); } else { fprintf (stderr, "Can't create file %s\n", argv[i]); break; } continue; } else if (strcmp (argv[i], "-process") == 0) /* change pid of process for send SIGPWR to */ { if ((init_pid = atoi (argv[++i])) == 0) { fprintf (stderr, "Invalid option -process %s\n", argv[i]); break; } if (kill (init_pid, 0)) { /* process not running */ fprintf (stderr, "Process %u does not running\n", init_pid); break; } continue; } else if (strcmp (argv[i], "-run") == 0) /* run specified command & give powerfile as argv[1] */ { /* must be full pathname */ run_path = argv[++i]; if (*run_path != '/') { fprintf (stderr, "You must specify full pathname for -run\n"); break; } if (access (run_path, X_OK)) { fprintf (stderr, "Haven't permission to run %s\n", run_path); break; } continue; } else if (strcmp (argv[i], "-swpwr") == 0) /* may have two parameters time_off,time_before_off */ { mode = 2; pwr_on_wait = (int) strtol (argv[++i], &p, 10); if (*p == ',') before_pwroff = atoi (++p); continue; } else { /* this must be portname */ if (argv[i][0] == '-') { /* portname can't start from '-' - this is wrong option */ fprintf (stderr, "Invalid option %s\n", argv[i]); break; } if (argv[i][0] == '/') strcpy (portname, argv[i]); else sprintf (portname, "/dev/%s", argv[i]); /* check for port locking & lock port */ p = strrchr (portname, '/') + 1; sprintf (lockfile, "%s/LCK..%s", LOCKDIR, p); if ((trace = fopen (lockfile, "r")) == NULL) { newlock: if (!mode) /* don't make lock in poweroff mode - filesystems possible ro already */ { #ifndef TEST if (fork ()) exit (EX_OK); #endif if ((trace = fopen (lockfile, "w")) == NULL) { fprintf (stderr, "Unable to create lock file, exiting!\n"); exit (EX_CANTCREAT); } fprintf (trace, "%u", getpid ()); fclose (trace); trace = NULL; } } else { fscanf (trace, "%d", &c); fclose (trace); trace = NULL; if (kill ((pid_t) c, 0) == 0) { fprintf (stderr, "Device %s already locked by process %u\n", portname, c); exit (EX_NOPERM); } else goto newlock; } tty = open (portname, O_RDWR); if (tty == -1 || isatty (tty) != 1) { fprintf (stderr, "mgeupsd: %s not a tty\n", portname); exit (EX_USAGE); } else { close (tty); /* I open it for real later */ goto operate; } } } /* if we here something wrong */ fprintf (stderr, "Usage: mgeupsd [-i check_interval] [-l] [-q] [-p on|off] [-s on|off] port\n"); exit (EX_USAGE); operate: /* we don't need stdxxx and control tty */ fclose (stderr); fclose (stdout); fclose (stdin); setsid (); signal (SIGTERM, sigexit); signal (SIGCHLD, SIG_IGN); /* setup port */ tty = open (portname, O_RDWR | O_NOCTTY); ioctl (tty, TIOCMBIC, &RTS); tcgetattr (tty, &ts); cfmakeraw (&ts); cfsetospeed (&ts, B2400); cfsetispeed (&ts, B2400); ts.c_cflag &= ~(CRTSCTS); ts.c_cc[VMIN] = 0; ts.c_cc[VTIME] = 5; tcsetattr (tty, TCSANOW, &ts); /* end setup */ if (mode) { /* turn power off */ rz: write (tty, "Z\r\n", 3); c = read (tty, iline, sizeof iline); if (c != 0) goto rz; rm: c = sprintf (iline, "Sm %u\r\n", pwr_on_wait); write (tty, iline, c); for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i); if (c != 4 || iline[0] != 'O' || iline[1] != 'K') goto rm; rn: c = sprintf (iline, "Sn %u\r\n", before_pwroff); write (tty, iline, c); for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i); if (c != 4 || iline[0] != 'O' || iline[1] != 'K') goto rn; rx: write (tty, "Sx0\r\n", 5); for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i); if (c != 4 || iline[0] != 'O' || iline[1] != 'K') goto rx; /* power must be turned off now, just wait for it */ if (mode == 1) pause (); else exit (EX_OK); /* or I must waitng to turn power back to on? write me if anyone need this */ } else { /* normal operation */ /* create pid-file first */ sprintf (pidfile, "%s/%s.pid", RUNDIR, progname); if ((trace = fopen (pidfile, "w")) != NULL) { fprintf (trace, "%u\n", getpid ()); fclose (trace); trace = NULL; } if (use_syslog) { openlog (progname, LOG_PID, LOG_DAEMON); syslog (LOG_INFO, "UPS daemon started"); } if (tfile) /* trace mode */ trace = fopen (tfile, "a"); /* I don't check for error if trace == NULL just no trace */ if (trace) setvbuf (trace, NULL, _IONBF, 0); /* init UPS */ nr_count = -1; ri: if (nr_count++ > 60) { nr_count = 0; if (use_syslog) syslog (LOG_NOTICE, "Unable to %s communication with UPS", start ? "start" : "restore"); } write (tty, "Z\r\n", 3); if (trace) fprintf (trace, "Command: Z\n"); c = read (tty, iline, sizeof iline); if (c != 0) { if (trace) { iline[c] = 0; fprintf (trace, "Answer: %s\n", iline); /* junk ? */ } goto ri; } write (tty, "Si 1\r\n", 6); if (trace) fprintf (trace, "Command: Si 1\n"); c = read (tty, iline, sizeof iline); if (c == 0) goto ri; while (c < 10) { i = read (tty, iline + c, sizeof iline); if (i) c += i; else goto ri; } if (strncmp (iline, "Pulsar", 6) != 0) goto ri; if (trace || use_syslog) { iline[c] = 0; if ((p = strchr (iline, '\n')) != NULL) *p = 0; if ((p = strchr (iline, '\r')) != NULL) *p = 0; /* remove CR LF for report */ } if (trace) fprintf (trace, "Device report: %s\n", iline); if (use_syslog) { if (!start) syslog (LOG_NOTICE, "Communication with UPS restored"); syslog (LOG_INFO, "Device: %s", iline); } while (c != 0) { c = read (tty, iline, sizeof iline); if (trace && c) fprintf (trace, "more: %s", iline); } /* check battery status first */ nr_count = 0; rbs: write (tty, "Bs\r\n", 4); if (trace) fprintf (trace, "Command: Bs\n"); /* answer must be 19 bytes long */ for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i); if (c != 19) { if (trace) { Status[c] = 0; fprintf (trace, "Answer: %s\n", Status); /* somethig wrong */ } if (nr_count++ < 3) goto rbs; else goto ri; } else Status[17] = 0; if (trace) fprintf (trace, "Answer: %s\n", Status); if (Status[BS_POWER_FAIL] == '1') { /* very strange - we start from battery */ if (use_syslog) syslog (LOG_WARNING, "UPS started without main supply"); } if (use_syslog) if (Status[BS_POWER_OK] == '1') { if (Status[BS_BAT] == '1') { /* battery not fully charged */ ckbat = 1; syslog (LOG_INFO, "UPS battery not fully charged"); } else syslog (LOG_INFO, "UPS battery completely charged"); } if (tdp) { if (Status[BS_TDP] != tdp) nr_count = 0; if (tdp == '1') { rbx3: write (tty, "Bx3\r\n", 5); if (trace) fprintf (trace, "Command: Bx3\n"); for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i); if (trace) { iline[c] = 0; fprintf (trace, "Answer: %s\n", iline); } if (c != 4 || iline[0] != 'O' || iline[1] != 'K') if (nr_count++ < 3) goto rbx3; else if (use_syslog) syslog (LOG_WARNING, "Can't turn off total discharge protection"); } else { rbx4: write (tty, "Bx4\r\n", 5); if (trace) fprintf (trace, "Command: Bx4\n"); for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i); if (trace) { iline[c] = 0; fprintf (trace, "Answer: %s\n", iline); } if (c != 4 || iline[0] != 'O' || iline[1] != 'K') if (nr_count++ < 3) goto rbx4; else if (use_syslog) syslog (LOG_WARNING, "Can't turn on total discharge protection"); } } if (pws) { if (Status[BS_PWS] != pws) nr_count = 0; if (pws == '1') { rsx11: write (tty, "Sx11\r\n", 6); if (trace) fprintf (trace, "Command: Sx11\n"); for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i); if (trace) { iline[c] = 0; fprintf (trace, "Answer: %s\n", iline); } if (c != 4 || iline[0] != 'O' || iline[1] != 'K') if (nr_count++ < 3) goto rsx11; else if (use_syslog) syslog (LOG_WARNING, "Can't turn on power save mode"); } else { rsx10: write (tty, "Sx10\r\n", 6); if (trace) fprintf (trace, "Command: Sx10\n"); for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i); if (trace) { iline[c] = 0; fprintf (trace, "Answer: %s\n", iline); } if (c != 4 || iline[0] != 'O' || iline[1] != 'K') if (nr_count++ < 3) goto rsx10; else if (use_syslog) syslog (LOG_WARNING, "Can't turn off power save mode"); } } FD_ZERO (&fds); FD_SET (tty, &fds); start = 0; while (1) { timeout.tv_sec = interval; timeout.tv_usec = 0; rsel: FD_SET (tty, &fds); if (select (tty + 1, &fds, 0, 0, &timeout) != 0) { if (FD_ISSET (tty, &fds)) { for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i); if (trace) { iline[c] = 0; fprintf (trace, "Unexpected answer: %s", iline); /* answer usualy have \n at end */ } } goto rsel; } /* now read status */ nr_count = 0; rs: write (tty, "Ss\r\n", 4); if (trace) fprintf (trace, "Command: Ss\n"); /* read status string */ for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i); if (c == 0) { if (nr_count++ < 3) goto rs; else { if (use_syslog) syslog (LOG_NOTICE, "Communication with UPS lost!"); goto ri; } } if (c != 10) { if (trace) { Status[c] = 0; fprintf (trace, "Short answer: %s\n", Status); } goto rs; } if (trace) { Status[8] = 0; fprintf (trace, "State: %s\n", Status); } /* check status */ if (Status[SS_OVERLOAD] == '1') { /* UPS overloaded */ if (use_syslog) syslog (LOG_WARNING, "UPS overloaded!"); /* UPS can operate no more 5 minutes, so do immediate reboot after 4 minutes */ if (!overload_time) (void) time (&overload_time); else if (overload_time + 4 * 60 < time ((time_t *) NULL)) /* immediate shutdown */ /* just set SS_BATTERY_LOW */ Status[SS_BATTERY_LOW] = '1'; } else if (overload_time) { if (use_syslog) syslog (LOG_INFO, "UPS overload obviated"); overload_time = 0; } if (Status[SS_BATTERY_LOW] == '1') { /* very bad, we must immidiate halt */ if (last_low_rep + lowinterval > time ((time_t *) NULL)) continue; if (use_syslog) syslog (LOG_CRIT, "UPS battery low! Shutdown immediately!"); unlink (pwrfile); if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0) { write (fd, "LOWBATT\n", 8); close (fd); if (run_path) { if (fork () == 0) { p = strrchr (run_path, '/') + 1; av[0] = p; av[1] = pwrfile; av[2] = NULL; close (tty); execv (run_path, av); exit (1); } } else { #ifndef TEST if (kill (init_pid, SIGUSR2)) syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid); #endif } time (&last_low_rep); continue; } } if (Status[SS_POWER_FAIL] == '1') { /* mains suply absent */ if (prev == Status[SS_POWER_FAIL]) /* nothing changed */ continue; else { /* report to init */ if (use_syslog) syslog (LOG_WARNING, "Power fail!"); unlink (pwrfile); if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0) { write (fd, "FAIL\n", 5); close (fd); if (run_path) { if (fork () == 0) { p = strrchr (run_path, '/') + 1; av[0] = p; av[1] = pwrfile; av[2] = NULL; close (tty); execv (run_path, av); exit (1); } } else { #ifndef TEST if (kill (init_pid, SIGINT)) syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid); #endif } prev = Status[SS_POWER_FAIL]; continue; } } } if (prev == '1') { /* mains suply return, report to init */ if (use_syslog) syslog (LOG_NOTICE, "Power restored!"); unlink (pwrfile); if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0) { write (fd, "OK\n", 3); close (fd); if (run_path) { if (fork () == 0) { p = strrchr (run_path, '/') + 1; av[0] = p; av[1] = pwrfile; av[2] = NULL; close (tty); execv (run_path, av); exit (1); } } else { #ifndef TEST if (kill (init_pid, SIGHUP)) syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid); #endif } prev = Status[SS_POWER_FAIL]; } if (use_syslog) ckbat = 1; } if (ckbat && use_syslog) { rckbat: write (tty, "Bs\r\n", 4); if (trace) fprintf (trace, "Command: Bs\n"); /* answer must be 19 bytes long */ for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i); if (c != 19) { if (trace) { Status[c] = 0; fprintf (trace, "Answer: %s\n", Status); /* somethig wrong */ } if (nr_count++ < 3) goto rckbat; else { if (use_syslog) syslog (LOG_NOTICE, "Communication with UPS lost!"); goto ri; } } else Status[17] = 0; if (trace) fprintf (trace, "Answer: %s\n", Status); if (Status[BS_POWER_FAIL] == '1') ckbat = 0; if (Status[BS_POWER_OK] == '1' && Status[BS_BAT] == '0') { ckbat = 0; /* don't check in future */ syslog (LOG_INFO, "UPS battery completely recharged"); } } } /* while(1) */ } return 0; }