Initial commit
authorChristophe Varoqui <christophe.varoqui@opensvc.com>
Fri, 30 Sep 2016 13:18:16 +0000 (15:18 +0200)
committerChristophe Varoqui <christophe.varoqui@opensvc.com>
Fri, 30 Sep 2016 13:18:16 +0000 (15:18 +0200)
unmid parses /dev/kmsg in search for unknown nmi, and triggers
a sysrq action if an unknown nmi is in the trigger mapping
defined in the config file.

Makefile [new file with mode: 0644]
README.iDRAC [new file with mode: 0644]
debug.c [new file with mode: 0644]
debug.h [new file with mode: 0644]
pidfile.c [new file with mode: 0644]
pidfile.h [new file with mode: 0644]
unmid.c [new file with mode: 0644]
util.c [new file with mode: 0644]
util.h [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..eb27070
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,87 @@
+EXEC = unmid
+
+ifndef SYSTEMD
+       ifeq ($(shell systemctl --version > /dev/null 2>&1 && echo 1), 1)
+               SYSTEMD = $(shell systemctl --version 2> /dev/null |  sed -n 's/systemd \([0-9]*\)/\1/p')
+       endif
+endif
+
+ifndef SYSTEMDPATH
+       SYSTEMDPATH=usr/lib
+endif
+
+ifndef RUN
+       ifeq ($(shell test -L /var/run -o ! -d /var/run && echo 1),1)
+               RUN=run
+       else
+               RUN=var/run
+       endif
+endif
+
+prefix          =
+exec_prefix     = $(prefix)
+bindir          = $(exec_prefix)/sbin
+libudevdir      = $(prefix)/$(SYSTEMDPATH)/udev
+udevrulesdir    = $(libudevdir)/rules.d
+multipathdir    = $(TOPDIR)/libmultipath
+man8dir         = $(prefix)/usr/share/man/man8
+man5dir         = $(prefix)/usr/share/man/man5
+man3dir         = $(prefix)/usr/share/man/man3
+syslibdir       = $(prefix)/$(LIB)
+incdir          = $(prefix)/usr/include
+libdir          = $(prefix)/$(LIB)/multipath
+unitdir         = $(prefix)/$(SYSTEMDPATH)/systemd/system
+mpathpersistdir = $(TOPDIR)/libmpathpersist
+mpathcmddir     = $(TOPDIR)/libmpathcmd
+
+GZIP            = gzip -9 -c
+RM              = rm -f
+LN              = ln -sf
+INSTALL_PROGRAM = install
+
+OPTFLAGS        = -Wunused -Wstrict-prototypes -O2 -g -pipe -Wformat-security -Wall \
+                 -Wp,-D_FORTIFY_SOURCE=2 -fstack-protector --param=ssp-buffer-size=4
+
+CFLAGS          = $(OPTFLAGS) -fPIC -DLIB_STRING=\"${LIB}\" -DRUN_DIR=\"${RUN}\" -DEXEC=\"${EXEC}\"
+SHARED_FLAGS    = -shared
+
+%.o:    %.c
+       $(CC) $(CFLAGS) -c -o $@ $<
+
+DESTDIR =
+
+ifdef SYSTEMD
+        CFLAGS += -DUSE_SYSTEMD=$(SYSTEMD)
+        ifeq ($(shell test $(SYSTEMD) -gt 209 && echo 1), 1)
+                LIBDEPS += -lsystemd
+        else
+                LIBDEPS += -lsystemd-daemon
+        endif
+endif
+
+OBJS = unmid.o pidfile.o util.o debug.o
+
+all : $(EXEC)
+
+$(EXEC): $(OBJS)
+       $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $(EXEC) $(LIBDEPS)
+#      $(GZIP) $(EXEC).8 > $(EXEC).8.gz
+
+install:
+       $(INSTALL_PROGRAM) -d $(DESTDIR)$(bindir)
+       $(INSTALL_PROGRAM) -m 755 $(EXEC) $(DESTDIR)$(bindir)
+ifdef SYSTEMD
+       $(INSTALL_PROGRAM) -d $(DESTDIR)$(unitdir)
+       $(INSTALL_PROGRAM) -m 644 $(EXEC).service $(DESTDIR)$(unitdir)
+endif
+       $(INSTALL_PROGRAM) -d $(DESTDIR)$(man8dir)
+       $(INSTALL_PROGRAM) -m 644 $(EXEC).8.gz $(DESTDIR)$(man8dir)
+
+uninstall:
+       $(RM) $(DESTDIR)$(bindir)/$(EXEC)
+       $(RM) $(DESTDIR)$(man8dir)/$(EXEC).8.gz
+       $(RM) $(DESTDIR)$(unitdir)/$(EXEC).service
+
+clean:
+       $(RM) core *.o $(EXEC) *.gz
+
diff --git a/README.iDRAC b/README.iDRAC
new file mode 100644 (file)
index 0000000..319840e
--- /dev/null
@@ -0,0 +1,15 @@
+Allow ipmi over LAN
+===================
+
+Menu: Overview > iDRAC Settings > Network
+Section: IPMI Settings
+Option: Enable IPMI Over LAN
+
+=> Set value to "checked", and apply.
+
+
+Trigger a NMI through ipmitool
+==============================
+
+ipmitool -H 192.168.0.120 -U root -P calvin -I lanplus chassis power diag
+
diff --git a/debug.c b/debug.c
new file mode 100644 (file)
index 0000000..7b7a42e
--- /dev/null
+++ b/debug.c
@@ -0,0 +1,26 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include "debug.h"
+
+void dlog (int sink, int prio, const char * fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+
+       if (prio > log_threshold)
+               return;
+
+       if (sink & LOGSINK_SYSLOG)
+               vsyslog(prio, fmt, ap);
+
+       if (sink & LOGSINK_CONSOLE)
+               vfprintf(stdout, fmt, ap);
+
+       va_end(ap);
+}
diff --git a/debug.h b/debug.h
new file mode 100644 (file)
index 0000000..06143fd
--- /dev/null
+++ b/debug.h
@@ -0,0 +1,21 @@
+#include <stdarg.h>
+#include <syslog.h>
+
+#define __LOGSINK_NONE 0
+#define __LOGSINK_SYSLOG 1
+#define __LOGSINK_CONSOLE 2
+
+#define LOGSINK_NONE (1 << __LOGSINK_NONE)
+#define LOGSINK_SYSLOG (1 << __LOGSINK_SYSLOG)
+#define LOGSINK_CONSOLE (1 << __LOGSINK_CONSOLE)
+
+#define LOG_DEFAULT_THRESHOLD LOG_INFO
+
+extern int logsink;
+extern int log_threshold;
+
+void dlog (int sink, int prio, const char * fmt, ...)
+       __attribute__((format(printf, 3, 4)));
+
+#define condlog(prio, fmt, args...) \
+       dlog(logsink, prio, fmt "\n", ##args)
diff --git a/pidfile.c b/pidfile.c
new file mode 100644 (file)
index 0000000..54c5819
--- /dev/null
+++ b/pidfile.c
@@ -0,0 +1,66 @@
+#include <sys/types.h> /* for pid_t */
+#include <sys/stat.h>  /* for open */
+#include <errno.h>     /* for EACCESS and EAGAIN */
+#include <stdio.h>     /* for snprintf() */
+#include <string.h>    /* for memset() */
+#include <unistd.h>    /* for ftruncate() */
+#include <fcntl.h>     /* for fcntl() */
+#include <syslog.h>    /* for LOG_ */
+
+#include "debug.h"
+#include "pidfile.h"
+
+int pidfile_create(const char *pidFile, pid_t pid)
+{
+       char buf[20];
+       struct flock lock;
+       int fd, value;
+
+       if((fd = open(pidFile, O_WRONLY | O_CREAT,
+                      (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) < 0) {
+               condlog(LOG_ERR, "Cannot open pidfile [%s], error was [%s]",
+                       pidFile, strerror(errno));
+               return -errno;
+       }
+       lock.l_type = F_WRLCK;
+       lock.l_start = 0;
+       lock.l_whence = SEEK_SET;
+       lock.l_len = 0;
+
+       if (fcntl(fd, F_SETLK, &lock) < 0) {
+               if (errno != EACCES && errno != EAGAIN)
+                       condlog(LOG_ERR, "Cannot lock pidfile [%s], error was [%s]",
+                               pidFile, strerror(errno));
+               else
+                       condlog(LOG_ERR, "process is already running");
+               goto fail;
+       }
+       if (ftruncate(fd, 0) < 0) {
+               condlog(LOG_ERR, "Cannot truncate pidfile [%s], error was [%s]",
+                       pidFile, strerror(errno));
+               goto fail;
+       }
+       memset(buf, 0, sizeof(buf));
+       snprintf(buf, sizeof(buf)-1, "%u", pid);
+       if (write(fd, buf, strlen(buf)) != strlen(buf)) {
+               condlog(LOG_ERR, "Cannot write pid to pidfile [%s], error was [%s]",
+                       pidFile, strerror(errno));
+               goto fail;
+       }
+       if ((value = fcntl(fd, F_GETFD, 0)) < 0) {
+               condlog(LOG_ERR, "Cannot get close-on-exec flag from pidfile [%s], "
+                       "error was [%s]", pidFile, strerror(errno));
+               goto fail;
+       }
+       value |= FD_CLOEXEC;
+       if (fcntl(fd, F_SETFD, value) < 0) {
+               condlog(LOG_ERR, "Cannot set close-on-exec flag from pidfile [%s], "
+                       "error was [%s]", pidFile, strerror(errno));
+               goto fail;
+       }
+       condlog(LOG_DEBUG, "pid file %s created", pidFile);
+       return fd;
+fail:
+       close(fd);
+       return -errno;
+}
diff --git a/pidfile.h b/pidfile.h
new file mode 100644 (file)
index 0000000..d308892
--- /dev/null
+++ b/pidfile.h
@@ -0,0 +1 @@
+int pidfile_create(const char *pidFile, pid_t pid);
diff --git a/unmid.c b/unmid.c
new file mode 100644 (file)
index 0000000..276575e
--- /dev/null
+++ b/unmid.c
@@ -0,0 +1,469 @@
+#include <stdio.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <limits.h>
+#include <linux/oom.h>
+#include <signal.h>
+#include <string.h>
+#include <sched.h>
+#include <syslog.h>
+#include <regex.h>
+
+#ifdef USE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+#include "pidfile.h"
+#include "debug.h"
+#include "util.h"
+
+#define KMSG                   "/proc/kmsg"
+#define SYSRQ                  "/proc/sysrq-trigger"
+#define DEFAULT_CONFIGFILE     "/etc/" EXEC ".conf"
+#define DEFAULT_PIDFILE                "/" RUN_DIR "/" EXEC ".pid"
+
+#define DAEMON_STARTING 0
+#define DAEMON_RUNNING 1
+#define DAEMON_SHUTDOWN 2
+#define DAEMON_RECONFIGURE 3
+
+
+/* Local variables */
+static volatile sig_atomic_t exit_sig;
+static volatile sig_atomic_t reconfig_sig;
+
+struct config {
+       unsigned int reason;
+       char trigger;
+       regex_t re;
+};
+
+int daemon_pid = 0;
+int daemon_state = DAEMON_RUNNING;
+char * configfile = NULL;
+struct config *conf;
+int conflines;
+int logsink;
+int log_threshold = LOG_DEFAULT_THRESHOLD;
+FILE * kmesg_fp;
+
+static void *
+signal_set(int signo, void (*func) (int))
+{
+       int r;
+       struct sigaction sig;
+       struct sigaction osig;
+
+       sig.sa_handler = func;
+       sigemptyset(&sig.sa_mask);
+       sig.sa_flags = 0;
+
+       r = sigaction(signo, &sig, &osig);
+
+       if (r < 0)
+               return (SIG_ERR);
+       else
+               return (osig.sa_handler);
+}
+
+void
+handle_signals(void)
+{
+       if (exit_sig) {
+               condlog(LOG_INFO, "exit signal received");
+               daemon_state = DAEMON_SHUTDOWN;
+       }
+       if (reconfig_sig) {
+               condlog(LOG_INFO, "reconfigure signal received");
+               daemon_state = DAEMON_RECONFIGURE;
+       }
+       exit_sig = 0;
+       reconfig_sig = 0;
+}
+
+static void
+sighup (int sig)
+{
+       reconfig_sig = 1;
+}
+
+static void
+sigend (int sig)
+{
+       exit_sig = 1;
+}
+
+static void
+signal_init(void)
+{
+       signal_set(SIGHUP, sighup);
+       signal_set(SIGINT, sigend);
+       signal_set(SIGTERM, sigend);
+       signal_set(SIGPIPE, sigend);
+}
+
+static void
+setscheduler (void)
+{
+       int res;
+       static struct sched_param sched_param = {
+               .sched_priority = 99
+       };
+
+       res = sched_setscheduler (0, SCHED_RR, &sched_param);
+
+       if (res == -1)
+               condlog(LOG_WARNING, "Could not set SCHED_RR at priority 99");
+       return;
+}
+
+static void
+set_oom_adj (void)
+{
+#ifdef OOM_SCORE_ADJ_MIN
+       int retry = 1;
+       char *file = "/proc/self/oom_score_adj";
+       int score = OOM_SCORE_ADJ_MIN;
+#else
+       int retry = 0;
+       char *file = "/proc/self/oom_adj";
+       int score = OOM_ADJUST_MIN;
+#endif
+       FILE *fp;
+       struct stat st;
+       char *envp;
+
+       envp = getenv("OOMScoreAdjust");
+       if (envp) {
+               condlog(LOG_DEBUG, "Using systemd provided OOMScoreAdjust");
+               return;
+       }
+       do {
+               if (stat(file, &st) == 0){
+                       fp = fopen(file, "w");
+                       if (!fp) {
+                               condlog(LOG_ERR, "couldn't fopen %s : %s", file,
+                                       strerror(errno));
+                               return;
+                       }
+                       fprintf(fp, "%i", score);
+                       fclose(fp);
+                       return;
+               }
+               if (errno != ENOENT) {
+                       condlog(LOG_ERR, "couldn't stat %s : %s", file,
+                               strerror(errno));
+                       return;
+               }
+#ifdef OOM_ADJUST_MIN
+               file = "/proc/self/oom_adj";
+               score = OOM_ADJUST_MIN;
+#else
+               retry = 0;
+#endif
+       } while (retry--);
+       condlog(LOG_ERR, "couldn't adjust oom score");
+}
+
+static int
+load_config (void)
+{
+       FILE * fp;
+       char buf[BUFSIZ];
+        char re[BUFSIZ];
+       struct config *entry;
+       conflines = 0;
+
+       condlog(LOG_INFO, "read %s", configfile);
+       if (!filepresent(configfile)) {
+               condlog(LOG_ERR, "%s not found", configfile);
+               return 1;
+       }
+       fp = fopen(configfile, "r");
+       if (fp == NULL) {
+               condlog(LOG_ERR, "cannot open %s", configfile);
+               return 1;
+       }
+       while (!feof(fp)) {
+               if (NULL == fgets(buf, BUFSIZ, fp)) {
+                       if (feof(fp))
+                               break;
+                       condlog(LOG_ERR, "error reading %s", configfile);
+                       return 1;
+               }
+
+               // make room for a new struct
+               conf = realloc(conf, (conflines+1)*sizeof(struct config));
+               if (!conf) {
+                       condlog(LOG_ERR, "error allocating memory to store config");
+                       return 1;
+               }
+
+               // point the last slot
+               entry = &conf[conflines];
+
+               if (sscanf(buf, "%x %c", &entry->reason, &entry->trigger) != 2) {
+                       condlog(LOG_ERR, "syntax error in line: %s", buf);
+                       return 1;
+               }
+               snprintf(re, BUFSIZ, "NMI received for unknown reason %x on CPU", entry->reason);
+                condlog(LOG_INFO, " pattern '%s' loaded with trigger '%c'", re, entry->trigger);
+               regcomp(&entry->re, re, REG_EXTENDED|REG_NOSUB);
+               conflines++;
+       }
+       return 0;
+}
+
+static int
+reconfigure (void)
+{
+       free(conf);
+       load_config();
+       daemon_state = DAEMON_RUNNING;
+       return 0;
+}
+
+static int
+open_kmesg (void) {
+       kmesg_fp = fopen(KMSG, "r");
+       if (kmesg_fp == NULL) {
+               condlog(LOG_ERR, "cannot open " KMSG);
+               return 1;
+       }
+       if ( 0 != fseek(kmesg_fp, 0, SEEK_END)) {
+               condlog(LOG_ERR, "cannot seek " KMSG);
+               return 1;
+       }
+       condlog(LOG_INFO, KMSG " opened");
+       return 0;
+}
+
+static void
+trigger (char * c)
+{
+       FILE * fp = fopen(SYSRQ, "w");
+       if (fp < 0) {
+               condlog(LOG_ERR, "error opening " SYSRQ);
+               return;
+       }
+       fputs(c, fp);
+       fflush(fp);
+       fclose(fp);
+}
+
+static void
+parse_kmsg(char * kmsg)
+{
+       int i;
+       struct config * e;
+       for (i=0; i<conflines; i++) {
+               e = &conf[i];
+               if (!regexec(&e->re, kmsg, 0, NULL, 0)) {
+                       condlog(LOG_INFO, "nmi reason %x detected: trigger sysrq %c", e->reason, e->trigger);
+                       trigger(&e->trigger);
+               }
+       }
+}
+
+static int
+work (void)
+{
+       char buf[BUFSIZ] = "\0";
+
+       if (NULL == fgets(buf, BUFSIZ, kmesg_fp)) {
+               if (exit_sig + reconfig_sig > 0) {
+                       // step out to let the signal handlers do their job
+                       return 0;
+               }
+               condlog(LOG_ERR, "error reading " KMSG);
+               return 1;
+       }
+       printf("DEBUG:: %s", buf);
+       parse_kmsg(buf);
+       fflush(stdout);
+
+       return 0;
+}
+
+static int
+child (void * param)
+{
+       int pid_fd = -1;
+
+       mlockall(MCL_CURRENT | MCL_FUTURE);
+       signal_init();
+
+       pid_fd = pidfile_create(DEFAULT_PIDFILE, daemon_pid);
+       if (pid_fd < 0) {
+               condlog(LOG_WARNING, "failed to create pidfile");
+               exit(1);
+       }
+
+       load_config();
+       if (!conf) {
+               condlog(LOG_ERR, "invalid configuration");
+               goto failed;
+       }
+
+       setscheduler();
+       set_oom_adj();
+
+#ifdef USE_SYSTEMD
+       sd_notify(0, "READY=1");
+#endif
+
+       if (open_kmesg() != 0)
+               goto failed;
+
+       daemon_state = DAEMON_RUNNING;
+
+       while (daemon_state != DAEMON_SHUTDOWN) {
+               if (daemon_state == DAEMON_RECONFIGURE)
+                       reconfigure();
+               work();
+               handle_signals();
+       }
+
+       /* We're done here */
+       condlog(LOG_DEBUG, "unlink pidfile");
+       unlink(DEFAULT_PIDFILE);
+
+       condlog(LOG_INFO, "shut down");
+
+#ifdef USE_SYSTEMD
+       sd_notify(0, "ERRNO=0");
+#endif
+       exit(0);
+
+failed:
+#ifdef USE_SYSTEMD
+       sd_notify(0, "ERRNO=1");
+#endif
+       if (pid_fd >= 0)
+               close(pid_fd);
+       exit(1);
+}
+
+static int
+daemonize(void)
+{
+       int pid;
+       int dev_null_fd;
+
+       if( (pid = fork()) < 0){
+               fprintf(stderr, "Failed first fork : %s\n", strerror(errno));
+               return -1;
+       }
+       else if (pid != 0)
+               return pid;
+
+       setsid();
+
+       if ( (pid = fork()) < 0)
+               fprintf(stderr, "Failed second fork : %s\n", strerror(errno));
+       else if (pid != 0)
+               _exit(0);
+
+       if (chdir("/") < 0)
+               fprintf(stderr, "cannot chdir to '/', continuing\n");
+
+       dev_null_fd = open("/dev/null", O_RDWR);
+       if (dev_null_fd < 0){
+               fprintf(stderr, "cannot open /dev/null for input & output : %s\n",
+                       strerror(errno));
+               _exit(0);
+       }
+
+       close(STDIN_FILENO);
+       if (dup(dev_null_fd) < 0) {
+               fprintf(stderr, "cannot dup /dev/null to stdin : %s\n",
+                       strerror(errno));
+               _exit(0);
+       }
+       close(STDOUT_FILENO);
+       if (dup(dev_null_fd) < 0) {
+               fprintf(stderr, "cannot dup /dev/null to stdout : %s\n",
+                       strerror(errno));
+               _exit(0);
+       }
+       close(STDERR_FILENO);
+       if (dup(dev_null_fd) < 0) {
+               fprintf(stderr, "cannot dup /dev/null to stderr : %s\n",
+                       strerror(errno));
+               _exit(0);
+       }
+       close(dev_null_fd);
+       daemon_pid = getpid();
+       return 0;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+       extern char *optarg;
+       extern int optind;
+       int arg;
+       int err;
+       int foreground = 0;
+
+       if (getuid() != 0) {
+               fprintf(stderr, "need to be root\n");
+               exit(1);
+       }
+
+       /* make sure we don't lock any path */
+       if (chdir("/") < 0)
+               fprintf(stderr, "can't chdir to root directory : %s\n",
+                       strerror(errno));
+       umask(umask(077) | 022);
+
+       logsink = LOGSINK_SYSLOG;
+
+       while ((arg = getopt(argc, argv, ":df:v:")) != EOF ) {
+               switch(arg) {
+               case 'd':
+                       foreground = 1;
+                       logsink = logsink | LOGSINK_CONSOLE;
+                       break;
+               case 'f':
+                       configfile = optarg;
+                       break;
+               case 'v':
+                       log_threshold = atoi(optarg);
+                       break;
+               default:
+                       fprintf(stderr, "Invalid argument '-%c'\n",
+                               optopt);
+                       exit(1);
+               }
+       }
+
+       if (!configfile)
+               configfile = DEFAULT_CONFIGFILE;
+
+       if (foreground) {
+               if (!isatty(fileno(stdout)))
+                       setbuf(stdout, NULL);
+               err = 0;
+               daemon_pid = getpid();
+       } else
+               err = daemonize();
+
+       if (err < 0)
+               /* error */
+               exit(1);
+       else if (err > 0)
+               /* parent dies */
+               exit(0);
+       else
+               /* child lives */
+               return (child(NULL));
+}
+
+
diff --git a/util.c b/util.c
new file mode 100644 (file)
index 0000000..99c8476
--- /dev/null
+++ b/util.c
@@ -0,0 +1,11 @@
+#include <sys/stat.h>
+
+int
+filepresent (char * run) {
+       struct stat buf;
+
+       if(!stat(run, &buf))
+               return 1;
+       return 0;
+}
+
diff --git a/util.h b/util.h
new file mode 100644 (file)
index 0000000..51ea971
--- /dev/null
+++ b/util.h
@@ -0,0 +1,18 @@
+#ifndef _UTIL_H
+#define _UTIL_H
+
+#include <sys/types.h>
+
+size_t strchop(char *);
+int basenamecpy (const char * src, char * dst, int);
+int filepresent (char * run);
+int get_word (char * sentence, char ** word);
+size_t strlcpy(char *dst, const char *src, size_t size);
+size_t strlcat(char *dst, const char *src, size_t size);
+
+#define safe_sprintf(var, format, args...)     \
+       snprintf(var, sizeof(var), format, ##args) >= sizeof(var)
+#define safe_snprintf(var, size, format, args...)      \
+       snprintf(var, size, format, ##args) >= size
+
+#endif /* _UTIL_H */