home: hub: mkinitfs

Download patch

ref: d7fb217ced6eeef1fbd7102f3df8e04107769096
parent: 5c1b50919c78e9e7798b719634e4e8a99c1ceeb1
author: Natanael Copa <ncopa@alpinelinux.org>
date: Fri Sep 17 08:05:36 CDT 2021

Move nlplug-findfs to subdirectory

--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,6 @@
 bootchartd
 initramfs-init
 mkinitfs
-nlplug-findfs
+nlplug-findfs/nlplug-findfs
 *.[17]
 mkinitfs.conf
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@
 datadir		?= $(datarootdir)/mkinitfs
 mandir		?= $(datarootdir)/man
 
-SBIN_FILES	:= mkinitfs bootchartd nlplug-findfs
+SBIN_FILES	:= mkinitfs bootchartd nlplug-findfs/nlplug-findfs
 SHARE_FILES	:= initramfs-init fstab passwd group
 CONF_FILES	:= mkinitfs.conf \
 		features.d/ata.modules \
@@ -113,7 +113,7 @@
 %.o: %.c
 	$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
 
-nlplug-findfs: nlplug-findfs.o
+nlplug-findfs/nlplug-findfs: nlplug-findfs/nlplug-findfs.o
 	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
 
 .SUFFIXES:	.in
--- a/arg.h
+++ /dev/null
@@ -1,48 +1,0 @@
-/*
- * Copy me if you can.
- * by 20h
- */
-
-#ifndef ARG_H__
-#define ARG_H__
-
-extern char *argv0;
-
-/* use main(int argc, char *argv[]) */
-#define ARGBEGIN	for (argv0 = *argv, argv++, argc--;\
-					argv[0] && argv[0][0] == '-'\
-					&& argv[0][1];\
-					argc--, argv++) {\
-				char argc_;\
-				char **argv_;\
-				int brk_;\
-				if (argv[0][1] == '-' && argv[0][2] == '\0') {\
-					argv++;\
-					argc--;\
-					break;\
-				}\
-				for (brk_ = 0, argv[0]++, argv_ = argv;\
-						argv[0][0] && !brk_;\
-						argv[0]++) {\
-					if (argv_ != argv)\
-						break;\
-					argc_ = argv[0][0];\
-					switch (argc_)
-#define ARGEND			}\
-			}
-
-#define ARGC()		argc_
-
-#define EARGF(x)	((argv[0][1] == '\0' && argv[1] == NULL)?\
-				((x), abort(), (char *)0) :\
-				(brk_ = 1, (argv[0][1] != '\0')?\
-					(&argv[0][1]) :\
-					(argc--, argv++, argv[0])))
-
-#define ARGF()		((argv[0][1] == '\0' && argv[1] == NULL)?\
-				(char *)0 :\
-				(brk_ = 1, (argv[0][1] != '\0')?\
-					(&argv[0][1]) :\
-					(argc--, argv++, argv[0])))
-
-#endif
--- a/nlplug-findfs.c
+++ /dev/null
@@ -1,1433 +1,0 @@
-/*
- * Copy me if you can.
- * by 20h
- *
- * Copyright (c) 2015 Natanael Copa <ncopa@alpinelinux.org>
- * Copyright (c) 2016 Timo Teräs <timo.teras@iki.fi>
- */
-
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-
-#include <dirent.h>
-#include <err.h>
-#include <errno.h>
-#include <limits.h>
-#include <poll.h>
-#include <fnmatch.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <termios.h>
-#include <unistd.h>
-#include <stdint.h>
-
-#include <sys/eventfd.h>
-#include <sys/signalfd.h>
-#include <sys/mount.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/sysmacros.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <sys/wait.h>
-
-#include <linux/netlink.h>
-
-#include <libkmod.h>
-#include <blkid.h>
-#include <libcryptsetup.h>
-
-#include "arg.h"
-
-#define MAX_EVENT_TIMEOUT	5000
-#define DEFAULT_EVENT_TIMEOUT	250
-/* usb mass storage needs 1 sec to settle */
-#define USB_STORAGE_TIMEOUT	1000
-
-#define FOUND_DEVICE	0x1
-#define FOUND_BOOTREPO	0x2
-#define FOUND_APKOVL	0x4
-
-#define LVM_PATH	"/sbin/lvm"
-#define MDADM_PATH	"/sbin/mdadm"
-#define ZPOOL_PATH	"/usr/sbin/zpool"
-
-static int dodebug;
-static char *default_envp[2];
-char *argv0;
-static int use_mdadm, use_lvm, use_zpool;
-
-#if defined(DEBUG)
-#include <stdarg.h>
-static void dbg(const char *fmt, ...)
-{
-	va_list fmtargs;
-	if (!dodebug)
-		return;
-
-	flockfile(stderr);
-	fprintf(stderr, "%s: ", argv0);
-	va_start(fmtargs, fmt);
-	vfprintf(stderr, fmt, fmtargs);
-	va_end(fmtargs);
-	fprintf(stderr, "\n");
-	funlockfile(stderr);
-}
-#else
-#define dbg(...)
-#endif
-
-#define envcmp(env, key) (strncmp(env, key "=", strlen(key "=")) == 0)
-
-#ifndef container_of
-#define container_of(ptr, type, member) ({                      \
-        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
-        (type *)( (char *)__mptr - offsetof(type,member) );})
-#endif
-
-struct list_head {
-	struct list_head *next, *prev;
-};
-#define LIST_INITIALIZER(l) (struct list_head){ .next = &l, .prev = &l }
-
-static inline void list_init(struct list_head *list)
-{
-	list->next = list;
-	list->prev = list;
-}
-
-static inline void __list_add(struct list_head *new, struct list_head *prev,
-			      struct list_head *next)
-{
-	next->prev = new;
-	new->next = next;
-	new->prev = prev;
-	prev->next = new;
-}
-
-static inline void list_add(struct list_head *new, struct list_head *head)
-{
-	__list_add(new, head, head->next);
-}
-
-static inline void list_add_tail(struct list_head *new, struct list_head *head)
-{
-	__list_add(new, head->prev, head);
-}
-
-static inline void __list_del(struct list_head * prev, struct list_head * next)
-{
-	next->prev = prev;
-	prev->next = next;
-}
-
-static inline void list_del(struct list_head *entry)
-{
-	__list_del(entry->prev, entry->next);
-	entry->next = NULL;
-	entry->prev = NULL;
-}
-
-static inline int list_hashed(const struct list_head *n)
-{
-	return n->next != n && n->next != NULL;
-}
-
-static inline int list_empty(const struct list_head *n)
-{
-	return !list_hashed(n);
-}
-
-#define list_next(ptr, type, member) \
-	(list_hashed(ptr) ? container_of((ptr)->next,type,member) : NULL)
-
-#define list_entry(ptr, type, member) container_of(ptr,type,member)
-
-#define list_for_each_entry(pos, head, member)				\
-	for (pos = list_entry((head)->next, typeof(*pos), member);	\
-	     &pos->member != (head); 					\
-	     pos = list_entry(pos->member.next, typeof(*pos), member))
-
-static char **clone_array(char *const *const a)
-{
-	size_t i, s;
-	char **c, *p;
-
-	if (!a) return 0;
-
-	s = sizeof(char*);
-	for (i = 0; a[i]; i++)
-		s += sizeof(char*) + strlen(a[i]) + 1;
-	c = malloc(s);
-	p = (char*)(c + i + 1);
-	for (i = 0; a[i]; i++) {
-		c[i] = p;
-		p += sprintf(p, "%s", a[i]) + 1;
-	}
-	c[i] = 0;
-	return c;
-}
-
-struct spawn_task {
-	struct list_head node;
-	void (*done)(void *ctx, int status);
-	pid_t pid;
-	void *ctx;
-	char **argv, **envp;
-};
-
-#define SPAWNMGR_PID_HASH_SIZE 32
-struct spawn_manager {
-	int num_running;
-	int max_running;
-	struct list_head queue;
-	struct list_head running[SPAWNMGR_PID_HASH_SIZE];
-};
-
-static struct spawn_manager spawnmgr;
-
-static void dbgT(struct spawn_task *task, const char *fmt, ...)
-{
-#if defined(DEBUG)
-	va_list fmtargs;
-	int i;
-
-	if (!dodebug)
-		return;
-
-	flockfile(stderr);
-	fprintf(stderr, "%s: [%d] ", argv0, task->pid);
-	va_start(fmtargs, fmt);
-	vfprintf(stderr, fmt, fmtargs);
-	va_end(fmtargs);
-	for (i = 0; task->argv[i]; i++)
-		fprintf(stderr, " %s", task->argv[i]);
-	if (task->envp) {
-		fprintf(stderr, ":");
-		for (i = 1; task->envp[i]; i++)
-			fprintf(stderr, " %s", task->envp[i]);
-	}
-	fprintf(stderr, "\n");
-	funlockfile(stderr);
-#endif
-}
-
-static void spawn_init(struct spawn_manager *mgr)
-{
-	int i;
-
-	mgr->max_running = sysconf(_SC_NPROCESSORS_ONLN);
-	list_init(&mgr->queue);
-	for (i = 0; i < SPAWNMGR_PID_HASH_SIZE; i++)
-		list_init(&mgr->running[i]);
-
-	dbg("max_running=%d", mgr->max_running);
-}
-
-static void spawn_task_done(struct spawn_task *task, int status)
-{
-	if (task->done) task->done(task->ctx, status);
-	list_del(&task->node);
-	free(task->argv);
-	free(task->envp);
-	free(task);
-}
-
-static void spawn_execute(struct spawn_manager *mgr, struct spawn_task *task)
-{
-	pid_t pid;
-
-	if (!(pid = fork())) {
-		execve(task->argv[0], task->argv, task->envp ? task->envp : default_envp);
-		err(127, task->argv[0]);
-	}
-	if (pid < 0)
-		err(1, "fork");
-
-	task->pid = pid;
-	list_add_tail(&task->node, &mgr->running[pid % SPAWNMGR_PID_HASH_SIZE]);
-	mgr->num_running++;
-
-	dbgT(task, "spawned (%d running):", mgr->num_running);
-}
-
-static void spawn_command_cb(struct spawn_manager *mgr, char **argv, char **envp, void (*done)(void *, int), void *ctx)
-{
-	struct spawn_task *task;
-
-	task = malloc(sizeof *task);
-	if (!task) return;
-	*task = (struct spawn_task) {
-		.done = done,
-		.node = LIST_INITIALIZER(task->node),
-		.argv = clone_array(argv),
-		.envp = clone_array(envp),
-		.ctx  = ctx,
-	};
-
-	if (mgr->num_running < mgr->max_running)
-		spawn_execute(mgr, task);
-	else
-		list_add_tail(&task->node, &mgr->queue);
-}
-
-static void spawn_command(struct spawn_manager *mgr, char **argv, char **envp)
-{
-	spawn_command_cb(mgr, argv, envp, 0, 0);
-}
-
-static void spawn_reap(struct spawn_manager *mgr, pid_t pid, int status)
-{
-	struct spawn_task *task;
-
-	list_for_each_entry(task, &mgr->running[pid % SPAWNMGR_PID_HASH_SIZE], node) {
-		if (task->pid == pid)
-			goto found;
-	}
-	dbg("pid %d not found", pid);
-	return;
-
-found:
-	mgr->num_running--;
-	dbgT(task, "reaped (%d running):", mgr->num_running);
-	spawn_task_done(task, status);
-
-	if (!list_empty(&mgr->queue) && mgr->num_running < mgr->max_running) {
-		struct spawn_task *task = list_next(&mgr->queue, struct spawn_task, node);
-		list_del(&task->node);
-		spawn_execute(mgr, task);
-	}
-}
-
-static int spawn_active(struct spawn_manager *mgr)
-{
-	return mgr->num_running || !list_empty(&mgr->queue);
-}
-
-struct cryptdev {
-	char *device;
-	char *name;
-	char *key;
-	char devnode[256];
-};
-
-struct cryptconf {
-	struct cryptdev data;
-	struct cryptdev header;
-	size_t payload_offset;
-	pthread_t tid;
-	pthread_mutex_t mutex;
-	uint32_t flags;
-};
-
-struct ueventconf {
-	char **program_argv;
-	char *search_device;
-	blkid_cache blkid_cache;
-	struct cryptconf crypt;
-	char *subsystem_filter;
-	int modalias_count;
-	int fork_count;
-	char *bootrepos;
-	char *apkovls;
-	int timeout;
-	int usb_storage_timeout;
-	int uevent_timeout;
-	int efd;
-	int found;
-
-	int running_threads;
-
-	pthread_mutex_t trigger_mutex;
-	pthread_cond_t trigger_cond;
-	struct list_head trigger_list;
-
-	int cryptsetup_running;
-};
-
-struct uevent {
-	struct ueventconf *conf;
-	int ref;
-	size_t bufsize;
-	char *message;
-	char *subsystem;
-	char *action;
-	char *modalias;
-	char *devname;
-	char *devpath;
-	char *major;
-	char *minor;
-	char devnode[256];
-	char *envp[64];
-	char buf[];
-};
-
-static struct uevent *uevent_ref(struct uevent *ev)
-{
-	ev->ref++;
-	return ev;
-}
-
-static void uevent_unref(struct uevent *ev)
-{
-	ev->ref--;
-	if (ev->ref != 0) return;
-	free(ev);
-}
-
-static void sighandler(int sig)
-{
-	switch (sig) {
-	case SIGHUP:
-	case SIGINT:
-	case SIGQUIT:
-	case SIGABRT:
-	case SIGTERM:
-		exit(0);
-	default:
-		break;
-	}
-}
-
-static void initsignals(void)
-{
-	signal(SIGHUP, sighandler);
-	signal(SIGINT, sighandler);
-	signal(SIGQUIT, sighandler);
-	signal(SIGABRT, sighandler);
-	signal(SIGTERM, sighandler);
-	signal(SIGCHLD, 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 | SOCK_NONBLOCK | SOCK_CLOEXEC,
-		    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 = 512*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");
-
-	return fd;
-}
-
-static int load_kmod(const char *modalias, char *driver, size_t len)
-{
-	static struct kmod_ctx *ctx = NULL;
-	struct kmod_list *list = NULL;
-	struct kmod_list *node;
-	int r, count=0;
-
-	if (driver) driver[0] = 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: %d", modalias, r);
-		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));
-		if (driver) strlcpy(driver, kmod_module_get_name(mod), len);
-		kmod_module_unref(mod);
-	}
-	kmod_module_unref_list(list);
-	return count;
-}
-
-static void start_mdadm(char *devnode)
-{
-	char *mdadm_argv[] = {
-		MDADM_PATH,
-		"--incremental",
-		"--run",
-		"--quiet",
-		devnode,
-		NULL
-	};
-	if (use_mdadm)
-		spawn_command(&spawnmgr, mdadm_argv, 0);
-}
-
-static void start_lvm2(char *devnode)
-{
-	char *lvm2_argv[] = {
-		LVM_PATH, "vgchange",
-		"--activate" , "ay", "--noudevsync", "--sysinit", "-q", "-q",
-		NULL
-	};
-	if (use_lvm)
-		spawn_command(&spawnmgr, lvm2_argv, 0);
-}
-
-static void start_zpool(char *uuid) {
-	char *zpool_argv[] = {
-		ZPOOL_PATH, "import", "-N", uuid,
-		NULL
-	};
-	if (use_zpool && uuid)
-		spawn_command(&spawnmgr, zpool_argv, 0);
-}
-
-static int read_pass(char *pass, size_t pass_size)
-{
-	struct termios old_flags, new_flags;
-	int r;
-
-	tcgetattr(STDIN_FILENO, &old_flags);
-	new_flags = old_flags;
-	new_flags.c_lflag &= ~ECHO;
-	new_flags.c_lflag |= ECHONL;
-
-	if (isatty(STDIN_FILENO)) {
-		r = tcsetattr(STDIN_FILENO, TCSANOW, &new_flags);
-		if (r < 0) {
-			warn("tcsetattr");
-			return r;
-		}
-	}// else {
-	//	fprintf(stderr, "The program isn't executed in a TTY, the echo-disabling has been skipped.\n");
-	//}
-
-	if (fgets(pass, pass_size, stdin) == NULL) {
-		warn("fgets");
-		return -1;
-	}
-	pass[strlen(pass) - 1] = '\0';
-
-	if (isatty(STDIN_FILENO)) {
-		if (tcsetattr(STDIN_FILENO, TCSANOW, &old_flags) < 0) {
-			warn("tcsetattr");
-		}
-	}// else {
-	//	fprintf(stderr, "The program isn't executed in a TTY, the echo-reenabling has been skipped.\n");
-	//}
-
-	return 0;
-}
-
-static void notify_main(struct ueventconf *conf)
-{
-	uint64_t one = 1;
-	write(conf->efd, &one, sizeof one);
-}
-
-static void *cryptsetup_thread(void *data)
-{
-	struct ueventconf *c = (struct ueventconf *)data;
-	const char *data_devnode, *header_devnode;
-	struct crypt_params_luks1 param_struct;
-	struct crypt_params_luks1 *params = NULL;
-	struct crypt_device *cd;
-	int r, passwd_tries = 5;
-
-	data_devnode = header_devnode = c->crypt.data.devnode;
-
-	if(c->crypt.header.devnode[0] != '\0') {
-		params = &param_struct;
-		params->hash = NULL; /* No way of finding this */
-		params->data_alignment = c->crypt.payload_offset; /* Memset did set that to 0, so default is 0 */
-		params->data_device = c->crypt.data.devnode;
-		header_devnode = c->crypt.header.devnode;
-	}
-
-	r = crypt_init(&cd, header_devnode);
-	if (r < 0) {
-		warnx("crypt_init(%s)", header_devnode);
-		goto notify_out;
-	}
-
-	r = crypt_load(cd, CRYPT_LUKS, params);
-	if (r < 0) {
-		warnx("crypt_load(%s)", data_devnode);
-		goto free_out;
-	}
-
-	r = crypt_set_data_device(cd, data_devnode);
-	if (r < 0) {
-		warnx("crypt_set_data_device(%s)", data_devnode);
-		goto free_out;
-	}
-
-	struct stat st;
-	if (!stat(c->crypt.data.key, &st)) {
-		pthread_mutex_lock(&c->crypt.mutex);
-		r = crypt_activate_by_keyfile(cd, c->crypt.data.name,
-					      CRYPT_ANY_SLOT,
-					      c->crypt.data.key, st.st_size,
-					      c->crypt.flags);
-		pthread_mutex_unlock(&c->crypt.mutex);
-		if (r >= 0)
-			goto free_out;
-	}
-
-	while (passwd_tries > 0) {
-		char pass[1024];
-
-		printf("Enter passphrase for %s: ", c->crypt.data.devnode);
-		fflush(stdout);
-
-		if (read_pass(pass, sizeof(pass)) < 0)
-			goto free_out;
-		passwd_tries--;
-
-		pthread_mutex_lock(&c->crypt.mutex);
-		r = crypt_activate_by_passphrase(cd, c->crypt.data.name,
-						 CRYPT_ANY_SLOT,
-						 pass, strlen(pass),
-						 c->crypt.flags);
-		pthread_mutex_unlock(&c->crypt.mutex);
-		memset(pass, 0, sizeof(pass)); /* wipe pass after use */
-
-		if (r >= 0)
-			goto free_out;
-		printf("No key available with this passphrase.\n");
-	}
-	printf("Mounting %s failed, amount of tries exhausted.\n", c->crypt.data.devnode);
-
-free_out:
-	crypt_free(cd);
-notify_out:
-	c->cryptsetup_running = 0;
-	notify_main(c);
-	return NULL;
-}
-
-static void start_thread(struct ueventconf *conf, void *(*thread_main)(void *))
-{
-	pthread_t tid;
-	pthread_attr_t attr;
-
-	pthread_attr_init(&attr);
-	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-	if (pthread_create(&tid, &attr, thread_main, conf) != 0)
-		err(1, "failed to create thread");
-	pthread_attr_destroy(&attr);
-}
-
-static void start_cryptsetup(struct ueventconf *conf)
-{
-	if(conf->crypt.header.devnode[0] != '\0') {
-		dbg("starting cryptsetup %s -> %s (header: %s)",
-		    conf->crypt.data.devnode, conf->crypt.data.name,
-		    conf->crypt.header.devnode);
-	} else {
-		dbg("starting cryptsetup %s -> %s", conf->crypt.data.devnode,
-		    conf->crypt.data.name);
-	}
-	load_kmod("dm-crypt", NULL, 0);
-	conf->cryptsetup_running = 1;
-	conf->running_threads = 1;
-	start_thread(conf, cryptsetup_thread);
-}
-
-static int is_mounted(const char *devnode) {
-	char line[PATH_MAX];
-	FILE *f = fopen("/proc/mounts", "r");
-	int r = 0;
-	if (f == NULL)
-		return 0;
-	while (fgets(line, sizeof(line), f) != NULL) {
-		strtok(line, " ");
-		if (strcmp(devnode, line) == 0) {
-			r = 1;
-			break;
-		}
-	}
-	fclose(f);
-	return r;
-}
-
-struct recurse_opts {
-	size_t pathlen;
-	char path[PATH_MAX], *filename;
-	int is_dir;
-	int matchdepth;
-	int curdepth, maxdepth;
-	void (*callback)(struct recurse_opts *opts, void *userdata);
-	void *userdata;
-};
-
-static int recurse_push(struct recurse_opts *opts, size_t *oldlen, const char *path)
-{
-	size_t pathlen = strlen(path);
-	if (opts->pathlen + 1 + pathlen + 1 >= sizeof opts->path)
-		return 0;
-	*oldlen = opts->pathlen;
-	opts->path[opts->pathlen++] = '/';
-	strcpy(&opts->path[opts->pathlen], path);
-	opts->pathlen += strlen(path);
-	return 1;
-}
-
-static void recurse_pop(struct recurse_opts *opts, size_t len)
-{
-	opts->pathlen = len;
-	opts->path[len] = 0;
-}
-
-static void do_recurse_dir(struct recurse_opts *opts)
-{
-	size_t oldlen;
-	struct dirent *entry;
-	DIR *d;
-	int is_dir;
-
-	d = opendir(opts->path);
-	if (!d) return;
-
-	while ((entry = readdir(d)) != NULL) {
-		if (strcmp(entry->d_name, ".") == 0 ||
-		    strcmp(entry->d_name, "..") == 0)
-			continue;
-
-		if (!recurse_push(opts, &oldlen, entry->d_name))
-			continue;
-
-		if (entry->d_type == DT_UNKNOWN) {
-			/* some filesystems like iso9660 does not support
-			   the d_type so we use lstat */
-			struct stat st;
-			if (lstat(opts->path, &st) < 0) {
-				dbg("%s: %s", opts->path, strerror(errno));
-				goto next;
-			}
-			is_dir = S_ISDIR(st.st_mode);
-		} else
-			is_dir = entry->d_type & DT_DIR;
-
-		if (opts->matchdepth == 0 || opts->matchdepth == opts->curdepth) {
-			opts->filename = &opts->path[oldlen+1];
-			opts->is_dir = is_dir;
-			opts->callback(opts, opts->userdata);
-		}
-
-		if (is_dir && opts->curdepth < opts->maxdepth) {
-			opts->curdepth++;
-			do_recurse_dir(opts);
-			opts->curdepth--;
-		}
-next:
-		recurse_pop(opts, oldlen);
-	}
-	closedir(d);
-}
-
-static void recurse_dir(struct recurse_opts *opts)
-{
-	opts->pathlen = strlen(opts->path);
-	opts->curdepth = 1;
-	do_recurse_dir(opts);
-}
-
-struct trigger_entry {
-	struct list_head node;
-	int max_depth;
-	char pathname[];
-};
-
-static void trigger_uevent_cb(struct recurse_opts *opts, void *data)
-{
-	size_t oldlen;
-	int fd;
-
-	if (!recurse_push(opts, &oldlen, "uevent"))
-		return;
-
-	fd = open(opts->path, O_WRONLY | O_CLOEXEC);
-	if (fd >= 0) {
-		write(fd, "add", 3);
-		close(fd);
-	}
-	recurse_pop(opts, oldlen);
-}
-
-static void trigger_path(struct ueventconf *conf, char *path, char *subdir, int max_depth)
-{
-	struct trigger_entry *e;
-	size_t pathlen = strlen(path);
-
-	e = malloc(pathlen + (subdir ? strlen(subdir) : 0) + 1 + sizeof *e);
-	if (!e) return;
-
-	list_init(&e->node);
-	e->max_depth = max_depth;
-	strcpy(e->pathname, path);
-	if (subdir) strcpy(&e->pathname[pathlen], subdir);
-
-	pthread_mutex_lock(&conf->trigger_mutex);
-	conf->running_threads = 1;
-	list_add_tail(&e->node, &conf->trigger_list);
-	pthread_cond_signal(&conf->trigger_cond);
-	pthread_mutex_unlock(&conf->trigger_mutex);
-}
-
-static void *trigger_thread(void *data)
-{
-	struct ueventconf *conf = data;
-	struct recurse_opts opts;
-	struct trigger_entry *entry = NULL;
-
-	while (1) {
-		pthread_mutex_lock(&conf->trigger_mutex);
-		if (entry) {
-			list_del(&entry->node);
-			free(entry);
-		}
-		entry = list_next(&conf->trigger_list, struct trigger_entry, node);
-		while (!entry) {
-			notify_main(conf);
-			pthread_cond_wait(&conf->trigger_cond, &conf->trigger_mutex);
-			entry = list_next(&conf->trigger_list, struct trigger_entry, node);
-		}
-		pthread_mutex_unlock(&conf->trigger_mutex);
-
-		opts = (struct recurse_opts) {
-			.callback = trigger_uevent_cb,
-			.userdata = entry,
-			.maxdepth = entry->max_depth,
-			.matchdepth = entry->max_depth,
-		};
-		snprintf(opts.path, sizeof opts.path, "/sys%s", entry->pathname);
-		dbg("trigger_thread: scanning %s", opts.path);
-
-		recurse_dir(&opts);
-	}
-
-	return NULL;
-}
-
-static void append_line(const char *outfile, const char *data)
-{
-	int fd;
-	if (outfile == 0) return;
-	fd = open(outfile, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC);
-	if (fd == -1)
-		err(1, "%s", outfile);
-	write(fd, data, strlen(data));
-	write(fd, "\n", 1);
-	close(fd);
-}
-
-struct scandevctx {
-	struct ueventconf *conf;
-	int found;
-};
-
-static void scandev_cb(struct recurse_opts *opts, void *data)
-{
-	struct scandevctx *ctx = data;
-	struct ueventconf *conf = ctx->conf;
-
-	if (opts->is_dir) {
-		size_t oldlen;
-		int ok = 0;
-		if (recurse_push(opts, &oldlen, ".boot_repository")) {
-			ok = access(opts->path, F_OK) == 0;
-			recurse_pop(opts, oldlen);
-		}
-		if (ok) {
-			dbg("added boot repository %s to %s", opts->path, conf->bootrepos);
-			append_line(conf->bootrepos, opts->path);
-			ctx->found |= FOUND_BOOTREPO;
-		}
-	} else if (fnmatch("*.apkovl.tar.gz*", opts->filename, 0) == 0) {
-		dbg("found apkovl %s", opts->path);
-		append_line(conf->apkovls, opts->path);
-		ctx->found |= FOUND_APKOVL;
-	}
-}
-
-static int scandev(struct ueventconf *conf, const char *devnode, const char *type)
-{
-	struct scandevctx ctx = {
-		.conf = conf,
-	};
-	struct recurse_opts opts = {
-		.maxdepth = 1,
-		.callback = scandev_cb,
-		.userdata = &ctx,
-	};
-	char *devname;
-	int r;
-
-	/* skip already mounted devices */
-	if (is_mounted(devnode)) {
-		dbg("%s is mounted (%s). skipping", devnode, type);
-		return 0;
-	}
-	devname = strrchr(devnode, '/');
-	if (!devname)
-		return 0;
-
-	snprintf(opts.path, sizeof opts.path, "/media%s", devname);
-	dbg("mounting %s on %s (%s)", devnode, opts.path, type);
-	mkdir(opts.path, 0755);
-
-	r = mount(devnode, opts.path, type, MS_RDONLY, NULL);
-	if (r < 0) {
-		dbg("Failed to mount %s on %s: %s",
-		    devnode, opts.path, strerror(errno));
-		return 0;
-	}
-
-	recurse_dir(&opts);
-
-	if (ctx.found == 0)
-		umount(opts.path);
-
-	return ctx.found;
-}
-
-static int is_same_device(const struct uevent *ev, const char *nodepath)
-{
-	struct stat st;
-	unsigned int maj, min;
-	if (stat(nodepath, &st) < 0)
-		return 0;
-
-	if (ev->major == NULL || ev->minor == NULL)
-		return 0;
-
-	maj = atoi(ev->major);
-	min = atoi(ev->minor);
-	return S_ISBLK(st.st_mode) && makedev(maj, min) == st.st_rdev;
-}
-
-static void founddev(struct ueventconf *conf, int found)
-{
-	conf->found |= found;
-	if ((found & FOUND_DEVICE)
-	    || ((found & FOUND_BOOTREPO) &&
-		(found & FOUND_APKOVL))) {
-		/* we have found everything we need, so no
-		   no need to wait for anything new event */
-		if (conf->timeout)
-			dbg("FOUND! setting timeout to 0");
-		conf->timeout = 0;
-		conf->usb_storage_timeout= 0;
-	} else if ((found & FOUND_BOOTREPO) && conf->timeout) {
-		/* we have found boot repo, but not apkovl
-		   we reduce timeout to default timeout */
-		if (conf->timeout != conf->uevent_timeout)
-			dbg("Setting timeout to %d", conf->uevent_timeout);
-		conf->timeout = conf->uevent_timeout;
-	}
-}
-
-static int is_zfs_pool(const char *path, const char *label)
-{
-	char pool_name[256];
-	char *p;
-	snprintf(pool_name, sizeof(pool_name), "%s", path);
-	if ((p = strchr(pool_name, '/')))
-		*p = '\0';
-	return strcmp(label, pool_name) == 0 ? FOUND_DEVICE : 0;
-}
-
-static int searchdev(struct uevent *ev, const char *searchdev, int scanbootmedia)
-{
-	struct ueventconf *conf = ev->conf;
-	char *type = NULL, *label = NULL, *uuid = NULL;
-	int rc = 0;
-
-	if (searchdev == NULL && !scanbootmedia)
-		return 0;
-
-	if (searchdev && (strcmp(ev->devname, searchdev) == 0
-			  || strcmp(ev->devnode, searchdev) == 0
-	                  || is_same_device(ev, searchdev))) {
-		return FOUND_DEVICE;
-	}
-
-	if (conf->blkid_cache == NULL)
-		blkid_get_cache(&conf->blkid_cache, NULL);
-
-	type = blkid_get_tag_value(conf->blkid_cache, "TYPE", ev->devnode);
-	uuid = blkid_get_tag_value(conf->blkid_cache, "UUID", ev->devnode);
-	label = blkid_get_tag_value(conf->blkid_cache, "LABEL", ev->devnode);
-
-	if (searchdev != NULL) {
-		if (strncmp("LABEL=", searchdev, 6) == 0) {
-			if (label && strcmp(label, searchdev+6) == 0)
-				rc = FOUND_DEVICE;
-		} else if (strncmp("UUID=", searchdev, 5) == 0) {
-			if (uuid && strcmp(uuid, searchdev+5) == 0)
-				rc = FOUND_DEVICE;
-		}
-	}
-
-	dbg("searchdev: dev='%s' type='%s' label='%s' uuid='%s'",
-		ev->devnode, type, label, uuid);
-
-	if (!rc && type) {
-		if (strcmp("linux_raid_member", type) == 0) {
-			start_mdadm(ev->devnode);
-		} else if (strcmp("LVM2_member", type) == 0) {
-			start_lvm2(ev->devnode);
-		} else if (strcmp("zfs_member", type) == 0) {
-			start_zpool(uuid);
-			if (searchdev != NULL && label != NULL
-			    && strncmp("ZFS=", searchdev, 4) == 0) {
-				rc = is_zfs_pool(&searchdev[4], label);
-			}
-		} else if (scanbootmedia) {
-			rc = scandev(conf, ev->devnode, type);
-		}
-	}
-
-	if (type)
-		free(type);
-	if (label)
-		free(label);
-	if (uuid)
-		free(uuid);
-
-	return rc;
-}
-
-/* search for crypt.data and crypt.header.
-   returns true if we are ready to start cryptsetup. */
-static int search_cryptdevs(struct uevent *ev, struct cryptconf *crypt)
-{
-	if (crypt->data.devnode[0] == '\0' && searchdev(ev, crypt->data.device, 0)) {
-		strncpy(crypt->data.devnode,
-			crypt->data.device[0] == '/' ? crypt->data.device : ev->devnode,
-			sizeof(crypt->data.devnode));
-		/* if we don't have header or header is found, then we are
-		   ready to start crypsetup */
-		return (crypt->header.device == NULL)
-			|| (crypt->header.devnode[0] != '\0');
-	}
-
-	if (crypt->header.device == NULL)
-		return 0;
-
-	if (crypt->header.devnode[0] == '\0' && searchdev(ev, crypt->header.device, 0)) {
-		strncpy(crypt->header.devnode,
-			crypt->header.device[0] == '/' ? crypt->header.device : ev->devnode,
-			sizeof(crypt->header.devnode));
-		/* if we also have found data dev, then we are ready to
-		   start cryptsetup */
-		return crypt->data.devnode[0] != '\0';
-	}
-	return 0;
-}
-
-static void uevent_handle(struct uevent *ev)
-{
-	struct ueventconf *conf = ev->conf;
-	int found;
-
-	if (!ev->subsystem || strcmp(ev->subsystem, "block") != 0)
-		return;
-
-	if (strcmp(ev->action, "add") != 0 &&
-	    strcmp(ev->action, "change") != 0)
-		return;
-
-	snprintf(ev->devnode, sizeof(ev->devnode), "/dev/%s", ev->devname);
-	pthread_mutex_lock(&conf->crypt.mutex);
-	found = searchdev(ev, conf->search_device, (conf->apkovls || conf->bootrepos));
-	pthread_mutex_unlock(&conf->crypt.mutex);
-	if (found) {
-		founddev(conf, found);
-	} else if (search_cryptdevs(ev, &conf->crypt)) {
-		start_cryptsetup(conf);
-	}
-}
-
-static void uevent_mdev_done_cb(void *ctx, int status)
-{
-	struct uevent *ev = ctx;
-	uevent_handle(ev);
-	uevent_unref(ev);
-}
-
-static void uevent_dispatch(struct uevent *ev)
-{
-	struct ueventconf *conf = ev->conf;
-	int add;
-
-	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;
-	}
-
-	if (ev->action == NULL)
-		return;
-
-	dbg("uevent: action='%s' subsystem='%s' devname='%s' devpath='%s'",
-		ev->action, ev->subsystem, ev->devname, ev->devpath);
-
-	add = strcmp(ev->action, "add") == 0;
-
-	if (add && ev->subsystem && strcmp(ev->subsystem, "bus") == 0) {
-		trigger_path(conf, ev->devpath, "/devices", 1);
-	} else if (add && ev->modalias) {
-		char buf[128];
-		load_kmod(ev->modalias, buf, sizeof buf);
-		conf->modalias_count++;
-		/* increase timeout so usb drives gets time to settle */
-		if (strcmp(buf, "usb_storage") == 0)
-			conf->usb_storage_timeout = USB_STORAGE_TIMEOUT;
-
-	} else if (ev->devname) {
-		if (conf->program_argv[0] != NULL) {
-			spawn_command_cb(&spawnmgr, conf->program_argv, ev->envp,
-					 uevent_mdev_done_cb, uevent_ref(ev));
-			conf->fork_count++;
-		} else {
-			uevent_handle(ev);
-		}
-	}
-}
-
-static void uevent_process(char *buf, const size_t len, struct ueventconf *conf)
-{
-	struct uevent *ev;
-	int i, nenvp, slen = 0;
-	char *key, *value;
-
-	ev = malloc(len + sizeof *ev);
-	if (!ev) return;
-
-	memset(ev, 0, sizeof *ev);
-	memcpy(ev->buf, buf, len);
-	ev->ref = 1;
-	ev->conf = conf;
-	ev->bufsize = len;
-
-	nenvp = sizeof(default_envp) / sizeof(default_envp[0]) - 1;
-	memcpy(&ev->envp, default_envp, nenvp * sizeof(default_envp[0]));
-
-	for (i = 0; i < len; i += slen + 1) {
-		key = ev->buf + i;
-		value = strchr(key, '=');
-		slen = strlen(ev->buf + i);
-
-		if (i == 0 && slen != 0) {
-			/* first line, the message */
-			ev->message = key;
-			continue;
-		}
-
-		if (!slen || !value)
-			continue;
-
-		value++;
-		if (envcmp(key, "MODALIAS")) {
-			ev->modalias = value;
-		} else if (envcmp(key, "ACTION")) {
-			ev->action = value;
-		} else if (envcmp(key, "SUBSYSTEM")) {
-			ev->subsystem = value;
-		} else if (envcmp(key, "DEVNAME")) {
-			ev->devname = value;
-		} else if (envcmp(key, "DEVPATH")) {
-			ev->devpath = value;
-		} else if (envcmp(key, "MAJOR")) {
-			ev->major = value;
-		} else if (envcmp(key, "MINOR")) {
-			ev->minor = value;
-		}
-
-		if (!envcmp(key, "PATH"))
-			ev->envp[nenvp++]= key;
-	}
-	ev->envp[nenvp++] = 0;
-
-	uevent_dispatch(ev);
-	uevent_unref(ev);
-}
-
-static void usage(int rc)
-{
-	printf("coldplug system til given device is found\n"
-	"usage: %s [options] DEVICE\n"
-	"\n"
-	"options:\n"
-	" -a OUTFILE      add paths to found apkovls to OUTFILE\n"
-	" -b OUTFILE      add found boot repositories to OUTFILE\n"
-	" -c CRYPTDEVICE  run cryptsetup luksOpen when CRYPTDEVICE is found\n"
-	" -h              show this help\n"
-	" -H HEADERDEVICE use HEADERDEVICE as the LUKS header\n"
-	" -k CRYPTKEY     path to keyfile\n"
-	" -m CRYPTNAME    use CRYPTNAME name for crypto device mapping\n"
-	" -o OFFSET       cryptsetup payload offset\n"
-	" -D              allow discards on crypto device\n"
-	" -d              enable debugging ouput\n"
-	" -f SUBSYSTEM    filter subsystem\n"
-	" -p PROGRAM      use PROGRAM as handler for every event with DEVNAME\n"
-	" -t TIMEOUT      timeout after TIMEOUT milliseconds without uevents\n"
-	"\n", argv0);
-
-	exit(rc);
-}
-
-static int regular_file(const char *path)
-{
-	struct stat st;
-	int r = stat(path, &st);
-	return r == -1 ? 0 : S_ISREG(st.st_mode);
-}
-
-int main(int argc, char *argv[])
-{
-	struct pollfd fds[3];
-	int numfds = 3;
-	int r;
-	struct ueventconf conf;
-	int event_count = 0;
-	size_t total_bytes = 0;
-	int not_found_is_ok = 0;
-	char *program_argv[2] = {0,0};
-	sigset_t sigchldmask;
-
-	for (r = 0; environ[r]; r++) {
-		if (envcmp(environ[r], "PATH"))
-			default_envp[0] = environ[r];
-	}
-
-	spawn_init(&spawnmgr);
-
-	memset(&conf, 0, sizeof(conf));
-	pthread_mutex_init(&conf.trigger_mutex, NULL);
-	pthread_cond_init(&conf.trigger_cond, NULL);
-	list_init(&conf.trigger_list);
-	pthread_mutex_init(&conf.crypt.mutex, NULL);
-
-	conf.program_argv = program_argv;
-	conf.timeout = MAX_EVENT_TIMEOUT;
-	conf.usb_storage_timeout = 0;
-	conf.uevent_timeout = DEFAULT_EVENT_TIMEOUT;
-	use_lvm = access(LVM_PATH, X_OK) == 0;
-	use_mdadm = access(MDADM_PATH, X_OK) == 0;
-	use_zpool = access(ZPOOL_PATH, X_OK) == 0;
-
-	argv0 = strrchr(argv[0], '/');
-	if (argv0++ == NULL)
-		argv0 = argv[0];
-
-	ARGBEGIN {
-	case 'a':
-		conf.apkovls = EARGF(usage(1));;
-		break;
-	case 'b':
-		conf.bootrepos = EARGF(usage(1));
-		break;
-	case 'c':
-		conf.crypt.data.device = EARGF(usage(1));
-		break;
-	case 'H':
-		conf.crypt.header.device = EARGF(usage(1));
-		/* the header may be in a regular file and not a device */
-		if (regular_file(conf.crypt.header.device)) {
-			snprintf(conf.crypt.header.devnode,
-				sizeof(conf.crypt.header.devnode),
-				"%s", conf.crypt.header.device);
-		}
-		break;
-	case 'h':
-		usage(0);
-		break;
-	case 'k':
-		conf.crypt.data.key = EARGF(usage(1));
-		break;
-	case 'm':
-		conf.crypt.data.name = EARGF(usage(1));
-		break;
-	case 'n':
-		not_found_is_ok = 1;
-		break;
-	case 'D':
-		conf.crypt.flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
-		break;
-	case 'd':
-		dodebug = 1;
-		break;
-	case 'f':
-		conf.subsystem_filter = EARGF(usage(1));
-		break;
-	case 'o':
-		if(sscanf(EARGF(usage(1)), "%zu", &conf.crypt.payload_offset) != 1)
-			err(1, "sscanf");
-		break;
-	case 'p':
-		conf.program_argv[0] = EARGF(usage(1));
-		break;
-	case 't':
-		conf.uevent_timeout = atoi(EARGF(usage(1)));
-		break;
-	default:
-		usage(1);
-	} ARGEND;
-
-	if (argc > 0)
-		conf.search_device = argv[0];
-
-	initsignals();
-	sigemptyset(&sigchldmask);
-	sigaddset(&sigchldmask, SIGCHLD);
-	sigprocmask(SIG_BLOCK, &sigchldmask, NULL);
-
-	fds[0].fd = init_netlink_socket();
-	fds[0].events = POLLIN;
-
-	fds[1].fd = signalfd(-1, &sigchldmask, SFD_NONBLOCK|SFD_CLOEXEC);
-	fds[1].events = POLLIN;
-
-	fds[2].fd = eventfd(0, EFD_CLOEXEC);
-	fds[2].events = POLLIN;
-	conf.efd = fds[2].fd;
-
-	trigger_path(&conf, "/bus", NULL, 1);
-	trigger_path(&conf, "/class", NULL, 2);
-	start_thread(&conf, trigger_thread);
-
-	while (1) {
-		int t = conf.timeout + conf.usb_storage_timeout;
-		r = poll(fds, numfds, (spawn_active(&spawnmgr) || conf.running_threads) ? -1 : t);
-		if (r == -1) {
-			if (errno == EINTR || errno == ERESTART)
-				continue;
-			err(1, "poll");
-		}
-		if (r == 0) {
-			dbg("exit due to timeout (%i)", t);
-			break;
-		}
-
-		if (fds[0].revents & POLLIN) {
-			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;
-
-			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[0].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++;
-			uevent_process(buf, len, &conf);
-		}
-
-		if (fds[0].revents & POLLHUP) {
-			dbg("parent hung up\n");
-			break;
-		}
-
-		if (fds[1].revents & POLLIN) {
-			struct signalfd_siginfo fdsi;
-			pid_t pid;
-			int status;
-
-			while (read(fds[1].fd, &fdsi, sizeof fdsi) > 0)
-				;
-			while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
-				spawn_reap(&spawnmgr, pid, status);
-		}
-
-		if (fds[2].revents & POLLIN) {
-			uint64_t val = 0;
-			if (read(fds[2].fd, &val, sizeof(val)) < 0)
-				warn("eventfd");
-			pthread_mutex_lock(&conf.trigger_mutex);
-			conf.running_threads = !list_empty(&conf.trigger_list)
-				|| conf.cryptsetup_running;
-			pthread_mutex_unlock(&conf.trigger_mutex);
-		}
-	}
-	close(fds[2].fd);
-
-	pthread_mutex_destroy(&conf.crypt.mutex);
-	pthread_mutex_destroy(&conf.trigger_mutex);
-	pthread_cond_destroy(&conf.trigger_cond);
-	if (conf.blkid_cache) blkid_put_cache(conf.blkid_cache);
-
-	dbg("modaliases: %i, forks: %i, events: %i, total bufsize: %zu",
-		conf.modalias_count,
-		conf.fork_count,
-		event_count, total_bytes);
-
-	return conf.found || not_found_is_ok ? 0 : 1;
-}
--- /dev/null
+++ b/nlplug-findfs/arg.h
@@ -1,0 +1,48 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN	for (argv0 = *argv, argv++, argc--;\
+					argv[0] && argv[0][0] == '-'\
+					&& argv[0][1];\
+					argc--, argv++) {\
+				char argc_;\
+				char **argv_;\
+				int brk_;\
+				if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+					argv++;\
+					argc--;\
+					break;\
+				}\
+				for (brk_ = 0, argv[0]++, argv_ = argv;\
+						argv[0][0] && !brk_;\
+						argv[0]++) {\
+					if (argv_ != argv)\
+						break;\
+					argc_ = argv[0][0];\
+					switch (argc_)
+#define ARGEND			}\
+			}
+
+#define ARGC()		argc_
+
+#define EARGF(x)	((argv[0][1] == '\0' && argv[1] == NULL)?\
+				((x), abort(), (char *)0) :\
+				(brk_ = 1, (argv[0][1] != '\0')?\
+					(&argv[0][1]) :\
+					(argc--, argv++, argv[0])))
+
+#define ARGF()		((argv[0][1] == '\0' && argv[1] == NULL)?\
+				(char *)0 :\
+				(brk_ = 1, (argv[0][1] != '\0')?\
+					(&argv[0][1]) :\
+					(argc--, argv++, argv[0])))
+
+#endif
--- /dev/null
+++ b/nlplug-findfs/nlplug-findfs.c
@@ -1,0 +1,1433 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ *
+ * Copyright (c) 2015 Natanael Copa <ncopa@alpinelinux.org>
+ * Copyright (c) 2016 Timo Teräs <timo.teras@iki.fi>
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <poll.h>
+#include <fnmatch.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#include <sys/eventfd.h>
+#include <sys/signalfd.h>
+#include <sys/mount.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <linux/netlink.h>
+
+#include <libkmod.h>
+#include <blkid.h>
+#include <libcryptsetup.h>
+
+#include "arg.h"
+
+#define MAX_EVENT_TIMEOUT	5000
+#define DEFAULT_EVENT_TIMEOUT	250
+/* usb mass storage needs 1 sec to settle */
+#define USB_STORAGE_TIMEOUT	1000
+
+#define FOUND_DEVICE	0x1
+#define FOUND_BOOTREPO	0x2
+#define FOUND_APKOVL	0x4
+
+#define LVM_PATH	"/sbin/lvm"
+#define MDADM_PATH	"/sbin/mdadm"
+#define ZPOOL_PATH	"/usr/sbin/zpool"
+
+static int dodebug;
+static char *default_envp[2];
+char *argv0;
+static int use_mdadm, use_lvm, use_zpool;
+
+#if defined(DEBUG)
+#include <stdarg.h>
+static void dbg(const char *fmt, ...)
+{
+	va_list fmtargs;
+	if (!dodebug)
+		return;
+
+	flockfile(stderr);
+	fprintf(stderr, "%s: ", argv0);
+	va_start(fmtargs, fmt);
+	vfprintf(stderr, fmt, fmtargs);
+	va_end(fmtargs);
+	fprintf(stderr, "\n");
+	funlockfile(stderr);
+}
+#else
+#define dbg(...)
+#endif
+
+#define envcmp(env, key) (strncmp(env, key "=", strlen(key "=")) == 0)
+
+#ifndef container_of
+#define container_of(ptr, type, member) ({                      \
+        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
+        (type *)( (char *)__mptr - offsetof(type,member) );})
+#endif
+
+struct list_head {
+	struct list_head *next, *prev;
+};
+#define LIST_INITIALIZER(l) (struct list_head){ .next = &l, .prev = &l }
+
+static inline void list_init(struct list_head *list)
+{
+	list->next = list;
+	list->prev = list;
+}
+
+static inline void __list_add(struct list_head *new, struct list_head *prev,
+			      struct list_head *next)
+{
+	next->prev = new;
+	new->next = next;
+	new->prev = prev;
+	prev->next = new;
+}
+
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head, head->next);
+}
+
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+	__list_add(new, head->prev, head);
+}
+
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+	next->prev = prev;
+	prev->next = next;
+}
+
+static inline void list_del(struct list_head *entry)
+{
+	__list_del(entry->prev, entry->next);
+	entry->next = NULL;
+	entry->prev = NULL;
+}
+
+static inline int list_hashed(const struct list_head *n)
+{
+	return n->next != n && n->next != NULL;
+}
+
+static inline int list_empty(const struct list_head *n)
+{
+	return !list_hashed(n);
+}
+
+#define list_next(ptr, type, member) \
+	(list_hashed(ptr) ? container_of((ptr)->next,type,member) : NULL)
+
+#define list_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define list_for_each_entry(pos, head, member)				\
+	for (pos = list_entry((head)->next, typeof(*pos), member);	\
+	     &pos->member != (head); 					\
+	     pos = list_entry(pos->member.next, typeof(*pos), member))
+
+static char **clone_array(char *const *const a)
+{
+	size_t i, s;
+	char **c, *p;
+
+	if (!a) return 0;
+
+	s = sizeof(char*);
+	for (i = 0; a[i]; i++)
+		s += sizeof(char*) + strlen(a[i]) + 1;
+	c = malloc(s);
+	p = (char*)(c + i + 1);
+	for (i = 0; a[i]; i++) {
+		c[i] = p;
+		p += sprintf(p, "%s", a[i]) + 1;
+	}
+	c[i] = 0;
+	return c;
+}
+
+struct spawn_task {
+	struct list_head node;
+	void (*done)(void *ctx, int status);
+	pid_t pid;
+	void *ctx;
+	char **argv, **envp;
+};
+
+#define SPAWNMGR_PID_HASH_SIZE 32
+struct spawn_manager {
+	int num_running;
+	int max_running;
+	struct list_head queue;
+	struct list_head running[SPAWNMGR_PID_HASH_SIZE];
+};
+
+static struct spawn_manager spawnmgr;
+
+static void dbgT(struct spawn_task *task, const char *fmt, ...)
+{
+#if defined(DEBUG)
+	va_list fmtargs;
+	int i;
+
+	if (!dodebug)
+		return;
+
+	flockfile(stderr);
+	fprintf(stderr, "%s: [%d] ", argv0, task->pid);
+	va_start(fmtargs, fmt);
+	vfprintf(stderr, fmt, fmtargs);
+	va_end(fmtargs);
+	for (i = 0; task->argv[i]; i++)
+		fprintf(stderr, " %s", task->argv[i]);
+	if (task->envp) {
+		fprintf(stderr, ":");
+		for (i = 1; task->envp[i]; i++)
+			fprintf(stderr, " %s", task->envp[i]);
+	}
+	fprintf(stderr, "\n");
+	funlockfile(stderr);
+#endif
+}
+
+static void spawn_init(struct spawn_manager *mgr)
+{
+	int i;
+
+	mgr->max_running = sysconf(_SC_NPROCESSORS_ONLN);
+	list_init(&mgr->queue);
+	for (i = 0; i < SPAWNMGR_PID_HASH_SIZE; i++)
+		list_init(&mgr->running[i]);
+
+	dbg("max_running=%d", mgr->max_running);
+}
+
+static void spawn_task_done(struct spawn_task *task, int status)
+{
+	if (task->done) task->done(task->ctx, status);
+	list_del(&task->node);
+	free(task->argv);
+	free(task->envp);
+	free(task);
+}
+
+static void spawn_execute(struct spawn_manager *mgr, struct spawn_task *task)
+{
+	pid_t pid;
+
+	if (!(pid = fork())) {
+		execve(task->argv[0], task->argv, task->envp ? task->envp : default_envp);
+		err(127, task->argv[0]);
+	}
+	if (pid < 0)
+		err(1, "fork");
+
+	task->pid = pid;
+	list_add_tail(&task->node, &mgr->running[pid % SPAWNMGR_PID_HASH_SIZE]);
+	mgr->num_running++;
+
+	dbgT(task, "spawned (%d running):", mgr->num_running);
+}
+
+static void spawn_command_cb(struct spawn_manager *mgr, char **argv, char **envp, void (*done)(void *, int), void *ctx)
+{
+	struct spawn_task *task;
+
+	task = malloc(sizeof *task);
+	if (!task) return;
+	*task = (struct spawn_task) {
+		.done = done,
+		.node = LIST_INITIALIZER(task->node),
+		.argv = clone_array(argv),
+		.envp = clone_array(envp),
+		.ctx  = ctx,
+	};
+
+	if (mgr->num_running < mgr->max_running)
+		spawn_execute(mgr, task);
+	else
+		list_add_tail(&task->node, &mgr->queue);
+}
+
+static void spawn_command(struct spawn_manager *mgr, char **argv, char **envp)
+{
+	spawn_command_cb(mgr, argv, envp, 0, 0);
+}
+
+static void spawn_reap(struct spawn_manager *mgr, pid_t pid, int status)
+{
+	struct spawn_task *task;
+
+	list_for_each_entry(task, &mgr->running[pid % SPAWNMGR_PID_HASH_SIZE], node) {
+		if (task->pid == pid)
+			goto found;
+	}
+	dbg("pid %d not found", pid);
+	return;
+
+found:
+	mgr->num_running--;
+	dbgT(task, "reaped (%d running):", mgr->num_running);
+	spawn_task_done(task, status);
+
+	if (!list_empty(&mgr->queue) && mgr->num_running < mgr->max_running) {
+		struct spawn_task *task = list_next(&mgr->queue, struct spawn_task, node);
+		list_del(&task->node);
+		spawn_execute(mgr, task);
+	}
+}
+
+static int spawn_active(struct spawn_manager *mgr)
+{
+	return mgr->num_running || !list_empty(&mgr->queue);
+}
+
+struct cryptdev {
+	char *device;
+	char *name;
+	char *key;
+	char devnode[256];
+};
+
+struct cryptconf {
+	struct cryptdev data;
+	struct cryptdev header;
+	size_t payload_offset;
+	pthread_t tid;
+	pthread_mutex_t mutex;
+	uint32_t flags;
+};
+
+struct ueventconf {
+	char **program_argv;
+	char *search_device;
+	blkid_cache blkid_cache;
+	struct cryptconf crypt;
+	char *subsystem_filter;
+	int modalias_count;
+	int fork_count;
+	char *bootrepos;
+	char *apkovls;
+	int timeout;
+	int usb_storage_timeout;
+	int uevent_timeout;
+	int efd;
+	int found;
+
+	int running_threads;
+
+	pthread_mutex_t trigger_mutex;
+	pthread_cond_t trigger_cond;
+	struct list_head trigger_list;
+
+	int cryptsetup_running;
+};
+
+struct uevent {
+	struct ueventconf *conf;
+	int ref;
+	size_t bufsize;
+	char *message;
+	char *subsystem;
+	char *action;
+	char *modalias;
+	char *devname;
+	char *devpath;
+	char *major;
+	char *minor;
+	char devnode[256];
+	char *envp[64];
+	char buf[];
+};
+
+static struct uevent *uevent_ref(struct uevent *ev)
+{
+	ev->ref++;
+	return ev;
+}
+
+static void uevent_unref(struct uevent *ev)
+{
+	ev->ref--;
+	if (ev->ref != 0) return;
+	free(ev);
+}
+
+static void sighandler(int sig)
+{
+	switch (sig) {
+	case SIGHUP:
+	case SIGINT:
+	case SIGQUIT:
+	case SIGABRT:
+	case SIGTERM:
+		exit(0);
+	default:
+		break;
+	}
+}
+
+static void initsignals(void)
+{
+	signal(SIGHUP, sighandler);
+	signal(SIGINT, sighandler);
+	signal(SIGQUIT, sighandler);
+	signal(SIGABRT, sighandler);
+	signal(SIGTERM, sighandler);
+	signal(SIGCHLD, 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 | SOCK_NONBLOCK | SOCK_CLOEXEC,
+		    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 = 512*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");
+
+	return fd;
+}
+
+static int load_kmod(const char *modalias, char *driver, size_t len)
+{
+	static struct kmod_ctx *ctx = NULL;
+	struct kmod_list *list = NULL;
+	struct kmod_list *node;
+	int r, count=0;
+
+	if (driver) driver[0] = 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: %d", modalias, r);
+		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));
+		if (driver) strlcpy(driver, kmod_module_get_name(mod), len);
+		kmod_module_unref(mod);
+	}
+	kmod_module_unref_list(list);
+	return count;
+}
+
+static void start_mdadm(char *devnode)
+{
+	char *mdadm_argv[] = {
+		MDADM_PATH,
+		"--incremental",
+		"--run",
+		"--quiet",
+		devnode,
+		NULL
+	};
+	if (use_mdadm)
+		spawn_command(&spawnmgr, mdadm_argv, 0);
+}
+
+static void start_lvm2(char *devnode)
+{
+	char *lvm2_argv[] = {
+		LVM_PATH, "vgchange",
+		"--activate" , "ay", "--noudevsync", "--sysinit", "-q", "-q",
+		NULL
+	};
+	if (use_lvm)
+		spawn_command(&spawnmgr, lvm2_argv, 0);
+}
+
+static void start_zpool(char *uuid) {
+	char *zpool_argv[] = {
+		ZPOOL_PATH, "import", "-N", uuid,
+		NULL
+	};
+	if (use_zpool && uuid)
+		spawn_command(&spawnmgr, zpool_argv, 0);
+}
+
+static int read_pass(char *pass, size_t pass_size)
+{
+	struct termios old_flags, new_flags;
+	int r;
+
+	tcgetattr(STDIN_FILENO, &old_flags);
+	new_flags = old_flags;
+	new_flags.c_lflag &= ~ECHO;
+	new_flags.c_lflag |= ECHONL;
+
+	if (isatty(STDIN_FILENO)) {
+		r = tcsetattr(STDIN_FILENO, TCSANOW, &new_flags);
+		if (r < 0) {
+			warn("tcsetattr");
+			return r;
+		}
+	}// else {
+	//	fprintf(stderr, "The program isn't executed in a TTY, the echo-disabling has been skipped.\n");
+	//}
+
+	if (fgets(pass, pass_size, stdin) == NULL) {
+		warn("fgets");
+		return -1;
+	}
+	pass[strlen(pass) - 1] = '\0';
+
+	if (isatty(STDIN_FILENO)) {
+		if (tcsetattr(STDIN_FILENO, TCSANOW, &old_flags) < 0) {
+			warn("tcsetattr");
+		}
+	}// else {
+	//	fprintf(stderr, "The program isn't executed in a TTY, the echo-reenabling has been skipped.\n");
+	//}
+
+	return 0;
+}
+
+static void notify_main(struct ueventconf *conf)
+{
+	uint64_t one = 1;
+	write(conf->efd, &one, sizeof one);
+}
+
+static void *cryptsetup_thread(void *data)
+{
+	struct ueventconf *c = (struct ueventconf *)data;
+	const char *data_devnode, *header_devnode;
+	struct crypt_params_luks1 param_struct;
+	struct crypt_params_luks1 *params = NULL;
+	struct crypt_device *cd;
+	int r, passwd_tries = 5;
+
+	data_devnode = header_devnode = c->crypt.data.devnode;
+
+	if(c->crypt.header.devnode[0] != '\0') {
+		params = &param_struct;
+		params->hash = NULL; /* No way of finding this */
+		params->data_alignment = c->crypt.payload_offset; /* Memset did set that to 0, so default is 0 */
+		params->data_device = c->crypt.data.devnode;
+		header_devnode = c->crypt.header.devnode;
+	}
+
+	r = crypt_init(&cd, header_devnode);
+	if (r < 0) {
+		warnx("crypt_init(%s)", header_devnode);
+		goto notify_out;
+	}
+
+	r = crypt_load(cd, CRYPT_LUKS, params);
+	if (r < 0) {
+		warnx("crypt_load(%s)", data_devnode);
+		goto free_out;
+	}
+
+	r = crypt_set_data_device(cd, data_devnode);
+	if (r < 0) {
+		warnx("crypt_set_data_device(%s)", data_devnode);
+		goto free_out;
+	}
+
+	struct stat st;
+	if (!stat(c->crypt.data.key, &st)) {
+		pthread_mutex_lock(&c->crypt.mutex);
+		r = crypt_activate_by_keyfile(cd, c->crypt.data.name,
+					      CRYPT_ANY_SLOT,
+					      c->crypt.data.key, st.st_size,
+					      c->crypt.flags);
+		pthread_mutex_unlock(&c->crypt.mutex);
+		if (r >= 0)
+			goto free_out;
+	}
+
+	while (passwd_tries > 0) {
+		char pass[1024];
+
+		printf("Enter passphrase for %s: ", c->crypt.data.devnode);
+		fflush(stdout);
+
+		if (read_pass(pass, sizeof(pass)) < 0)
+			goto free_out;
+		passwd_tries--;
+
+		pthread_mutex_lock(&c->crypt.mutex);
+		r = crypt_activate_by_passphrase(cd, c->crypt.data.name,
+						 CRYPT_ANY_SLOT,
+						 pass, strlen(pass),
+						 c->crypt.flags);
+		pthread_mutex_unlock(&c->crypt.mutex);
+		memset(pass, 0, sizeof(pass)); /* wipe pass after use */
+
+		if (r >= 0)
+			goto free_out;
+		printf("No key available with this passphrase.\n");
+	}
+	printf("Mounting %s failed, amount of tries exhausted.\n", c->crypt.data.devnode);
+
+free_out:
+	crypt_free(cd);
+notify_out:
+	c->cryptsetup_running = 0;
+	notify_main(c);
+	return NULL;
+}
+
+static void start_thread(struct ueventconf *conf, void *(*thread_main)(void *))
+{
+	pthread_t tid;
+	pthread_attr_t attr;
+
+	pthread_attr_init(&attr);
+	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+	if (pthread_create(&tid, &attr, thread_main, conf) != 0)
+		err(1, "failed to create thread");
+	pthread_attr_destroy(&attr);
+}
+
+static void start_cryptsetup(struct ueventconf *conf)
+{
+	if(conf->crypt.header.devnode[0] != '\0') {
+		dbg("starting cryptsetup %s -> %s (header: %s)",
+		    conf->crypt.data.devnode, conf->crypt.data.name,
+		    conf->crypt.header.devnode);
+	} else {
+		dbg("starting cryptsetup %s -> %s", conf->crypt.data.devnode,
+		    conf->crypt.data.name);
+	}
+	load_kmod("dm-crypt", NULL, 0);
+	conf->cryptsetup_running = 1;
+	conf->running_threads = 1;
+	start_thread(conf, cryptsetup_thread);
+}
+
+static int is_mounted(const char *devnode) {
+	char line[PATH_MAX];
+	FILE *f = fopen("/proc/mounts", "r");
+	int r = 0;
+	if (f == NULL)
+		return 0;
+	while (fgets(line, sizeof(line), f) != NULL) {
+		strtok(line, " ");
+		if (strcmp(devnode, line) == 0) {
+			r = 1;
+			break;
+		}
+	}
+	fclose(f);
+	return r;
+}
+
+struct recurse_opts {
+	size_t pathlen;
+	char path[PATH_MAX], *filename;
+	int is_dir;
+	int matchdepth;
+	int curdepth, maxdepth;
+	void (*callback)(struct recurse_opts *opts, void *userdata);
+	void *userdata;
+};
+
+static int recurse_push(struct recurse_opts *opts, size_t *oldlen, const char *path)
+{
+	size_t pathlen = strlen(path);
+	if (opts->pathlen + 1 + pathlen + 1 >= sizeof opts->path)
+		return 0;
+	*oldlen = opts->pathlen;
+	opts->path[opts->pathlen++] = '/';
+	strcpy(&opts->path[opts->pathlen], path);
+	opts->pathlen += strlen(path);
+	return 1;
+}
+
+static void recurse_pop(struct recurse_opts *opts, size_t len)
+{
+	opts->pathlen = len;
+	opts->path[len] = 0;
+}
+
+static void do_recurse_dir(struct recurse_opts *opts)
+{
+	size_t oldlen;
+	struct dirent *entry;
+	DIR *d;
+	int is_dir;
+
+	d = opendir(opts->path);
+	if (!d) return;
+
+	while ((entry = readdir(d)) != NULL) {
+		if (strcmp(entry->d_name, ".") == 0 ||
+		    strcmp(entry->d_name, "..") == 0)
+			continue;
+
+		if (!recurse_push(opts, &oldlen, entry->d_name))
+			continue;
+
+		if (entry->d_type == DT_UNKNOWN) {
+			/* some filesystems like iso9660 does not support
+			   the d_type so we use lstat */
+			struct stat st;
+			if (lstat(opts->path, &st) < 0) {
+				dbg("%s: %s", opts->path, strerror(errno));
+				goto next;
+			}
+			is_dir = S_ISDIR(st.st_mode);
+		} else
+			is_dir = entry->d_type & DT_DIR;
+
+		if (opts->matchdepth == 0 || opts->matchdepth == opts->curdepth) {
+			opts->filename = &opts->path[oldlen+1];
+			opts->is_dir = is_dir;
+			opts->callback(opts, opts->userdata);
+		}
+
+		if (is_dir && opts->curdepth < opts->maxdepth) {
+			opts->curdepth++;
+			do_recurse_dir(opts);
+			opts->curdepth--;
+		}
+next:
+		recurse_pop(opts, oldlen);
+	}
+	closedir(d);
+}
+
+static void recurse_dir(struct recurse_opts *opts)
+{
+	opts->pathlen = strlen(opts->path);
+	opts->curdepth = 1;
+	do_recurse_dir(opts);
+}
+
+struct trigger_entry {
+	struct list_head node;
+	int max_depth;
+	char pathname[];
+};
+
+static void trigger_uevent_cb(struct recurse_opts *opts, void *data)
+{
+	size_t oldlen;
+	int fd;
+
+	if (!recurse_push(opts, &oldlen, "uevent"))
+		return;
+
+	fd = open(opts->path, O_WRONLY | O_CLOEXEC);
+	if (fd >= 0) {
+		write(fd, "add", 3);
+		close(fd);
+	}
+	recurse_pop(opts, oldlen);
+}
+
+static void trigger_path(struct ueventconf *conf, char *path, char *subdir, int max_depth)
+{
+	struct trigger_entry *e;
+	size_t pathlen = strlen(path);
+
+	e = malloc(pathlen + (subdir ? strlen(subdir) : 0) + 1 + sizeof *e);
+	if (!e) return;
+
+	list_init(&e->node);
+	e->max_depth = max_depth;
+	strcpy(e->pathname, path);
+	if (subdir) strcpy(&e->pathname[pathlen], subdir);
+
+	pthread_mutex_lock(&conf->trigger_mutex);
+	conf->running_threads = 1;
+	list_add_tail(&e->node, &conf->trigger_list);
+	pthread_cond_signal(&conf->trigger_cond);
+	pthread_mutex_unlock(&conf->trigger_mutex);
+}
+
+static void *trigger_thread(void *data)
+{
+	struct ueventconf *conf = data;
+	struct recurse_opts opts;
+	struct trigger_entry *entry = NULL;
+
+	while (1) {
+		pthread_mutex_lock(&conf->trigger_mutex);
+		if (entry) {
+			list_del(&entry->node);
+			free(entry);
+		}
+		entry = list_next(&conf->trigger_list, struct trigger_entry, node);
+		while (!entry) {
+			notify_main(conf);
+			pthread_cond_wait(&conf->trigger_cond, &conf->trigger_mutex);
+			entry = list_next(&conf->trigger_list, struct trigger_entry, node);
+		}
+		pthread_mutex_unlock(&conf->trigger_mutex);
+
+		opts = (struct recurse_opts) {
+			.callback = trigger_uevent_cb,
+			.userdata = entry,
+			.maxdepth = entry->max_depth,
+			.matchdepth = entry->max_depth,
+		};
+		snprintf(opts.path, sizeof opts.path, "/sys%s", entry->pathname);
+		dbg("trigger_thread: scanning %s", opts.path);
+
+		recurse_dir(&opts);
+	}
+
+	return NULL;
+}
+
+static void append_line(const char *outfile, const char *data)
+{
+	int fd;
+	if (outfile == 0) return;
+	fd = open(outfile, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC);
+	if (fd == -1)
+		err(1, "%s", outfile);
+	write(fd, data, strlen(data));
+	write(fd, "\n", 1);
+	close(fd);
+}
+
+struct scandevctx {
+	struct ueventconf *conf;
+	int found;
+};
+
+static void scandev_cb(struct recurse_opts *opts, void *data)
+{
+	struct scandevctx *ctx = data;
+	struct ueventconf *conf = ctx->conf;
+
+	if (opts->is_dir) {
+		size_t oldlen;
+		int ok = 0;
+		if (recurse_push(opts, &oldlen, ".boot_repository")) {
+			ok = access(opts->path, F_OK) == 0;
+			recurse_pop(opts, oldlen);
+		}
+		if (ok) {
+			dbg("added boot repository %s to %s", opts->path, conf->bootrepos);
+			append_line(conf->bootrepos, opts->path);
+			ctx->found |= FOUND_BOOTREPO;
+		}
+	} else if (fnmatch("*.apkovl.tar.gz*", opts->filename, 0) == 0) {
+		dbg("found apkovl %s", opts->path);
+		append_line(conf->apkovls, opts->path);
+		ctx->found |= FOUND_APKOVL;
+	}
+}
+
+static int scandev(struct ueventconf *conf, const char *devnode, const char *type)
+{
+	struct scandevctx ctx = {
+		.conf = conf,
+	};
+	struct recurse_opts opts = {
+		.maxdepth = 1,
+		.callback = scandev_cb,
+		.userdata = &ctx,
+	};
+	char *devname;
+	int r;
+
+	/* skip already mounted devices */
+	if (is_mounted(devnode)) {
+		dbg("%s is mounted (%s). skipping", devnode, type);
+		return 0;
+	}
+	devname = strrchr(devnode, '/');
+	if (!devname)
+		return 0;
+
+	snprintf(opts.path, sizeof opts.path, "/media%s", devname);
+	dbg("mounting %s on %s (%s)", devnode, opts.path, type);
+	mkdir(opts.path, 0755);
+
+	r = mount(devnode, opts.path, type, MS_RDONLY, NULL);
+	if (r < 0) {
+		dbg("Failed to mount %s on %s: %s",
+		    devnode, opts.path, strerror(errno));
+		return 0;
+	}
+
+	recurse_dir(&opts);
+
+	if (ctx.found == 0)
+		umount(opts.path);
+
+	return ctx.found;
+}
+
+static int is_same_device(const struct uevent *ev, const char *nodepath)
+{
+	struct stat st;
+	unsigned int maj, min;
+	if (stat(nodepath, &st) < 0)
+		return 0;
+
+	if (ev->major == NULL || ev->minor == NULL)
+		return 0;
+
+	maj = atoi(ev->major);
+	min = atoi(ev->minor);
+	return S_ISBLK(st.st_mode) && makedev(maj, min) == st.st_rdev;
+}
+
+static void founddev(struct ueventconf *conf, int found)
+{
+	conf->found |= found;
+	if ((found & FOUND_DEVICE)
+	    || ((found & FOUND_BOOTREPO) &&
+		(found & FOUND_APKOVL))) {
+		/* we have found everything we need, so no
+		   no need to wait for anything new event */
+		if (conf->timeout)
+			dbg("FOUND! setting timeout to 0");
+		conf->timeout = 0;
+		conf->usb_storage_timeout= 0;
+	} else if ((found & FOUND_BOOTREPO) && conf->timeout) {
+		/* we have found boot repo, but not apkovl
+		   we reduce timeout to default timeout */
+		if (conf->timeout != conf->uevent_timeout)
+			dbg("Setting timeout to %d", conf->uevent_timeout);
+		conf->timeout = conf->uevent_timeout;
+	}
+}
+
+static int is_zfs_pool(const char *path, const char *label)
+{
+	char pool_name[256];
+	char *p;
+	snprintf(pool_name, sizeof(pool_name), "%s", path);
+	if ((p = strchr(pool_name, '/')))
+		*p = '\0';
+	return strcmp(label, pool_name) == 0 ? FOUND_DEVICE : 0;
+}
+
+static int searchdev(struct uevent *ev, const char *searchdev, int scanbootmedia)
+{
+	struct ueventconf *conf = ev->conf;
+	char *type = NULL, *label = NULL, *uuid = NULL;
+	int rc = 0;
+
+	if (searchdev == NULL && !scanbootmedia)
+		return 0;
+
+	if (searchdev && (strcmp(ev->devname, searchdev) == 0
+			  || strcmp(ev->devnode, searchdev) == 0
+	                  || is_same_device(ev, searchdev))) {
+		return FOUND_DEVICE;
+	}
+
+	if (conf->blkid_cache == NULL)
+		blkid_get_cache(&conf->blkid_cache, NULL);
+
+	type = blkid_get_tag_value(conf->blkid_cache, "TYPE", ev->devnode);
+	uuid = blkid_get_tag_value(conf->blkid_cache, "UUID", ev->devnode);
+	label = blkid_get_tag_value(conf->blkid_cache, "LABEL", ev->devnode);
+
+	if (searchdev != NULL) {
+		if (strncmp("LABEL=", searchdev, 6) == 0) {
+			if (label && strcmp(label, searchdev+6) == 0)
+				rc = FOUND_DEVICE;
+		} else if (strncmp("UUID=", searchdev, 5) == 0) {
+			if (uuid && strcmp(uuid, searchdev+5) == 0)
+				rc = FOUND_DEVICE;
+		}
+	}
+
+	dbg("searchdev: dev='%s' type='%s' label='%s' uuid='%s'",
+		ev->devnode, type, label, uuid);
+
+	if (!rc && type) {
+		if (strcmp("linux_raid_member", type) == 0) {
+			start_mdadm(ev->devnode);
+		} else if (strcmp("LVM2_member", type) == 0) {
+			start_lvm2(ev->devnode);
+		} else if (strcmp("zfs_member", type) == 0) {
+			start_zpool(uuid);
+			if (searchdev != NULL && label != NULL
+			    && strncmp("ZFS=", searchdev, 4) == 0) {
+				rc = is_zfs_pool(&searchdev[4], label);
+			}
+		} else if (scanbootmedia) {
+			rc = scandev(conf, ev->devnode, type);
+		}
+	}
+
+	if (type)
+		free(type);
+	if (label)
+		free(label);
+	if (uuid)
+		free(uuid);
+
+	return rc;
+}
+
+/* search for crypt.data and crypt.header.
+   returns true if we are ready to start cryptsetup. */
+static int search_cryptdevs(struct uevent *ev, struct cryptconf *crypt)
+{
+	if (crypt->data.devnode[0] == '\0' && searchdev(ev, crypt->data.device, 0)) {
+		strncpy(crypt->data.devnode,
+			crypt->data.device[0] == '/' ? crypt->data.device : ev->devnode,
+			sizeof(crypt->data.devnode));
+		/* if we don't have header or header is found, then we are
+		   ready to start crypsetup */
+		return (crypt->header.device == NULL)
+			|| (crypt->header.devnode[0] != '\0');
+	}
+
+	if (crypt->header.device == NULL)
+		return 0;
+
+	if (crypt->header.devnode[0] == '\0' && searchdev(ev, crypt->header.device, 0)) {
+		strncpy(crypt->header.devnode,
+			crypt->header.device[0] == '/' ? crypt->header.device : ev->devnode,
+			sizeof(crypt->header.devnode));
+		/* if we also have found data dev, then we are ready to
+		   start cryptsetup */
+		return crypt->data.devnode[0] != '\0';
+	}
+	return 0;
+}
+
+static void uevent_handle(struct uevent *ev)
+{
+	struct ueventconf *conf = ev->conf;
+	int found;
+
+	if (!ev->subsystem || strcmp(ev->subsystem, "block") != 0)
+		return;
+
+	if (strcmp(ev->action, "add") != 0 &&
+	    strcmp(ev->action, "change") != 0)
+		return;
+
+	snprintf(ev->devnode, sizeof(ev->devnode), "/dev/%s", ev->devname);
+	pthread_mutex_lock(&conf->crypt.mutex);
+	found = searchdev(ev, conf->search_device, (conf->apkovls || conf->bootrepos));
+	pthread_mutex_unlock(&conf->crypt.mutex);
+	if (found) {
+		founddev(conf, found);
+	} else if (search_cryptdevs(ev, &conf->crypt)) {
+		start_cryptsetup(conf);
+	}
+}
+
+static void uevent_mdev_done_cb(void *ctx, int status)
+{
+	struct uevent *ev = ctx;
+	uevent_handle(ev);
+	uevent_unref(ev);
+}
+
+static void uevent_dispatch(struct uevent *ev)
+{
+	struct ueventconf *conf = ev->conf;
+	int add;
+
+	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;
+	}
+
+	if (ev->action == NULL)
+		return;
+
+	dbg("uevent: action='%s' subsystem='%s' devname='%s' devpath='%s'",
+		ev->action, ev->subsystem, ev->devname, ev->devpath);
+
+	add = strcmp(ev->action, "add") == 0;
+
+	if (add && ev->subsystem && strcmp(ev->subsystem, "bus") == 0) {
+		trigger_path(conf, ev->devpath, "/devices", 1);
+	} else if (add && ev->modalias) {
+		char buf[128];
+		load_kmod(ev->modalias, buf, sizeof buf);
+		conf->modalias_count++;
+		/* increase timeout so usb drives gets time to settle */
+		if (strcmp(buf, "usb_storage") == 0)
+			conf->usb_storage_timeout = USB_STORAGE_TIMEOUT;
+
+	} else if (ev->devname) {
+		if (conf->program_argv[0] != NULL) {
+			spawn_command_cb(&spawnmgr, conf->program_argv, ev->envp,
+					 uevent_mdev_done_cb, uevent_ref(ev));
+			conf->fork_count++;
+		} else {
+			uevent_handle(ev);
+		}
+	}
+}
+
+static void uevent_process(char *buf, const size_t len, struct ueventconf *conf)
+{
+	struct uevent *ev;
+	int i, nenvp, slen = 0;
+	char *key, *value;
+
+	ev = malloc(len + sizeof *ev);
+	if (!ev) return;
+
+	memset(ev, 0, sizeof *ev);
+	memcpy(ev->buf, buf, len);
+	ev->ref = 1;
+	ev->conf = conf;
+	ev->bufsize = len;
+
+	nenvp = sizeof(default_envp) / sizeof(default_envp[0]) - 1;
+	memcpy(&ev->envp, default_envp, nenvp * sizeof(default_envp[0]));
+
+	for (i = 0; i < len; i += slen + 1) {
+		key = ev->buf + i;
+		value = strchr(key, '=');
+		slen = strlen(ev->buf + i);
+
+		if (i == 0 && slen != 0) {
+			/* first line, the message */
+			ev->message = key;
+			continue;
+		}
+
+		if (!slen || !value)
+			continue;
+
+		value++;
+		if (envcmp(key, "MODALIAS")) {
+			ev->modalias = value;
+		} else if (envcmp(key, "ACTION")) {
+			ev->action = value;
+		} else if (envcmp(key, "SUBSYSTEM")) {
+			ev->subsystem = value;
+		} else if (envcmp(key, "DEVNAME")) {
+			ev->devname = value;
+		} else if (envcmp(key, "DEVPATH")) {
+			ev->devpath = value;
+		} else if (envcmp(key, "MAJOR")) {
+			ev->major = value;
+		} else if (envcmp(key, "MINOR")) {
+			ev->minor = value;
+		}
+
+		if (!envcmp(key, "PATH"))
+			ev->envp[nenvp++]= key;
+	}
+	ev->envp[nenvp++] = 0;
+
+	uevent_dispatch(ev);
+	uevent_unref(ev);
+}
+
+static void usage(int rc)
+{
+	printf("coldplug system til given device is found\n"
+	"usage: %s [options] DEVICE\n"
+	"\n"
+	"options:\n"
+	" -a OUTFILE      add paths to found apkovls to OUTFILE\n"
+	" -b OUTFILE      add found boot repositories to OUTFILE\n"
+	" -c CRYPTDEVICE  run cryptsetup luksOpen when CRYPTDEVICE is found\n"
+	" -h              show this help\n"
+	" -H HEADERDEVICE use HEADERDEVICE as the LUKS header\n"
+	" -k CRYPTKEY     path to keyfile\n"
+	" -m CRYPTNAME    use CRYPTNAME name for crypto device mapping\n"
+	" -o OFFSET       cryptsetup payload offset\n"
+	" -D              allow discards on crypto device\n"
+	" -d              enable debugging ouput\n"
+	" -f SUBSYSTEM    filter subsystem\n"
+	" -p PROGRAM      use PROGRAM as handler for every event with DEVNAME\n"
+	" -t TIMEOUT      timeout after TIMEOUT milliseconds without uevents\n"
+	"\n", argv0);
+
+	exit(rc);
+}
+
+static int regular_file(const char *path)
+{
+	struct stat st;
+	int r = stat(path, &st);
+	return r == -1 ? 0 : S_ISREG(st.st_mode);
+}
+
+int main(int argc, char *argv[])
+{
+	struct pollfd fds[3];
+	int numfds = 3;
+	int r;
+	struct ueventconf conf;
+	int event_count = 0;
+	size_t total_bytes = 0;
+	int not_found_is_ok = 0;
+	char *program_argv[2] = {0,0};
+	sigset_t sigchldmask;
+
+	for (r = 0; environ[r]; r++) {
+		if (envcmp(environ[r], "PATH"))
+			default_envp[0] = environ[r];
+	}
+
+	spawn_init(&spawnmgr);
+
+	memset(&conf, 0, sizeof(conf));
+	pthread_mutex_init(&conf.trigger_mutex, NULL);
+	pthread_cond_init(&conf.trigger_cond, NULL);
+	list_init(&conf.trigger_list);
+	pthread_mutex_init(&conf.crypt.mutex, NULL);
+
+	conf.program_argv = program_argv;
+	conf.timeout = MAX_EVENT_TIMEOUT;
+	conf.usb_storage_timeout = 0;
+	conf.uevent_timeout = DEFAULT_EVENT_TIMEOUT;
+	use_lvm = access(LVM_PATH, X_OK) == 0;
+	use_mdadm = access(MDADM_PATH, X_OK) == 0;
+	use_zpool = access(ZPOOL_PATH, X_OK) == 0;
+
+	argv0 = strrchr(argv[0], '/');
+	if (argv0++ == NULL)
+		argv0 = argv[0];
+
+	ARGBEGIN {
+	case 'a':
+		conf.apkovls = EARGF(usage(1));;
+		break;
+	case 'b':
+		conf.bootrepos = EARGF(usage(1));
+		break;
+	case 'c':
+		conf.crypt.data.device = EARGF(usage(1));
+		break;
+	case 'H':
+		conf.crypt.header.device = EARGF(usage(1));
+		/* the header may be in a regular file and not a device */
+		if (regular_file(conf.crypt.header.device)) {
+			snprintf(conf.crypt.header.devnode,
+				sizeof(conf.crypt.header.devnode),
+				"%s", conf.crypt.header.device);
+		}
+		break;
+	case 'h':
+		usage(0);
+		break;
+	case 'k':
+		conf.crypt.data.key = EARGF(usage(1));
+		break;
+	case 'm':
+		conf.crypt.data.name = EARGF(usage(1));
+		break;
+	case 'n':
+		not_found_is_ok = 1;
+		break;
+	case 'D':
+		conf.crypt.flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS;
+		break;
+	case 'd':
+		dodebug = 1;
+		break;
+	case 'f':
+		conf.subsystem_filter = EARGF(usage(1));
+		break;
+	case 'o':
+		if(sscanf(EARGF(usage(1)), "%zu", &conf.crypt.payload_offset) != 1)
+			err(1, "sscanf");
+		break;
+	case 'p':
+		conf.program_argv[0] = EARGF(usage(1));
+		break;
+	case 't':
+		conf.uevent_timeout = atoi(EARGF(usage(1)));
+		break;
+	default:
+		usage(1);
+	} ARGEND;
+
+	if (argc > 0)
+		conf.search_device = argv[0];
+
+	initsignals();
+	sigemptyset(&sigchldmask);
+	sigaddset(&sigchldmask, SIGCHLD);
+	sigprocmask(SIG_BLOCK, &sigchldmask, NULL);
+
+	fds[0].fd = init_netlink_socket();
+	fds[0].events = POLLIN;
+
+	fds[1].fd = signalfd(-1, &sigchldmask, SFD_NONBLOCK|SFD_CLOEXEC);
+	fds[1].events = POLLIN;
+
+	fds[2].fd = eventfd(0, EFD_CLOEXEC);
+	fds[2].events = POLLIN;
+	conf.efd = fds[2].fd;
+
+	trigger_path(&conf, "/bus", NULL, 1);
+	trigger_path(&conf, "/class", NULL, 2);
+	start_thread(&conf, trigger_thread);
+
+	while (1) {
+		int t = conf.timeout + conf.usb_storage_timeout;
+		r = poll(fds, numfds, (spawn_active(&spawnmgr) || conf.running_threads) ? -1 : t);
+		if (r == -1) {
+			if (errno == EINTR || errno == ERESTART)
+				continue;
+			err(1, "poll");
+		}
+		if (r == 0) {
+			dbg("exit due to timeout (%i)", t);
+			break;
+		}
+
+		if (fds[0].revents & POLLIN) {
+			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;
+
+			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[0].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++;
+			uevent_process(buf, len, &conf);
+		}
+
+		if (fds[0].revents & POLLHUP) {
+			dbg("parent hung up\n");
+			break;
+		}
+
+		if (fds[1].revents & POLLIN) {
+			struct signalfd_siginfo fdsi;
+			pid_t pid;
+			int status;
+
+			while (read(fds[1].fd, &fdsi, sizeof fdsi) > 0)
+				;
+			while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
+				spawn_reap(&spawnmgr, pid, status);
+		}
+
+		if (fds[2].revents & POLLIN) {
+			uint64_t val = 0;
+			if (read(fds[2].fd, &val, sizeof(val)) < 0)
+				warn("eventfd");
+			pthread_mutex_lock(&conf.trigger_mutex);
+			conf.running_threads = !list_empty(&conf.trigger_list)
+				|| conf.cryptsetup_running;
+			pthread_mutex_unlock(&conf.trigger_mutex);
+		}
+	}
+	close(fds[2].fd);
+
+	pthread_mutex_destroy(&conf.crypt.mutex);
+	pthread_mutex_destroy(&conf.trigger_mutex);
+	pthread_cond_destroy(&conf.trigger_cond);
+	if (conf.blkid_cache) blkid_put_cache(conf.blkid_cache);
+
+	dbg("modaliases: %i, forks: %i, events: %i, total bufsize: %zu",
+		conf.modalias_count,
+		conf.fork_count,
+		event_count, total_bytes);
+
+	return conf.found || not_found_is_ok ? 0 : 1;
+}