Upgrade kmod to v28

Test: make
Change-Id: Ib72ea77e13d0d8621347a240715db48d918481d8
diff --git a/.gitignore b/.gitignore
index 58ef42a..2347858 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,8 +16,9 @@
 /config.log
 /config.status
 /configure
-/coverage
 /cov-int
+/coverage
+/kmod-*.tar.*
 /libtool
 /stamp-h1
 /test-suite.log
diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml
deleted file mode 100644
index db47ca1..0000000
--- a/.semaphore/semaphore.yml
+++ /dev/null
@@ -1,46 +0,0 @@
-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 02c753e..2adb3c6 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,26 +1,21 @@
 language: c
+dist: focal
 
 matrix:
   include:
     - compiler: gcc
-      env: MYCC=gcc
-    - compiler: gcc
-      env: MYCC=gcc-4.8
-    - compiler: gcc
-      env: MYCC=gcc-4.9
+      env: CC=gcc
     - compiler: clang
-      env: MYCC=clang
+      env: CC=clang
 
 before_install:
-  - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
   - sudo apt-get update -qq
+  - sudo apt-get install -qq libzstd-dev zstd
   - sudo apt-get install -qq liblzma-dev
   - sudo apt-get install -qq zlib1g-dev
   - sudo apt-get install -qq xsltproc docbook-xsl
   - sudo apt-get install -qq cython
   - sudo apt-get install -qq linux-headers-generic
-  - if [ "$MYCC" = "gcc-4.8" ]; then sudo apt-get install -qq gcc-4.8; sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 90; gcc --version; fi
-  - if [ "$MYCC" = "gcc-4.9" ]; then sudo apt-get install -qq gcc-4.9; sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.9 90; gcc --version; fi
 
 before_script:
   - unset PYTHON_CFLAGS # hack to broken travis setup
@@ -28,7 +23,7 @@
 
 script:
   - ./autogen.sh c --without-openssl && make -j
-  - if [ "$MYCC" != "gcc-4.8" ]; then make -j check; fi
+  - make -j check
 
 notifications:
   irc:
diff --git a/METADATA b/METADATA
index eac483f..6422324 100644
--- a/METADATA
+++ b/METADATA
@@ -5,11 +5,11 @@
     type: GIT
     value: "https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git"
   }
-  version: "v27"
+  version: "v28"
   license_type: RESTRICTED
   last_upgrade_date {
-    year: 2020
-    month: 10
-    day: 28
+    year: 2021
+    month: 1
+    day: 7
   }
 }
diff --git a/Makefile.am b/Makefile.am
index 8eadb99..acde92b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -31,6 +31,8 @@
 	-e 's,@exec_prefix\@,$(exec_prefix),g' \
 	-e 's,@libdir\@,$(libdir),g' \
 	-e 's,@includedir\@,$(includedir),g' \
+	-e 's,@libzstd_CFLAGS\@,${libzstd_CFLAGS},g' \
+	-e 's,@libzstd_LIBS\@,${libzstd_LIBS},g' \
 	-e 's,@liblzma_CFLAGS\@,${liblzma_CFLAGS},g' \
 	-e 's,@liblzma_LIBS\@,${liblzma_LIBS},g' \
 	-e 's,@zlib_CFLAGS\@,${zlib_CFLAGS},g' \
@@ -42,8 +44,21 @@
 %.pc: %.pc.in Makefile
 	$(SED_PROCESS)
 
+# Rules for libtool versioning (from https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html)
+# 1. Start with version information of ‘0:0:0’ for each libtool library.
+# 2. Update the version information only immediately before a public release of
+#    your software. More frequent updates are unnecessary, and only guarantee that
+#    the current interface number gets larger faster.
+# 3. If the library source code has changed at all since the last update, then
+#    increment revision (‘c:r:a’ becomes ‘c:r+1:a’).
+# 4. If any interfaces have been added, removed, or changed since the last
+#    update, increment current, and set revision to 0.
+# 5. If any interfaces have been added since the last public release, then
+#    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=5
+LIBKMOD_REVISION=6
 LIBKMOD_AGE=3
 
 noinst_LTLIBRARIES = shared/libshared.la
@@ -90,7 +105,7 @@
 	${top_srcdir}/libkmod/libkmod.sym
 libkmod_libkmod_la_LIBADD = \
 	shared/libshared.la \
-	${liblzma_LIBS} ${zlib_LIBS} ${libcrypto_LIBS}
+	${libzstd_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libcrypto_LIBS}
 
 noinst_LTLIBRARIES += libkmod/libkmod-internal.la
 libkmod_libkmod_internal_la_SOURCES = $(libkmod_libkmod_la_SOURCES)
@@ -411,7 +426,7 @@
 EXTRA_DIST += testsuite/rootfs-pristine
 
 DISTCHECK_CONFIGURE_FLAGS=--enable-gtk-doc --enable-python --sysconfdir=/etc \
-	--with-zlib --with-openssl \
+	--with-zlib --with-zstd --with-openssl \
 	--with-bashcompletiondir=$$dc_install_base/$(bashcompletiondir)
 
 distclean-local: $(DISTCLEAN_LOCAL_HOOKS)
diff --git a/NEWS b/NEWS
index 1c80582..ae56657 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,16 @@
+kmod 28
+=======
+
+- Improvements
+	- Add Zstandard to the supported compression formats using libzstd
+	  (pass --with-zstd to configure)
+
+- Bug fixes
+	- Ignore ill-formed kernel command line, e.g. with "ivrs_acpihid[00:14.5]=AMD0020:0"
+	  option in it
+	- Fix some memory leaks
+	- Fix 0-length builtin.alias.bin: it needs at least the index header
+
 kmod 27
 =======
 
@@ -415,7 +428,7 @@
 - New features:
 	- libkmod now keeps a file opened after the first call to
 	  kmod_module_get_{info,versions,symbols,dependency_symbols}. This
-	  reduces signficantly the amount of time depmod tool takes to
+	  reduces significantly the amount of time depmod tool takes to
 	  execute. Particularly if compressed modules are used.
 	- Remove --with-rootprefix from build system. It was not a great idea
 	  after all and should not be use since it causes more harm then
diff --git a/autogen.sh b/autogen.sh
index 67b119f..e4997c4 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -32,7 +32,14 @@
 
 cd $oldpwd
 
-hackargs="--enable-debug --enable-python --with-xz --with-zlib --with-openssl"
+hackargs="\
+--enable-debug \
+--enable-python \
+--with-zstd \
+--with-xz \
+--with-zlib \
+--with-openssl \
+"
 
 if [ "x$1" = "xc" ]; then
         shift
diff --git a/configure.ac b/configure.ac
index 4a65d6b..0cf2eda 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,6 @@
 AC_PREREQ(2.64)
 AC_INIT([kmod],
-	[27],
+	[28],
 	[linux-modules@vger.kernel.org],
 	[kmod],
 	[http://git.kernel.org/?p=utils/kernel/kmod/kmod.git])
@@ -83,6 +83,17 @@
         [], [with_rootlibdir=$libdir])
 AC_SUBST([rootlibdir], [$with_rootlibdir])
 
+AC_ARG_WITH([zstd],
+	AS_HELP_STRING([--with-zstd], [handle Zstandard-compressed modules @<:@default=disabled@:>@]),
+	[], [with_zstd=no])
+AS_IF([test "x$with_zstd" != "xno"], [
+	PKG_CHECK_MODULES([libzstd], [libzstd >= 1.4.4])
+	AC_DEFINE([ENABLE_ZSTD], [1], [Enable Zstandard for modules.])
+], [
+	AC_MSG_NOTICE([Zstandard support not requested])
+])
+CC_FEATURE_APPEND([with_features], [with_zstd], [ZSTD])
+
 AC_ARG_WITH([xz],
 	AS_HELP_STRING([--with-xz], [handle Xz-compressed modules @<:@default=disabled@:>@]),
 	[], [with_xz=no])
@@ -307,7 +318,7 @@
 	tools:			${enable_tools}
 	python bindings:	${enable_python}
 	logging:		${enable_logging}
-	compression:		xz=${with_xz}  zlib=${with_zlib}
+	compression:		zstd=${with_zstd}  xz=${with_xz}  zlib=${with_zlib}
 	debug:			${enable_debug}
 	coverage:		${enable_coverage}
 	doc:			${enable_gtk_doc}
diff --git a/libkmod/libkmod-builtin.c b/libkmod/libkmod-builtin.c
index aaec5dd..fc9a376 100644
--- a/libkmod/libkmod-builtin.c
+++ b/libkmod/libkmod-builtin.c
@@ -314,6 +314,7 @@
 		offset = get_string(iter, pos, &line, &linesz);
 		if (offset <= 0) {
 			count = (offset) ? -errno : -EOF;
+			free(*modinfo);
 			goto fail;
 		}
 
diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c
index 7b62367..971f20b 100644
--- a/libkmod/libkmod-config.c
+++ b/libkmod/libkmod-config.c
@@ -488,8 +488,9 @@
 		if (underscores(modname) < 0) {
 			ERR(config->ctx, "Ignoring bad option on kernel command line while parsing module name: '%s'\n",
 			    modname);
+		} else {
+			kmod_config_add_options(config, modname, param);
 		}
-		kmod_config_add_options(config, modname, param);
 	}
 }
 
diff --git a/libkmod/libkmod-file.c b/libkmod/libkmod-file.c
index 5eeba6a..b6a8cc9 100644
--- a/libkmod/libkmod-file.c
+++ b/libkmod/libkmod-file.c
@@ -26,6 +26,9 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#ifdef ENABLE_ZSTD
+#include <zstd.h>
+#endif
 #ifdef ENABLE_XZ
 #include <lzma.h>
 #endif
@@ -45,6 +48,9 @@
 };
 
 struct kmod_file {
+#ifdef ENABLE_ZSTD
+	bool zstd_used;
+#endif
 #ifdef ENABLE_XZ
 	bool xz_used;
 #endif
@@ -60,6 +66,141 @@
 	struct kmod_elf *elf;
 };
 
+#ifdef ENABLE_ZSTD
+static int zstd_read_block(struct kmod_file *file, size_t block_size,
+			   ZSTD_inBuffer *input, size_t *input_capacity)
+{
+	ssize_t rdret;
+	int ret;
+
+	if (*input_capacity < block_size) {
+		free((void *)input->src);
+		input->src = malloc(block_size);
+		if (input->src == NULL) {
+			ret = -errno;
+			ERR(file->ctx, "zstd: %m\n");
+			return ret;
+		}
+		*input_capacity = block_size;
+	}
+
+	rdret = read(file->fd, (void *)input->src, block_size);
+	if (rdret < 0) {
+		ret = -errno;
+		ERR(file->ctx, "zstd: %m\n");
+		return ret;
+	}
+
+	input->pos = 0;
+	input->size = rdret;
+	return 0;
+}
+
+static int zstd_ensure_outbuffer_space(ZSTD_outBuffer *buffer, size_t min_free)
+{
+	uint8_t *old_buffer = buffer->dst;
+	int ret = 0;
+
+	if (buffer->size - buffer->pos >= min_free)
+		return 0;
+
+	buffer->size += min_free;
+	buffer->dst = realloc(buffer->dst, buffer->size);
+	if (buffer->dst == NULL) {
+		ret = -errno;
+		free(old_buffer);
+	}
+
+	return ret;
+}
+
+static int zstd_decompress_block(struct kmod_file *file, ZSTD_DStream *dstr,
+				 ZSTD_inBuffer *input, ZSTD_outBuffer *output,
+				 size_t *next_block_size)
+{
+	size_t out_buf_min_size = ZSTD_DStreamOutSize();
+	int ret = 0;
+
+	do {
+		ssize_t dsret;
+
+		ret = zstd_ensure_outbuffer_space(output, out_buf_min_size);
+		if (ret) {
+			ERR(file->ctx, "zstd: %s\n", strerror(-ret));
+			break;
+		}
+
+		dsret = ZSTD_decompressStream(dstr, output, input);
+		if (ZSTD_isError(dsret)) {
+			ret = -EINVAL;
+			ERR(file->ctx, "zstd: %s\n", ZSTD_getErrorName(dsret));
+			break;
+		}
+		if (dsret > 0)
+			*next_block_size = (size_t)dsret;
+	} while (input->pos < input->size
+		 || output->pos > output->size
+		 || output->size - output->pos < out_buf_min_size);
+
+	return ret;
+}
+
+static int load_zstd(struct kmod_file *file)
+{
+	ZSTD_DStream *dstr;
+	size_t next_block_size;
+	size_t zst_inb_capacity = 0;
+	ZSTD_inBuffer zst_inb = { 0 };
+	ZSTD_outBuffer zst_outb = { 0 };
+	int ret;
+
+	dstr = ZSTD_createDStream();
+	if (dstr == NULL) {
+		ret = -EINVAL;
+		ERR(file->ctx, "zstd: Failed to create decompression stream\n");
+		goto out;
+	}
+
+	next_block_size = ZSTD_initDStream(dstr);
+
+	while (true) {
+		ret = zstd_read_block(file, next_block_size, &zst_inb,
+				      &zst_inb_capacity);
+		if (ret != 0)
+			goto out;
+		if (zst_inb.size == 0) /* EOF */
+			break;
+
+		ret = zstd_decompress_block(file, dstr, &zst_inb, &zst_outb,
+					    &next_block_size);
+		if (ret != 0)
+			goto out;
+	}
+
+	ZSTD_freeDStream(dstr);
+	free((void *)zst_inb.src);
+	file->zstd_used = true;
+	file->memory = zst_outb.dst;
+	file->size = zst_outb.pos;
+	return 0;
+out:
+	if (dstr != NULL)
+		ZSTD_freeDStream(dstr);
+	free((void *)zst_inb.src);
+	free((void *)zst_outb.dst);
+	return ret;
+}
+
+static void unload_zstd(struct kmod_file *file)
+{
+	if (!file->zstd_used)
+		return;
+	free(file->memory);
+}
+
+static const char magic_zstd[] = {0x28, 0xB5, 0x2F, 0xFD};
+#endif
+
 #ifdef ENABLE_XZ
 static void xz_uncompress_belch(struct kmod_file *file, lzma_ret ret)
 {
@@ -238,6 +379,9 @@
 	const char *magic_bytes;
 	const struct file_ops ops;
 } comp_types[] = {
+#ifdef ENABLE_ZSTD
+	{sizeof(magic_zstd), magic_zstd, {load_zstd, unload_zstd}},
+#endif
 #ifdef ENABLE_XZ
 	{sizeof(magic_xz), magic_xz, {load_xz, unload_xz}},
 #endif
diff --git a/libkmod/libkmod-index.c b/libkmod/libkmod-index.c
index 05669dc..2383e7e 100644
--- a/libkmod/libkmod-index.c
+++ b/libkmod/libkmod-index.c
@@ -611,7 +611,7 @@
 static const char _idx_empty_str[] = "";
 
 struct index_mm {
-	struct kmod_ctx *ctx;
+	const struct kmod_ctx *ctx;
 	void *mm;
 	uint32_t root_offset;
 	size_t size;
@@ -739,10 +739,10 @@
 	free(node);
 }
 
-struct index_mm *index_mm_open(struct kmod_ctx *ctx, const char *filename,
-						unsigned long long *stamp)
+int index_mm_open(const struct kmod_ctx *ctx, const char *filename,
+		  unsigned long long *stamp, struct index_mm **pidx)
 {
-	int fd;
+	int fd, err;
 	struct stat st;
 	struct index_mm *idx;
 	struct {
@@ -752,28 +752,32 @@
 	} hdr;
 	void *p;
 
+	assert(pidx != NULL);
+
 	DBG(ctx, "file=%s\n", filename);
 
 	idx = malloc(sizeof(*idx));
 	if (idx == NULL) {
 		ERR(ctx, "malloc: %m\n");
-		return NULL;
+		return -ENOMEM;
 	}
 
 	if ((fd = open(filename, O_RDONLY|O_CLOEXEC)) < 0) {
 		DBG(ctx, "open(%s, O_RDONLY|O_CLOEXEC): %m\n", filename);
+		err = -errno;
 		goto fail_open;
 	}
 
-	if (fstat(fd, &st) < 0)
+	if (fstat(fd, &st) < 0 || (size_t) st.st_size < sizeof(hdr)) {
+		err = -EINVAL;
 		goto fail_nommap;
-	if ((size_t) st.st_size < sizeof(hdr))
-		goto fail_nommap;
+	}
 
-	if ((idx->mm = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0))
-							== MAP_FAILED) {
+	idx->mm = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+	if (idx->mm == MAP_FAILED) {
 		ERR(ctx, "mmap(NULL, %"PRIu64", PROT_READ, %d, MAP_PRIVATE, 0): %m\n",
 							st.st_size, fd);
+		err = -errno;
 		goto fail_nommap;
 	}
 
@@ -785,12 +789,14 @@
 	if (hdr.magic != INDEX_MAGIC) {
 		ERR(ctx, "magic check fail: %x instead of %x\n", hdr.magic,
 								INDEX_MAGIC);
+		err = -EINVAL;
 		goto fail;
 	}
 
 	if (hdr.version >> 16 != INDEX_VERSION_MAJOR) {
 		ERR(ctx, "major version check fail: %u instead of %u\n",
 					hdr.version >> 16, INDEX_VERSION_MAJOR);
+		err = -EINVAL;
 		goto fail;
 	}
 
@@ -800,8 +806,9 @@
 	close(fd);
 
 	*stamp = stat_mstamp(&st);
+	*pidx = idx;
 
-	return idx;
+	return 0;
 
 fail:
 	munmap(idx->mm, st.st_size);
@@ -809,7 +816,7 @@
 	close(fd);
 fail_open:
 	free(idx);
-	return NULL;
+	return err;
 }
 
 void index_mm_close(struct index_mm *idx)
diff --git a/libkmod/libkmod-index.h b/libkmod/libkmod-index.h
index 52aebac..db671b0 100644
--- a/libkmod/libkmod-index.h
+++ b/libkmod/libkmod-index.h
@@ -40,8 +40,8 @@
 
 /* Implementation using mmap */
 struct index_mm;
-struct index_mm *index_mm_open(struct kmod_ctx *ctx, const char *filename,
-						unsigned long long *stamp);
+int index_mm_open(const struct kmod_ctx *ctx, const char *filename,
+		  unsigned long long *stamp, struct index_mm **pidx);
 void index_mm_close(struct index_mm *index);
 char *index_mm_search(struct index_mm *idx, const char *key);
 struct index_value *index_mm_searchwild(struct index_mm *idx, const char *key);
diff --git a/libkmod/libkmod-internal.h b/libkmod/libkmod-internal.h
index b22ac2a..398af9c 100644
--- a/libkmod/libkmod-internal.h
+++ b/libkmod/libkmod-internal.h
@@ -11,7 +11,7 @@
 #include "libkmod.h"
 
 static _always_inline_ _printf_format_(2, 3) void
-	kmod_log_null(struct kmod_ctx *ctx, const char *format, ...) {}
+	kmod_log_null(const struct kmod_ctx *ctx, const char *format, ...) {}
 
 #define kmod_log_cond(ctx, prio, arg...) \
 	do { \
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 714ee21..76a6dc3 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -577,13 +577,13 @@
 
 	DBG(ctx, "lookup modules.builtin.modinfo %s\n", alias);
 	err = kmod_lookup_alias_from_kernel_builtin_file(ctx, alias, list);
-	CHECK_ERR_AND_FINISH(err, fail, list, finish);
-
-	if (err == 0) {
+	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);
-		CHECK_ERR_AND_FINISH(err, fail, list, finish);
 	}
+	CHECK_ERR_AND_FINISH(err, fail, list, finish);
+
 
 finish:
 	DBG(ctx, "lookup %s=%d, list=%p\n", alias, err, *list);
diff --git a/libkmod/libkmod.c b/libkmod/libkmod.c
index c9d9e2a..43423d6 100644
--- a/libkmod/libkmod.c
+++ b/libkmod/libkmod.c
@@ -528,20 +528,17 @@
 						struct kmod_list **list)
 {
 	struct kmod_list *l;
-	int ret = kmod_lookup_alias_from_alias_bin(ctx,
-						KMOD_INDEX_MODULES_BUILTIN_ALIAS,
-						name, list);
-	if (ret > 0) {
-		kmod_list_foreach(l, *list) {
-			struct kmod_module *mod = l->data;
-			kmod_module_set_builtin(mod, true);
-		}
-	} else if (ret == -ENOSYS) {
-		/*
-		 * If the system does not support this yet, then
-		 * there is no need to return an error.
-		 */
-		ret = 0;
+	int ret;
+
+	assert(*list == NULL);
+
+	ret = kmod_lookup_alias_from_alias_bin(ctx,
+					       KMOD_INDEX_MODULES_BUILTIN_ALIAS,
+					       name, list);
+
+	kmod_list_foreach(l, *list) {
+		struct kmod_module *mod = l->data;
+		kmod_module_set_builtin(mod, true);
 	}
 
 	return ret;
@@ -858,6 +855,7 @@
  */
 KMOD_EXPORT int kmod_load_resources(struct kmod_ctx *ctx)
 {
+	int ret = 0;
 	size_t i;
 
 	if (ctx == NULL)
@@ -874,17 +872,25 @@
 
 		snprintf(path, sizeof(path), "%s/%s.bin", ctx->dirname,
 							index_files[i].fn);
-		ctx->indexes[i] = index_mm_open(ctx, path,
-						&ctx->indexes_stamp[i]);
-		if (ctx->indexes[i] == NULL)
-			goto fail;
+		ret = index_mm_open(ctx, path, &ctx->indexes_stamp[i],
+				    &ctx->indexes[i]);
+
+		/*
+		 * modules.builtin.alias are considered optional since it's
+		 * recently added and older installations may not have it;
+		 * we allow failing for any reason
+		 */
+		if (ret) {
+			if (i != KMOD_INDEX_MODULES_BUILTIN_ALIAS)
+				break;
+			ret = 0;
+		}
 	}
 
-	return 0;
+	if (ret)
+		kmod_unload_resources(ctx);
 
-fail:
-	kmod_unload_resources(ctx);
-	return -ENOMEM;
+	return ret;
 }
 
 /**
diff --git a/libkmod/libkmod.pc.in b/libkmod/libkmod.pc.in
index e4fdf21..3acca6a 100644
--- a/libkmod/libkmod.pc.in
+++ b/libkmod/libkmod.pc.in
@@ -7,5 +7,5 @@
 Description: Library to deal with kernel modules
 Version: @VERSION@
 Libs: -L${libdir} -lkmod
-Libs.private: @liblzma_LIBS@ @zlib_LIBS@
+Libs.private: @libzstd_LIBS@ @liblzma_LIBS@ @zlib_LIBS@
 Cflags: -I${includedir}
diff --git a/man/modules.dep.xml b/man/modules.dep.xml
index e53293a..ed63369 100644
--- a/man/modules.dep.xml
+++ b/man/modules.dep.xml
@@ -48,7 +48,7 @@
       libkmod.
     </para>
     <para>
-      Its text counterpar is located in the same directory with the name
+      Its text counterpart is located in the same directory with the name
       <filename>modules.dep</filename>. The text version is maintained only
       for easy of reading by humans and is in no way used by any kmod tool.
     </para>
diff --git a/shared/macro.h b/shared/macro.h
index 4fc5405..b59f7dc 100644
--- a/shared/macro.h
+++ b/shared/macro.h
@@ -45,9 +45,14 @@
 	})
 
 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + _array_size_chk(arr))
+
 #define XSTRINGIFY(x) #x
 #define STRINGIFY(x) XSTRINGIFY(x)
 
+#define XCONCATENATE(x, y) x ## y
+#define CONCATENATE(x, y) XCONCATENATE(x, y)
+#define UNIQ(x) CONCATENATE(x, __COUNTER__)
+
 /* Temporaries for importing index handling */
 #define NOFAIL(x) (x)
 #define fatal(x...) do { } while (0)
@@ -69,5 +74,3 @@
 #define noreturn __attribute__((noreturn))
 #endif
 #endif
-
-#define UNIQ __COUNTER__
diff --git a/shared/util.c b/shared/util.c
index fd2028d..b487b5f 100644
--- a/shared/util.c
+++ b/shared/util.c
@@ -46,6 +46,9 @@
 #ifdef ENABLE_XZ
 	{".ko.xz", sizeof(".ko.xz") - 1},
 #endif
+#ifdef ENABLE_ZSTD
+	{".ko.zst", sizeof(".ko.zst") - 1},
+#endif
 	{ }
 };
 
diff --git a/testsuite/mkosi/mkosi.fedora b/testsuite/mkosi/mkosi.fedora
index 5252f87..7a2ee5e 100644
--- a/testsuite/mkosi/mkosi.fedora
+++ b/testsuite/mkosi/mkosi.fedora
@@ -19,6 +19,7 @@
 	make
 	pkgconf-pkg-config
 	xml-common
+	libzstd-devel
 	xz-devel
 	zlib-devel
 	openssl-devel
diff --git a/testsuite/module-playground/.gitignore b/testsuite/module-playground/.gitignore
index db63fe4..fca12f3 100644
--- a/testsuite/module-playground/.gitignore
+++ b/testsuite/module-playground/.gitignore
@@ -2,6 +2,7 @@
 *.ko
 !mod-simple-*.ko
 !cache/*.ko
+.cache.mk
 *.mod.c
 .tmp_versions
 *.mod
diff --git a/testsuite/populate-modules.sh b/testsuite/populate-modules.sh
index 5140f7a..358e740 100755
--- a/testsuite/populate-modules.sh
+++ b/testsuite/populate-modules.sh
@@ -66,10 +66,13 @@
 
 gzip_array=(
     "test-depmod/modules-order-compressed/lib/modules/4.4.4/kernel/drivers/block/cciss.ko"
-    "test-depmod/modules-order-compressed/lib/modules/4.4.4/kernel/drivers/scsi/hpsa.ko"
     "test-depmod/modules-order-compressed/lib/modules/4.4.4/kernel/drivers/scsi/scsi_mod.ko"
     )
 
+zstd_array=(
+    "test-depmod/modules-order-compressed/lib/modules/4.4.4/kernel/drivers/scsi/hpsa.ko"
+    )
+
 attach_sha256_array=(
     "test-modinfo/mod-simple-sha256.ko"
     )
@@ -101,6 +104,11 @@
     gzip $ROOTFS/$m
 done
 
+# zstd-compress these modules
+for m in "${zstd_array[@]}"; do
+    zstd --rm $ROOTFS/$m
+done
+
 for m in "${attach_sha1_array[@]}"; do
     cat ${MODULE_PLAYGROUND}/dummy.sha1 >> ${ROOTFS}/$m
 done
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.alias b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.alias
new file mode 100644
index 0000000..ba76e18
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.alias
@@ -0,0 +1 @@
+# Aliases extracted from modules themselves.
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.alias.bin b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.alias.bin
new file mode 100644
index 0000000..7075435
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.alias.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin
new file mode 100644
index 0000000..1cbec61
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin
@@ -0,0 +1 @@
+kernel/fake_builtin.ko
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin.alias.bin b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin.alias.bin
new file mode 100644
index 0000000..7075435
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin.alias.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin.bin b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin.bin
new file mode 100644
index 0000000..0423f03
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.builtin.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.dep b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.dep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.dep
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.dep.bin b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.dep.bin
new file mode 100644
index 0000000..7075435
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.dep.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.devname b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.devname
new file mode 100644
index 0000000..58f6d6d
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.devname
@@ -0,0 +1 @@
+# Device nodes to trigger on-demand module loading.
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.softdep b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.softdep
new file mode 100644
index 0000000..5554ccc
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.softdep
@@ -0,0 +1 @@
+# Soft dependencies extracted from modules themselves.
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.symbols b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.symbols
new file mode 100644
index 0000000..618c345
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.symbols
@@ -0,0 +1 @@
+# Aliases for symbols, used by symbol_request().
diff --git a/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.symbols.bin b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.symbols.bin
new file mode 100644
index 0000000..7075435
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources-empty-builtin-aliases-bin/lib/modules/5.6.0/modules.symbols.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.alias b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.alias
new file mode 100644
index 0000000..ba76e18
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.alias
@@ -0,0 +1 @@
+# Aliases extracted from modules themselves.
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.alias.bin b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.alias.bin
new file mode 100644
index 0000000..7075435
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.alias.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.builtin b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.builtin
new file mode 100644
index 0000000..1cbec61
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.builtin
@@ -0,0 +1 @@
+kernel/fake_builtin.ko
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.builtin.bin b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.builtin.bin
new file mode 100644
index 0000000..0423f03
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.builtin.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.dep b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.dep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.dep
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.dep.bin b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.dep.bin
new file mode 100644
index 0000000..7075435
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.dep.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.devname b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.devname
new file mode 100644
index 0000000..58f6d6d
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.devname
@@ -0,0 +1 @@
+# Device nodes to trigger on-demand module loading.
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.softdep b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.softdep
new file mode 100644
index 0000000..5554ccc
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.softdep
@@ -0,0 +1 @@
+# Soft dependencies extracted from modules themselves.
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.symbols b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.symbols
new file mode 100644
index 0000000..618c345
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.symbols
@@ -0,0 +1 @@
+# Aliases for symbols, used by symbol_request().
diff --git a/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.symbols.bin b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.symbols.bin
new file mode 100644
index 0000000..7075435
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-init-load-resources/lib/modules/5.6.0/modules.symbols.bin
Binary files differ
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline6/correct.txt b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline6/correct.txt
new file mode 100644
index 0000000..7a087ef
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline6/correct.txt
@@ -0,0 +1,6 @@
+options psmouse foo=2
+options psmouse bar=1
+options psmouse zinga=test
+
+# End of configuration files. Dumping indexes now:
+
diff --git a/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline6/proc/cmdline b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline6/proc/cmdline
new file mode 100644
index 0000000..14bbd2e
--- /dev/null
+++ b/testsuite/rootfs-pristine/test-modprobe/module-param-kcmdline6/proc/cmdline
@@ -0,0 +1 @@
+psmouse.foo=2 ivrs_acpihid[14:00.5]=AMD0020:00 psmouse.bar=1 psmouse.zinga=test
diff --git a/testsuite/test-init.c b/testsuite/test-init.c
index 9651280..edbfc23 100644
--- a/testsuite/test-init.c
+++ b/testsuite/test-init.c
@@ -29,6 +29,44 @@
 
 #include "testsuite.h"
 
+static noreturn int test_load_resources(const struct test *t)
+{
+	struct kmod_ctx *ctx;
+	const char *null_config = NULL;
+	int err;
+
+	ctx = kmod_new(NULL, &null_config);
+	if (ctx == NULL)
+		exit(EXIT_FAILURE);
+
+	kmod_set_log_priority(ctx, 7);
+
+	err = kmod_load_resources(ctx);
+	if (err != 0) {
+		ERR("could not load libkmod resources: %s\n", strerror(-err));
+		exit(EXIT_FAILURE);
+	}
+
+	kmod_unref(ctx);
+
+	exit(EXIT_SUCCESS);
+}
+DEFINE_TEST(test_load_resources,
+	    .description = "test if kmod_load_resources works (recent modprobe on kernel without modules.builtin.modinfo)",
+	    .config = {
+		[TC_ROOTFS] = TESTSUITE_ROOTFS "test-init-load-resources/",
+		[TC_UNAME_R] = "5.6.0",
+	    },
+	    .need_spawn = true);
+
+DEFINE_TEST(test_load_resources,
+	    .description = "test if kmod_load_resources works with empty modules.builtin.aliases.bin (recent depmod on kernel without modules.builtin.modinfo)",
+	    .config = {
+		[TC_ROOTFS] = TESTSUITE_ROOTFS "test-init-load-resources-empty-builtin-aliases-bin/",
+		[TC_UNAME_R] = "5.6.0",
+	    },
+	    .need_spawn = true);
+
 static noreturn int test_initlib(const struct test *t)
 {
 	struct kmod_ctx *ctx;
diff --git a/testsuite/test-modprobe.c b/testsuite/test-modprobe.c
index 1cace82..f908d56 100644
--- a/testsuite/test-modprobe.c
+++ b/testsuite/test-modprobe.c
@@ -328,6 +328,31 @@
 	);
 
 
+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,
+	.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-kcmdline6",
+	},
+	.output = {
+		.out = TESTSUITE_ROOTFS "test-modprobe/module-param-kcmdline6/correct.txt",
+	},
+	.modules_loaded = "",
+	);
+
+
 static noreturn int modprobe_force(const struct test *t)
 {
 	const char *progname = ABS_TOP_BUILDDIR "/tools/modprobe";
diff --git a/testsuite/test-util.c b/testsuite/test-util.c
index 5e25e58..621446b 100644
--- a/testsuite/test-util.c
+++ b/testsuite/test-util.c
@@ -157,6 +157,9 @@
 #ifdef ENABLE_XZ
 		{ "/bla.ko.xz", true },
 #endif
+#ifdef ENABLE_ZSTD
+		{ "/bla.ko.zst", true },
+#endif
 		{ "/bla.ko.x", false },
 		{ "/bla.ko.", false },
 		{ "/bla.koz", false },
diff --git a/testsuite/testsuite.h b/testsuite/testsuite.h
index 7ed96bf..f190249 100644
--- a/testsuite/testsuite.h
+++ b/testsuite/testsuite.h
@@ -139,7 +139,7 @@
 
 /* Test definitions */
 #define DEFINE_TEST(_name, ...) \
-	static const struct test s##_name##UNIQ \
+	static const struct test UNIQ(s##_name) \
 	__attribute__((used, section("kmod_tests"), aligned(8))) = { \
 		.name = #_name, \
 		.func = _name, \
diff --git a/tools/depmod.c b/tools/depmod.c
index fbbce10..3f31cdf 100644
--- a/tools/depmod.c
+++ b/tools/depmod.c
@@ -2408,18 +2408,18 @@
 	struct index_node *idx;
 	struct kmod_list *l, *builtin = NULL;
 
-	idx = index_create();
+	if (out == stdout)
+		return 0;
 
-	if (idx == NULL) {
-		ret = -ENOMEM;
-		goto fail;
-	}
+	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 fail;
+		goto out;
 	}
 
 	kmod_list_foreach(l, builtin) {
@@ -2429,7 +2429,7 @@
 
 		ret = kmod_module_get_info(mod, &info_list);
 		if (ret < 0)
-			goto fail;
+			goto out;
 
 		kmod_list_foreach(ll, info_list) {
 			char alias[PATH_MAX];
@@ -2454,13 +2454,16 @@
 		count++;
 	}
 
-	if (count)
+out:
+	/* do not bother writing the index if we are going to discard it */
+	if (!ret)
 		index_write(idx, out);
-	index_destroy(idx);
-fail:
+
 	if (builtin)
 		kmod_module_unref_list(builtin);
 
+	index_destroy(idx);
+
 	return ret;
 }