From c77a1e252be396d8f47d80608dc0123cb370ed9e Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Wed, 23 Sep 2015 11:29:22 +0200 Subject: add new tool nlplug-findfs nlplug-findfs is a tool that will help us to find a given filesystem using coldplug. The only reason that initramfs exist in first place is to set up a rootfs and switch to it. The nlplug-findfs will handle coldplug events til a given filesystem/device is found and then mount it. --- Makefile | 25 ++- arg.h | 39 +++++ nlplug-findfs.c | 511 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 572 insertions(+), 3 deletions(-) create mode 100644 arg.h create mode 100644 nlplug-findfs.c diff --git a/Makefile b/Makefile index 383027d..a62c294 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ sysconfdir ?= /etc/mkinitfs datarootdir ?= /usr/share datadir ?= $(datarootdir)/mkinitfs -SBIN_FILES := mkinitfs bootchartd +SBIN_FILES := mkinitfs bootchartd nlplug-findfs SHARE_FILES := initramfs-init fstab passwd group CONF_FILES := mkinitfs.conf \ features.d/ata.modules \ @@ -42,7 +42,7 @@ CONF_FILES := mkinitfs.conf \ features.d/virtio.modules \ features.d/xfs.modules -SCRIPTS := $(SBIN_FILES) initramfs-init +SCRIPTS := mkinitfs bootchartd initramfs-init IN_FILES := $(addsuffix .in,$(SCRIPTS)) GIT_REV := $(shell test -d .git && git describe || echo exported) @@ -63,7 +63,8 @@ SED_REPLACE := -e 's:@VERSION@:$(FULL_VERSION):g' \ -e 's:@datadir@:$(datadir):g' -all: $(SCRIPTS) + +all: $(SBIN_FILES) $(SCRIPTS) clean: rm -f $(SCRIPTS) @@ -72,6 +73,24 @@ help: @echo mkinitfs $(VERSION) @echo "usage: make install [DESTDIR=]" +CFLAGS ?= -Wall -Werror -g +CFLAGS += -D_GNU_SOURCE -DDEBUG + +PKGCONF ?= pkg-config +BLKID_CFLAGS := $(shell $(PKGCONF) --cflags blkid) +BLKID_LIBS := $(shell $(PKGCONF) --libs blkid) +LIBKMOD_CFLAGS := $(shell $(PKGCONF) --cflags libkmod) +LIBKMOD_LIBS := $(shell $(PKGCONF) --libs libkmod) + +CFLAGS += $(BLKID_CFLAGS) $(LIBKMOD_CFLAGS) +LIBS = $(BLKID_LIBS) $(LIBKMOD_LIBS) + +%.o: %.c + $(CC) $(CFLAGS) -o $@ -c $< + +nlplug-findfs: nlplug-findfs.o + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + .SUFFIXES: .in .in: ${SED} ${SED_REPLACE} ${SED_EXTRA} $< > $@ diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..92a313f --- /dev/null +++ b/arg.h @@ -0,0 +1,39 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef __ARG_H__ +#define __ARG_H__ + +#define USED(x) ((void)(x)) + +#define ARGBEGIN for (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/nlplug-findfs.c b/nlplug-findfs.c new file mode 100644 index 0000000..28f3c9b --- /dev/null +++ b/nlplug-findfs.c @@ -0,0 +1,511 @@ + +/* + * Copy me if you can. + * by 20h + * + * Copyright (c) 2015 Natanael Copa + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "arg.h" + +#define EVENT_TIMEOUT 2000 + +static int dodebug; +char *argv0; + +#if defined(DEBUG) +#include +static void dbg(const char *fmt, ...) +{ + va_list fmtargs; + if (!dodebug) + return; + + fprintf(stderr, "%s: ", argv0); + va_start(fmtargs, fmt); + vfprintf(stderr, fmt, fmtargs); + va_end(fmtargs); + fprintf(stderr, "\n"); +} +#else +#define dbg(...) +#endif + +struct uevent { + char *buf; + size_t bufsize; + char *message; + char *subsystem; + char *action; + char *modalias; + char *devname; + char *major; + char *minor; + char devnode[256]; +}; + +struct ueventconf { + char **program_argv; + char *search_device; + char *crypt_device; + char *crypt_name; + char *mountpoint; + char *subsystem_filter; + int modalias_count; + int fork_count; +}; + + +static void sighandler(int sig) +{ + switch(sig) { + case SIGHUP: + case SIGINT: + case SIGQUIT: + case SIGABRT: + case SIGTERM: + exit(0); + break; + default: + break; + } +} + +static void initsignals(void) +{ + signal(SIGHUP, sighandler); + signal(SIGINT, sighandler); + signal(SIGQUIT, sighandler); + signal(SIGABRT, sighandler); + signal(SIGTERM, sighandler); + signal(SIGPIPE, SIG_IGN); +} + +static int init_netlink_socket(void) +{ + struct sockaddr_nl nls; + int fd, slen; + + memset(&nls, 0, sizeof(nls)); + nls.nl_family = AF_NETLINK; + nls.nl_pid = getpid(); + nls.nl_groups = -1; + + fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if (fd < 0) + err(1, "socket"); + + /* kernel will not create events bigger than 16kb, but we need + buffer up all events during coldplug */ + slen = 1024*1024; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &slen, + sizeof(slen)) < 0) { + err(1, "setsockopt"); + } + slen = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &slen, + sizeof(slen)) < 0) { + err(1, "setsockopt"); + } + + if (bind(fd, (void *)&nls, sizeof(nls))) + err(1, "bind"); + + fcntl(fd, F_SETFD, FD_CLOEXEC); + return fd; +} + +void run_child(char **argv) +{ + pid_t pid; + + if (!(pid = fork())) { + dbg("running %s", argv[0]); + if (execv(argv[0], argv) < 0) + err(1, argv[0]); + exit(0); + } + if (pid < 0) + err(1,"fork"); + + waitpid(pid, NULL, 0); +} + + +int load_kmod(const char *modalias) +{ + static struct kmod_ctx *ctx = NULL; + struct kmod_list *list = NULL; + struct kmod_list *node; + int r, count=0; + + if (ctx == NULL) { + dbg("initializing kmod"); + ctx = kmod_new(NULL, NULL); + if (ctx == NULL) + return -1; + kmod_set_log_fn(ctx, NULL, NULL); + r = kmod_load_resources(ctx); + } + + r = kmod_module_new_from_lookup(ctx, modalias, &list); + if (r < 0) { + dbg("alias '%s' lookup failure", modalias); + return r; + } + + kmod_list_foreach(node, list) { + struct kmod_module *mod = kmod_module_get_module(node); + const char *fmt; + r = kmod_module_probe_insert_module(mod, + KMOD_PROBE_APPLY_BLACKLIST, + NULL, NULL, NULL, NULL); + if (r == 0) { + fmt = "module '%s' inserted"; + count++; + } else if (r == KMOD_PROBE_APPLY_BLACKLIST) { + fmt = "module '%s' is blacklisted"; + } else { + fmt = "module '%s' failed"; + } + dbg(fmt, kmod_module_get_name(mod)); + kmod_module_unref(mod); + } + kmod_module_unref_list(list); + return count; +} + +void start_mdadm(char *devnode) +{ + char *mdadm_argv[] = { + "/sbin/mdadm", + "--incremental", + "--quiet", + devnode, + NULL + }; + run_child(mdadm_argv); +} + +void start_lvm2(char *devnode) +{ + char *lvm2_argv[] = { + "/sbin/lvm", "vgchange", + "--activate" , "ay", "--noudevsync", "--sysinit", + NULL + }; + run_child(lvm2_argv); +} + +void start_cryptsetup(char *devnode, char *cryptdm) +{ + char *cryptsetup_argv[] = { + "/sbin/cryptsetup", "luksOpen", + devnode, cryptdm ? cryptdm : "crypdm", NULL + }; + load_kmod("dm-crypt"); + run_child(cryptsetup_argv); +} + +int is_searchdev(char *devname, const char *searchdev, const char *mountpoint) +{ + static blkid_cache cache = NULL; + char *type = NULL, *label = NULL, *uuid = NULL; + char devnode[256]; + int rc = 0; + + if (searchdev == NULL) + return 0; + + if (strcmp(devname, searchdev) == 0) { + return 1; + } + + if (cache == NULL) + blkid_get_cache(&cache, NULL); + + snprintf(devnode, sizeof(devnode), "/dev/%s", devname); + type = blkid_get_tag_value(cache, "TYPE", devnode); + + if (strncmp("LABEL=", searchdev, 6) == 0) { + label = blkid_get_tag_value(cache, "LABEL", devnode); + rc = label && strcmp(label, searchdev+6) == 0; + } else if (strncmp("UUID=", searchdev, 5) == 0) { + uuid = blkid_get_tag_value(cache, "UUID", devnode); + rc = (uuid && strcmp(uuid, searchdev+5) == 0); + } + + if (type || label || uuid) { + dbg("%s:\n" + "\ttype='%s'\n" + "\tlabel='%s'\n" + "\tuuid='%s'\n", devnode, + type ? type : NULL, + label ? label : NULL, + uuid ? uuid : NULL); + } + + if (!rc && type) { + if (strcmp("linux_raid_member", type) == 0) { + start_mdadm(devnode); + } else if (strcmp("LVM2_member", type) == 0) { + start_lvm2(devnode); + } + } + + if (rc && type && mountpoint) + if(mount(devnode, mountpoint, type, MS_RDONLY, NULL)) + err(1, "mount %s on %s", devnode, mountpoint); + + if (type) + free(type); + if (label) + free(label); + if (uuid) + free(uuid); + + return rc; +} + +int dispatch_uevent(struct uevent *ev, struct ueventconf *conf) +{ + if (conf->subsystem_filter && ev->subsystem + && strcmp(ev->subsystem, conf->subsystem_filter) != 0) { + dbg("subsystem '%s' filtered out (by '%s').", + ev->subsystem, conf->subsystem_filter); + return 0; + } + + if (ev->action == NULL) + return 0; + + if (ev->modalias != NULL && strcmp(ev->action, "add") == 0) { + load_kmod(ev->modalias); + conf->modalias_count++; + + } else if (ev->devname != NULL) { + if (conf->program_argv[0] != NULL) { + run_child(conf->program_argv); + conf->fork_count++; + } + + if (ev->subsystem && strcmp(ev->subsystem, "block") == 0 + && strcmp(ev->action, "add") == 0) { + snprintf(ev->devnode, sizeof(ev->devnode), "/dev/%s", + ev->devname); + if (is_searchdev(ev->devname, conf->search_device, + conf->mountpoint)) + return 1; + + if (is_searchdev(ev->devname, conf->crypt_device, NULL)) + start_cryptsetup(ev->devnode, conf->crypt_name); + } + } + return 0; +} + +int process_uevent(char *buf, const size_t len, struct ueventconf *conf) +{ + struct uevent ev; + + int i, slen = 0; + char *key, *value; + + memset(&ev, 0, sizeof(ev)); + ev.buf = buf; + ev.bufsize = len; + clearenv(); + setenv("PATH", "/sbin:/bin", 1); + + for (i = 0; i < len; i += slen + 1) { + + key = buf + i; + value = strchr(key, '='); + slen = strlen(buf+i); + + if (i == 0 && slen != 0) { + /* first line, the message */ + ev.message = key; + continue; + } + + if (!slen) + continue; + + value[0] = '\0'; + value++; + + if (strcmp(key, "MODALIAS") == 0) { + ev.modalias = value; + } else if (strcmp(key, "ACTION") == 0) { + ev.action = value; + } else if (strcmp(key, "SUBSYSTEM") == 0) { + ev.subsystem = value; + } else if (strcmp(key, "DEVNAME") == 0) { + ev.devname = value; + } else if (strcmp(key, "MAJOR") == 0) { + ev.major = value; + } else if (strcmp(key, "MINOR") == 0) { + ev.minor = value; + } + + if (strcmp(key, "PATH")) { + setenv(key, value, 1); + // dbg("%s = \"%s\"", key, value); + } + } + return dispatch_uevent(&ev, conf); +} + +void usage(void) +{ + errx(1, "usage: %s [-dku] [-f subsystem] [-s device] [-m dir] PATH\n", argv0); +} + +int main(int argc, char *argv[]) +{ + struct pollfd fds; + int r; + struct ueventconf conf; + int event_count = 0; + size_t total_bytes; + int ret = 1; + char *trigger_argv[2] = {0,0}; + char *program_argv[2] = {0,0}; + + + memset(&conf, 0, sizeof(conf)); + conf.program_argv = program_argv; + argv0 = argv[0]; + + ARGBEGIN { + case 'c': + conf.crypt_device = EARGF(usage()); + break; + case 'C': + conf.crypt_name = EARGF(usage()); + break; + case 'd': + dodebug = 1; + break; + case 'm': + conf.mountpoint = EARGF(usage()); + break; + case 'f': + conf.subsystem_filter = EARGF(usage()); + break; + case 'p': + conf.program_argv[0] = EARGF(usage()); + break; + case 's': + conf.search_device = EARGF(usage()); + break; + case 't': + trigger_argv[0] = EARGF(usage()); + break; + default: + usage(); + } ARGEND; + + if (argc > 0) + conf.search_device = argv[0]; + + if (argc > 1) + conf.mountpoint = argv[1]; + + initsignals(); + + fds.fd = init_netlink_socket(); + fds.events = POLLIN; + + /* trigger events here */ + if (trigger_argv[0]) + run_child(trigger_argv); + + while ((r = poll(&fds, 1, EVENT_TIMEOUT)) > 0) { + size_t len; + struct iovec iov; + char cbuf[CMSG_SPACE(sizeof(struct ucred))]; + char buf[16384]; + struct cmsghdr *chdr; + struct ucred *cred; + struct msghdr hdr; + struct sockaddr_nl cnls; + + if (!(fds.revents & POLLIN)) + continue; + + iov.iov_base = &buf; + iov.iov_len = sizeof(buf); + memset(&hdr, 0, sizeof(hdr)); + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + hdr.msg_control = cbuf; + hdr.msg_controllen = sizeof(cbuf); + hdr.msg_name = &cnls; + hdr.msg_namelen = sizeof(cnls); + + len = recvmsg(fds.fd, &hdr, 0); + if (len < 0) { + if (errno == EINTR) + continue; + err(1, "recvmsg"); + } + if (len < 32 || len >= sizeof(buf)) + continue; + + total_bytes += len; + chdr = CMSG_FIRSTHDR(&hdr); + if (chdr == NULL || chdr->cmsg_type != SCM_CREDENTIALS) + continue; + + /* filter out messages that are not from root or kernel */ + cred = (struct ucred *)CMSG_DATA(chdr); + if (cred->uid != 0 || cnls.nl_pid > 0) + continue; + + event_count++; + if (process_uevent(buf, len, &conf)) { + ret = 0; + dbg("FOUND %s", conf.search_device); + break; + } + + if (fds.revents & POLLHUP) { + dbg("parent hung up\n"); + break; + } + } + if (r == -1) + err(1, "poll"); + + if (r == 0) + dbg("exit due to timeout"); + dbg("modaliases: %i, forks: %i, events: %i, total bufsize: %zu", + conf.modalias_count, + conf.fork_count, + event_count, total_bytes); + + return ret; +} + + -- cgit v1.2.3