nldev

NetLink DEVice manager; a lightweight netlink frontend for mdev.
git clone git://r-36.net/nldev
Log | Files | Refs | LICENSE

commit e2c236b300e4fa9f4f7fdb672251892c2be3e149
Author: Christoph Lohmann <20h@r-36.net>
Date:   Sun, 15 Apr 2012 23:08:48 +0200

Initial commit.

Diffstat:
LICENSE | 21+++++++++++++++++++++
Makefile | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
arg.h | 41+++++++++++++++++++++++++++++++++++++++++
config.mk | 23+++++++++++++++++++++++
nldev.1 | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
nldev.c | 288+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 498 insertions(+), 0 deletions(-)

diff --git a/LICENSE 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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; +} +