Upgrade kmod to v30 am: 65d4b95025 am: d9c006ff3c am: 3eb265e5e7 am: dfd13c6f51 am: adcf6b87fb

Original change: https://android-review.googlesource.com/c/platform/external/kmod/+/2240916

Change-Id: Iea563ec646f9fbfb49440d3b749425a4f4f346c4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.gitignore b/.gitignore
index 2347858..cad86ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
 *.gcno
 /*.tar.xz
 /*.md5sum
+/*.mbx
+/*.cover
 .deps/
 .libs/
 /Makefile
@@ -18,6 +20,7 @@
 /configure
 /cov-int
 /coverage
+/gtk-doc.make
 /kmod-*.tar.*
 /libtool
 /stamp-h1
diff --git a/METADATA b/METADATA
index 6422324..f19e9ca 100644
--- a/METADATA
+++ b/METADATA
@@ -5,11 +5,11 @@
     type: GIT
     value: "https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git"
   }
-  version: "v28"
+  version: "v30"
   license_type: RESTRICTED
   last_upgrade_date {
-    year: 2021
-    month: 1
-    day: 7
+    year: 2022
+    month: 10
+    day: 3
   }
 }
diff --git a/Makefile.am b/Makefile.am
index acde92b..0e48770 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -57,9 +57,9 @@
 #    increment age.
 # 6. If any interfaces have been removed or changed since the last public
 #    release, then set age to 0.
-LIBKMOD_CURRENT=5
-LIBKMOD_REVISION=6
-LIBKMOD_AGE=3
+LIBKMOD_CURRENT=6
+LIBKMOD_REVISION=0
+LIBKMOD_AGE=4
 
 noinst_LTLIBRARIES = shared/libshared.la
 shared_libshared_la_SOURCES = \
@@ -249,7 +249,7 @@
 				find $(ROOTFS) -type d -exec chmod +w {} \; && \
 				find $(ROOTFS) -type f -name .gitignore -exec rm -f {} \; && \
 				$(top_srcdir)/testsuite/populate-modules.sh \
-					$(MODULE_PLAYGROUND) $(ROOTFS) ) && \
+					$(MODULE_PLAYGROUND) $(ROOTFS) $(top_builddir)/config.h ) && \
 				touch testsuite/stamp-rootfs
 
 build-module-playground:
@@ -280,13 +280,7 @@
 TESTSUITE_OVERRIDE_LIBS_LDFLAGS = \
 	avoid-version -module -shared -export-dynamic -rpath /nowhere -ldl
 
-check-sysconfdir:
-	$(AM_V_at)if test "$(sysconfdir)" != "/etc" -a "$(sysconfdir)" != "/etc/"; then \
-		echo "warning: Some tests will fail without --sysconfdir=/etc" >&2; \
-	fi
-.PHONY: check-sysconfdir
-
-check-am: rootfs check-sysconfdir
+check-am: rootfs
 
 
 EXTRA_DIST += \
@@ -341,6 +335,10 @@
 	testsuite/libtestsuite.la libkmod/libkmod-internal.la \
 	shared/libshared.la
 
+if KMOD_SYSCONFDIR_NOT_ETC
+TESTSUITE_CPPFLAGS += -DKMOD_SYSCONFDIR_NOT_ETC
+endif
+
 check_LTLIBRARIES += testsuite/libtestsuite.la
 testsuite_libtestsuite_la_SOURCES = \
 	testsuite/testsuite.c testsuite/testsuite.h
diff --git a/NEWS b/NEWS
index ae56657..fe95103 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,129 @@
+kmod 30
+=======
+
+- Improvements
+	- Stop adding duplicate information on modules.builtin.alias.bin, just use
+	  the modules.builtin.bin index
+
+	- Speedup depmod, particularly under qemu with emulated arch, by
+	  avoiding a lot of open/read/close of modules.alias.bin. On an
+	  emulated ARM rootfs, depmod with only 2 modules was taking ~32s
+	  vs ~0.07s now.
+
+	- Add kmod_module_new_from_name_lookup() which allows doing a lookup by
+	  module name, without considering the aliases. Other than that search
+	  order is similar to kmod_module_new_from_lookup().
+
+	- modinfo learned the --modname option to explicitely show information
+	  about the module, even if there is an alias with the same name. This
+	  allows showing information about e.g. kernel/lib/crc32.ko, even if
+	  kernel also exports a crc32 alias in modules.alias:
+
+		alias crc32 crc32_pclmul
+		alias crc32 crc32_generic
+
+	  Same behavior will be used to other modules and to aliases provided
+	  by user/distro.
+
+	- depmod.conf learned a new "excludedir" directive so distro/user can
+	  configure more directories to be excluded from its search, besides
+	  the hardcoded values "build" and "source".
+
+	- Better group modprobe options on help output under "Management, Query and General".
+
+	- modprobe learned a --wait <MSEC> option to be used together with -r
+	  when removing a module. This allows modprobe to keep trying the
+	  removal if it fails because the module is still in use. An exponential backoff
+	  time is used for further retries.
+
+	  The wait behavior provided by the kernel when not passing O_NONBLOCK
+	  to delete_module() was removed in v3.13 due to not be used and the
+	  consequences of having to support it in the kernel. However there may
+	  be some users, particularly on testsuites for individual susbsystems, that
+	  would want that. So provide a userspace implementation inside modprobe for
+	  such users. "rmmod" doesn't have a --wait as it remains a bare minimal over
+	  the API provided by the kernel. In future the --wait behavior can be added
+	  to libkmod for testsuites not exec'ing modprobe for module removal.
+
+	- kmod_module_remove_module() learned a new flag to silence output when
+	  caller wants to handle them - this is particularly important for the
+	  --wait flag to modprobe, as it's not desired to keep seeing error messages
+	  while waiting for the module to be unused.
+
+	- Add SM3 hash algo support to modinfo output, as already available in the kernel.
+
+- Bug Fixes
+	- Fix modinfo output when showing information for a .ko module when running
+	  on a kernel that has that module as builtin.
+
+	- Fix kmod_module_new_from_lookup() returning > 0 rather than 0
+	  when it matches an alias.
+
+	- Fix modinfo segfault when module doesn't exist.
+
+	- Add missing function in the html documentation: kmod_get_dirname().
+
+	- Fix modprobe incorrectly handling number of arguments when prepending values from
+	  MODPROBE_OPTIONS environment variable.
+
+	- Fix modprobe -r --remove-dependencies and since "dependencies" was a
+	  misnomer, add the preferred argument option: "--remove-holders". This
+	  is the same name used by the kernel. It allows users to also remove
+	  other modules holding the one that is being removed.
+
+	- Fix off-by-one in max module name length in depmod.
+
+- Infra/internal
+	- Start some changes in the out-of-tree test modules in kmod so they are useful
+	  for being really inserted in the kernel rather than relying on kmod's mock
+	  interface. This helps manual testing and may be used to exercise to test
+	  changes in the kernel.
+
+kmod 29
+=======
+
+- Improvements
+	- Add support to use /usr/local as a place for configuration files. This makes it easier
+	  to install locally without overriding distro files.
+
+- Bug fixes
+	- Fix `modinfo -F` when module is builtin: when we asked by a specific field from modinfo,
+	  it was not working correctly if the module was builtin
+
+	- Documentation fixes on precedence order of /etc and /run: the correct order is
+	  /etc/modprobe.d, /run/modprobe.d, /lib/modprobe.d
+
+	- Fix the priority order that we use for searching configuration files. The
+	  correct one is /etc, /run, /usr/local/lib, /lib, for both modprobe.d
+	  and depmo.d
+
+	- Fix kernel command line parsing when there are quotes present. Grub
+	  mangles the command line and changes it from 'module.option="val with
+	  spaces"' to '"module.option=val with spaces"'. Although this is weird
+	  behavior and grub could have been fixed, the kernel understands it
+	  correctly for builtin modules. So change libkmod to also parse it
+	  correctly. This also brings another hidden behavior from the kernel:
+	  newline in the kernel command line is also allowed and can be used to
+	  separate options.
+
+	- Fix a memory leak, overflow and double free on error path
+
+	- Fix documentation for return value from kmod_module_get_info(): we
+	  return the number of entries we added to the list
+
+	- Fix output of modules.builtin.alias.bin index: we were writing an empty file due to
+	  the misuse of kmod_module_get_info()
+
+- Infra/internal
+	- Retire integration with semaphoreci
+
+	- Declare the github mirror also as an official upstream source: now besides accepting
+	  patches via mailing list, PRs on github are also acceptable
+
+	- Misc improvements to testsuite, so we can use it reliably regardless
+	  of the configuration used: now tests will skip if we don't have the
+	  build dependencies)
+
 kmod 28
 =======
 
diff --git a/README b/README
deleted file mode 100644
index a0226e3..0000000
--- a/README
+++ /dev/null
@@ -1,131 +0,0 @@
-kmod - Linux kernel module handling
-
-Information
-===========
-
-Build Status:
-	https://lucasdemarchi.semaphoreci.com/projects/kmod
-
-Mailing list:
-	linux-modules@vger.kernel.org (no subscription needed)
-	https://lore.kernel.org/linux-modules/
-
-Patchwork:
-	https://patchwork.kernel.org/project/linux-modules/
-
-Signed packages:
-	http://www.kernel.org/pub/linux/utils/kernel/kmod/
-
-Git:
-	git://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
-	http://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
-	https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
-
-Gitweb:
-	http://git.kernel.org/?p=utils/kernel/kmod/kmod.git
-
-Irc:
-	#kmod on irc.freenode.org
-
-License:
-	LGPLv2.1+ for libkmod, testsuite and helper libraries
-	GPLv2+ for tools/*
-
-
-OVERVIEW
-========
-
-kmod is a set of tools to handle common tasks with Linux kernel modules like
-insert, remove, list, check properties, resolve dependencies and aliases.
-
-These tools are designed on top of libkmod, a library that is shipped with
-kmod. See libkmod/README for more details on this library and how to use it.
-The aim is to be compatible with tools, configurations and indexes from
-module-init-tools project.
-
-Compilation and installation
-============================
-
-In order to compiler the source code you need following software packages:
-	- GCC compiler
-	- GNU C library
-
-Optional dependencies:
-	- ZLIB library
-	- LZMA library
-
-Typical configuration:
-	./configure CFLAGS="-g -O2" --prefix=/usr \
-			--sysconfdir=/etc --libdir=/usr/lib
-
-Configure automatically searches for all required components and packages.
-
-To compile and install run:
-	make && make install
-
-Hacking
-=======
-
-Run 'autogen.sh' script before configure. If you want to accept the recommended
-flags, you just need to run 'autogen.sh c'. Note that the recommended
-flags require cython be installed to compile successfully.
-
-Make sure to read the CODING-STYLE file and the other READMEs: libkmod/README
-and testsuite/README.
-
-Compatibility with module-init-tools
-====================================
-
-kmod replaces module-init-tools, which is end-of-life. Most of its tools are
-rewritten on top of libkmod so it can be used as a drop in replacements.
-Somethings however were changed. Reasons vary from "the feature was already
-long deprecated on module-init-tools" to "it would be too much trouble to
-support it".
-
-There are several features that are being added in kmod, but we don't
-keep track of them here.
-
-modprobe
---------
-
-* 'modprobe -l' was marked as deprecated and does not exist anymore
-
-* 'modprobe -t' is gone, together with 'modprobe -l'
-
-* modprobe doesn't parse configuration files with names not ending in
-  '.alias' or '.conf'. modprobe used to warn about these files.
-
-* modprobe doesn't parse 'config' and 'include' commands in configuration
-  files.
-
-* modprobe from m-i-t does not honour softdeps for install commands. E.g.:
-  config:
-
-        install bli "echo bli"
-	install bla "echo bla"
-	softdep bla pre: bli
-
-  With m-i-t, the output of 'modprobe --show-depends bla' will be:
-        install "echo bla"
-
-  While with kmod:
-        install "echo bli"
-        install "echo bla"
-
-* kmod doesn't dump the configuration as is in the config files. Instead it
-  dumps the configuration as it was parsed. Therefore, comments and file names
-  are not dumped, but on the good side we know what the exact configuration
-  kmod is using. We did this because if we only want to know the entire content
-  of configuration files, it's enough to use find(1) in modprobe.d directories
-
-depmod
-------
-
-* there's no 'depmod -m' option: legacy modules.*map files are gone
-
-lsmod
------
-
-* module-init-tools used /proc/modules to parse module info. kmod uses
-  /sys/module/*, but there's a fallback to /proc/modules if the latter isn't
-  available
diff --git a/README.md b/README.md
index d3b84bd..590c8a8 100644
--- a/README.md
+++ b/README.md
@@ -2,4 +2,130 @@
 
 [![Coverity Scan Status](https://scan.coverity.com/projects/2096/badge.svg)](https://scan.coverity.com/projects/2096)
 
-This is a ***mirror only***. Please see [README](../master/README) file for more information.
+
+Information
+===========
+
+Mailing list:
+	linux-modules@vger.kernel.org (no subscription needed)
+	https://lore.kernel.org/linux-modules/
+
+Signed packages:
+	http://www.kernel.org/pub/linux/utils/kernel/kmod/
+
+Git:
+	git://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
+	http://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
+	https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git
+
+Gitweb:
+	http://git.kernel.org/?p=utils/kernel/kmod/kmod.git
+	https://github.com/kmod-project/kmod
+
+Irc:
+	#kmod on irc.freenode.org
+
+License:
+	LGPLv2.1+ for libkmod, testsuite and helper libraries
+	GPLv2+ for tools/*
+
+
+OVERVIEW
+========
+
+kmod is a set of tools to handle common tasks with Linux kernel modules like
+insert, remove, list, check properties, resolve dependencies and aliases.
+
+These tools are designed on top of libkmod, a library that is shipped with
+kmod. See libkmod/README for more details on this library and how to use it.
+The aim is to be compatible with tools, configurations and indexes from
+module-init-tools project.
+
+Compilation and installation
+============================
+
+In order to compiler the source code you need following software packages:
+	- GCC compiler
+	- GNU C library
+
+Optional dependencies:
+	- ZLIB library
+	- LZMA library
+	- ZSTD library
+	- OPENSSL library (signature handling in modinfo)
+
+Typical configuration:
+	./configure CFLAGS="-g -O2" --prefix=/usr \
+			--sysconfdir=/etc --libdir=/usr/lib
+
+Configure automatically searches for all required components and packages.
+
+To compile and install run:
+	make && make install
+
+Hacking
+=======
+
+Run 'autogen.sh' script before configure. If you want to accept the recommended
+flags, you just need to run 'autogen.sh c'. Note that the recommended
+flags require cython be installed to compile successfully.
+
+Make sure to read the CODING-STYLE file and the other READMEs: libkmod/README
+and testsuite/README.
+
+Compatibility with module-init-tools
+====================================
+
+kmod replaces module-init-tools, which is end-of-life. Most of its tools are
+rewritten on top of libkmod so it can be used as a drop in replacements.
+Somethings however were changed. Reasons vary from "the feature was already
+long deprecated on module-init-tools" to "it would be too much trouble to
+support it".
+
+There are several features that are being added in kmod, but we don't
+keep track of them here.
+
+modprobe
+--------
+
+* 'modprobe -l' was marked as deprecated and does not exist anymore
+
+* 'modprobe -t' is gone, together with 'modprobe -l'
+
+* modprobe doesn't parse configuration files with names not ending in
+  '.alias' or '.conf'. modprobe used to warn about these files.
+
+* modprobe doesn't parse 'config' and 'include' commands in configuration
+  files.
+
+* modprobe from m-i-t does not honour softdeps for install commands. E.g.:
+  config:
+
+        install bli "echo bli"
+	install bla "echo bla"
+	softdep bla pre: bli
+
+  With m-i-t, the output of 'modprobe --show-depends bla' will be:
+        install "echo bla"
+
+  While with kmod:
+        install "echo bli"
+        install "echo bla"
+
+* kmod doesn't dump the configuration as is in the config files. Instead it
+  dumps the configuration as it was parsed. Therefore, comments and file names
+  are not dumped, but on the good side we know what the exact configuration
+  kmod is using. We did this because if we only want to know the entire content
+  of configuration files, it's enough to use find(1) in modprobe.d directories
+
+depmod
+------
+
+* there's no 'depmod -m' option: legacy modules.*map files are gone
+
+lsmod
+-----
+
+* module-init-tools used /proc/modules to parse module info. kmod uses
+  /sys/module/*, but there's a fallback to /proc/modules if the latter isn't
+  available
diff --git a/configure.ac b/configure.ac
index 0cf2eda..6989e93 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 AC_PREREQ(2.64)
 AC_INIT([kmod],
-	[28],
+	[30],
 	[linux-modules@vger.kernel.org],
 	[kmod],
 	[http://git.kernel.org/?p=utils/kernel/kmod/kmod.git])
@@ -224,6 +224,8 @@
 ], [
 AM_CONDITIONAL([ENABLE_GTK_DOC], false)])
 
+# Some tests are skipped when sysconfdir != /etc.
+AM_CONDITIONAL([KMOD_SYSCONFDIR_NOT_ETC], [test "x$sysconfdir" != "x/etc"])
 
 #####################################################################
 # Default CFLAGS and LDFLAGS
diff --git a/libkmod/docs/libkmod-sections.txt b/libkmod/docs/libkmod-sections.txt
index e59ab7a..33d9eec 100644
--- a/libkmod/docs/libkmod-sections.txt
+++ b/libkmod/docs/libkmod-sections.txt
@@ -15,6 +15,7 @@
 kmod_set_log_fn
 kmod_get_userdata
 kmod_set_userdata
+kmod_get_dirname
 </SECTION>
 
 <SECTION>
@@ -46,6 +47,7 @@
 <FILE>libkmod-module</FILE>
 kmod_module
 kmod_module_new_from_lookup
+kmod_module_new_from_name_lookup
 kmod_module_new_from_name
 kmod_module_new_from_path
 
diff --git a/libkmod/libkmod-builtin.c b/libkmod/libkmod-builtin.c
index fc9a376..a002cb5 100644
--- a/libkmod/libkmod-builtin.c
+++ b/libkmod/libkmod-builtin.c
@@ -246,7 +246,7 @@
 
 	len = dot - line;
 
-	if (len > PATH_MAX) {
+	if (len >= PATH_MAX) {
 		sv_errno = ENAMETOOLONG;
 		goto fail;
 	}
@@ -313,7 +313,7 @@
 	while (offset < iter->next) {
 		offset = get_string(iter, pos, &line, &linesz);
 		if (offset <= 0) {
-			count = (offset) ? -errno : -EOF;
+			count = (offset) ? -errno : -EINVAL;
 			free(*modinfo);
 			goto fail;
 		}
diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c
index 971f20b..e83621b 100644
--- a/libkmod/libkmod-config.c
+++ b/libkmod/libkmod-config.c
@@ -498,8 +498,15 @@
 {
 	char buf[KCMD_LINE_SIZE];
 	int fd, err;
-	char *p, *modname,  *param = NULL, *value = NULL;
-	bool is_quoted = false, is_module = true;
+	char *p, *p_quote_start, *modname,  *param = NULL, *value = NULL;
+	bool is_quoted = false, iter = true;
+	enum state {
+		STATE_IGNORE,
+		STATE_MODNAME,
+		STATE_PARAM,
+		STATE_VALUE,
+		STATE_COMPLETE,
+	} state;
 
 	fd = open("/proc/cmdline", O_RDONLY|O_CLOEXEC);
 	if (fd < 0) {
@@ -516,54 +523,102 @@
 		return err;
 	}
 
-	for (p = buf, modname = buf; *p != '\0' && *p != '\n'; p++) {
-		if (*p == '"') {
+	state = STATE_MODNAME;
+	p_quote_start = NULL;
+	for (p = buf, modname = buf; iter; p++) {
+		switch (*p) {
+		case '"':
 			is_quoted = !is_quoted;
 
-			if (is_quoted) {
-				/* don't consider a module until closing quotes */
-				is_module = false;
-			} else if (param != NULL && value != NULL) {
-				/*
-				 * If we are indeed expecting a value and
-				 * closing quotes, then this can be considered
-				 * a valid option for a module
-				 */
-				is_module = true;
+			/*
+			 * only allow starting quote as first char when looking
+			 * for a modname: anything else is considered ill-formed
+			 */
+			if (is_quoted && state == STATE_MODNAME && p == modname) {
+				p_quote_start = p;
+				modname = p + 1;
+			} else if (state != STATE_VALUE) {
+				state = STATE_IGNORE;
 			}
 
-			continue;
-		}
-		if (is_quoted)
-			continue;
-
-		switch (*p) {
+			break;
+		case '\0':
+			iter = false;
+			/* fall-through */
 		case ' ':
-			*p = '\0';
-			if (is_module)
-				kcmdline_parse_result(config, modname, param, value);
-			param = value = NULL;
-			modname = p + 1;
-			is_module = true;
+		case '\n':
+		case '\t':
+		case '\v':
+		case '\f':
+		case '\r':
+			if (is_quoted && state == STATE_VALUE) {
+				/* no state change*/;
+			} else if (is_quoted) {
+				/* spaces are only allowed in the value part */
+				state = STATE_IGNORE;
+			} else if (state == STATE_VALUE || state == STATE_PARAM) {
+				*p = '\0';
+				state = STATE_COMPLETE;
+			} else {
+				/*
+				 * go to next option, ignoring any possible
+				 * partial match we have
+				 */
+				modname = p + 1;
+				state = STATE_MODNAME;
+				p_quote_start = NULL;
+			}
 			break;
 		case '.':
-			if (param == NULL) {
+			if (state == STATE_MODNAME) {
 				*p = '\0';
 				param = p + 1;
+				state = STATE_PARAM;
+			} else if (state == STATE_PARAM) {
+				state = STATE_IGNORE;
 			}
 			break;
 		case '=':
-			if (param != NULL)
+			if (state == STATE_PARAM) {
+				/*
+				 * Don't set *p to '\0': the value var shadows
+				 * param
+				 */
 				value = p + 1;
-			else
-				is_module = false;
+				state = STATE_VALUE;
+			} else if (state == STATE_MODNAME) {
+				state = STATE_IGNORE;
+			}
 			break;
 		}
-	}
 
-	*p = '\0';
-	if (is_module)
-		kcmdline_parse_result(config, modname, param, value);
+		if (state == STATE_COMPLETE) {
+			/*
+			 * We may need to re-quote to unmangle what the
+			 * bootloader passed. Example: grub passes the option as
+			 * "parport.dyndbg=file drivers/parport/ieee1284_ops.c +mpf"
+			 * instead of
+			 * parport.dyndbg="file drivers/parport/ieee1284_ops.c +mpf"
+			 */
+			if (p_quote_start && p_quote_start < modname) {
+				/*
+				 * p_quote_start
+				 * |
+				 * |modname  param  value
+				 * ||        |      |
+				 * vv        v      v
+				 * "parport\0dyndbg=file drivers/parport/ieee1284_ops.c +mpf" */
+				memmove(p_quote_start, modname, value - modname);
+				value--; modname--; param--;
+				*value = '"';
+			}
+			kcmdline_parse_result(config, modname, param, value);
+			/* start over on next iteration */
+			modname = p + 1;
+			state = STATE_MODNAME;
+			p_quote_start = NULL;
+		}
+	}
 
 	return 0;
 }
@@ -854,8 +909,10 @@
 		memcpy(cf->path, path, pathlen);
 
 		tmp = kmod_list_append(path_list, cf);
-		if (tmp == NULL)
+		if (tmp == NULL) {
+			free(cf);
 			goto oom;
+		}
 		path_list = tmp;
 	}
 
diff --git a/libkmod/libkmod-internal.h b/libkmod/libkmod-internal.h
index 398af9c..c22644a 100644
--- a/libkmod/libkmod-internal.h
+++ b/libkmod/libkmod-internal.h
@@ -25,10 +25,12 @@
 #  else
 #    define DBG(ctx, arg...) kmod_log_null(ctx, ## arg)
 #  endif
+#  define NOTICE(ctx, arg...) kmod_log_cond(ctx, LOG_NOTICE, ## arg)
 #  define INFO(ctx, arg...) kmod_log_cond(ctx, LOG_INFO, ## arg)
 #  define ERR(ctx, arg...) kmod_log_cond(ctx, LOG_ERR, ## arg)
 #else
 #  define DBG(ctx, arg...) kmod_log_null(ctx, ## arg)
+#  define NOTICE(ctx, arg...) kmod_log_null(ctx, ## arg)
 #  define INFO(ctx, arg...) kmod_log_null(ctx, ## arg)
 #  define ERR(ctx, arg...) kmod_log_null(ctx, ## arg)
 #endif
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 76a6dc3..12d8ed1 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -431,17 +431,18 @@
 			return -EEXIST;
 		}
 
-		*mod = kmod_module_ref(m);
-		return 0;
+		kmod_module_ref(m);
+	} else {
+		err = kmod_module_new(ctx, name, name, namelen, NULL, 0, &m);
+		if (err < 0) {
+			free(abspath);
+			return err;
+		}
+
+		m->path = abspath;
 	}
 
-	err = kmod_module_new(ctx, name, name, namelen, NULL, 0, &m);
-	if (err < 0) {
-		free(abspath);
-		return err;
-	}
-
-	m->path = abspath;
+	m->builtin = KMOD_MODULE_BUILTIN_NO;
 	*mod = m;
 
 	return 0;
@@ -498,13 +499,26 @@
 	return mod;
 }
 
-#define CHECK_ERR_AND_FINISH(_err, _label_err, _list, label_finish)	\
-	do {								\
-		if ((_err) < 0)						\
-			goto _label_err;				\
-		if (*(_list) != NULL)					\
-			goto finish;					\
-	} while (0)
+typedef int (*lookup_func)(struct kmod_ctx *ctx, const char *name, struct kmod_list **list) __attribute__((nonnull(1, 2, 3)));
+
+static int __kmod_module_new_from_lookup(struct kmod_ctx *ctx, const lookup_func lookup[],
+					 size_t lookup_count, const char *s,
+					 struct kmod_list **list)
+{
+	unsigned int i;
+
+	for (i = 0; i < lookup_count; i++) {
+		int err;
+
+		err = lookup[i](ctx, s, list);
+		if (err < 0 && err != -ENOSYS)
+			return err;
+		else if (*list != NULL)
+			return 0;
+	}
+
+	return 0;
+}
 
 /**
  * kmod_module_new_from_lookup:
@@ -520,7 +534,7 @@
  *
  * The search order is: 1. aliases in configuration file; 2. module names in
  * modules.dep index; 3. symbol aliases in modules.symbols index; 4. aliases
- * in modules.alias index.
+ * from install commands; 5. builtin indexes from kernel.
  *
  * The initial refcount is 1, and needs to be decremented to release the
  * resources of the kmod_module. The returned @list must be released by
@@ -537,8 +551,17 @@
 						const char *given_alias,
 						struct kmod_list **list)
 {
-	int err;
+	const lookup_func lookup[] = {
+		kmod_lookup_alias_from_config,
+		kmod_lookup_alias_from_moddep_file,
+		kmod_lookup_alias_from_symbols_file,
+		kmod_lookup_alias_from_commands,
+		kmod_lookup_alias_from_aliases_file,
+		kmod_lookup_alias_from_builtin_file,
+		kmod_lookup_alias_from_kernel_builtin_file,
+	};
 	char alias[PATH_MAX];
+	int err;
 
 	if (ctx == NULL || given_alias == NULL)
 		return -ENOENT;
@@ -555,46 +578,75 @@
 
 	DBG(ctx, "input alias=%s, normalized=%s\n", given_alias, alias);
 
-	/* Aliases from config file override all the others */
-	err = kmod_lookup_alias_from_config(ctx, alias, list);
-	CHECK_ERR_AND_FINISH(err, fail, list, finish);
+	err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup),
+					    alias, list);
 
-	DBG(ctx, "lookup modules.dep %s\n", alias);
-	err = kmod_lookup_alias_from_moddep_file(ctx, alias, list);
-	CHECK_ERR_AND_FINISH(err, fail, list, finish);
+	DBG(ctx, "lookup=%s found=%d\n", alias, err >= 0 && *list);
 
-	DBG(ctx, "lookup modules.symbols %s\n", alias);
-	err = kmod_lookup_alias_from_symbols_file(ctx, alias, list);
-	CHECK_ERR_AND_FINISH(err, fail, list, finish);
-
-	DBG(ctx, "lookup install and remove commands %s\n", alias);
-	err = kmod_lookup_alias_from_commands(ctx, alias, list);
-	CHECK_ERR_AND_FINISH(err, fail, list, finish);
-
-	DBG(ctx, "lookup modules.aliases %s\n", alias);
-	err = kmod_lookup_alias_from_aliases_file(ctx, alias, list);
-	CHECK_ERR_AND_FINISH(err, fail, list, finish);
-
-	DBG(ctx, "lookup modules.builtin.modinfo %s\n", alias);
-	err = kmod_lookup_alias_from_kernel_builtin_file(ctx, alias, list);
-	if (err == -ENOSYS) {
-		/* Optional index missing, try the old one */
-		DBG(ctx, "lookup modules.builtin %s\n", alias);
-		err = kmod_lookup_alias_from_builtin_file(ctx, alias, list);
+	if (err < 0) {
+		kmod_module_unref_list(*list);
+		*list = NULL;
 	}
-	CHECK_ERR_AND_FINISH(err, fail, list, finish);
 
-
-finish:
-	DBG(ctx, "lookup %s=%d, list=%p\n", alias, err, *list);
-	return err;
-fail:
-	DBG(ctx, "Failed to lookup %s\n", alias);
-	kmod_module_unref_list(*list);
-	*list = NULL;
 	return err;
 }
-#undef CHECK_ERR_AND_FINISH
+
+/**
+ * kmod_module_new_from_name_lookup:
+ * @ctx: kmod library context
+ * @modname: module name to look for
+ * @mod: returned module on success
+ *
+ * Lookup by module name, without considering possible aliases. This is similar
+ * to kmod_module_new_from_lookup(), but don't consider as source indexes and
+ * configurations that work with aliases. When succesful, this always resolves
+ * to one and only one module.
+ *
+ * The search order is: 1. module names in modules.dep index;
+ * 2. builtin indexes from kernel.
+ *
+ * The initial refcount is 1, and needs to be decremented to release the
+ * resources of the kmod_module. Since libkmod keeps track of all
+ * kmod_modules created, they are all released upon @ctx destruction too. Do
+ * not unref @ctx before all the desired operations with the returned list are
+ * completed.
+ *
+ * Returns: 0 on success or < 0 otherwise. It fails if any of the lookup
+ * methods failed, which is basically due to memory allocation failure. If
+ * module is not found, it still returns 0, but @mod is left untouched.
+ */
+KMOD_EXPORT int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx,
+						 const char *modname,
+						 struct kmod_module **mod)
+{
+	const lookup_func lookup[] = {
+		kmod_lookup_alias_from_moddep_file,
+		kmod_lookup_alias_from_builtin_file,
+		kmod_lookup_alias_from_kernel_builtin_file,
+	};
+	char name_norm[PATH_MAX];
+	struct kmod_list *list = NULL;
+	int err;
+
+	if (ctx == NULL || modname == NULL || mod == NULL)
+		return -ENOENT;
+
+	modname_normalize(modname, name_norm, NULL);
+
+	DBG(ctx, "input modname=%s, normalized=%s\n", modname, name_norm);
+
+	err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup),
+					    name_norm, &list);
+
+	DBG(ctx, "lookup=%s found=%d\n", name_norm, err >= 0 && list);
+
+	if (err >= 0 && list != NULL)
+		*mod = kmod_module_get_module(list);
+
+	kmod_module_unref_list(list);
+
+	return err;
+}
 
 /**
  * kmod_module_unref_list:
@@ -771,11 +823,13 @@
 /**
  * kmod_module_remove_module:
  * @mod: kmod module
- * @flags: flags to pass to Linux kernel when removing the module. The only valid flag is
+ * @flags: flags used when removing the module.
  * KMOD_REMOVE_FORCE: force remove module regardless if it's still in
- * use by a kernel subsystem or other process;
- * KMOD_REMOVE_NOWAIT is always enforced, causing us to pass O_NONBLOCK to
+ * use by a kernel subsystem or other process; passed directly to Linux kernel
+ * KMOD_REMOVE_NOWAIT: is always enforced, causing us to pass O_NONBLOCK to
  * delete_module(2).
+ * KMOD_REMOVE_NOLOG: when module removal fails, do not log anything as the
+ * caller may want to handle retries and log when appropriate.
  *
  * Remove a module from Linux kernel.
  *
@@ -784,6 +838,8 @@
 KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod,
 							unsigned int flags)
 {
+	unsigned int libkmod_flags = flags & 0xff;
+
 	int err;
 
 	if (mod == NULL)
@@ -796,7 +852,8 @@
 	err = delete_module(mod->name, flags);
 	if (err != 0) {
 		err = -errno;
-		ERR(mod->ctx, "could not remove '%s': %m\n", mod->name);
+		if (!(libkmod_flags & KMOD_REMOVE_NOLOG))
+			ERR(mod->ctx, "could not remove '%s': %m\n", mod->name);
 	}
 
 	return err;
@@ -2277,7 +2334,7 @@
  *
  * After use, free the @list by calling kmod_module_info_free_list().
  *
- * Returns: 0 on success or < 0 otherwise.
+ * Returns: number of entries in @list on success or < 0 otherwise.
  */
 KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, struct kmod_list **list)
 {
@@ -2912,7 +2969,10 @@
 			goto fail;
 		}
 
-		kmod_module_new_from_name(ctx, modname, &mod);
+		err = kmod_module_new_from_name(ctx, modname, &mod);
+		if (err < 0)
+			goto fail;
+
 		kmod_module_set_builtin(mod, true);
 
 		*list = kmod_list_append(*list, mod);
diff --git a/libkmod/libkmod-signature.c b/libkmod/libkmod-signature.c
index 9877cf3..47aedd0 100644
--- a/libkmod/libkmod-signature.c
+++ b/libkmod/libkmod-signature.c
@@ -55,6 +55,7 @@
 	PKEY_HASH_SHA384,
 	PKEY_HASH_SHA512,
 	PKEY_HASH_SHA224,
+	PKEY_HASH_SM3,
 	PKEY_HASH__LAST
 };
 
@@ -67,6 +68,7 @@
 	[PKEY_HASH_SHA384]	= "sha384",
 	[PKEY_HASH_SHA512]	= "sha512",
 	[PKEY_HASH_SHA224]	= "sha224",
+	[PKEY_HASH_SM3]		= "sm3",
 };
 
 enum pkey_id_type {
@@ -160,6 +162,10 @@
 		return PKEY_HASH_SHA512;
 	case NID_sha224:
 		return PKEY_HASH_SHA224;
+# ifndef OPENSSL_NO_SM3
+	case NID_sm3:
+		return PKEY_HASH_SM3;
+# endif
 	default:
 		return -1;
 	}
diff --git a/libkmod/libkmod.c b/libkmod/libkmod.c
index 43423d6..7c2b889 100644
--- a/libkmod/libkmod.c
+++ b/libkmod/libkmod.c
@@ -64,6 +64,7 @@
 static const char *default_config_paths[] = {
 	SYSCONFDIR "/modprobe.d",
 	"/run/modprobe.d",
+	"/usr/local/lib/modprobe.d",
 	"/lib/modprobe.d",
 	NULL
 };
@@ -234,10 +235,11 @@
  *           Otherwise, give an absolute dirname.
  * @config_paths: ordered array of paths (directories or files) where
  *                to load from user-defined configuration parameters such as
- *                alias, blacklists, commands (install, remove). If
- *                NULL defaults to /run/modprobe.d, /etc/modprobe.d and
- *                /lib/modprobe.d. Give an empty vector if configuration should
- *                not be read. This array must be null terminated.
+ *                alias, blacklists, commands (install, remove). If NULL
+ *                defaults to /etc/modprobe.d, /run/modprobe.d,
+ *                /usr/local/lib/modprobe.d and /lib/modprobe.d. Give an empty
+ *                vector if configuration should not be read. This array must
+ *                be null terminated.
  *
  * Create kmod library context. This reads the kmod configuration
  * and fills in the default values.
diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
index 3cab2e5..7251aa7 100644
--- a/libkmod/libkmod.h
+++ b/libkmod/libkmod.h
@@ -129,6 +129,9 @@
 						struct kmod_module **mod);
 int kmod_module_new_from_lookup(struct kmod_ctx *ctx, const char *given_alias,
 						struct kmod_list **list);
+int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx,
+				     const char *modname,
+				     struct kmod_module **mod);
 int kmod_module_new_from_loaded(struct kmod_ctx *ctx,
 						struct kmod_list **list);
 
@@ -142,6 +145,8 @@
 enum kmod_remove {
 	KMOD_REMOVE_FORCE = O_TRUNC,
 	KMOD_REMOVE_NOWAIT = O_NONBLOCK, /* always set */
+	/* libkmod-only defines, not passed to kernel */
+	KMOD_REMOVE_NOLOG = 1,
 };
 
 /* Insertion flags */
diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym
index 5f5e1fb..0c04fda 100644
--- a/libkmod/libkmod.sym
+++ b/libkmod/libkmod.sym
@@ -30,6 +30,7 @@
 	kmod_module_new_from_name;
 	kmod_module_new_from_path;
 	kmod_module_new_from_lookup;
+	kmod_module_new_from_name_lookup;
 	kmod_module_new_from_loaded;
 	kmod_module_ref;
 	kmod_module_unref;
diff --git a/man/depmod.d.xml b/man/depmod.d.xml
index 4341a56..76548e9 100644
--- a/man/depmod.d.xml
+++ b/man/depmod.d.xml
@@ -40,8 +40,9 @@
 
   <refsynopsisdiv>
     <para><filename>/usr/lib/depmod.d/*.conf</filename></para>
-    <para><filename>/etc/depmod.d/*.conf</filename></para>
+    <para><filename>/usr/local/lib/depmod.d/*.conf</filename></para>
     <para><filename>/run/depmod.d/*.conf</filename></para>
+    <para><filename>/etc/depmod.d/*.conf</filename></para>
   </refsynopsisdiv>
 
   <refsect1><title>DESCRIPTION</title>
@@ -130,6 +131,20 @@
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term>exclude <replaceable>excludedir</replaceable>
+        </term>
+        <listitem>
+          <para>
+            This specifies the trailing directories that will be excluded
+            during the search for kernel modules.
+          </para>
+          <para>
+	    The <replaceable>excludedir</replaceable> is the trailing directory
+	    to exclude
+          </para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
diff --git a/man/modprobe.d.xml b/man/modprobe.d.xml
index 211af84..0ab3e91 100644
--- a/man/modprobe.d.xml
+++ b/man/modprobe.d.xml
@@ -41,8 +41,9 @@
 
   <refsynopsisdiv>
     <para><filename>/lib/modprobe.d/*.conf</filename></para>
-    <para><filename>/etc/modprobe.d/*.conf</filename></para>
+    <para><filename>/usr/local/lib/modprobe.d/*.conf</filename></para>
     <para><filename>/run/modprobe.d/*.conf</filename></para>
+    <para><filename>/etc/modprobe.d/*.conf</filename></para>
   </refsynopsisdiv>
 
   <refsect1><title>DESCRIPTION</title>
diff --git a/man/modprobe.xml b/man/modprobe.xml
index 0372b6b..db39c7a 100644
--- a/man/modprobe.xml
+++ b/man/modprobe.xml
@@ -390,6 +390,23 @@
       </varlistentry>
       <varlistentry>
         <term>
+          <option>-w</option>
+        </term>
+        <term>
+        <option>--wait=</option>TIMEOUT_MSEC
+        </term>
+        <listitem>
+          <para>
+            This option causes <command>modprobe -r</command> to continue trying to
+            remove a module if it fails due to the module being busy, i.e. its refcount
+            is not 0 at the time the call is made. Modprobe tries to remove the module
+            with an incremental sleep time between each tentative up until the maximum
+            wait time in milliseconds passed in this option.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
           <option>-S</option>
         </term>
         <term>
diff --git a/shared/util.c b/shared/util.c
index b487b5f..4b547ff 100644
--- a/shared/util.c
+++ b/shared/util.c
@@ -466,6 +466,78 @@
 	       (unsigned long long) ts->tv_nsec / NSEC_PER_USEC;
 }
 
+unsigned long long ts_msec(const struct timespec *ts)
+{
+	return (unsigned long long) ts->tv_sec * MSEC_PER_SEC +
+	       (unsigned long long) ts->tv_nsec / NSEC_PER_MSEC;
+}
+
+static struct timespec msec_ts(unsigned long long msec)
+{
+	struct timespec ts = {
+		.tv_sec = msec / MSEC_PER_SEC,
+		.tv_nsec = (msec % MSEC_PER_SEC) * NSEC_PER_MSEC,
+	};
+
+	return ts;
+}
+
+int sleep_until_msec(unsigned long long msec)
+{
+	struct timespec ts = msec_ts(msec);
+
+	if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) < 0 &&
+	    errno != EINTR)
+		return -errno;
+
+	return 0;
+}
+
+/*
+ * Exponential retry backoff with tail
+ */
+unsigned long long get_backoff_delta_msec(unsigned long long t0,
+					  unsigned long long tend,
+					  unsigned long long *delta)
+{
+	unsigned long long t;
+
+	t = now_msec();
+
+	if (!*delta)
+		*delta = 1;
+	else
+		*delta <<= 1;
+
+	while (t + *delta > tend)
+		*delta >>= 1;
+
+	if (!*delta && tend > t)
+		*delta = tend - t;
+
+	return t + *delta;
+}
+
+unsigned long long now_usec(void)
+{
+	struct timespec ts;
+
+	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+		return 0;
+
+	return ts_usec(&ts);
+}
+
+unsigned long long now_msec(void)
+{
+	struct timespec ts;
+
+	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
+		return 0;
+
+	return ts_msec(&ts);
+}
+
 unsigned long long stat_mstamp(const struct stat *st)
 {
 #ifdef HAVE_STRUCT_STAT_ST_MTIM
diff --git a/shared/util.h b/shared/util.h
index c6a31df..7030653 100644
--- a/shared/util.h
+++ b/shared/util.h
@@ -7,6 +7,7 @@
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <time.h>
 
 #include <shared/macro.h>
 
@@ -42,7 +43,23 @@
 int mkdir_p(const char *path, int len, mode_t mode);
 int mkdir_parents(const char *path, mode_t mode);
 unsigned long long stat_mstamp(const struct stat *st);
+
+/* time-related functions
+ * ************************************************************************ */
+#define USEC_PER_SEC	1000000ULL
+#define USEC_PER_MSEC	1000ULL
+#define MSEC_PER_SEC	1000ULL
+#define NSEC_PER_MSEC	1000000ULL
+
 unsigned long long ts_usec(const struct timespec *ts);
+unsigned long long ts_msec(const struct timespec *ts);
+unsigned long long now_usec(void);
+unsigned long long now_msec(void);
+int sleep_until_msec(unsigned long long msec);
+unsigned long long get_backoff_delta_msec(unsigned long long t0,
+					  unsigned long long tend,
+					  unsigned long long *delta);
+
 
 /* endianess and alignments                                                 */
 /* ************************************************************************ */
diff --git a/testsuite/module-playground/.gitignore b/testsuite/module-playground/.gitignore
index fca12f3..6d9c7b1 100644
--- a/testsuite/module-playground/.gitignore
+++ b/testsuite/module-playground/.gitignore
@@ -1,4 +1,3 @@
-*o.cmd
 *.ko
 !mod-simple-*.ko
 !cache/*.ko
@@ -8,6 +7,7 @@
 *.mod
 *.a
 *.cmd
+*.o.d
 
 modules.order
 Module.symvers
diff --git a/testsuite/module-playground/mod-simple.c b/testsuite/module-playground/mod-simple.c
index cb38580..503e4d8 100644
--- a/testsuite/module-playground/mod-simple.c
+++ b/testsuite/module-playground/mod-simple.c
@@ -1,16 +1,32 @@
+#include <linux/debugfs.h>
 #include <linux/init.h>
 #include <linux/module.h>
 
+static struct dentry *debugfs_dir;
+
+static int test_show(struct seq_file *s, void *data)
+{
+	seq_puts(s, "test");
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(test);
+
 static int __init test_module_init(void)
 {
+	debugfs_dir = debugfs_create_dir(KBUILD_MODNAME, NULL);
+	debugfs_create_file("test", 0444, debugfs_dir, NULL, &test_fops);
+
 	return 0;
 }
 
 static void test_module_exit(void)
 {
+	debugfs_remove_recursive(debugfs_dir);
 }
+
 module_init(test_module_init);
 module_exit(test_module_exit);
 
 MODULE_AUTHOR("Lucas De Marchi <lucas.demarchi@intel.com>");
-MODULE_LICENSE("LGPL");
+MODULE_LICENSE("GPL");
diff --git a/testsuite/populate-modules.sh b/testsuite/populate-modules.sh
index 358e740..099f026 100755
--- a/testsuite/populate-modules.sh
+++ b/testsuite/populate-modules.sh
@@ -4,6 +4,12 @@
 
 MODULE_PLAYGROUND=$1
 ROOTFS=$2
+CONFIG_H=$3
+
+feature_enabled() {
+	local feature=$1
+	grep KMOD_FEATURES  $CONFIG_H | head -n 1 | grep -q \+$feature
+}
 
 declare -A map
 map=(
@@ -66,6 +72,9 @@
 
 gzip_array=(
     "test-depmod/modules-order-compressed/lib/modules/4.4.4/kernel/drivers/block/cciss.ko"
+    )
+
+xz_array=(
     "test-depmod/modules-order-compressed/lib/modules/4.4.4/kernel/drivers/scsi/scsi_mod.ko"
     )
 
@@ -85,38 +94,47 @@
     "test-modinfo/mod-simple-pkcs7.ko"
     )
 
-for k in ${!map[@]}; do
+for k in "${!map[@]}"; do
     dst=${ROOTFS}/$k
     src=${MODULE_PLAYGROUND}/${map[$k]}
 
-    if test "${dst: -1}" = "/"; then
-        install -d $dst
-        install -t $dst $src
+    if [[ $dst = */ ]]; then
+        install -d "$dst"
+        install -t "$dst" "$src"
     else
-        install -D $src $dst
+        install -D "$src" "$dst"
     fi
 done
 
 # start poking the final rootfs...
 
-# gzip these modules
-for m in "${gzip_array[@]}"; do
-    gzip $ROOTFS/$m
-done
+# compress modules with each format if feature is enabled
+if feature_enabled ZLIB; then
+	for m in "${gzip_array[@]}"; do
+	    gzip "$ROOTFS/$m"
+	done
+fi
 
-# zstd-compress these modules
-for m in "${zstd_array[@]}"; do
-    zstd --rm $ROOTFS/$m
-done
+if feature_enabled XZ; then
+	for m in "${xz_array[@]}"; do
+	    xz "$ROOTFS/$m"
+	done
+fi
+
+if feature_enabled ZSTD; then
+	for m in "${zstd_array[@]}"; do
+	    zstd --rm $ROOTFS/$m
+	done
+fi
 
 for m in "${attach_sha1_array[@]}"; do
-    cat ${MODULE_PLAYGROUND}/dummy.sha1 >> ${ROOTFS}/$m
+    cat "${MODULE_PLAYGROUND}/dummy.sha1" >>"${ROOTFS}/$m"
 done
 
 for m in "${attach_sha256_array[@]}"; do
-    cat ${MODULE_PLAYGROUND}/dummy.sha256 >> ${ROOTFS}/$m
+    cat "${MODULE_PLAYGROUND}/dummy.sha256" >>"${ROOTFS}/$m"
 done
 
 for m in "${attach_pkcs7_array[@]}"; do
-    cat ${MODULE_PLAYGROUND}/dummy.pkcs7 >> ${ROOTFS}/$m
+    cat "${MODULE_PLAYGROUND}/dummy.pkcs7" >>"${ROOTFS}/$m"
 done
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/correct.txt b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/correct.txt
new file mode 100644
index 0000000..d80da6d
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/correct.txt
@@ -0,0 +1,5 @@
+options psmouse foo
+options parport dyndbg="file drivers/parport/ieee1284_ops.c +mpf"
+
+# End of configuration files. Dumping indexes now:
+
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/module-param-kcmdline7/correct.txt b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/module-param-kcmdline7/correct.txt
new file mode 100644
index 0000000..d80da6d
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/module-param-kcmdline7/correct.txt
@@ -0,0 +1,5 @@
+options psmouse foo
+options parport dyndbg="file drivers/parport/ieee1284_ops.c +mpf"
+
+# End of configuration files. Dumping indexes now:
+
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/module-param-kcmdline7/proc/cmdline b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/module-param-kcmdline7/proc/cmdline
new file mode 100644
index 0000000..86f9052
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/module-param-kcmdline7/proc/cmdline
@@ -0,0 +1 @@
+psmouse.foo parport.dyndbg="file drivers/parport/ieee1284_ops.c +mpf" quiet rw
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/proc/cmdline b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/proc/cmdline
new file mode 100644
index 0000000..86f9052
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline7/proc/cmdline
@@ -0,0 +1 @@
+psmouse.foo parport.dyndbg="file drivers/parport/ieee1284_ops.c +mpf" quiet rw
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/correct.txt b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/correct.txt
new file mode 100644
index 0000000..d80da6d
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/correct.txt
@@ -0,0 +1,5 @@
+options psmouse foo
+options parport dyndbg="file drivers/parport/ieee1284_ops.c +mpf"
+
+# End of configuration files. Dumping indexes now:
+
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/module-param-kcmdline7/correct.txt b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/module-param-kcmdline7/correct.txt
new file mode 100644
index 0000000..d80da6d
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/module-param-kcmdline7/correct.txt
@@ -0,0 +1,5 @@
+options psmouse foo
+options parport dyndbg="file drivers/parport/ieee1284_ops.c +mpf"
+
+# End of configuration files. Dumping indexes now:
+
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/module-param-kcmdline7/proc/cmdline b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/module-param-kcmdline7/proc/cmdline
new file mode 100644
index 0000000..86f9052
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/module-param-kcmdline7/proc/cmdline
@@ -0,0 +1 @@
+psmouse.foo parport.dyndbg="file drivers/parport/ieee1284_ops.c +mpf" quiet rw
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/proc/cmdline b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/proc/cmdline
new file mode 100644
index 0000000..eab04ad
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline8/proc/cmdline
@@ -0,0 +1 @@
+psmouse.foo "parport.dyndbg=file drivers/parport/ieee1284_ops.c +mpf" quiet rw
diff --git a/testsuite/test-blacklist.c b/testsuite/test-blacklist.c
index 969567d..d03eedb 100644
--- a/testsuite/test-blacklist.c
+++ b/testsuite/test-blacklist.c
@@ -95,6 +95,9 @@
 }
 
 DEFINE_TEST(blacklist_1,
+#if defined(KMOD_SYSCONFDIR_NOT_ETC)
+        .skip = true,
+#endif
 	.description = "check if modules are correctly blacklisted",
 	.config = {
 		[TC_ROOTFS] = TESTSUITE_ROOTFS "test-blacklist/",
diff --git a/testsuite/test-depmod.c b/testsuite/test-depmod.c
index 47dafb4..d7802d7 100644
--- a/testsuite/test-depmod.c
+++ b/testsuite/test-depmod.c
@@ -25,7 +25,6 @@
 
 #include "testsuite.h"
 
-#ifdef ENABLE_ZLIB
 #define MODULES_ORDER_UNAME "4.4.4"
 #define MODULES_ORDER_ROOTFS TESTSUITE_ROOTFS "test-depmod/modules-order-compressed"
 #define MODULES_ORDER_LIB_MODULES MODULES_ORDER_ROOTFS "/lib/modules/" MODULES_ORDER_UNAME
@@ -42,6 +41,9 @@
 }
 
 DEFINE_TEST(depmod_modules_order_for_compressed,
+#if defined(KMOD_SYSCONFDIR_NOT_ETC)
+        .skip = true,
+#endif
 	.description = "check if depmod let aliases in right order when using compressed modules",
 	.config = {
 		[TC_UNAME_R] = MODULES_ORDER_UNAME,
@@ -54,7 +56,6 @@
 			{ }
 		},
 	});
-#endif
 
 #define SEARCH_ORDER_SIMPLE_ROOTFS TESTSUITE_ROOTFS "test-depmod/search-order-simple"
 static noreturn int depmod_search_order_simple(const struct test *t)
@@ -121,6 +122,9 @@
 	exit(EXIT_FAILURE);
 }
 DEFINE_TEST(depmod_detect_loop,
+#if defined(KMOD_SYSCONFDIR_NOT_ETC)
+        .skip = true,
+#endif
 	.description = "check if depmod detects module loops correctly",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -144,6 +148,9 @@
 	exit(EXIT_FAILURE);
 }
 DEFINE_TEST(depmod_search_order_external_first,
+#if defined(KMOD_SYSCONFDIR_NOT_ETC)
+        .skip = true,
+#endif
 	.description = "check if depmod honor external keyword with higher priority",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -196,6 +203,9 @@
 	exit(EXIT_FAILURE);
 }
 DEFINE_TEST(depmod_search_order_override,
+#if defined(KMOD_SYSCONFDIR_NOT_ETC)
+        .skip = true,
+#endif
 	.description = "check if depmod honor override keyword",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
diff --git a/testsuite/test-initstate.c b/testsuite/test-initstate.c
index da2303a..9332e8f 100644
--- a/testsuite/test-initstate.c
+++ b/testsuite/test-initstate.c
@@ -45,7 +45,7 @@
 		exit(EXIT_FAILURE);
 
 	err = kmod_module_new_from_lookup(ctx, "fake-builtin", &list);
-	if (err != 0) {
+	if (err < 0) {
 		ERR("could not create module from lookup: %s\n", strerror(-err));
 		exit(EXIT_FAILURE);
 	}
diff --git a/testsuite/test-modprobe.c b/testsuite/test-modprobe.c
index f908d56..0255f1a 100644
--- a/testsuite/test-modprobe.c
+++ b/testsuite/test-modprobe.c
@@ -83,6 +83,9 @@
 	exit(EXIT_FAILURE);
 }
 DEFINE_TEST(modprobe_show_alias_to_none,
+#if defined(KMOD_SYSCONFDIR_NOT_ETC)
+        .skip = true,
+#endif
 	.description = "check if modprobe --show-depends doesn't explode with an alias to nothing",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -172,6 +175,9 @@
 	exit(EXIT_FAILURE);
 }
 DEFINE_TEST(modprobe_softdep_loop,
+#if defined(KMOD_SYSCONFDIR_NOT_ETC)
+        .skip = true,
+#endif
 	.description = "check if modprobe breaks softdep loop",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -207,7 +213,7 @@
 	.modules_loaded = "mod-loop-b,mod-loop-a",
 	);
 
-static noreturn int modprobe_param_kcmdline(const struct test *t)
+static noreturn int modprobe_param_kcmdline_show_deps(const struct test *t)
 {
 	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
 	const char *const args[] = {
@@ -219,7 +225,7 @@
 	test_spawn_prog(progname, args);
 	exit(EXIT_FAILURE);
 }
-DEFINE_TEST(modprobe_param_kcmdline,
+DEFINE_TEST(modprobe_param_kcmdline_show_deps,
 	.description = "check if params from kcmdline are passed to (f)init_module call",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -231,7 +237,7 @@
 	.modules_loaded = "",
 	);
 
-static noreturn int modprobe_param_kcmdline2(const struct test *t)
+static noreturn int modprobe_param_kcmdline(const struct test *t)
 {
 	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
 	const char *const args[] = {
@@ -243,7 +249,7 @@
 	test_spawn_prog(progname, args);
 	exit(EXIT_FAILURE);
 }
-DEFINE_TEST(modprobe_param_kcmdline2,
+DEFINE_TEST_WITH_FUNC(modprobe_param_kcmdline2, modprobe_param_kcmdline,
 	.description = "check if params with no value are parsed correctly from kcmdline",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -255,19 +261,7 @@
 	.modules_loaded = "",
 	);
 
-static noreturn int modprobe_param_kcmdline3(const struct test *t)
-{
-	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
-	const char *const args[] = {
-		progname,
-		"-c",
-		NULL,
-	};
-
-	test_spawn_prog(progname, args);
-	exit(EXIT_FAILURE);
-}
-DEFINE_TEST(modprobe_param_kcmdline3,
+DEFINE_TEST_WITH_FUNC(modprobe_param_kcmdline3, modprobe_param_kcmdline,
 	.description = "check if unrelated strings in kcmdline are correctly ignored",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -279,19 +273,7 @@
 	.modules_loaded = "",
 	);
 
-static noreturn int modprobe_param_kcmdline4(const struct test *t)
-{
-	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
-	const char *const args[] = {
-		progname,
-		"-c",
-		NULL,
-	};
-
-	test_spawn_prog(progname, args);
-	exit(EXIT_FAILURE);
-}
-DEFINE_TEST(modprobe_param_kcmdline4,
+DEFINE_TEST_WITH_FUNC(modprobe_param_kcmdline4, modprobe_param_kcmdline,
 	.description = "check if unrelated strings in kcmdline are correctly ignored",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -303,19 +285,7 @@
 	.modules_loaded = "",
 	);
 
-static noreturn int modprobe_param_kcmdline5(const struct test *t)
-{
-	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
-	const char *const args[] = {
-		progname,
-		"-c",
-		NULL,
-	};
-
-	test_spawn_prog(progname, args);
-	exit(EXIT_FAILURE);
-}
-DEFINE_TEST(modprobe_param_kcmdline5,
+DEFINE_TEST_WITH_FUNC(modprobe_param_kcmdline5, modprobe_param_kcmdline,
 	.description = "check if params with spaces are parsed correctly from kcmdline",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -327,20 +297,7 @@
 	.modules_loaded = "",
 	);
 
-
-static noreturn int modprobe_param_kcmdline6(const struct test *t)
-{
-	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
-	const char *const args[] = {
-		progname,
-		"-c",
-		NULL,
-	};
-
-	test_spawn_prog(progname, args);
-	exit(EXIT_FAILURE);
-}
-DEFINE_TEST(modprobe_param_kcmdline6,
+DEFINE_TEST_WITH_FUNC(modprobe_param_kcmdline6, modprobe_param_kcmdline,
 	.description = "check if dots on other parts of kcmdline don't confuse our parser",
 	.config = {
 		[TC_UNAME_R] = "4.4.4",
@@ -352,6 +309,30 @@
 	.modules_loaded = "",
 	);
 
+DEFINE_TEST_WITH_FUNC(modprobe_param_kcmdline7, modprobe_param_kcmdline,
+	.description = "check if dots on other parts of kcmdline don't confuse our parser",
+	.config = {
+		[TC_UNAME_R] = "4.4.4",
+		[TC_ROOTFS] = TESTSUITE_ROOTFS "test-modprobe/module-param-kcmdline7",
+	},
+	.output = {
+		.out = TESTSUITE_ROOTFS "test-modprobe/module-param-kcmdline7/correct.txt",
+	},
+	.modules_loaded = "",
+	);
+
+DEFINE_TEST_WITH_FUNC(modprobe_param_kcmdline8, modprobe_param_kcmdline,
+	.description = "check if dots on other parts of kcmdline don't confuse our parser",
+	.config = {
+		[TC_UNAME_R] = "4.4.4",
+		[TC_ROOTFS] = TESTSUITE_ROOTFS "test-modprobe/module-param-kcmdline8",
+	},
+	.output = {
+		.out = TESTSUITE_ROOTFS "test-modprobe/module-param-kcmdline8/correct.txt",
+	},
+	.modules_loaded = "",
+	);
+
 
 static noreturn int modprobe_force(const struct test *t)
 {
diff --git a/testsuite/test-util.c b/testsuite/test-util.c
index 621446b..fb8c9ef 100644
--- a/testsuite/test-util.c
+++ b/testsuite/test-util.c
@@ -229,4 +229,45 @@
 	);
 
 
+static int test_backoff_time(const struct test *t)
+{
+	unsigned long long delta;
+
+	/* Check exponential increments */
+	get_backoff_delta_msec(now_msec(), now_msec() + 10, &delta);
+	assert_return(delta == 1, EXIT_FAILURE);
+	get_backoff_delta_msec(now_msec(), now_msec() + 10, &delta);
+	assert_return(delta == 2, EXIT_FAILURE);
+	get_backoff_delta_msec(now_msec(), now_msec() + 10, &delta);
+	assert_return(delta == 4, EXIT_FAILURE);
+	get_backoff_delta_msec(now_msec(), now_msec() + 10, &delta);
+	assert_return(delta == 8, EXIT_FAILURE);
+
+	{
+		unsigned long long t0, tend;
+
+		/* Check tail */
+		delta = 4;
+		tend = now_msec() + 3;
+		t0 = tend - 10;
+		get_backoff_delta_msec(t0, tend, &delta);
+		assert_return(delta == 2, EXIT_FAILURE);
+		tend = now_msec() + 1;
+		t0 = tend - 9;
+		get_backoff_delta_msec(t0, tend, &delta);
+		assert_return(delta == 1, EXIT_FAILURE);
+		tend = now_msec();
+		t0 = tend - 10;
+		get_backoff_delta_msec(t0, tend, &delta);
+		assert_return(delta == 0, EXIT_FAILURE);
+	}
+
+	return EXIT_SUCCESS;
+}
+DEFINE_TEST(test_backoff_time,
+	.description = "check implementation of get_backoff_delta_msec()",
+	.need_spawn = false,
+	);
+
+
 TESTSUITE_MAIN();
diff --git a/testsuite/testsuite.c b/testsuite/testsuite.c
index e46f3d8..6a2d296 100644
--- a/testsuite/testsuite.c
+++ b/testsuite/testsuite.c
@@ -37,6 +37,7 @@
 #include "testsuite.h"
 
 static const char *ANSI_HIGHLIGHT_GREEN_ON = "\x1B[1;32m";
+static const char *ANSI_HIGHLIGHT_YELLOW_ON = "\x1B[1;33m";
 static const char *ANSI_HIGHLIGHT_RED_ON =  "\x1B[1;31m";
 static const char *ANSI_HIGHLIGHT_OFF = "\x1B[0m";
 
@@ -50,6 +51,7 @@
 };
 
 #define OVERRIDE_LIBDIR ABS_TOP_BUILDDIR "/testsuite/.libs/"
+#define TEST_TIMEOUT_USEC 2 * USEC_PER_SEC
 
 struct _env_config {
 	const char *key;
@@ -61,19 +63,6 @@
 	[TC_DELETE_MODULE_RETCODES] = { S_TC_DELETE_MODULE_RETCODES, OVERRIDE_LIBDIR "delete_module.so" },
 };
 
-#define USEC_PER_SEC  1000000ULL
-#define USEC_PER_MSEC  1000ULL
-#define TEST_TIMEOUT_USEC 2 * USEC_PER_SEC
-static unsigned long long now_usec(void)
-{
-	struct timespec ts;
-
-	if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
-		return 0;
-
-	return ts_usec(&ts);
-}
-
 static void help(void)
 {
 	const struct option *itr;
@@ -948,6 +937,14 @@
 	int err;
 	bool matchout, match_modules;
 
+	if (t->skip) {
+		LOG("%sSKIPPED%s: %s\n",
+			ANSI_HIGHLIGHT_YELLOW_ON, ANSI_HIGHLIGHT_OFF,
+			t->name);
+		err = EXIT_SUCCESS;
+		goto exit;
+	}
+
 	/* Close write-fds */
 	if (t->output.out != NULL)
 		close(fdout[1]);
diff --git a/testsuite/testsuite.h b/testsuite/testsuite.h
index f190249..44d1730 100644
--- a/testsuite/testsuite.h
+++ b/testsuite/testsuite.h
@@ -109,6 +109,8 @@
 	const struct keyval *env_vars;
 	bool need_spawn;
 	bool expected_fail;
+	/* allow to skip tests that don't meet compile-time dependencies */
+	bool skip;
 	bool print_outputs;
 } __attribute__((aligned(8)));
 
@@ -138,14 +140,16 @@
 
 
 /* Test definitions */
-#define DEFINE_TEST(_name, ...) \
+#define DEFINE_TEST_WITH_FUNC(_name, _func, ...) \
 	static const struct test UNIQ(s##_name) \
 	__attribute__((used, section("kmod_tests"), aligned(8))) = { \
 		.name = #_name, \
-		.func = _name, \
+		.func = _func, \
 		## __VA_ARGS__ \
 	};
 
+#define DEFINE_TEST(_name, ...) DEFINE_TEST_WITH_FUNC(_name, _name, __VA_ARGS__)
+
 #define TESTSUITE_MAIN() \
 	extern struct test __start_kmod_tests[] __attribute__((weak, visibility("hidden")));	\
 	extern struct test __stop_kmod_tests[] __attribute__((weak, visibility("hidden")));	\
diff --git a/tools/depmod.c b/tools/depmod.c
index 3f31cdf..364b7d4 100644
--- a/tools/depmod.c
+++ b/tools/depmod.c
@@ -51,8 +51,9 @@
 static const char CFG_BUILTIN_KEY[] = "built-in";
 static const char CFG_EXTERNAL_KEY[] = "external";
 static const char *default_cfg_paths[] = {
-	"/run/depmod.d",
 	SYSCONFDIR "/depmod.d",
+	"/run/depmod.d",
+	"/usr/local/lib/depmod.d",
 	"/lib/depmod.d",
 	NULL
 };
@@ -457,6 +458,11 @@
 	char path[];
 };
 
+struct cfg_exclude {
+	struct cfg_exclude *next;
+	char exclude_dir[];
+};
+
 struct cfg {
 	const char *kversion;
 	char dirname[PATH_MAX];
@@ -468,6 +474,7 @@
 	struct cfg_override *overrides;
 	struct cfg_search *searches;
 	struct cfg_external *externals;
+	struct cfg_exclude *excludes;
 };
 
 static enum search_type cfg_define_search_type(const char *path)
@@ -579,6 +586,30 @@
 	free(ext);
 }
 
+static int cfg_exclude_add(struct cfg *cfg, const char *path)
+{
+	struct cfg_exclude *exc;
+	size_t len = strlen(path);
+
+	exc = malloc(sizeof(struct cfg_exclude) + len + 1);
+	if (exc == NULL) {
+		ERR("exclude add: out of memory\n");
+		return -ENOMEM;
+	}
+	memcpy(exc->exclude_dir, path, len + 1);
+
+	DBG("exclude add: %s\n", path);
+
+	exc->next = cfg->excludes;
+	cfg->excludes = exc;
+	return 0;
+}
+
+static void cfg_exclude_free(struct cfg_exclude *exc)
+{
+	free(exc);
+}
+
 static int cfg_kernel_matches(const struct cfg *cfg, const char *pattern)
 {
 	regex_t re;
@@ -656,6 +687,11 @@
 			}
 
 			cfg_external_add(cfg, dir);
+		} else if (streq(cmd, "exclude")) {
+			const char *sp;
+			while ((sp = strtok_r(NULL, "\t ", &saveptr)) != NULL) {
+				cfg_exclude_add(cfg, sp);
+			}
 		} else if (streq(cmd, "include")
 				|| streq(cmd, "make_map_files")) {
 			INF("%s:%u: command %s not implemented yet\n",
@@ -856,6 +892,12 @@
 		cfg->externals = cfg->externals->next;
 		cfg_external_free(tmp);
 	}
+
+	while (cfg->excludes) {
+		struct cfg_exclude *tmp = cfg->excludes;
+		cfg->excludes = cfg->excludes->next;
+		cfg_exclude_free(tmp);
+	}
 }
 
 
@@ -1228,6 +1270,25 @@
 	return 0;
 }
 
+static bool should_exclude_dir(const struct cfg *cfg, const char *name)
+{
+	struct cfg_exclude *exc;
+
+	if (name[0] == '.' && (name[1] == '\0' ||
+			(name[1] == '.' && name[2] == '\0')))
+		return true;
+
+	if (streq(name, "build") || streq(name, "source"))
+		return true;
+
+	for (exc = cfg->excludes; exc != NULL; exc = exc->next) {
+		if (streq(name, exc->exclude_dir))
+			return true;
+	}
+
+	return false;
+}
+
 static int depmod_modules_search_dir(struct depmod *depmod, DIR *d, size_t baselen, struct scratchbuf *s_path)
 {
 	struct dirent *de;
@@ -1239,11 +1300,9 @@
 		size_t namelen;
 		uint8_t is_dir;
 
-		if (name[0] == '.' && (name[1] == '\0' ||
-				       (name[1] == '.' && name[2] == '\0')))
+		if (should_exclude_dir(depmod->cfg, name))
 			continue;
-		if (streq(name, "build") || streq(name, "source"))
-			continue;
+
 		namelen = strlen(name);
 		if (scratchbuf_alloc(s_path, baselen + namelen + 2) < 0) {
 			err = -ENOMEM;
@@ -2345,6 +2404,103 @@
 	return 0;
 }
 
+static int flush_stream(FILE *in, int endchar)
+{
+	size_t i = 0;
+	int c;
+
+	for (c = fgetc(in);
+	     c != EOF && c != endchar && c != '\0';
+	     c = fgetc(in))
+		;
+
+	return c == endchar ? i : 0;
+}
+
+static int flush_stream_to(FILE *in, int endchar, char *dst, size_t dst_sz)
+{
+	size_t i = 0;
+	int c;
+
+	for (c = fgetc(in);
+	     c != EOF && c != endchar && c != '\0' && i < dst_sz;
+	     c = fgetc(in))
+		dst[i++] = c;
+
+	if (i == dst_sz) {
+		WRN("Could not flush stream: %d. Partial content: %.*s\n",
+		    ENOSPC, (int) dst_sz, dst);
+		i--;
+	}
+
+	return c == endchar ? i : 0;
+}
+
+static int output_builtin_alias_bin(struct depmod *depmod, FILE *out)
+{
+	FILE *in;
+	struct index_node *idx;
+	int ret;
+
+	if (out == stdout)
+		return 0;
+
+	in = dfdopen(depmod->cfg->dirname, "modules.builtin.modinfo", O_RDONLY, "r");
+	if (in == NULL)
+		return 0;
+
+	idx = index_create();
+	if (idx == NULL) {
+		fclose(in);
+		return -ENOMEM;
+	}
+
+	/* format: modname.key=value\0 */
+	while (!feof(in) && !ferror(in)) {
+		char alias[PATH_MAX];
+		char modname[PATH_MAX];
+		char value[PATH_MAX];
+		size_t len;
+
+		len = flush_stream_to(in, '.', modname, sizeof(modname));
+		modname[len] = '\0';
+		if (!len)
+			continue;
+
+		len = flush_stream_to(in, '=', value, sizeof(value));
+		value[len] = '\0';
+		if (!streq(value, "alias")) {
+			flush_stream(in, '\0');
+			continue;
+		}
+
+		len = flush_stream_to(in, '\0', value, sizeof(value));
+		value[len] = '\0';
+		if (!len)
+			continue;
+
+		alias[0] = '\0';
+		if (alias_normalize(value, alias, NULL) < 0) {
+			WRN("Unmatched bracket in %s\n", value);
+			continue;
+		}
+
+		index_insert(idx, alias, modname, 0);
+	}
+
+	if (ferror(in)) {
+		ret = -EINVAL;
+	} else {
+		index_write(idx, out);
+		ret = 0;
+	}
+
+	index_destroy(idx);
+	fclose(in);
+
+	return ret;
+}
+
 static int output_devname(struct depmod *depmod, FILE *out)
 {
 	size_t i;
@@ -2402,71 +2558,6 @@
 	return 0;
 }
 
-static int output_builtin_alias_bin(struct depmod *depmod, FILE *out)
-{
-	int ret = 0, count = 0;
-	struct index_node *idx;
-	struct kmod_list *l, *builtin = NULL;
-
-	if (out == stdout)
-		return 0;
-
-	idx = index_create();
-	if (idx == NULL)
-		return -ENOMEM;
-
-	ret = kmod_module_get_builtin(depmod->ctx, &builtin);
-	if (ret < 0) {
-		if (ret == -ENOENT)
-			ret = 0;
-		goto out;
-	}
-
-	kmod_list_foreach(l, builtin) {
-		struct kmod_list *ll, *info_list = NULL;
-		struct kmod_module *mod = l->data;
-		const char *modname = kmod_module_get_name(mod);
-
-		ret = kmod_module_get_info(mod, &info_list);
-		if (ret < 0)
-			goto out;
-
-		kmod_list_foreach(ll, info_list) {
-			char alias[PATH_MAX];
-			const char *key = kmod_module_info_get_key(ll);
-			const char *value = kmod_module_info_get_value(ll);
-
-			if (!streq(key, "alias"))
-				continue;
-
-			alias[0] = '\0';
-			if (alias_normalize(value, alias, NULL) < 0) {
-				WRN("Unmatched bracket in %s\n", value);
-				continue;
-			}
-
-			index_insert(idx, alias, modname, 0);
-		}
-
-		kmod_module_info_free_list(info_list);
-
-		index_insert(idx, modname, modname, 0);
-		count++;
-	}
-
-out:
-	/* do not bother writing the index if we are going to discard it */
-	if (!ret)
-		index_write(idx, out);
-
-	if (builtin)
-		kmod_module_unref_list(builtin);
-
-	index_destroy(idx);
-
-	return ret;
-}
-
 static int depmod_output(struct depmod *depmod, FILE *out)
 {
 	static const struct depfile {
diff --git a/tools/modinfo.c b/tools/modinfo.c
index 0231bb0..d0aab20 100644
--- a/tools/modinfo.c
+++ b/tools/modinfo.c
@@ -178,7 +178,11 @@
 	is_builtin = (filename == NULL);
 
 	if (is_builtin) {
-		printf("%-16s%s%c", "name:", kmod_module_get_name(mod), separator);
+		if (field == NULL)
+			printf("%-16s%s%c", "name:",
+			       kmod_module_get_name(mod), separator);
+		else if (field != NULL && streq(field, "name"))
+			printf("%s%c", kmod_module_get_name(mod), separator);
 		filename = "(builtin)";
 	}
 
@@ -289,6 +293,24 @@
 	return err;
 }
 
+static int modinfo_name_do(struct kmod_ctx *ctx, const char *name)
+{
+	struct kmod_module *mod = NULL;
+	int err;
+
+	err = kmod_module_new_from_name_lookup(ctx, name, &mod);
+	if (err < 0 || mod == NULL) {
+		ERR("Module name %s not found.\n", name);
+		return err < 0 ? err : -ENOENT;
+	}
+
+	err = modinfo_do(mod);
+	kmod_module_unref(mod);
+
+	return err;
+}
+
+
 static int modinfo_alias_do(struct kmod_ctx *ctx, const char *alias)
 {
 	struct kmod_list *l, *list = NULL;
@@ -314,7 +336,7 @@
 	return err;
 }
 
-static const char cmdopts_s[] = "adlpn0F:k:b:Vh";
+static const char cmdopts_s[] = "adlpn0mF:k:b:Vh";
 static const struct option cmdopts[] = {
 	{"author", no_argument, 0, 'a'},
 	{"description", no_argument, 0, 'd'},
@@ -322,6 +344,7 @@
 	{"parameters", no_argument, 0, 'p'},
 	{"filename", no_argument, 0, 'n'},
 	{"null", no_argument, 0, '0'},
+	{"modname", no_argument, 0, 'm'},
 	{"field", required_argument, 0, 'F'},
 	{"set-version", required_argument, 0, 'k'},
 	{"basedir", required_argument, 0, 'b'},
@@ -333,7 +356,7 @@
 static void help(void)
 {
 	printf("Usage:\n"
-		"\t%s [options] filename [args]\n"
+		"\t%s [options] <modulename|filename> [args]\n"
 		"Options:\n"
 		"\t-a, --author                Print only 'author'\n"
 		"\t-d, --description           Print only 'description'\n"
@@ -341,6 +364,7 @@
 		"\t-p, --parameters            Print only 'parm'\n"
 		"\t-n, --filename              Print only 'filename'\n"
 		"\t-0, --null                  Use \\0 instead of \\n\n"
+		"\t-m, --modname               Handle argument as module name instead of alias or filename\n"
 		"\t-F, --field=FIELD           Print only provided FIELD\n"
 		"\t-k, --set-version=VERSION   Use VERSION instead of `uname -r`\n"
 		"\t-b, --basedir=DIR           Use DIR as filesystem root for /lib/modules\n"
@@ -368,6 +392,7 @@
 	const char *kversion = NULL;
 	const char *root = NULL;
 	const char *null_config = NULL;
+	bool arg_is_modname = false;
 	int i, err;
 
 	for (;;) {
@@ -394,6 +419,9 @@
 		case '0':
 			separator = '\0';
 			break;
+		case 'm':
+			arg_is_modname = true;
+			break;
 		case 'F':
 			field = optarg;
 			break;
@@ -450,7 +478,9 @@
 		const char *name = argv[i];
 		int r;
 
-		if (is_module_filename(name))
+		if (arg_is_modname)
+			r = modinfo_name_do(ctx, name);
+		else if (is_module_filename(name))
 			r = modinfo_path_do(ctx, name);
 		else
 			r = modinfo_alias_do(ctx, name);
diff --git a/tools/modprobe.c b/tools/modprobe.c
index 9387537..2a2ae21 100644
--- a/tools/modprobe.c
+++ b/tools/modprobe.c
@@ -32,6 +32,7 @@
 #include <sys/wait.h>
 
 #include <shared/array.h>
+#include <shared/util.h>
 #include <shared/macro.h>
 
 #include <libkmod/libkmod.h>
@@ -54,14 +55,19 @@
 static int force = 0;
 static int strip_modversion = 0;
 static int strip_vermagic = 0;
-static int remove_dependencies = 0;
+static int remove_holders = 0;
+static unsigned long long wait_msec = 0;
 static int quiet_inuse = 0;
 
-static const char cmdopts_s[] = "arRibfDcnC:d:S:sqvVh";
+static const char cmdopts_s[] = "arw:RibfDcnC:d:S:sqvVh";
 static const struct option cmdopts[] = {
 	{"all", no_argument, 0, 'a'},
+
 	{"remove", no_argument, 0, 'r'},
 	{"remove-dependencies", no_argument, 0, 5},
+	{"remove-holders", no_argument, 0, 5},
+	{"wait", required_argument, 0, 'w'},
+
 	{"resolve-alias", no_argument, 0, 'R'},
 	{"first-time", no_argument, 0, 3},
 	{"ignore-install", no_argument, 0, 'i'},
@@ -107,8 +113,11 @@
 		"\t                            be a module name to be inserted\n"
 		"\t                            or removed (-r)\n"
 		"\t-r, --remove                Remove modules instead of inserting\n"
-		"\t    --remove-dependencies   Also remove modules depending on it\n"
-		"\t-R, --resolve-alias         Only lookup and print alias and exit\n"
+		"\t    --remove-dependencies   Deprecated: use --remove-holders\n"
+		"\t    --remove-holders        Also remove module holders (use together with -r)\n"
+		"\t-w, --wait <MSEC>           When removing a module, wait up to MSEC for\n"
+		"\t                            module's refcount to become 0 so it can be\n"
+		"\t                            removed (use together with -r)\n"
 		"\t    --first-time            Fail if module already inserted or removed\n"
 		"\t-i, --ignore-install        Ignore install commands\n"
 		"\t-i, --ignore-remove         Ignore remove commands\n"
@@ -120,6 +129,7 @@
 		"\t    --force-vermagic        Ignore module's version magic\n"
 		"\n"
 		"Query Options:\n"
+		"\t-R, --resolve-alias         Only lookup and print alias and exit\n"
 		"\t-D, --show-depends          Only print module dependencies and exit\n"
 		"\t-c, --showconfig            Print out known configuration and exit\n"
 		"\t-c, --show-config           Same as --showconfig\n"
@@ -320,10 +330,11 @@
 static int rmmod_do_remove_module(struct kmod_module *mod)
 {
 	const char *modname = kmod_module_get_name(mod);
-	struct kmod_list *deps, *itr;
+	unsigned long long interval_msec = 0, t0_msec = 0,
+		      tend_msec = 0;
 	int flags = 0, err;
 
-	SHOW("rmmod %s\n", kmod_module_get_name(mod));
+	SHOW("rmmod %s\n", modname);
 
 	if (dry_run)
 		return 0;
@@ -331,33 +342,55 @@
 	if (force)
 		flags |= KMOD_REMOVE_FORCE;
 
-	err = kmod_module_remove_module(mod, flags);
-	if (err == -EEXIST) {
-		if (!first_time)
-			err = 0;
-		else
-			LOG("Module %s is not in kernel.\n", modname);
-	}
+	if (wait_msec)
+		flags |= KMOD_REMOVE_NOLOG;
 
-	deps = kmod_module_get_dependencies(mod);
-	if (deps != NULL) {
-		kmod_list_foreach(itr, deps) {
-			struct kmod_module *dep = kmod_module_get_module(itr);
-			if (kmod_module_get_refcnt(dep) == 0)
-				rmmod_do_remove_module(dep);
-			kmod_module_unref(dep);
+	do {
+		err = kmod_module_remove_module(mod, flags);
+		if (err == -EEXIST) {
+			if (!first_time)
+				err = 0;
+			else
+				LOG("Module %s is not in kernel.\n", modname);
+			break;
+		} else if (err == -EAGAIN && wait_msec) {
+			unsigned long long until_msec;
+
+			if (!t0_msec) {
+				t0_msec = now_msec();
+				tend_msec = t0_msec + wait_msec;
+				interval_msec = 1;
+			}
+
+			until_msec = get_backoff_delta_msec(t0_msec, tend_msec,
+							  &interval_msec);
+			err = sleep_until_msec(until_msec);
+
+			if (!t0_msec)
+				err = -ENOTSUP;
+
+			if (err < 0) {
+				ERR("Failed to sleep: %s\n", strerror(-err));
+				err = -EAGAIN;
+				break;
+			}
+		} else {
+			break;
 		}
-		kmod_module_unref_list(deps);
-	}
+	} while (interval_msec);
+
+	if (err < 0 && wait_msec)
+		ERR("could not remove '%s': %s\n", modname, strerror(-err));
 
 	return err;
 }
 
-#define RMMOD_FLAG_DO_DEPENDENCIES	0x1
+#define RMMOD_FLAG_REMOVE_HOLDERS	0x1
 #define RMMOD_FLAG_IGNORE_BUILTIN	0x2
 static int rmmod_do_module(struct kmod_module *mod, int flags);
 
-static int rmmod_do_deps_list(struct kmod_list *list, bool stop_on_errors)
+/* Remove modules in reverse order */
+static int rmmod_do_modlist(struct kmod_list *list, bool stop_on_errors)
 {
 	struct kmod_list *l;
 
@@ -391,7 +424,8 @@
 		cmd = kmod_module_get_remove_commands(mod);
 	}
 
-	if (cmd == NULL && !ignore_loaded) {
+	/* Quick check if module is loaded, otherwise there's nothing to do */
+	if (!cmd && !ignore_loaded) {
 		int state = kmod_module_get_initstate(mod);
 
 		if (state < 0) {
@@ -413,17 +447,20 @@
 		}
 	}
 
-	rmmod_do_deps_list(post, false);
+	/* 1. @mod's post-softdeps in reverse order */
+	rmmod_do_modlist(post, false);
 
-	if ((flags & RMMOD_FLAG_DO_DEPENDENCIES) && remove_dependencies) {
-		struct kmod_list *deps = kmod_module_get_dependencies(mod);
+	/* 2. Other modules holding @mod */
+	if (flags & RMMOD_FLAG_REMOVE_HOLDERS) {
+		struct kmod_list *holders = kmod_module_get_holders(mod);
 
-		err = rmmod_do_deps_list(deps, true);
+		err = rmmod_do_modlist(holders, true);
 		if (err < 0)
 			goto error;
 	}
 
-	if (!ignore_loaded && !cmd) {
+	/* 3. @mod itself, but check for refcnt first */
+	if (!cmd && !ignore_loaded && !wait_msec) {
 		int usage = kmod_module_get_refcnt(mod);
 
 		if (usage > 0) {
@@ -435,7 +472,7 @@
 		}
 	}
 
-	if (cmd == NULL)
+	if (!cmd)
 		err = rmmod_do_remove_module(mod);
 	else
 		err = command_do(mod, "remove", cmd, NULL);
@@ -443,7 +480,22 @@
 	if (err < 0)
 		goto error;
 
-	rmmod_do_deps_list(pre, false);
+	/* 4. Other modules that became unused: errors are non-fatal */
+	if (!cmd) {
+		struct kmod_list *deps, *itr;
+
+		deps = kmod_module_get_dependencies(mod);
+		kmod_list_foreach(itr, deps) {
+			struct kmod_module *dep = kmod_module_get_module(itr);
+			if (kmod_module_get_refcnt(dep) == 0)
+				rmmod_do_remove_module(dep);
+			kmod_module_unref(dep);
+		}
+		kmod_module_unref_list(deps);
+	}
+
+	/* 5. @mod's pre-softdeps in reverse order: errors are non-fatal */
+	rmmod_do_modlist(pre, false);
 
 error:
 	kmod_module_unref_list(pre);
@@ -468,7 +520,9 @@
 
 	kmod_list_foreach(l, list) {
 		struct kmod_module *mod = kmod_module_get_module(l);
-		err = rmmod_do_module(mod, RMMOD_FLAG_DO_DEPENDENCIES);
+		int flags = remove_holders ? RMMOD_FLAG_REMOVE_HOLDERS : 0;
+
+		err = rmmod_do_module(mod, flags);
 		kmod_module_unref(mod);
 		if (err < 0)
 			break;
@@ -683,7 +737,7 @@
 static char **prepend_options_from_env(int *p_argc, char **orig_argv)
 {
 	const char *p, *env = getenv("MODPROBE_OPTIONS");
-	char **new_argv, *str_start, *str_end, *str, *s, *quote;
+	char **new_argv, *str_end, *str, *s, *quote;
 	int i, argc = *p_argc;
 	size_t envlen, space_count = 0;
 
@@ -701,10 +755,10 @@
 		return NULL;
 
 	new_argv[0] = orig_argv[0];
-	str_start = str = (char *) (new_argv + argc + space_count + 3);
+	str = (char *) (new_argv + argc + space_count + 3);
 	memcpy(str, env, envlen + 1);
 
-	str_end = str_start + envlen;
+	str_end = str + envlen;
 
 	quote = NULL;
 	for (i = 1, s = str; *s != '\0'; s++) {
@@ -743,7 +797,7 @@
 	}
 
 	memcpy(new_argv + i, orig_argv + 1, sizeof(char *) * (argc - 1));
-	new_argv[i + argc] = NULL;
+	new_argv[i + argc - 1] = NULL;
 	*p_argc = i + argc - 1;
 
 	return new_argv;
@@ -786,11 +840,18 @@
 			do_remove = 1;
 			break;
 		case 5:
-			remove_dependencies = 1;
+			remove_holders = 1;
 			break;
-		case 'R':
-			lookup_only = 1;
+		case 'w': {
+			char *endptr = NULL;
+			wait_msec = strtoul(optarg, &endptr, 0);
+			if (!*optarg || *endptr) {
+				ERR("unexpected wait value '%s'.\n", optarg);
+				err = -1;
+				goto done;
+			}
 			break;
+		}
 		case 3:
 			first_time = 1;
 			break;
@@ -814,6 +875,9 @@
 			dry_run = 1;
 			do_show = 1;
 			break;
+		case 'R':
+			lookup_only = 1;
+			break;
 		case 'c':
 			do_show_config = 1;
 			break;