aboutsummaryrefslogtreecommitdiffstats
path: root/nldev.c
diff options
context:
space:
mode:
authorChristoph Lohmann <20h@r-36.net>2012-04-15 23:08:48 +0200
committerChristoph Lohmann <20h@r-36.net>2012-04-15 23:08:48 +0200
commite2c236b300e4fa9f4f7fdb672251892c2be3e149 (patch)
tree7b5781929b5f0ff95edbc7e4c18339910cff5d22 /nldev.c
downloadnldev-e2c236b300e4fa9f4f7fdb672251892c2be3e149.tar.bz2
nldev-e2c236b300e4fa9f4f7fdb672251892c2be3e149.tar.xz
Initial commit.
Diffstat (limited to 'nldev.c')
-rw-r--r--nldev.c288
1 files changed, 288 insertions, 0 deletions
diff --git a/nldev.c b/nldev.c
new file mode 100644
index 0000000..400379a
--- /dev/null
+++ b/nldev.c
@@ -0,0 +1,288 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <poll.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#include <libgen.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+#include "arg.h"
+
+char *argv0;
+int listfd = -1;
+int dofork = 0, dodebug = 0;
+
+void
+edie(char *fmt, ...)
+{
+ va_list fmtargs;
+
+ va_start(fmtargs, fmt);
+ vfprintf(stderr, fmt, fmtargs);
+ va_end(fmtargs);
+ fprintf(stderr, ": ");
+
+ perror(NULL);
+
+ exit(1);
+}
+
+void
+die(char *fmt, ...)
+{
+ va_list fmtargs;
+
+ va_start(fmtargs, fmt);
+ vfprintf(stderr, fmt, fmtargs);
+ va_end(fmtargs);
+
+ exit(1);
+}
+
+void
+dbg(char *fmt, ...)
+{
+ va_list fmtargs;
+
+ if (dodebug) {
+ fprintf(stderr, "%s: ", argv0);
+ va_start(fmtargs, fmt);
+ vfprintf(stderr, fmt, fmtargs);
+ va_end(fmtargs);
+ fprintf(stderr, "\n");
+ }
+}
+
+void
+disableoom(void)
+{
+ int fd;
+
+ fd = open("/proc/self/oom_score_adj", O_RDWR);
+ if (fd < 0) {
+ fd = open("/proc/self/oom_adj", O_RDWR);
+ if (fd < 0)
+ edie("disabling oom failed.");
+ write(fd, "-17", 3);
+ close(fd);
+ } else {
+ write(fd, "-1000", 5);
+ close(fd);
+ }
+}
+
+void
+child(char *runpath)
+{
+ int fd, pid;
+
+ if (!(pid = fork())) {
+ if (dofork) {
+ fd = open("/dev/null", O_RDWR);
+ if (fd >= 0) {
+ if (write(0, 0, 0) < 0)
+ dup2(fd, 0);
+ if (write(2, 0, 0) < 0)
+ dup2(fd, 0);
+ if (fd > 2)
+ close(fd);
+ }
+ }
+
+ dbg("running %s", runpath);
+ if (execlp(runpath, basename(runpath), NULL) < 0)
+ edie("execvp");
+ exit(0);
+ }
+ if (pid < 0)
+ edie("fork");
+
+ waitpid(pid, NULL, 0);
+}
+
+void
+sighandler(int sig)
+{
+ switch(sig) {
+ case SIGHUP:
+ case SIGINT:
+ case SIGQUIT:
+ case SIGABRT:
+ case SIGTERM:
+ case SIGKILL:
+ if (listfd >= 0) {
+ shutdown(listfd, SHUT_RDWR);
+ close(listfd);
+ }
+ exit(0);
+ break;
+ default:
+ break;
+ }
+}
+
+void
+initsignals(void)
+{
+ signal(SIGHUP, sighandler);
+ signal(SIGINT, sighandler);
+ signal(SIGQUIT, sighandler);
+ signal(SIGABRT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGKILL, sighandler);
+
+ signal(SIGCHLD, SIG_IGN);
+ signal(SIGPIPE, SIG_IGN);
+}
+
+void
+usage(void)
+{
+ die("usage: %s [-hdb] [-kl] [-f subsystem] [-r run]\n", argv0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct sockaddr_nl nls;
+ struct pollfd fds;
+ char buf[4097], *subsystem, *runpath, *key, *value;
+ int i, len, slen, showudev, showkernel;
+
+ showkernel = 1;
+ showudev = 1;
+ subsystem = NULL;
+ runpath = "/bin/mdev";
+
+ ARGBEGIN {
+ case 'b':
+ dofork = 1;
+ break;
+ case 'd':
+ dodebug = 1;
+ break;
+ case 'f':
+ subsystem = EARGF(usage());
+ break;
+ case 'k':
+ showudev = 0;
+ break;
+ case 'l':
+ showkernel = 0;
+ break;
+ case 'r':
+ runpath = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ memset(&nls, 0, sizeof(nls));
+ nls.nl_family = AF_NETLINK;
+ nls.nl_pid = getpid();
+ nls.nl_groups = -1;
+
+ fds.events = POLLIN;
+ fds.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
+ listfd = fds.fd;
+ if (fds.fd < 0)
+ edie("socket");
+
+ slen = 128*1024*1024;
+ if (setsockopt(fds.fd, SOL_SOCKET, SO_RCVBUFFORCE, &slen,
+ sizeof(slen)) < 0) {
+ edie("setsockopt");
+ }
+
+ if (bind(fds.fd, (void *)&nls, sizeof(nls)))
+ edie("bind");
+
+ if (dofork) {
+ if (daemon(0, 0) < 0)
+ edie("daemon");
+ umask(022);
+ }
+
+ initsignals();
+ disableoom();
+
+ buf[sizeof(buf)-1] = '\0';
+ while (poll(&fds, 1, -1) > -1) {
+ unsetenv("ACTION");
+ unsetenv("DEVPATH");
+ unsetenv("SUBSYSTEM");
+ unsetenv("SEQNUM");
+ unsetenv("MODALIAS");
+ unsetenv("DEVNAME");
+ unsetenv("DEVTYPE");
+ unsetenv("MAJOR");
+ unsetenv("MINOR");
+ unsetenv("FIRMWARE");
+
+ len = recv(fds.fd, buf, sizeof(buf)-1, MSG_DONTWAIT);
+ if (len < 0)
+ edie("recv");
+
+ if (strstr(buf, "libudev")) {
+ if (!showudev)
+ continue;
+ } else {
+ if (!showkernel)
+ continue;
+ }
+
+ for (i = 0; i < len; i += slen + 1) {
+ key = buf + i;
+ value = strchr(key, '=');
+ slen = strlen(buf+i);
+
+ if (!slen || value == NULL)
+ continue;
+ if (subsystem && !strncmp(key, "SUBSYSTEM=", 10)
+ && !strstr(key+10, subsystem)) {
+ dbg("subsystem filter '%s' applied.",
+ subsystem);
+ break;
+ }
+
+ value[0] = '\0';
+ value++;
+
+ /*
+ * We generally trust the kernel. But there
+ * might be some udev flaw. (It's >20k sloc!)
+ */
+ if (strcmp(key, "PATH")) {
+ setenv(key, value, 1);
+ dbg("%s = \"%s\"", key, value);
+ }
+ }
+ if (getenv("ACTION") != NULL &&
+ getenv("DEVPATH") != NULL &&
+ getenv("SUBSYSTEM") != NULL &&
+ getenv("SEQNUM") != NULL) {
+ child(runpath);
+ }
+ }
+
+ shutdown(listfd, SHUT_RDWR);
+ close(listfd);
+
+ return 0;
+}
+