aboutsummaryrefslogtreecommitdiffstats
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
downloadnldev-e2c236b300e4fa9f4f7fdb672251892c2be3e149.tar.bz2
nldev-e2c236b300e4fa9f4f7fdb672251892c2be3e149.tar.xz
Initial commit.
-rw-r--r--LICENSE21
-rw-r--r--Makefile56
-rw-r--r--arg.h41
-rw-r--r--config.mk23
-rw-r--r--nldev.169
-rw-r--r--nldev.c288
6 files changed, 498 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..789dd34
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT/X Consortium License
+
+© 2012 Christoph Lohmann <20h@r-36.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d2acef7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,56 @@
+# nldev - NetLink Device manager
+# See LICENSE file for copyright and license details.
+
+include config.mk
+
+SRC = ${NAME}.c
+OBJ = ${SRC:.c=.o}
+
+all: options ${NAME}
+
+options:
+ @echo ${NAME} build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+.c.o:
+ @echo CC $<
+ @${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.mk
+
+${NAME}: ${OBJ}
+ @echo CC -o $@
+ @${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+ @echo cleaning
+ @rm -f ${NAME} ${OBJ} ${NAME}-${VERSION}.tar.gz
+
+dist: clean
+ @echo creating dist tarball
+ @mkdir -p ${NAME}-${VERSION}
+ @cp -R LICENSE Makefile README.md config.mk \
+ ${SRC} ${NAME}.1 *.h ${NAME}-${VERSION}
+ @tar -cf ${NAME}-${VERSION}.tar ${NAME}-${VERSION}
+ @gzip ${NAME}-${VERSION}.tar
+ @rm -rf ${NAME}-${VERSION}
+
+install: all
+ @echo installing executable file to ${DESTDIR}${PREFIX}/bin
+ @mkdir -p ${DESTDIR}${PREFIX}/bin
+ @cp -f ${NAME} ${DESTDIR}${PREFIX}/bin
+ @chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME}
+ @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
+ @mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ @cp -f ${NAME}.1 ${DESTDIR}${MANPREFIX}/man1
+ @chmod 644 ${DESTDIR}${MANPREFIX}/man1/${NAME}.1
+
+uninstall:
+ @echo removing executable file from ${DESTDIR}${PREFIX}/bin
+ @rm -f ${DESTDIR}${PREFIX}/bin/${NAME}
+ @echo removing manual page from ${DESTDIR}${PREFIX}/man1
+ @rm -f ${DESTDIR}${MANPREFIX}/man1/${NAME}.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..6414822
--- /dev/null
+++ b/arg.h
@@ -0,0 +1,41 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef __ARG_H__
+#define __ARG_H__
+
+extern char *argv0;
+
+#define USED(x) ((void)(x))
+
+#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
+ argv[0] && argv[0][1]\
+ && argv[0][0] == '-';\
+ argc--, argv++) {\
+ char _argc;\
+ char **_argv;\
+ if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+ argv++;\
+ argc--;\
+ break;\
+ }\
+ for (argv[0]++, _argv = argv; argv[0][0];\
+ argv[0]++) {\
+ if (_argv != argv)\
+ break;\
+ _argc = argv[0][0];\
+ switch (_argc)
+
+#define ARGEND }\
+ USED(_argc);\
+ }\
+ USED(argv);\
+ USED(argc);
+
+#define EARGF(x) ((argv[1] == NULL)? ((x), abort(), (char *)0) :\
+ (argc--, argv++, argv[0]))
+
+#endif
+
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..b9b98de
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,23 @@
+# nldev metadata
+NAME = nldev
+VERSION = 0.3
+
+# Customize below to fit your system
+
+# paths
+PREFIX ?= /usr
+MANPREFIX = ${PREFIX}/share/man
+
+# includes and libs
+INCS = -I. -I/usr/include
+LIBS = -L/usr/lib -lc
+
+# flags
+CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE
+CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
+LDFLAGS = -static -g ${LIBS}
+#LDFLAGS = -s ${LIBS}
+
+# compiler and linker
+CC = cc
+
diff --git a/nldev.1 b/nldev.1
new file mode 100644
index 0000000..18e403e
--- /dev/null
+++ b/nldev.1
@@ -0,0 +1,69 @@
+.Dd April 15, 2012
+.Dt NLDEV 1
+.Os
+.
+.Sh NAME
+.Nm nldev
+.Nd a simple netlink device manager
+.
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl h
+.Op Fl d
+.Op Fl b
+.Op Fl k
+.Op Fl l
+.Op Fl f Ar subsystem
+.Op Fl r Ar runpath
+.Ek
+.
+.Sh DESCRIPTION
+.Bd -filled
+.Nm
+is a simple netlink device manager. It does this by emulating
+the behaviour of the hotplug system built into the Linux kernel.
+The defaults are meant to fit to the mdev applet included in
+busybox.
+.Ed
+.
+.Sh OPTIONS
+.Nm
+options and default settings.
+.Pp
+.Bl -tag -width ".Fl test Ao Ar string Ac"
+.
+.It Fl b
+Run in background.
+.
+.It Fl d
+Turn on debug messages. This will not work in conjunction with
+-b.
+.
+.It Fl f Ar subsystem
+This option will filter for the subsystem key in a netlink message
+and compare it to the subsystem string.
+.
+.It Fl h
+Show usage.
+.
+.It Fl k
+Only show netlink messages from the kernel.
+.
+.Bd -filled
+.It Fl l
+Only show netlink messages from libudev.
+.
+.It Fl r Ar runpath
+This option specifies the runpath for the helper that is launched
+on every received netlink event (default: /bin/mdev).
+.El
+.
+.Sh AUTHORS
+See LICENSE file for authors in the distribution.
+.
+.Sh LICENSE
+.Nm
+is released under the MIT/X Consortium License.
+.
+
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;
+}
+