Upgrade kmod to v26

Test: None
Change-Id: I908104a8e3a4d88dad099d5332c487471dfad957
diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml
new file mode 100644
index 0000000..db47ca1
--- /dev/null
+++ b/.semaphore/semaphore.yml
@@ -0,0 +1,46 @@
+version: v1.0
+name: Build and Check
+agent:
+  machine:
+    type: e1-standard-2
+    os_image: ubuntu1804
+
+blocks:
+  - name: "Build"
+    task:
+      jobs:
+        - name: Build gcc-8
+          commands:
+            - sem-version c 8
+        - name: Build gcc-7
+          commands:
+            - sem-version c 7
+        - name: Build gcc-6
+          commands:
+            - sem-version c 6
+
+      prologue:
+        commands:
+          - sudo apt update
+          - sudo apt --yes install docbook-xsl liblzma-dev zlib1g-dev cython linux-headers-generic libssl-dev
+          - checkout
+
+      epilogue:
+        commands:
+          - ./autogen.sh c
+          - make
+
+  - name: "Unit tests"
+    task:
+      jobs:
+        - name: check
+          commands:
+            - sem-version c 8
+            - ./autogen.sh c
+            - make check
+
+      prologue:
+        commands:
+          - sudo apt update
+          - sudo apt --yes install docbook-xsl liblzma-dev zlib1g-dev cython linux-headers-generic libssl-dev
+          - checkout
diff --git a/.travis.yml b/.travis.yml
index f3262a5..4b36e1f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,7 +22,7 @@
       env: MYCC=gcc-4.9
     - compiler: clang
       env: MYCC=clang
-script: ./bootstrap-configure && make -j && make -j check
+script: ./autogen.sh c --without-openssl && make -j && make -j check
 notifications:
   irc:
     channels:
diff --git a/METADATA b/METADATA
index 35dcacc..11a0872 100644
--- a/METADATA
+++ b/METADATA
@@ -5,10 +5,10 @@
     type: GIT
     value: "https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git"
   }
-  version: "v25"
+  version: "v26"
   last_upgrade_date {
-    year: 2018
-    month: 10
-    day: 29
+    year: 2019
+    month: 2
+    day: 7
   }
 }
diff --git a/Makefile.am b/Makefile.am
index 194e111..ddb25f0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -35,13 +35,15 @@
 	-e 's,@liblzma_LIBS\@,${liblzma_LIBS},g' \
 	-e 's,@zlib_CFLAGS\@,${zlib_CFLAGS},g' \
 	-e 's,@zlib_LIBS\@,${zlib_LIBS},g' \
+	-e 's,@openssl_CFLAGS\@,${openssl_CFLAGS},g' \
+	-e 's,@openssl_LIBS\@,${openssl_LIBS},g' \
 	< $< > $@ || rm $@
 
 %.pc: %.pc.in Makefile
 	$(SED_PROCESS)
 
 LIBKMOD_CURRENT=5
-LIBKMOD_REVISION=3
+LIBKMOD_REVISION=4
 LIBKMOD_AGE=3
 
 noinst_LTLIBRARIES = shared/libshared.la
@@ -87,7 +89,7 @@
 	${top_srcdir}/libkmod/libkmod.sym
 libkmod_libkmod_la_LIBADD = \
 	shared/libshared.la \
-	${liblzma_LIBS} ${zlib_LIBS}
+	${liblzma_LIBS} ${zlib_LIBS} ${openssl_LIBS}
 
 noinst_LTLIBRARIES += libkmod/libkmod-internal.la
 libkmod_libkmod_internal_la_SOURCES = $(libkmod_libkmod_la_SOURCES)
@@ -270,6 +272,7 @@
 
 EXTRA_DIST += \
 	testsuite/module-playground/cache \
+	testsuite/module-playground/dummy.pkcs7 \
 	testsuite/module-playground/dummy.sha1 \
 	testsuite/module-playground/dummy.sha256 \
 	testsuite/module-playground/Makefile \
@@ -404,7 +407,7 @@
 EXTRA_DIST += testsuite/rootfs-pristine
 
 DISTCHECK_CONFIGURE_FLAGS=--enable-gtk-doc --enable-python --sysconfdir=/etc \
-	--with-zlib \
+	--with-zlib --with-openssl \
 	--with-bashcompletiondir=$$dc_install_base/$(bashcompletiondir)
 
 distclean-local: $(DISTCLEAN_LOCAL_HOOKS)
@@ -459,7 +462,7 @@
 
 kmod-coverity-%.tar.xz:
 	rm -rf $< cov-int
-	./bootstrap-configure --disable-python --disable-manpages
+	./autogen.sh c --disable-python --disable-manpages
 	make clean
 	cov-build --dir cov-int make -j 4
 	tar caf $@ cov-int
diff --git a/NEWS b/NEWS
index 58a770e..58c4e2f 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,50 @@
+kmod 26
+=======
+
+- Improvements
+	- Add more error-checking in library functions and remove warnings on newer
+	  toolchains
+
+	- Depmod now handles parallel invoctions better by protecting the temporary
+	  files being used
+
+	- Improvements to testsuite and added tests to check the our behavior
+	  regardless of the features enabled in the kernel, or libraries we link to
+
+	- Teach the --show-exports option to modprobe. This works similarly to
+	  --show-modversions, but it reports the exported symbols from that module.
+	  Under the hood this reads the .symtab and .strtab section rather than
+	  __versions so it shows useful data even if kernel is configured without
+	  modversions (CONFIG_MODVERSIONS)
+
+	- Teach pkcs7 parsing to modinfo by using openssl. This allows modinfo to
+	  correctly parse the signature appended to a module by the kernel build
+	  system when configured with CONFIG_MODULE_SIG_ALL, or when externally
+	  signed by the distro. Traditionally modules were signed and a struct
+	  was appended together with the signature to the end of the module.
+	  This has changed on the kernel for pkcs#7 and now the structure isn't
+	  filled out with useful information.  So we have to parse the signature
+	  block in order to return useful data to the user.
+
+	  If kmod is linked with openssl we parse the signature and return the
+	  fields as we do for other signatures. An example of the relevant part
+	  on the output of modinfo is below:
+
+	  Before:
+		  sig_id:         PKCS#7
+		  signer:
+		  sig_key:
+		  sig_hashalgo:   md4
+	  After:
+		  sig_id:         PKCS#7
+		  signer:         Fedora kernel signing key
+		  sig_key:        51:C4:0C:6D:7E:A5:6C:D8:8F:B4:3A:DF:91:78:4F:18:BC:D5:E4:C5
+		  sig_hashalgo:   sha256
+
+	  If kmod is not linked to openssl we just start printing "unknonwn" in the
+	  sig_hashalgo field rather than the bogus value.
+
+
 kmod 25
 =======
 
diff --git a/README b/README
index fa215af..a0226e3 100644
--- a/README
+++ b/README
@@ -3,11 +3,12 @@
 Information
 ===========
 
-Build status:
-	[![Build Status](https://semaphoreci.com/api/v1/projects/29d989ba-0f70-4006-be21-550f6692b73b/449920/shields_badge.svg)](https://semaphoreci.com/lucasdemarchi/kmod)
+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/
@@ -65,8 +66,8 @@
 Hacking
 =======
 
-Run 'bootstrap' script before configure. If you want to accept the recommended
-flags, you just need to run 'bootstrap-configure'. Note that the recommended
+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
diff --git a/README.md b/README.md
index cd4cdc8..d3b84bd 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
 ## kmod - Linux kernel module handling
 
-[![Build Status](https://semaphoreci.com/api/v1/projects/29d989ba-0f70-4006-be21-550f6692b73b/449920/shields_badge.svg)](https://semaphoreci.com/lucasdemarchi/kmod)<br/>
 [![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.
diff --git a/TODO b/TODO
index 537e7e1..3fe06eb 100644
--- a/TODO
+++ b/TODO
@@ -35,6 +35,11 @@
    - kmod_module_symbols_free_list()
    - kmod_module_dependency_symbols_free_list()
 
+* libkmod API breaking changes:
+   - dedicated error value for all kmod_*_get_crc() functions. Currently there
+     is no way for callers to distinguish between a valid CRC=0 and the error
+     code 0.
+
 * index: drop the "open(), seek(), read()" implementation and use another one
   with mmap(). When lookup() is called and the file is not mmaped, mmap it.
   Another possibility is to drop the mmap implementation relying on VFS to have
diff --git a/autogen.sh b/autogen.sh
deleted file mode 120000
index ac7bcbb..0000000
--- a/autogen.sh
+++ /dev/null
@@ -1 +0,0 @@
-bootstrap
\ No newline at end of file
diff --git a/bootstrap b/autogen.sh
similarity index 74%
rename from bootstrap
rename to autogen.sh
index c980f24..67b119f 100755
--- a/bootstrap
+++ b/autogen.sh
@@ -32,22 +32,27 @@
 
 cd $oldpwd
 
-hackargs="--enable-debug --enable-python --with-xz --with-zlib"
+hackargs="--enable-debug --enable-python --with-xz --with-zlib --with-openssl"
 
 if [ "x$1" = "xc" ]; then
-        $topdir/configure CFLAGS='-g -O2' $args
+        shift
+        $topdir/configure CFLAGS='-g -O2' $args $hackargs "$@"
         make clean
 elif [ "x$1" = "xg" ]; then
-        $topdir/configure CFLAGS='-g -Og' $args
+        shift
+        $topdir/configure CFLAGS='-g -Og' $args "$@"
         make clean
 elif [ "x$1" = "xl" ]; then
-        $topdir/configure CC=clang CXX=clang++ $args
+        shift
+        $topdir/configure CC=clang CXX=clang++ $args "$@"
         make clean
 elif [ "x$1" = "xa" ]; then
-        $topdir/configure CFLAGS='-g -O2 -Wsuggest-attribute=pure -Wsuggest-attribute=const' $args
+        shift
+        $topdir/configure CFLAGS='-g -O2 -Wsuggest-attribute=pure -Wsuggest-attribute=const' $args "$@"
         make clean
 elif [ "x$1" = "xs" ]; then
-        scan-build $topdir/configure CFLAGS='-g -O0 -std=gnu11' $args
+        shift
+        scan-build $topdir/configure CFLAGS='-g -O0 -std=gnu11' $args "$@"
         scan-build make
 else
         echo
@@ -59,6 +64,6 @@
         echo
         echo If you are debugging or hacking on kmod, consider configuring
         echo like below:
-        echo 
-        echo "$topdir/configure CFLAGS="-g -O2" $args $hackargs"
+        echo
+        echo "$topdir/configure CFLAGS='-g -O2' $args $hackargs"
 fi
diff --git a/bootstrap-configure b/bootstrap-configure
deleted file mode 100755
index 33fbc2c..0000000
--- a/bootstrap-configure
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-
-. ./bootstrap && \
-	exec ./configure CFLAGS="-g -O2" $args $hackargs "$@"
diff --git a/configure.ac b/configure.ac
index fbc7391..ee72283 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 AC_PREREQ(2.64)
 AC_INIT([kmod],
-	[25],
+	[26],
 	[linux-modules@vger.kernel.org],
 	[kmod],
 	[http://git.kernel.org/?p=utils/kernel/kmod/kmod.git])
@@ -106,6 +106,17 @@
 ])
 CC_FEATURE_APPEND([with_features], [with_zlib], [ZLIB])
 
+AC_ARG_WITH([openssl],
+	AS_HELP_STRING([--with-openssl], [handle PKCS7 signatures @<:@default=disabled@:>@]),
+	[], [with_openssl=no])
+AS_IF([test "x$with_openssl" != "xno"], [
+	PKG_CHECK_MODULES([openssl], [openssl >= 1.1.0])
+	AC_DEFINE([ENABLE_OPENSSL], [1], [Enable openssl for modinfo.])
+], [
+	AC_MSG_NOTICE([openssl support not requested])
+])
+CC_FEATURE_APPEND([with_features], [with_openssl], [OPENSSL])
+
 AC_ARG_WITH([bashcompletiondir],
 	AS_HELP_STRING([--with-bashcompletiondir=DIR], [Bash completions directory]),
 	[],
diff --git a/libkmod/libkmod-internal.h b/libkmod/libkmod-internal.h
index 346579c..a65ddd1 100644
--- a/libkmod/libkmod-internal.h
+++ b/libkmod/libkmod-internal.h
@@ -188,5 +188,8 @@
 	const char *algo, *hash_algo, *id_type;
 	const char *sig;
 	size_t sig_len;
+	void (*free)(void *);
+	void *private;
 };
 bool kmod_module_signature_info(const struct kmod_file *file, struct kmod_signature_info *sig_info) _must_check_ __attribute__((nonnull(1, 2)));
+void kmod_module_signature_info_free(struct kmod_signature_info *sig_info) __attribute__((nonnull));
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 0a3ef11..bffe715 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -2273,7 +2273,7 @@
 	struct kmod_elf *elf;
 	char **strings;
 	int i, count, ret = -ENOMEM;
-	struct kmod_signature_info sig_info;
+	struct kmod_signature_info sig_info = {};
 
 	if (mod == NULL || list == NULL)
 		return -ENOENT;
@@ -2357,6 +2357,9 @@
 	ret = count;
 
 list_error:
+	/* aux structures freed in normal case also */
+	kmod_module_signature_info_free(&sig_info);
+
 	if (ret < 0) {
 		kmod_module_info_free_list(*list);
 		*list = NULL;
@@ -2519,7 +2522,7 @@
 {
 	struct kmod_module_version *version;
 
-	if (entry == NULL)
+	if (entry == NULL || entry->data == NULL)
 		return NULL;
 
 	version = entry->data;
@@ -2532,14 +2535,13 @@
  *
  * Get the crc of a kmod module version.
  *
- * Returns: the crc of this kmod module version on success or NULL on
- * failure. The string is owned by the version, do not free it.
+ * Returns: the crc of this kmod module version if available, otherwise default to 0.
  */
 KMOD_EXPORT uint64_t kmod_module_version_get_crc(const struct kmod_list *entry)
 {
 	struct kmod_module_version *version;
 
-	if (entry == NULL)
+	if (entry == NULL || entry->data == NULL)
 		return 0;
 
 	version = entry->data;
@@ -2660,7 +2662,7 @@
 {
 	struct kmod_module_symbol *symbol;
 
-	if (entry == NULL)
+	if (entry == NULL || entry->data == NULL)
 		return NULL;
 
 	symbol = entry->data;
@@ -2673,14 +2675,13 @@
  *
  * Get the crc of a kmod module symbol.
  *
- * Returns: the crc of this kmod module symbol on success or NULL on
- * failure. The string is owned by the symbol, do not free it.
+ * Returns: the crc of this kmod module symbol if available, otherwise default to 0.
  */
 KMOD_EXPORT uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry)
 {
 	struct kmod_module_symbol *symbol;
 
-	if (entry == NULL)
+	if (entry == NULL || entry->data == NULL)
 		return 0;
 
 	symbol = entry->data;
@@ -2806,7 +2807,7 @@
 {
 	struct kmod_module_dependency_symbol *dependency_symbol;
 
-	if (entry == NULL)
+	if (entry == NULL || entry->data == NULL)
 		return NULL;
 
 	dependency_symbol = entry->data;
@@ -2819,14 +2820,13 @@
  *
  * Get the crc of a kmod module dependency_symbol.
  *
- * Returns: the crc of this kmod module dependency_symbol on success or NULL on
- * failure. The string is owned by the dependency_symbol, do not free it.
+ * Returns: the crc of this kmod module dependency_symbol if available, otherwise default to 0.
  */
 KMOD_EXPORT uint64_t kmod_module_dependency_symbol_get_crc(const struct kmod_list *entry)
 {
 	struct kmod_module_dependency_symbol *dependency_symbol;
 
-	if (entry == NULL)
+	if (entry == NULL || entry->data == NULL)
 		return 0;
 
 	dependency_symbol = entry->data;
@@ -2846,7 +2846,7 @@
 {
 	struct kmod_module_dependency_symbol *dependency_symbol;
 
-	if (entry == NULL)
+	if (entry == NULL || entry->data == NULL)
 		return 0;
 
 	dependency_symbol = entry->data;
diff --git a/libkmod/libkmod-signature.c b/libkmod/libkmod-signature.c
index b3546ad..27d0a8f 100644
--- a/libkmod/libkmod-signature.c
+++ b/libkmod/libkmod-signature.c
@@ -18,6 +18,10 @@
  */
 
 #include <inttypes.h>
+#ifdef ENABLE_OPENSSL
+#include <openssl/cms.h>
+#include <openssl/ssl.h>
+#endif
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -91,6 +95,217 @@
 	uint32_t sig_len;    /* Length of signature data (big endian) */
 };
 
+static bool fill_default(const char *mem, off_t size,
+			 const struct module_signature *modsig, size_t sig_len,
+			 struct kmod_signature_info *sig_info)
+{
+	size -= sig_len;
+	sig_info->sig = mem + size;
+	sig_info->sig_len = sig_len;
+
+	size -= modsig->key_id_len;
+	sig_info->key_id = mem + size;
+	sig_info->key_id_len = modsig->key_id_len;
+
+	size -= modsig->signer_len;
+	sig_info->signer = mem + size;
+	sig_info->signer_len = modsig->signer_len;
+
+	sig_info->algo = pkey_algo[modsig->algo];
+	sig_info->hash_algo = pkey_hash_algo[modsig->hash];
+	sig_info->id_type = pkey_id_type[modsig->id_type];
+
+	return true;
+}
+
+#ifdef ENABLE_OPENSSL
+
+struct pkcs7_private {
+	CMS_ContentInfo *cms;
+	unsigned char *key_id;
+	BIGNUM *sno;
+};
+
+static void pkcs7_free(void *s)
+{
+	struct kmod_signature_info *si = s;
+	struct pkcs7_private *pvt = si->private;
+
+	CMS_ContentInfo_free(pvt->cms);
+	BN_free(pvt->sno);
+	free(pvt->key_id);
+	free(pvt);
+	si->private = NULL;
+}
+
+static int obj_to_hash_algo(const ASN1_OBJECT *o)
+{
+	int nid;
+
+	nid = OBJ_obj2nid(o);
+	switch (nid) {
+	case NID_md4:
+		return PKEY_HASH_MD4;
+	case NID_md5:
+		return PKEY_HASH_MD5;
+	case NID_sha1:
+		return PKEY_HASH_SHA1;
+	case NID_ripemd160:
+		return PKEY_HASH_RIPE_MD_160;
+	case NID_sha256:
+		return PKEY_HASH_SHA256;
+	case NID_sha384:
+		return PKEY_HASH_SHA384;
+	case NID_sha512:
+		return PKEY_HASH_SHA512;
+	case NID_sha224:
+		return PKEY_HASH_SHA224;
+	default:
+		return -1;
+	}
+	return -1;
+}
+
+static const char *x509_name_to_str(X509_NAME *name)
+{
+	int i;
+	X509_NAME_ENTRY *e;
+	ASN1_STRING *d;
+	ASN1_OBJECT *o;
+	int nid = -1;
+	const char *str;
+
+	for (i = 0; i < X509_NAME_entry_count(name); i++) {
+		e = X509_NAME_get_entry(name, i);
+		o = X509_NAME_ENTRY_get_object(e);
+		nid = OBJ_obj2nid(o);
+		if (nid == NID_commonName)
+			break;
+	}
+	if (nid == -1)
+		return NULL;
+
+	d = X509_NAME_ENTRY_get_data(e);
+	str = (const char *)ASN1_STRING_get0_data(d);
+
+	return str;
+}
+
+static bool fill_pkcs7(const char *mem, off_t size,
+		       const struct module_signature *modsig, size_t sig_len,
+		       struct kmod_signature_info *sig_info)
+{
+	const char *pkcs7_raw;
+	CMS_ContentInfo *cms;
+	STACK_OF(CMS_SignerInfo) *sis;
+	CMS_SignerInfo *si;
+	int rc;
+	ASN1_OCTET_STRING *key_id;
+	X509_NAME *issuer;
+	ASN1_INTEGER *sno;
+	ASN1_OCTET_STRING *sig;
+	BIGNUM *sno_bn;
+	X509_ALGOR *dig_alg;
+	X509_ALGOR *sig_alg;
+	const ASN1_OBJECT *o;
+	BIO *in;
+	int len;
+	unsigned char *key_id_str;
+	struct pkcs7_private *pvt;
+	const char *issuer_str;
+
+	size -= sig_len;
+	pkcs7_raw = mem + size;
+
+	in = BIO_new_mem_buf(pkcs7_raw, sig_len);
+
+	cms = d2i_CMS_bio(in, NULL);
+	if (cms == NULL) {
+		BIO_free(in);
+		return false;
+	}
+
+	BIO_free(in);
+
+	sis = CMS_get0_SignerInfos(cms);
+	if (sis == NULL)
+		goto err;
+
+	si = sk_CMS_SignerInfo_value(sis, 0);
+	if (si == NULL)
+		goto err;
+
+	rc = CMS_SignerInfo_get0_signer_id(si, &key_id, &issuer, &sno);
+	if (rc == 0)
+		goto err;
+
+	sig = CMS_SignerInfo_get0_signature(si);
+	if (sig == NULL)
+		goto err;
+
+	CMS_SignerInfo_get0_algs(si, NULL, NULL, &dig_alg, &sig_alg);
+
+	sig_info->sig = (const char *)ASN1_STRING_get0_data(sig);
+	sig_info->sig_len = ASN1_STRING_length(sig);
+
+	sno_bn = ASN1_INTEGER_to_BN(sno, NULL);
+	if (sno_bn == NULL)
+		goto err;
+
+	len = BN_num_bytes(sno_bn);
+	key_id_str = malloc(len);
+	if (key_id_str == NULL)
+		goto err2;
+	BN_bn2bin(sno_bn, key_id_str);
+
+	sig_info->key_id = (const char *)key_id_str;
+	sig_info->key_id_len = len;
+
+	issuer_str = x509_name_to_str(issuer);
+	if (issuer_str != NULL) {
+		sig_info->signer = issuer_str;
+		sig_info->signer_len = strlen(issuer_str);
+	}
+
+	X509_ALGOR_get0(&o, NULL, NULL, dig_alg);
+
+	sig_info->hash_algo = pkey_hash_algo[obj_to_hash_algo(o)];
+	sig_info->id_type = pkey_id_type[modsig->id_type];
+
+	pvt = malloc(sizeof(*pvt));
+	if (pvt == NULL)
+		goto err3;
+
+	pvt->cms = cms;
+	pvt->key_id = key_id_str;
+	pvt->sno = sno_bn;
+	sig_info->private = pvt;
+
+	sig_info->free = pkcs7_free;
+
+	return true;
+err3:
+	free(key_id_str);
+err2:
+	BN_free(sno_bn);
+err:
+	CMS_ContentInfo_free(cms);
+	return false;
+}
+
+#else /* ENABLE OPENSSL */
+
+static bool fill_pkcs7(const char *mem, off_t size,
+		       const struct module_signature *modsig, size_t sig_len,
+		       struct kmod_signature_info *sig_info)
+{
+	sig_info->hash_algo = "unknown";
+	sig_info->id_type = pkey_id_type[modsig->id_type];
+	return true;
+}
+
+#endif /* ENABLE OPENSSL */
+
 #define SIG_MAGIC "~Module signature appended~\n"
 
 /*
@@ -111,7 +326,6 @@
 	const struct module_signature *modsig;
 	size_t sig_len;
 
-
 	size = kmod_file_get_size(file);
 	mem = kmod_file_get_contents(file);
 	if (size < (off_t)strlen(SIG_MAGIC))
@@ -133,21 +347,16 @@
 	    size < (int64_t)(modsig->signer_len + modsig->key_id_len + sig_len))
 		return false;
 
-	size -= sig_len;
-	sig_info->sig = mem + size;
-	sig_info->sig_len = sig_len;
+	switch (modsig->id_type) {
+	case PKEY_ID_PKCS7:
+		return fill_pkcs7(mem, size, modsig, sig_len, sig_info);
+	default:
+		return fill_default(mem, size, modsig, sig_len, sig_info);
+	}
+}
 
-	size -= modsig->key_id_len;
-	sig_info->key_id = mem + size;
-	sig_info->key_id_len = modsig->key_id_len;
-
-	size -= modsig->signer_len;
-	sig_info->signer = mem + size;
-	sig_info->signer_len = modsig->signer_len;
-
-	sig_info->algo = pkey_algo[modsig->algo];
-	sig_info->hash_algo = pkey_hash_algo[modsig->hash];
-	sig_info->id_type = pkey_id_type[modsig->id_type];
-
-	return true;
+void kmod_module_signature_info_free(struct kmod_signature_info *sig_info)
+{
+	if (sig_info->free)
+		sig_info->free(sig_info);
 }
diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
index f9e33c6..352627e 100644
--- a/libkmod/libkmod.h
+++ b/libkmod/libkmod.h
@@ -72,7 +72,7 @@
 	KMOD_INDEX_MODULES_SYMBOL,
 	KMOD_INDEX_MODULES_BUILTIN,
 	/* Padding to make sure enum is not mapped to char */
-	_KMOD_INDEX_PAD = (1 << 31),
+	_KMOD_INDEX_PAD = 1U << 31,
 };
 int kmod_dump_index(struct kmod_ctx *ctx, enum kmod_index type, int fd);
 
@@ -211,7 +211,7 @@
 	KMOD_MODULE_COMING,
 	KMOD_MODULE_GOING,
 	/* Padding to make sure enum is not mapped to char */
-	_KMOD_MODULE_PAD = (1 << 31),
+	_KMOD_MODULE_PAD = 1U << 31,
 };
 const char *kmod_module_initstate_str(enum kmod_module_initstate state);
 int kmod_module_get_initstate(const struct kmod_module *mod);
diff --git a/man/depmod.xml b/man/depmod.xml
index 0668aea..ea0be27 100644
--- a/man/depmod.xml
+++ b/man/depmod.xml
@@ -61,7 +61,6 @@
       <arg><option>-e</option></arg>
       <arg><option>-E <replaceable>Module.symvers</replaceable></option></arg>
       <arg><option>-F <replaceable>System.map</replaceable></option></arg>
-      <arg><option>-m</option></arg>
       <arg><option>-n</option></arg>
       <arg><option>-v</option></arg>
       <arg><option>-P <replaceable>prefix</replaceable></option></arg>
diff --git a/man/modprobe.d.xml b/man/modprobe.d.xml
index 47878e2..211af84 100644
--- a/man/modprobe.d.xml
+++ b/man/modprobe.d.xml
@@ -63,7 +63,7 @@
       module commands as underscore conversion happens automatically.
     </para>
     <para>
-      The format of and files under <filename>modprobe.d</filename> is
+      The format of files under <filename>modprobe.d</filename> is
       simple: one command per line, with blank lines and lines starting
       with '#' ignored (useful for adding comments).  A '\' at the end
       of a line causes it to continue on the next line, which makes the
diff --git a/man/modprobe.xml b/man/modprobe.xml
index 12d49f2..0372b6b 100644
--- a/man/modprobe.xml
+++ b/man/modprobe.xml
@@ -232,7 +232,7 @@
             information, such as the kernel and compiler versions.  If a module
             fails to load and the kernel complains that the "version magic"
             doesn't match, you can use this option to remove it.  Naturally,
-            this check is there for your protection, so this using option is
+            this check is there for your protection, so using this option is
             dangerous unless you know what you're doing.
           </para>
           <para>
diff --git a/testsuite/README b/testsuite/README
index 6efc61e..052569f 100644
--- a/testsuite/README
+++ b/testsuite/README
@@ -58,4 +58,4 @@
     too, as long as you tell them to operate on child process.
 
 9 - Make sure test passes when using "default" build flags, i.e. by running
-    bootstrap-configure instead of simpler bootstrap/autogen.sh
+    'autogen.sh c'
diff --git a/testsuite/mkosi/mkosi.arch b/testsuite/mkosi/mkosi.arch
index 324dfb0..ace5d95 100644
--- a/testsuite/mkosi/mkosi.arch
+++ b/testsuite/mkosi/mkosi.arch
@@ -20,6 +20,7 @@
 	docbook-xml
 	docbook-xsl
 	linux-headers
+	openssl
 
 [Partitions]
-RootSize = 2G
+RootSize = 3G
diff --git a/testsuite/mkosi/mkosi.build b/testsuite/mkosi/mkosi.build
index 53fc797..c0ba549 100755
--- a/testsuite/mkosi/mkosi.build
+++ b/testsuite/mkosi/mkosi.build
@@ -32,7 +32,7 @@
 kdir=$(find_kdir)
 IFS=/ read _ _ _ kver _ <<<"$kdir"
 
-../autogen.sh c
+../autogen.sh c --disable-python
 make -j
 make check KDIR="$kdir" KVER="$kver"
 make install
diff --git a/testsuite/mkosi/mkosi.clear b/testsuite/mkosi/mkosi.clear
new file mode 100644
index 0000000..03ba2f0
--- /dev/null
+++ b/testsuite/mkosi/mkosi.clear
@@ -0,0 +1,20 @@
+[Distribution]
+Distribution=clear
+Release=latest
+
+[Output]
+Output = clear-image.raw
+
+[Packages]
+Packages=
+	os-core-update
+BuildPackages=
+	os-core-dev
+	linux-dev
+
+[Partitions]
+RootSize = 5G
+
+[Host]
+# This is where swupd-extract is usually installed.
+ExtraSearchPaths=$SUDO_HOME/go/bin
\ No newline at end of file
diff --git a/testsuite/mkosi/mkosi.fedora b/testsuite/mkosi/mkosi.fedora
index 7be7dd9..5252f87 100644
--- a/testsuite/mkosi/mkosi.fedora
+++ b/testsuite/mkosi/mkosi.fedora
@@ -1,6 +1,6 @@
 [Distribution]
 Distribution=fedora
-Release=27
+Release=29
 
 [Output]
 Output = fedora-image.raw
@@ -21,6 +21,7 @@
 	xml-common
 	xz-devel
 	zlib-devel
+	openssl-devel
 
 [Partitions]
 RootSize = 2G
diff --git a/testsuite/module-playground/dummy.pkcs7 b/testsuite/module-playground/dummy.pkcs7
new file mode 100644
index 0000000..bcdb902
--- /dev/null
+++ b/testsuite/module-playground/dummy.pkcs7
Binary files differ
diff --git a/testsuite/populate-modules.sh b/testsuite/populate-modules.sh
index b77e71e..5140f7a 100755
--- a/testsuite/populate-modules.sh
+++ b/testsuite/populate-modules.sh
@@ -39,6 +39,7 @@
     ["test-modprobe/show-depends/lib/modules/4.4.4/kernel/mod-loop-a.ko"]="mod-loop-a.ko"
     ["test-modprobe/show-depends/lib/modules/4.4.4/kernel/mod-loop-b.ko"]="mod-loop-b.ko"
     ["test-modprobe/show-depends/lib/modules/4.4.4/kernel/mod-simple.ko"]="mod-simple.ko"
+    ["test-modprobe/show-exports/mod-loop-a.ko"]="mod-loop-a.ko"
     ["test-modprobe/softdep-loop/lib/modules/4.4.4/kernel/mod-loop-a.ko"]="mod-loop-a.ko"
     ["test-modprobe/softdep-loop/lib/modules/4.4.4/kernel/mod-loop-b.ko"]="mod-loop-b.ko"
     ["test-modprobe/install-cmd-loop/lib/modules/4.4.4/kernel/mod-loop-a.ko"]="mod-loop-a.ko"
@@ -57,6 +58,7 @@
     ["test-modinfo/mod-simple-sparc64.ko"]="mod-simple-sparc64.ko"
     ["test-modinfo/mod-simple-sha1.ko"]="mod-simple.ko"
     ["test-modinfo/mod-simple-sha256.ko"]="mod-simple.ko"
+    ["test-modinfo/mod-simple-pkcs7.ko"]="mod-simple.ko"
     ["test-modinfo/external/lib/modules/external/mod-simple.ko"]="mod-simple.ko"
     ["test-tools/insert/lib/modules/4.4.4/kernel/"]="mod-simple.ko"
     ["test-tools/remove/lib/modules/4.4.4/kernel/"]="mod-simple.ko"
@@ -76,6 +78,10 @@
     "test-modinfo/mod-simple-sha1.ko"
     )
 
+attach_pkcs7_array=(
+    "test-modinfo/mod-simple-pkcs7.ko"
+    )
+
 for k in ${!map[@]}; do
     dst=${ROOTFS}/$k
     src=${MODULE_PLAYGROUND}/${map[$k]}
@@ -102,3 +108,7 @@
 for m in "${attach_sha256_array[@]}"; do
     cat ${MODULE_PLAYGROUND}/dummy.sha256 >> ${ROOTFS}/$m
 done
+
+for m in "${attach_pkcs7_array[@]}"; do
+    cat ${MODULE_PLAYGROUND}/dummy.pkcs7 >> ${ROOTFS}/$m
+done
diff --git a/testsuite/rootfs-pristine/test-modinfo/correct-sig_hashalgo-openssl.txt b/testsuite/rootfs-pristine/test-modinfo/correct-sig_hashalgo-openssl.txt
new file mode 100644
index 0000000..f97c4fa
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modinfo/correct-sig_hashalgo-openssl.txt
@@ -0,0 +1,3 @@
+sha1
+sha256
+sha256
diff --git a/testsuite/rootfs-pristine/test-modinfo/correct-sig_hashalgo.txt b/testsuite/rootfs-pristine/test-modinfo/correct-sig_hashalgo.txt
index 6d0223e..23cb933 100644
--- a/testsuite/rootfs-pristine/test-modinfo/correct-sig_hashalgo.txt
+++ b/testsuite/rootfs-pristine/test-modinfo/correct-sig_hashalgo.txt
@@ -1,3 +1,4 @@
 sha1
 sha256
+unknown
 
diff --git a/testsuite/rootfs-pristine/test-modinfo/correct-sig_key-openssl.txt b/testsuite/rootfs-pristine/test-modinfo/correct-sig_key-openssl.txt
new file mode 100644
index 0000000..25a75a8
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modinfo/correct-sig_key-openssl.txt
@@ -0,0 +1,3 @@
+E3:C8:FC:A7:3F:B3:1D:DE:84:81:EF:38:E3:4C:DE:4B:0C:FD:1B:F9
+E3:C8:FC:A7:3F:B3:1D:DE:84:81:EF:38:E3:4C:DE:4B:0C:FD:1B:F9
+26:DA:C3:EB:0F:0D:1A:56:A2:D8:B2:13:F0:D7:53:47:1D:0D:48:68
diff --git a/testsuite/rootfs-pristine/test-modinfo/correct-signer-openssl.txt b/testsuite/rootfs-pristine/test-modinfo/correct-signer-openssl.txt
new file mode 100644
index 0000000..2b979f9
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modinfo/correct-signer-openssl.txt
@@ -0,0 +1,3 @@
+Magrathea: Glacier signing key
+Magrathea: Glacier signing key
+Build time autogenerated kernel key
diff --git a/testsuite/rootfs-pristine/test-modprobe/show-exports/correct.txt b/testsuite/rootfs-pristine/test-modprobe/show-exports/correct.txt
new file mode 100644
index 0000000..0d659ef
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/show-exports/correct.txt
@@ -0,0 +1 @@
+0x[0-9a-fA-F]+	printA
diff --git a/testsuite/test-modinfo.c b/testsuite/test-modinfo.c
index 8fdfe35..373dc95 100644
--- a/testsuite/test-modinfo.c
+++ b/testsuite/test-modinfo.c
@@ -27,7 +27,7 @@
 
 static const char *progname = ABS_TOP_BUILDDIR "/tools/modinfo";
 
-#define DEFINE_MODINFO_TEST(_field, ...) \
+#define DEFINE_MODINFO_TEST(_field, _flavor, ...) \
 static noreturn int test_modinfo_##_field(const struct test *t) \
 { \
 	const char *const args[] = { \
@@ -44,19 +44,28 @@
 		[TC_ROOTFS] = TESTSUITE_ROOTFS "test-modinfo/", \
 	}, \
 	.output = { \
-		.out = TESTSUITE_ROOTFS "test-modinfo/correct-" #_field ".txt", \
+		.out = TESTSUITE_ROOTFS "test-modinfo/correct-" #_field #_flavor ".txt", \
 	})
 
 #define DEFINE_MODINFO_GENERIC_TEST(_field) \
-	DEFINE_MODINFO_TEST(_field, \
+	DEFINE_MODINFO_TEST(_field, , \
 			    "/mod-simple-i386.ko", \
 			    "/mod-simple-x86_64.ko", \
 			    "/mod-simple-sparc64.ko")
 
+#ifdef ENABLE_OPENSSL
 #define DEFINE_MODINFO_SIGN_TEST(_field) \
-	DEFINE_MODINFO_TEST(_field, \
+	DEFINE_MODINFO_TEST(_field, -openssl, \
 			    "/mod-simple-sha1.ko", \
-			    "/mod-simple-sha256.ko")
+			    "/mod-simple-sha256.ko",	\
+			    "/mod-simple-pkcs7.ko")
+#else
+#define DEFINE_MODINFO_SIGN_TEST(_field) \
+	DEFINE_MODINFO_TEST(_field, , \
+			    "/mod-simple-sha1.ko", \
+			    "/mod-simple-sha256.ko",	\
+			    "/mod-simple-pkcs7.ko")
+#endif
 
 DEFINE_MODINFO_GENERIC_TEST(filename);
 DEFINE_MODINFO_GENERIC_TEST(author);
diff --git a/testsuite/test-modprobe.c b/testsuite/test-modprobe.c
index ee9d82d..1cace82 100644
--- a/testsuite/test-modprobe.c
+++ b/testsuite/test-modprobe.c
@@ -95,6 +95,29 @@
 	);
 
 
+static noreturn int modprobe_show_exports(const struct test *t)
+{
+	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
+	const char *const args[] = {
+		progname,
+		"--show-exports", "--quiet", "/mod-loop-a.ko",
+		NULL,
+	};
+
+	test_spawn_prog(progname, args);
+	exit(EXIT_FAILURE);
+}
+DEFINE_TEST(modprobe_show_exports,
+	.description = "check if modprobe --show-depends doesn't explode with an alias to nothing",
+	.config = {
+		[TC_ROOTFS] = TESTSUITE_ROOTFS "test-modprobe/show-exports",
+	},
+	.output = {
+		.out = TESTSUITE_ROOTFS "test-modprobe/show-exports/correct.txt",
+		.regex = true,
+	});
+
+
 static noreturn int modprobe_builtin(const struct test *t)
 {
 	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
diff --git a/testsuite/testsuite.c b/testsuite/testsuite.c
index 8512b56..e46f3d8 100644
--- a/testsuite/testsuite.c
+++ b/testsuite/testsuite.c
@@ -20,6 +20,7 @@
 #include <fcntl.h>
 #include <getopt.h>
 #include <limits.h>
+#include <regex.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -272,31 +273,311 @@
 		return test_run_spawned(t);
 }
 
-static int check_activity(int fd, bool activity,  const char *path,
-			  const char *stream)
+#define BUFSZ 4096
+
+enum fd_cmp_type {
+	FD_CMP_MONITOR,
+	FD_CMP_OUT,
+	FD_CMP_ERR,
+	FD_CMP_MAX = FD_CMP_ERR,
+};
+
+struct fd_cmp {
+	enum fd_cmp_type type;
+	int fd;
+	int fd_match;
+	bool activity;
+	const char *path;
+	const char *name;
+	char buf[BUFSZ];
+	char buf_match[BUFSZ];
+	unsigned int head;
+	unsigned int head_match;
+};
+
+static int fd_cmp_check_activity(struct fd_cmp *fd_cmp)
 {
 	struct stat st;
 
 	/* not monitoring or monitoring and it has activity */
-	if (fd < 0 || activity)
+	if (fd_cmp == NULL || fd_cmp->fd < 0 || fd_cmp->activity)
 		return 0;
 
 	/* monitoring, there was no activity and size matches */
-	if (stat(path, &st) == 0 && st.st_size == 0)
+	if (stat(fd_cmp->path, &st) == 0 && st.st_size == 0)
 		return 0;
 
-	ERR("Expecting output on %s, but test didn't produce any\n", stream);
+	ERR("Expecting output on %s, but test didn't produce any\n",
+	    fd_cmp->name);
 
 	return -1;
 }
 
-static inline bool test_run_parent_check_outputs(const struct test *t,
-			int fdout, int fderr, int fdmonitor, pid_t child)
+static bool fd_cmp_is_active(struct fd_cmp *fd_cmp)
 {
-	struct epoll_event ep_outpipe, ep_errpipe, ep_monitor;
-	int err, fd_ep, fd_matchout = -1, fd_matcherr = -1;
-	bool fd_activityout = false, fd_activityerr = false;
+	return fd_cmp->fd != -1;
+}
+
+static int fd_cmp_open_monitor(struct fd_cmp *fd_cmp, int fd, int fd_ep)
+{
+	struct epoll_event ep = {};
+
+	ep.events = EPOLLHUP;
+	ep.data.ptr = fd_cmp;
+	if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd, &ep) < 0) {
+		ERR("could not add monitor fd to epoll: %m\n");
+		return -errno;
+	}
+
+	return 0;
+}
+
+static int fd_cmp_open_std(struct fd_cmp *fd_cmp,
+			   const char *fn, int fd, int fd_ep)
+{
+	struct epoll_event ep = {};
+	int fd_match;
+
+	fd_match = open(fn, O_RDONLY);
+	if (fd_match < 0) {
+		ERR("could not open %s for read: %m\n", fn);
+		return -errno;
+	}
+	ep.events = EPOLLIN;
+	ep.data.ptr = fd_cmp;
+	if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd, &ep) < 0) {
+		ERR("could not add fd to epoll: %m\n");
+		close(fd_match);
+		return -errno;
+	}
+
+	return fd_match;
+}
+
+/* opens output file AND adds descriptor to epoll */
+static int fd_cmp_open(struct fd_cmp **fd_cmp_out,
+		       enum fd_cmp_type type, const char *fn, int fd,
+		       int fd_ep)
+{
+	int err = 0;
+	struct fd_cmp *fd_cmp;
+
+	fd_cmp = calloc(1, sizeof(*fd_cmp));
+	if (fd_cmp == NULL) {
+		ERR("could not allocate fd_cmp\n");
+		return -ENOMEM;
+	}
+
+	switch (type) {
+	case FD_CMP_MONITOR:
+		err = fd_cmp_open_monitor(fd_cmp, fd, fd_ep);
+		break;
+	case FD_CMP_OUT:
+		fd_cmp->name = "STDOUT";
+		err = fd_cmp_open_std(fd_cmp, fn, fd, fd_ep);
+		break;
+	case FD_CMP_ERR:
+		fd_cmp->name = "STDERR";
+		err = fd_cmp_open_std(fd_cmp, fn, fd, fd_ep);
+		break;
+	default:
+		ERR("unknown fd type %d\n", type);
+		err = -1;
+	}
+
+	if (err < 0) {
+		free(fd_cmp);
+		return err;
+	}
+
+	fd_cmp->fd_match = err;
+	fd_cmp->fd = fd;
+	fd_cmp->type = type;
+	fd_cmp->path = fn;
+
+	*fd_cmp_out = fd_cmp;
+	return 0;
+}
+
+static int fd_cmp_check_ev_in(struct fd_cmp *fd_cmp)
+{
+	if (fd_cmp->type == FD_CMP_MONITOR) {
+		ERR("Unexpected activity on monitor pipe\n");
+		return -EINVAL;
+	}
+	fd_cmp->activity = true;
+
+	return 0;
+}
+
+static void fd_cmp_delete_ep(struct fd_cmp *fd_cmp, int fd_ep)
+{
+	if (epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_cmp->fd, NULL) < 0) {
+		ERR("could not remove fd %d from epoll: %m\n", fd_cmp->fd);
+	}
+	fd_cmp->fd = -1;
+}
+
+static void fd_cmp_close(struct fd_cmp *fd_cmp)
+{
+	if (fd_cmp == NULL)
+		return;
+
+	if (fd_cmp->fd >= 0)
+		close(fd_cmp->fd);
+	free(fd_cmp);
+}
+
+static bool fd_cmp_regex_one(const char *pattern, const char *s)
+{
+	_cleanup_(regfree) regex_t re = { };
+
+	return !regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB) &&
+	       !regexec(&re, s, 0, NULL, 0);
+}
+
+/*
+ * read fd and fd_match, checking the first matches the regex of the second,
+ * line by line
+ */
+static bool fd_cmp_regex(struct fd_cmp *fd_cmp, const struct test *t)
+{
+	char *p, *p_match;
+	int done = 0, done_match = 0, r;
+
+	if (fd_cmp->head >= sizeof(fd_cmp->buf)) {
+		ERR("Read %zu bytes without a newline\n", sizeof(fd_cmp->buf));
+		ERR("output: %.*s", (int)sizeof(fd_cmp->buf), fd_cmp->buf);
+		return false;
+	}
+
+	r = read(fd_cmp->fd, fd_cmp->buf + fd_cmp->head,
+		 sizeof(fd_cmp->buf) - fd_cmp->head);
+	if (r <= 0)
+		return true;
+
+	fd_cmp->head += r;
+
+	/*
+	 * Process as many lines as read from fd and that fits in the buffer -
+	 * it's assumed that if we get N lines from fd, we should be able to
+	 * get the same amount from fd_match
+	 */
+	for (;;) {
+		p = memchr(fd_cmp->buf + done, '\n', fd_cmp->head - done);
+		if (!p)
+			break;
+		*p = '\0';
+
+		p_match = memchr(fd_cmp->buf_match + done_match, '\n',
+				 fd_cmp->head_match - done_match);
+		if (!p_match) {
+			if (fd_cmp->head_match >= sizeof(fd_cmp->buf_match)) {
+				ERR("Read %zu bytes without a match\n", sizeof(fd_cmp->buf_match));
+				ERR("output: %.*s", (int)sizeof(fd_cmp->buf_match), fd_cmp->buf_match);
+				return false;
+			}
+
+			/* pump more data from file */
+			r = read(fd_cmp->fd_match, fd_cmp->buf_match + fd_cmp->head_match,
+				 sizeof(fd_cmp->buf_match) - fd_cmp->head_match);
+			if (r <= 0) {
+				ERR("could not read match fd %d\n", fd_cmp->fd_match);
+				return false;
+			}
+			fd_cmp->head_match += r;
+			p_match = memchr(fd_cmp->buf_match + done_match, '\n',
+					 fd_cmp->head_match - done_match);
+			if (!p_match) {
+				ERR("could not find match line from fd %d\n", fd_cmp->fd_match);
+				return false;
+			}
+		}
+		*p_match = '\0';
+
+		if (!fd_cmp_regex_one(fd_cmp->buf_match + done_match, fd_cmp->buf + done)) {
+			ERR("Output does not match pattern on %s:\n", fd_cmp->name);
+			ERR("pattern: %s\n", fd_cmp->buf_match + done_match);
+			ERR("output : %s\n", fd_cmp->buf + done);
+			return false;
+		}
+
+		done = p - fd_cmp->buf + 1;
+		done_match = p_match - fd_cmp->buf_match + 1;
+	}
+
+	/*
+	 * Prepare for the next call: anything we processed we remove from the
+	 * buffer by memmoving the remaining bytes up to the beginning
+	 */
+	if (done) {
+		if (fd_cmp->head - done)
+			memmove(fd_cmp->buf, fd_cmp->buf + done, fd_cmp->head - done);
+		fd_cmp->head -= done;
+	}
+
+	if (done_match) {
+		if (fd_cmp->head_match - done_match)
+			memmove(fd_cmp->buf_match, fd_cmp->buf_match + done_match,
+				fd_cmp->head_match - done_match);
+		fd_cmp->head_match -= done_match;
+	}
+
+	return true;
+}
+
+/* read fd and fd_match, checking they match exactly */
+static bool fd_cmp_exact(struct fd_cmp *fd_cmp, const struct test *t)
+{
+	int r, rmatch, done = 0;
+
+	r = read(fd_cmp->fd, fd_cmp->buf, sizeof(fd_cmp->buf) - 1);
+	if (r <= 0)
+		/* try again later */
+		return true;
+
+	/* read as much data from fd_match as we read from fd */
+	for (;;) {
+		rmatch = read(fd_cmp->fd_match, fd_cmp->buf_match + done, r - done);
+		if (rmatch == 0)
+			break;
+
+		if (rmatch < 0) {
+			if (errno == EINTR)
+				continue;
+			ERR("could not read match fd %d\n", fd_cmp->fd_match);
+			return false;
+		}
+
+		done += rmatch;
+	}
+
+	fd_cmp->buf[r] = '\0';
+	fd_cmp->buf_match[r] = '\0';
+
+	if (t->print_outputs)
+		printf("%s: %s\n", fd_cmp->name, fd_cmp->buf);
+
+	if (!streq(fd_cmp->buf, fd_cmp->buf_match)) {
+		ERR("Outputs do not match on %s:\n", fd_cmp->name);
+		ERR("correct:\n%s\n", fd_cmp->buf_match);
+		ERR("wrong:\n%s\n", fd_cmp->buf);
+		return false;
+	}
+
+	return true;
+}
+
+static bool test_run_parent_check_outputs(const struct test *t,
+					  int fdout, int fderr, int fdmonitor,
+					  pid_t child)
+{
+	int err, fd_ep;
 	unsigned long long end_usec, start_usec;
+	struct fd_cmp *fd_cmp_out = NULL;
+	struct fd_cmp *fd_cmp_err = NULL;
+	struct fd_cmp *fd_cmp_monitor = NULL;
+	int n_fd = 0;
 
 	fd_ep = epoll_create1(EPOLL_CLOEXEC);
 	if (fd_ep < 0) {
@@ -305,57 +586,30 @@
 	}
 
 	if (t->output.out != NULL) {
-		fd_matchout = open(t->output.out, O_RDONLY);
-		if (fd_matchout < 0) {
-			err = -errno;
-			ERR("could not open %s for read: %m\n",
-							t->output.out);
+		err = fd_cmp_open(&fd_cmp_out,
+				  FD_CMP_OUT, t->output.out, fdout, fd_ep);
+		if (err < 0)
 			goto out;
-		}
-		memset(&ep_outpipe, 0, sizeof(struct epoll_event));
-		ep_outpipe.events = EPOLLIN;
-		ep_outpipe.data.ptr = &fdout;
-		if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fdout, &ep_outpipe) < 0) {
-			err = -errno;
-			ERR("could not add fd to epoll: %m\n");
-			goto out;
-		}
-	} else
-		fdout = -1;
+		n_fd++;
+	}
 
 	if (t->output.err != NULL) {
-		fd_matcherr = open(t->output.err, O_RDONLY);
-		if (fd_matcherr < 0) {
-			err = -errno;
-			ERR("could not open %s for read: %m\n",
-					t->output.err);
+		err = fd_cmp_open(&fd_cmp_err,
+				  FD_CMP_ERR, t->output.err, fderr, fd_ep);
+		if (err < 0)
 			goto out;
-
-		}
-		memset(&ep_errpipe, 0, sizeof(struct epoll_event));
-		ep_errpipe.events = EPOLLIN;
-		ep_errpipe.data.ptr = &fderr;
-		if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fderr, &ep_errpipe) < 0) {
-			err = -errno;
-			ERR("could not add fd to epoll: %m\n");
-			goto out;
-		}
-	} else
-		fderr = -1;
-
-	memset(&ep_monitor, 0, sizeof(struct epoll_event));
-	ep_monitor.events = EPOLLHUP;
-	ep_monitor.data.ptr = &fdmonitor;
-	if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fdmonitor, &ep_monitor) < 0) {
-		err = -errno;
-		ERR("could not add monitor fd to epoll: %m\n");
-		goto out;
+		n_fd++;
 	}
 
+	err = fd_cmp_open(&fd_cmp_monitor, FD_CMP_MONITOR, NULL, fdmonitor, fd_ep);
+	if (err < 0)
+		goto out;
+	n_fd++;
+
 	start_usec = now_usec();
 	end_usec = start_usec + TEST_TIMEOUT_USEC;
 
-	for (err = 0; fdmonitor >= 0 || fdout >= 0 || fderr >= 0;) {
+	for (err = 0; n_fd > 0;) {
 		int fdcount, i, timeout;
 		struct epoll_event ev[4];
 		unsigned long long curr_usec = now_usec();
@@ -374,96 +628,45 @@
 		}
 
 		for (i = 0;  i < fdcount; i++) {
-			int *fd = ev[i].data.ptr;
+			struct fd_cmp *fd_cmp = ev[i].data.ptr;
+			bool ret;
 
 			if (ev[i].events & EPOLLIN) {
-				ssize_t r, done = 0;
-				char buf[4096];
-				char bufmatch[4096];
-				int fd_match;
-
-				/*
-				 * compare the output from child with the one
-				 * saved as correct
-				 */
-
-				r = read(*fd, buf, sizeof(buf) - 1);
-				if (r <= 0)
-					continue;
-
-				if (*fd == fdout) {
-					fd_match = fd_matchout;
-					fd_activityout = true;
-				} else if (*fd == fderr) {
-					fd_match = fd_matcherr;
-					fd_activityerr = true;
-				} else {
-					ERR("Unexpected activity on monitor pipe\n");
-					err = -EINVAL;
+				err = fd_cmp_check_ev_in(fd_cmp);
+				if (err < 0)
 					goto out;
-				}
 
-				for (;;) {
-					int rmatch = read(fd_match,
-						bufmatch + done, r - done);
-					if (rmatch == 0)
-						break;
+				if (t->output.regex)
+					ret = fd_cmp_regex(fd_cmp, t);
+				else
+					ret = fd_cmp_exact(fd_cmp, t);
 
-					if (rmatch < 0) {
-						if (errno == EINTR)
-							continue;
-						err = -errno;
-						ERR("could not read match fd %d\n",
-								fd_match);
-						goto out;
-					}
-
-					done += rmatch;
-				}
-
-				buf[r] = '\0';
-				bufmatch[r] = '\0';
-
-				if (t->print_outputs)
-					printf("%s: %s\n",
-					       fd_match == fd_matchout ? "STDOUT:" : "STDERR:",
-					       buf);
-
-				if (!streq(buf, bufmatch)) {
-					ERR("Outputs do not match on %s:\n",
-						fd_match == fd_matchout ? "STDOUT" : "STDERR");
-					ERR("correct:\n%s\n", bufmatch);
-					ERR("wrong:\n%s\n", buf);
+				if (!ret) {
 					err = -1;
 					goto out;
 				}
 			} else if (ev[i].events & EPOLLHUP) {
-				if (epoll_ctl(fd_ep, EPOLL_CTL_DEL,
-							*fd, NULL) < 0) {
-					ERR("could not remove fd %d from epoll: %m\n",
-									*fd);
-				}
-				*fd = -1;
+				fd_cmp_delete_ep(fd_cmp, fd_ep);
+				n_fd--;
 			}
 		}
 	}
 
-	err = check_activity(fd_matchout, fd_activityout, t->output.out, "stdout");
-	err |= check_activity(fd_matcherr, fd_activityerr, t->output.err, "stderr");
+	err = fd_cmp_check_activity(fd_cmp_out);
+	err |= fd_cmp_check_activity(fd_cmp_err);
 
-	if (err == 0 && fdmonitor >= 0) {
+	if (err == 0 && fd_cmp_is_active(fd_cmp_monitor)) {
 		err = -EINVAL;
 		ERR("Test '%s' timed out, killing %d\n", t->name, child);
 		kill(child, SIGKILL);
 	}
 
 out:
-	if (fd_matchout >= 0)
-		close(fd_matchout);
-	if (fd_matcherr >= 0)
-		close(fd_matcherr);
-	if (fd_ep >= 0)
-		close(fd_ep);
+	fd_cmp_close(fd_cmp_out);
+	fd_cmp_close(fd_cmp_err);
+	fd_cmp_close(fd_cmp_monitor);
+	close(fd_ep);
+
 	return err == 0;
 }
 
diff --git a/testsuite/testsuite.h b/testsuite/testsuite.h
index 2b31483..7ed96bf 100644
--- a/testsuite/testsuite.h
+++ b/testsuite/testsuite.h
@@ -89,6 +89,12 @@
 		const char *err;
 
 		/*
+		 * whether to treat the correct files as regex to the real
+		 * output
+		 */
+		bool regex;
+
+		/*
 		 * Vector with pair of files
 		 * key = correct file
 		 * val = file to check
diff --git a/tools/depmod.c b/tools/depmod.c
index 989d907..391afe9 100644
--- a/tools/depmod.c
+++ b/tools/depmod.c
@@ -29,6 +29,7 @@
 #include <string.h>
 #include <unistd.h>
 #include <sys/stat.h>
+#include <sys/time.h>
 #include <sys/utsname.h>
 
 #include <shared/array.h>
@@ -1388,19 +1389,45 @@
 	return 0;
 }
 
+static FILE *dfdopen(const char *dname, const char *filename, int flags,
+		     const char *mode)
+{
+	int fd, dfd;
+	FILE *ret;
+
+	dfd = open(dname, O_RDONLY);
+	if (dfd < 0) {
+		WRN("could not open directory %s: %m\n", dname);
+		return NULL;
+	}
+
+	fd = openat(dfd, filename, flags);
+	if (fd < 0) {
+		WRN("could not open %s at %s: %m\n", filename, dname);
+		ret = NULL;
+	} else {
+		ret = fdopen(fd, mode);
+		if (!ret) {
+			WRN("could not associate stream with %s: %m\n", filename);
+			close(fd);
+		}
+	}
+	close(dfd);
+	return ret;
+}
+
+
+
 static void depmod_modules_sort(struct depmod *depmod)
 {
-	char order_file[PATH_MAX], line[PATH_MAX];
+	char line[PATH_MAX];
+	const char *order_file = "modules.order";
 	FILE *fp;
 	unsigned idx = 0, total = 0;
 
-	snprintf(order_file, sizeof(order_file), "%s/modules.order",
-		 depmod->cfg->dirname);
-	fp = fopen(order_file, "r");
-	if (fp == NULL) {
-		WRN("could not open %s: %m\n", order_file);
+	fp = dfdopen(depmod->cfg->dirname, order_file, O_RDONLY, "r");
+	if (fp == NULL)
 		return;
-	}
 
 	while (fgets(line, sizeof(line), fp) != NULL) {
 		size_t len = strlen(line);
@@ -1408,8 +1435,8 @@
 		if (len == 0)
 			continue;
 		if (line[len - 1] != '\n') {
-			ERR("%s:%u corrupted line misses '\\n'\n",
-				order_file, idx);
+			ERR("%s/%s:%u corrupted line misses '\\n'\n",
+				depmod->cfg->dirname, order_file, idx);
 			goto corrupted;
 		}
 	}
@@ -2286,18 +2313,14 @@
 {
 	FILE *in;
 	struct index_node *idx;
-	char infile[PATH_MAX], line[PATH_MAX], modname[PATH_MAX];
+	char line[PATH_MAX], modname[PATH_MAX];
 
 	if (out == stdout)
 		return 0;
 
-	snprintf(infile, sizeof(infile), "%s/modules.builtin",
-							depmod->cfg->dirname);
-	in = fopen(infile, "r");
-	if (in == NULL) {
-		WRN("could not open %s: %m\n", infile);
+	in = dfdopen(depmod->cfg->dirname, "modules.builtin", O_RDONLY, "r");
+	if (in == NULL)
 		return 0;
-	}
 
 	idx = index_create();
 	if (idx == NULL) {
@@ -2398,6 +2421,9 @@
 	};
 	const char *dname = depmod->cfg->dirname;
 	int dfd, err = 0;
+	struct timeval tv;
+
+	gettimeofday(&tv, NULL);
 
 	if (out != NULL)
 		dfd = -1;
@@ -2416,11 +2442,12 @@
 		int r, ferr;
 
 		if (fp == NULL) {
-			int flags = O_CREAT | O_TRUNC | O_WRONLY;
+			int flags = O_CREAT | O_EXCL | O_WRONLY;
 			int mode = 0644;
 			int fd;
 
-			snprintf(tmp, sizeof(tmp), "%s.tmp", itr->name);
+			snprintf(tmp, sizeof(tmp), "%s.%i.%li.%li", itr->name, getpid(),
+					tv.tv_usec, tv.tv_sec);
 			fd = openat(dfd, tmp, flags, mode);
 			if (fd < 0) {
 				ERR("openat(%s, %s, %o, %o): %m\n",
@@ -2451,7 +2478,6 @@
 			break;
 		}
 
-		unlinkat(dfd, itr->name, 0);
 		if (renameat(dfd, tmp, dfd, itr->name) != 0) {
 			err = -errno;
 			CRIT("renameat(%s, %s, %s, %s): %m\n",
diff --git a/tools/modprobe.c b/tools/modprobe.c
index 43605cc..a9e2331 100644
--- a/tools/modprobe.c
+++ b/tools/modprobe.c
@@ -76,6 +76,7 @@
 	{"show-config", no_argument, 0, 'c'},
 	{"show-modversions", no_argument, 0, 4},
 	{"dump-modversions", no_argument, 0, 4},
+	{"show-exports", no_argument, 0, 6},
 
 	{"dry-run", no_argument, 0, 'n'},
 	{"show", no_argument, 0, 'n'},
@@ -124,6 +125,7 @@
 		"\t-c, --show-config           Same as --showconfig\n"
 		"\t    --show-modversions      Dump module symbol version and exit\n"
 		"\t    --dump-modversions      Same as --show-modversions\n"
+		"\t    --show-exports          Only print module exported symbol versions and exit\n"
 		"\n"
 		"General Options:\n"
 		"\t-n, --dry-run               Do not execute operations, just print out\n"
@@ -232,6 +234,34 @@
 	return 0;
 }
 
+static int show_exports(struct kmod_ctx *ctx, const char *filename)
+{
+	struct kmod_list *l, *list = NULL;
+	struct kmod_module *mod;
+	int err = kmod_module_new_from_path(ctx, filename, &mod);
+	if (err < 0) {
+		LOG("Module %s not found.\n", filename);
+		return err;
+	}
+
+	err = kmod_module_get_symbols(mod, &list);
+	if (err < 0) {
+		LOG("could not get symbols of %s: %s\n",
+			filename, strerror(-err));
+		kmod_module_unref(mod);
+		return err;
+	}
+
+	kmod_list_foreach(l, list) {
+		const char *symbol = kmod_module_symbol_get_symbol(l);
+		uint64_t crc = kmod_module_symbol_get_crc(l);
+		printf("0x%08"PRIx64"\t%s\n", crc, symbol);
+	}
+	kmod_module_symbols_free_list(list);
+	kmod_module_unref(mod);
+	return 0;
+}
+
 static int command_do(struct kmod_module *module, const char *type,
 				const char *command, const char *cmdline_opts)
 {
@@ -727,6 +757,7 @@
 	int do_remove = 0;
 	int do_show_config = 0;
 	int do_show_modversions = 0;
+	int do_show_exports = 0;
 	int err;
 
 	argv = prepend_options_from_env(&argc, orig_argv);
@@ -783,6 +814,9 @@
 		case 4:
 			do_show_modversions = 1;
 			break;
+		case 6:
+			do_show_exports = 1;
+			break;
 		case 'n':
 			dry_run = 1;
 			break;
@@ -886,6 +920,8 @@
 		err = show_config(ctx);
 	else if (do_show_modversions)
 		err = show_modversions(ctx, args[0]);
+	else if (do_show_exports)
+		err = show_exports(ctx, args[0]);
 	else if (do_remove)
 		err = rmmod_all(ctx, args, nargs);
 	else if (use_all)