diff mbox series

[kmod] libkmod: add user soft dependecies

Message ID 20240318161517.58550-1-jtornosm@redhat.com (mailing list archive)
State New
Headers show
Series [kmod] libkmod: add user soft dependecies | expand

Commit Message

Jose Ignacio Tornos Martinez March 18, 2024, 4:15 p.m. UTC
It has been seen that for some network mac drivers (i.e. lan78xx) the
related module for the phy is loaded dynamically depending on the current
hardware. In this case, the associated phy is read using mdio bus and then
the associated phy module is loaded during runtime (kernel function
phy_request_driver_module). However, no software dependency is defined, so
the user tools will no be able to get this dependency. For example, if
dracut is used and the hardware is present, lan78xx will be included but no
phy module will be added, and in the next restart the device will not work
from boot because no related phy will be found during initramfs stage.

In order to solve this, we could define a normal 'pre' software dependency
in lan78xx module with all the possible phy modules (there may be some),
but proceeding in that way, all the possible phy modules would be loaded
while only one is necessary.

The idea is to add a new attribute when the software dependency is defined,
apart from the normal ones 'pre' and 'post', I have called it 'user', to be
used only by the user tools that need to detect this situation. In that
way, for example, dracut could check the 'user' attribute of the modules in
order to install these software dependencies in initramfs too. That is, for
the  commented lan78xx module, defining the 'user' attribute to the
software dependency with the possible phy modules list, only the necessary
phy would be loaded on demand keeping the same behavior but all the
possible phy modules would be available from initramfs.

A new function 'kmod_module_get_user_softdeps' in libkmod will be added for
this to avoid breaking the API and maintain backward compatibility. This
general procedure could be useful for other similar cases (not only for
dynamic phy loading).

Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
 libkmod/docs/libkmod-sections.txt |  1 +
 libkmod/libkmod-config.c          | 66 +++++++++++++++++++++++++++----
 libkmod/libkmod-internal.h        |  1 +
 libkmod/libkmod-module.c          | 50 +++++++++++++++++++++++
 libkmod/libkmod.h                 |  2 +
 libkmod/libkmod.sym               |  1 +
 6 files changed, 114 insertions(+), 7 deletions(-)

Comments

Lucas De Marchi March 20, 2024, 7:16 a.m. UTC | #1
On Mon, Mar 18, 2024 at 05:15:14PM +0100, Jose Ignacio Tornos Martinez wrote:
>It has been seen that for some network mac drivers (i.e. lan78xx) the
>related module for the phy is loaded dynamically depending on the current
>hardware. In this case, the associated phy is read using mdio bus and then
>the associated phy module is loaded during runtime (kernel function
>phy_request_driver_module). However, no software dependency is defined, so
>the user tools will no be able to get this dependency. For example, if
>dracut is used and the hardware is present, lan78xx will be included but no
>phy module will be added, and in the next restart the device will not work
>from boot because no related phy will be found during initramfs stage.
>
>In order to solve this, we could define a normal 'pre' software dependency
>in lan78xx module with all the possible phy modules (there may be some),
>but proceeding in that way, all the possible phy modules would be loaded
>while only one is necessary.
>
>The idea is to add a new attribute when the software dependency is defined,
>apart from the normal ones 'pre' and 'post', I have called it 'user', to be
>used only by the user tools that need to detect this situation. In that
>way, for example, dracut could check the 'user' attribute of the modules in
>order to install these software dependencies in initramfs too. That is, for
>the  commented lan78xx module, defining the 'user' attribute to the
>software dependency with the possible phy modules list, only the necessary
>phy would be loaded on demand keeping the same behavior but all the
>possible phy modules would be available from initramfs.
>
>A new function 'kmod_module_get_user_softdeps' in libkmod will be added for
>this to avoid breaking the API and maintain backward compatibility. This
>general procedure could be useful for other similar cases (not only for
>dynamic phy loading).

so it's basically a pre softdep, but without libkmod (userspace) trying
to load it before the module requested. So, it's "softer than soft" or
even "something before pre".

Thinking this way I find the name chosen odd, as the *user*space side of
module loading will actually *not* look into those deps.

Cc'ing some more people for suggestions, as I only know I don't like
"user", but my suggestions may considered equally bad too:

	dull / still / early / runtime_request / maybe

Anyway, we will need to explain exactly what this is about in
modprobe.d(5).  Other than the use case of creating a initramfs and not
missing any module, I don't think there would be any, right?


Lucas De Marchi

>
>Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
>---
> libkmod/docs/libkmod-sections.txt |  1 +
> libkmod/libkmod-config.c          | 66 +++++++++++++++++++++++++++----
> libkmod/libkmod-internal.h        |  1 +
> libkmod/libkmod-module.c          | 50 +++++++++++++++++++++++
> libkmod/libkmod.h                 |  2 +
> libkmod/libkmod.sym               |  1 +
> 6 files changed, 114 insertions(+), 7 deletions(-)
>
>diff --git a/libkmod/docs/libkmod-sections.txt b/libkmod/docs/libkmod-sections.txt
>index 33d9eec..04743e4 100644
>--- a/libkmod/docs/libkmod-sections.txt
>+++ b/libkmod/docs/libkmod-sections.txt
>@@ -62,6 +62,7 @@ kmod_module_remove_module
> kmod_module_get_module
> kmod_module_get_dependencies
> kmod_module_get_softdeps
>+kmod_module_get_user_softdeps
> kmod_module_apply_filter
> kmod_module_get_filtered_blacklist
> kmod_module_get_install_commands
>diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c
>index e83621b..c0e15be 100644
>--- a/libkmod/libkmod-config.c
>+++ b/libkmod/libkmod-config.c
>@@ -54,8 +54,10 @@ struct kmod_softdep {
> 	char *name;
> 	const char **pre;
> 	const char **post;
>+	const char **user;
> 	unsigned int n_pre;
> 	unsigned int n_post;
>+	unsigned int n_user;
> };
>
> const char *kmod_blacklist_get_modname(const struct kmod_list *l)
>@@ -110,6 +112,12 @@ const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned in
> 	return dep->post;
> }
>
>+const char * const *kmod_softdep_get_user(const struct kmod_list *l, unsigned int *count) {
>+	const struct kmod_softdep *dep = l->data;
>+	*count = dep->n_user;
>+	return dep->user;
>+}
>+
> static int kmod_config_add_command(struct kmod_config *config,
> 						const char *modname,
> 						const char *command,
>@@ -263,11 +271,11 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> 	struct kmod_softdep *dep;
> 	const char *s, *p;
> 	char *itr;
>-	unsigned int n_pre = 0, n_post = 0;
>+	unsigned int n_pre = 0, n_post = 0, n_user = 0;
> 	size_t modnamelen = strlen(modname) + 1;
> 	size_t buflen = 0;
> 	bool was_space = false;
>-	enum { S_NONE, S_PRE, S_POST } mode = S_NONE;
>+	enum { S_NONE, S_PRE, S_POST, S_USER } mode = S_NONE;
>
> 	DBG(config->ctx, "modname=%s\n", modname);
>
>@@ -298,6 +306,9 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> 		else if (plen == sizeof("post:") - 1 &&
> 				memcmp(p, "post:", sizeof("post:") - 1) == 0)
> 			mode = S_POST;
>+		else if (plen == sizeof("user:") - 1 &&
>+				memcmp(p, "user:", sizeof("user:") - 1) == 0)
>+			mode = S_USER;
> 		else if (*s != '\0' || (*s == '\0' && !was_space)) {
> 			if (mode == S_PRE) {
> 				buflen += plen + 1;
>@@ -305,6 +316,9 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> 			} else if (mode == S_POST) {
> 				buflen += plen + 1;
> 				n_post++;
>+			} else if (mode == S_USER) {
>+				buflen += plen + 1;
>+				n_user++;
> 			}
> 		}
> 		p = s + 1;
>@@ -312,11 +326,12 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> 			break;
> 	}
>
>-	DBG(config->ctx, "%u pre, %u post\n", n_pre, n_post);
>+	DBG(config->ctx, "%u pre, %u post, %u user\n", n_pre, n_post, n_user);
>
> 	dep = malloc(sizeof(struct kmod_softdep) + modnamelen +
> 		     n_pre * sizeof(const char *) +
> 		     n_post * sizeof(const char *) +
>+		     n_user * sizeof(const char *) +
> 		     buflen);
> 	if (dep == NULL) {
> 		ERR(config->ctx, "out-of-memory modname=%s\n", modname);
>@@ -324,9 +339,11 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> 	}
> 	dep->n_pre = n_pre;
> 	dep->n_post = n_post;
>+	dep->n_user = n_user;
> 	dep->pre = (const char **)((char *)dep + sizeof(struct kmod_softdep));
> 	dep->post = dep->pre + n_pre;
>-	dep->name = (char *)(dep->post + n_post);
>+	dep->user = dep->post + n_post;
>+	dep->name = (char *)(dep->user + n_user);
>
> 	memcpy(dep->name, modname, modnamelen);
>
>@@ -334,6 +351,7 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> 	itr = dep->name + modnamelen;
> 	n_pre = 0;
> 	n_post = 0;
>+	n_user = 0;
> 	mode = S_NONE;
> 	was_space = false;
> 	for (p = s = line; ; s++) {
>@@ -362,6 +380,9 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> 		else if (plen == sizeof("post:") - 1 &&
> 				memcmp(p, "post:", sizeof("post:") - 1) == 0)
> 			mode = S_POST;
>+		else if (plen == sizeof("user:") - 1 &&
>+				memcmp(p, "user:", sizeof("user:") - 1) == 0)
>+			mode = S_USER;
> 		else if (*s != '\0' || (*s == '\0' && !was_space)) {
> 			if (mode == S_PRE) {
> 				dep->pre[n_pre] = itr;
>@@ -375,6 +396,12 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> 				itr[plen] = '\0';
> 				itr += plen + 1;
> 				n_post++;
>+			} else if (mode == S_USER) {
>+				dep->user[n_user] = itr;
>+				memcpy(itr, p, plen);
>+				itr[plen] = '\0';
>+				itr += plen + 1;
>+				n_user++;
> 			}
> 		}
> 		p = s + 1;
>@@ -395,14 +422,15 @@ static int kmod_config_add_softdep(struct kmod_config *config,
> static char *softdep_to_char(struct kmod_softdep *dep) {
> 	const size_t sz_preprefix = sizeof("pre: ") - 1;
> 	const size_t sz_postprefix = sizeof("post: ") - 1;
>+	const size_t sz_userprefix = sizeof("user: ") - 1;
> 	size_t sz = 1; /* at least '\0' */
>-	size_t sz_pre, sz_post;
>+	size_t sz_pre, sz_post, sz_user;
> 	const char *start, *end;
> 	char *s, *itr;
>
> 	/*
>-	 * Rely on the fact that dep->pre[] and dep->post[] are strv's that
>-	 * point to a contiguous buffer
>+	 * Rely on the fact that dep->pre[] dep->post[] and dep->user[]
>+	 * are strv's that point to a contiguous buffer
> 	 */
> 	if (dep->n_pre > 0) {
> 		start = dep->pre[0];
>@@ -422,6 +450,15 @@ static char *softdep_to_char(struct kmod_softdep *dep) {
> 	} else
> 		sz_post = 0;
>
>+	if (dep->n_user > 0) {
>+		start = dep->user[0];
>+		end = dep->user[dep->n_user - 1]
>+					+ strlen(dep->user[dep->n_user - 1]);
>+		sz_user = end - start;
>+		sz += sz_user + sz_userprefix;
>+	} else
>+		sz_user = 0;
>+
> 	itr = s = malloc(sz);
> 	if (s == NULL)
> 		return NULL;
>@@ -456,6 +493,21 @@ static char *softdep_to_char(struct kmod_softdep *dep) {
> 		itr = p;
> 	}
>
>+	if (sz_user) {
>+		char *p;
>+
>+		memcpy(itr, "user: ", sz_userprefix);
>+		itr += sz_userprefix;
>+
>+		/* include last '\0' */
>+		memcpy(itr, dep->user[0], sz_user + 1);
>+		for (p = itr; p < itr + sz_user; p++) {
>+			if (*p == '\0')
>+				*p = ' ';
>+		}
>+		itr = p;
>+	}
>+
> 	*itr = '\0';
>
> 	return s;
>diff --git a/libkmod/libkmod-internal.h b/libkmod/libkmod-internal.h
>index 26a7e28..8e4f112 100644
>--- a/libkmod/libkmod-internal.h
>+++ b/libkmod/libkmod-internal.h
>@@ -145,6 +145,7 @@ const char *kmod_command_get_modname(const struct kmod_list *l) __attribute__((n
> const char *kmod_softdep_get_name(const struct kmod_list *l) __attribute__((nonnull(1)));
> const char * const *kmod_softdep_get_pre(const struct kmod_list *l, unsigned int *count) __attribute__((nonnull(1, 2)));
> const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned int *count);
>+const char * const *kmod_softdep_get_user(const struct kmod_list *l, unsigned int *count);
>
>
> /* libkmod-module.c */
>diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
>index 585da41..dbe676c 100644
>--- a/libkmod/libkmod-module.c
>+++ b/libkmod/libkmod-module.c
>@@ -1664,6 +1664,56 @@ KMOD_EXPORT int kmod_module_get_softdeps(const struct kmod_module *mod,
> 	return 0;
> }
>
>+/**
>+ * kmod_module_get_user_softdeps:
>+ * @mod: kmod module
>+ * @user: where to save the list of user soft dependencies.
>+ *
>+ * Get user dependencies for this kmod module. Soft dependencies come
>+ * from configuration file and are not cached in @mod because it may include
>+ * dependency cycles that would make we leak kmod_module. Any call
>+ * to this function will search for this module in configuration, allocate a
>+ * list and return the result.
>+ *
>+ * @user is newly created list of kmod_module and
>+ * should be unreferenced with kmod_module_unref_list().
>+ *
>+ * Returns: 0 on success or < 0 otherwise.
>+ */
>+KMOD_EXPORT int kmod_module_get_user_softdeps(const struct kmod_module *mod,
>+						struct kmod_list **user)
>+{
>+	const struct kmod_list *l;
>+	const struct kmod_config *config;
>+
>+	if (mod == NULL || user == NULL)
>+		return -ENOENT;
>+
>+	assert(*user == NULL);
>+
>+	config = kmod_get_config(mod->ctx);
>+
>+	kmod_list_foreach(l, config->softdeps) {
>+		const char *modname = kmod_softdep_get_name(l);
>+		const char * const *array;
>+		unsigned count;
>+
>+		if (fnmatch(modname, mod->name, 0) != 0)
>+			continue;
>+
>+		array = kmod_softdep_get_user(l, &count);
>+		*user = lookup_softdep(mod->ctx, array, count);
>+
>+		/*
>+		 * find only the first command, as modprobe from
>+		 * module-init-tools does
>+		 */
>+		break;
>+	}
>+
>+	return 0;
>+}
>+
> /**
>  * kmod_module_get_remove_commands:
>  * @mod: kmod module
>diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
>index 7251aa7..ec6d270 100644
>--- a/libkmod/libkmod.h
>+++ b/libkmod/libkmod.h
>@@ -196,6 +196,8 @@ const char *kmod_module_get_remove_commands(const struct kmod_module *mod);
> struct kmod_list *kmod_module_get_dependencies(const struct kmod_module *mod);
> int kmod_module_get_softdeps(const struct kmod_module *mod,
> 				struct kmod_list **pre, struct kmod_list **post);
>+int kmod_module_get_user_softdeps(const struct kmod_module *mod,
>+					struct kmod_list **user);
> int kmod_module_get_filtered_blacklist(const struct kmod_ctx *ctx,
> 					const struct kmod_list *input,
> 					struct kmod_list **output) __attribute__ ((deprecated));
>diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym
>index 0c04fda..26c3eef 100644
>--- a/libkmod/libkmod.sym
>+++ b/libkmod/libkmod.sym
>@@ -42,6 +42,7 @@ global:
>
> 	kmod_module_get_dependencies;
> 	kmod_module_get_softdeps;
>+	kmod_module_get_user_softdeps;
> 	kmod_module_get_filtered_blacklist;
>
> 	kmod_module_get_name;
>-- 
>2.44.0
>
Jose Ignacio Tornos Martinez March 20, 2024, 9:05 a.m. UTC | #2
> so it's basically a pre softdep, but without libkmod (userspace) trying
> to load it before the module requested. So, it's "softer than soft" or
> even "something before pre".
>
> Thinking this way I find the name chosen odd, as the *user*space side of
> module loading will actually *not* look into those deps.
>
> Cc'ing some more people for suggestions, as I only know I don't like
> "user", but my suggestions may considered equally bad too:
>
>        dull / still / early / runtime_request / maybe
Ok, I thought of "user" because it was only going to be used by user
applications but it could have other interpretations.
Maybe another idea: "internal" to inform that there are dependencies and
these are going to be solved internally?

> Anyway, we will need to explain exactly what this is about in
> modprobe.d(5).
Ok, I will complete it when the dependency name is decided.

> Other than the use case of creating a initramfs and not
> missing any module, I don't think there would be any, right?
Yes, my purpose is only that, I don't have detected any other case.

Thanks

Best regards
José Ignacio
Lucas De Marchi March 20, 2024, 1:57 p.m. UTC | #3
On Wed, Mar 20, 2024 at 10:05:56AM +0100, Jose Ignacio Tornos Martinez wrote:
>> so it's basically a pre softdep, but without libkmod (userspace) trying
>> to load it before the module requested. So, it's "softer than soft" or
>> even "something before pre".
>>
>> Thinking this way I find the name chosen odd, as the *user*space side of
>> module loading will actually *not* look into those deps.
>>
>> Cc'ing some more people for suggestions, as I only know I don't like
>> "user", but my suggestions may considered equally bad too:
>>
>>        dull / still / early / runtime_request / maybe
>Ok, I thought of "user" because it was only going to be used by user
>applications but it could have other interpretations.
>Maybe another idea: "internal" to inform that there are dependencies and
>these are going to be solved internally?

a night of sleep and I had a dream in which libkmod had the concept of
"weak dependency". Borrowing the concept from weak symbols, I think it
makes perfect sense... the symbol is there and it may or may not be used
by the linker at the end, but the symbol needs to be there until the
linking phase. At least the parallel makes sense in my head :)

Also, I don't think we should mix them with softdep like is done here
after a quick scan through the patch.

 From man page:

        softdep modulename pre: modules... post: modules...
	   The softdep command allows you to specify soft, or optional,
	   module dependencies.  modulename can be used without these
	   optional modules installed, but usually with some features
	   missing. For example, a driver for a storage HBA might
	   require another module be loaded in order to use management
	   features.

	   pre-deps and post-deps modules are lists of names and/or
	   aliases of other modules that modprobe will attempt to
	   install (or remove) in order before and after the main module
	   given in the modulename argument.

	   Example: Assume "softdep c pre: a b post: d e" is provided in
	   the configuration. Running "modprobe c" is now equivalent to
	   "modprobe a b c d e" without the softdep. Flags such as
	   --use-blacklist are applied to all the specified modules,
	   while module parameters only apply to module c.

	   Note: if there are install or remove commands with the same
	   modulename argument, softdep takes precedence.

	weakdep modulename modules...
	   The weakdep command allows you to specify weak module
	   dependecies. Those are similar to pre softdep, with the
	   difference that userspace doesn't attempt to load that
	   dependency before the specified module. Instead the kernel
	   may request one or multiple of them during module probe,
	   depending on the hardware it's binding to. The purpose of
	   weak module is to allow a driver to specify that a certain
	   dependency may be needed, so it should be present in the
	   filesystem (e.g. in initramfs) when that module is probed.

	   Example: Assume "weakdep c a b". A program creating an
	   initramfs knows it should add a, b, and c to the filesystem
	   since a and b may be required/desired at runtime. When c is
	   loaded and is being probed, it may issue calls to
	   request_module() causing a or b to also be loaded.

Also instead of delegating this to the distros, it'd be good if we start
adding those to the ELF section of the modules with

	MODULE_WEAKDEP("...");

... to be defined in the kernel in a similar way that MODULE_SOFTDEP()
is.

>
>> Anyway, we will need to explain exactly what this is about in
>> modprobe.d(5).
>Ok, I will complete it when the dependency name is decided.
>
>> Other than the use case of creating a initramfs and not
>> missing any module, I don't think there would be any, right?
>Yes, my purpose is only that, I don't have detected any other case.


thanks
Lucas De Marchi

>
>Thanks
>
>Best regards
>José Ignacio
>
Jose Ignacio Tornos Martinez March 20, 2024, 2:48 p.m. UTC | #4
> a night of sleep and I had a dream in which libkmod had the concept of
> "weak dependency". Borrowing the concept from weak symbols, I think it
> makes perfect sense... the symbol is there and it may or may not be used
> by the linker at the end, but the symbol needs to be there until the
> linking phase. At least the parallel makes sense in my head :)
Ok, I like your dream :)

> Also, I don't think we should mix them with softdep like is done here
> after a quick scan through the patch.
Ok, understood, and if a new case for softdep is not going to be used,
it is clearer: better not mixing with softdep processing.

> From man page:
>    softdep modulename pre: modules... post: modules...
>           The softdep command allows you to specify soft, or optional,
>           module dependencies.  modulename can be used without these
>           optional modules installed, but usually with some features
>           missing. For example, a driver for a storage HBA might
>           require another module be loaded in order to use management
>           features.
>
>           pre-deps and post-deps modules are lists of names and/or
>           aliases of other modules that modprobe will attempt to
>           install (or remove) in order before and after the main module
>           given in the modulename argument.
>
>           Example: Assume "softdep c pre: a b post: d e" is provided in
>           the configuration. Running "modprobe c" is now equivalent to
>           "modprobe a b c d e" without the softdep. Flags such as
>           --use-blacklist are applied to all the specified modules,
>           while module parameters only apply to module c.
>
>           Note: if there are install or remove commands with the same
>           modulename argument, softdep takes precedence.
>
>        weakdep modulename modules...
>           The weakdep command allows you to specify weak module
>           dependecies. Those are similar to pre softdep, with the
>           difference that userspace doesn't attempt to load that
>           dependency before the specified module. Instead the kernel
>           may request one or multiple of them during module probe,
>           depending on the hardware it's binding to. The purpose of
>           weak module is to allow a driver to specify that a certain
>           dependency may be needed, so it should be present in the
>           filesystem (e.g. in initramfs) when that module is probed.
>
>           Example: Assume "weakdep c a b". A program creating an
>           initramfs knows it should add a, b, and c to the filesystem
>           since a and b may be required/desired at runtime. When c is
>           loaded and is being probed, it may issue calls to
>           request_module() causing a or b to also be loaded.
Ok, thanks for completing this.
I will include this in my kmod patch (if it is ok for you).

> Also instead of delegating this to the distros, it'd be good if we start
> adding those to the ELF section of the modules with
>
>        MODULE_WEAKDEP("...");
>
> ... to be defined in the kernel in a similar way that MODULE_SOFTDEP()
> is.
Agree, better to define in kernel code, that's the reason for the patch.
Ok, I will implement in that way and I will create a kernel patch too for
this.
Indeed (with a different name), it was also in my mind but I didn't dare to
create something "new".

Thanks for you comments and help

Best regards
José Ignacio
diff mbox series

Patch

diff --git a/libkmod/docs/libkmod-sections.txt b/libkmod/docs/libkmod-sections.txt
index 33d9eec..04743e4 100644
--- a/libkmod/docs/libkmod-sections.txt
+++ b/libkmod/docs/libkmod-sections.txt
@@ -62,6 +62,7 @@  kmod_module_remove_module
 kmod_module_get_module
 kmod_module_get_dependencies
 kmod_module_get_softdeps
+kmod_module_get_user_softdeps
 kmod_module_apply_filter
 kmod_module_get_filtered_blacklist
 kmod_module_get_install_commands
diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c
index e83621b..c0e15be 100644
--- a/libkmod/libkmod-config.c
+++ b/libkmod/libkmod-config.c
@@ -54,8 +54,10 @@  struct kmod_softdep {
 	char *name;
 	const char **pre;
 	const char **post;
+	const char **user;
 	unsigned int n_pre;
 	unsigned int n_post;
+	unsigned int n_user;
 };
 
 const char *kmod_blacklist_get_modname(const struct kmod_list *l)
@@ -110,6 +112,12 @@  const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned in
 	return dep->post;
 }
 
+const char * const *kmod_softdep_get_user(const struct kmod_list *l, unsigned int *count) {
+	const struct kmod_softdep *dep = l->data;
+	*count = dep->n_user;
+	return dep->user;
+}
+
 static int kmod_config_add_command(struct kmod_config *config,
 						const char *modname,
 						const char *command,
@@ -263,11 +271,11 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 	struct kmod_softdep *dep;
 	const char *s, *p;
 	char *itr;
-	unsigned int n_pre = 0, n_post = 0;
+	unsigned int n_pre = 0, n_post = 0, n_user = 0;
 	size_t modnamelen = strlen(modname) + 1;
 	size_t buflen = 0;
 	bool was_space = false;
-	enum { S_NONE, S_PRE, S_POST } mode = S_NONE;
+	enum { S_NONE, S_PRE, S_POST, S_USER } mode = S_NONE;
 
 	DBG(config->ctx, "modname=%s\n", modname);
 
@@ -298,6 +306,9 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 		else if (plen == sizeof("post:") - 1 &&
 				memcmp(p, "post:", sizeof("post:") - 1) == 0)
 			mode = S_POST;
+		else if (plen == sizeof("user:") - 1 &&
+				memcmp(p, "user:", sizeof("user:") - 1) == 0)
+			mode = S_USER;
 		else if (*s != '\0' || (*s == '\0' && !was_space)) {
 			if (mode == S_PRE) {
 				buflen += plen + 1;
@@ -305,6 +316,9 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 			} else if (mode == S_POST) {
 				buflen += plen + 1;
 				n_post++;
+			} else if (mode == S_USER) {
+				buflen += plen + 1;
+				n_user++;
 			}
 		}
 		p = s + 1;
@@ -312,11 +326,12 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 			break;
 	}
 
-	DBG(config->ctx, "%u pre, %u post\n", n_pre, n_post);
+	DBG(config->ctx, "%u pre, %u post, %u user\n", n_pre, n_post, n_user);
 
 	dep = malloc(sizeof(struct kmod_softdep) + modnamelen +
 		     n_pre * sizeof(const char *) +
 		     n_post * sizeof(const char *) +
+		     n_user * sizeof(const char *) +
 		     buflen);
 	if (dep == NULL) {
 		ERR(config->ctx, "out-of-memory modname=%s\n", modname);
@@ -324,9 +339,11 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 	}
 	dep->n_pre = n_pre;
 	dep->n_post = n_post;
+	dep->n_user = n_user;
 	dep->pre = (const char **)((char *)dep + sizeof(struct kmod_softdep));
 	dep->post = dep->pre + n_pre;
-	dep->name = (char *)(dep->post + n_post);
+	dep->user = dep->post + n_post;
+	dep->name = (char *)(dep->user + n_user);
 
 	memcpy(dep->name, modname, modnamelen);
 
@@ -334,6 +351,7 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 	itr = dep->name + modnamelen;
 	n_pre = 0;
 	n_post = 0;
+	n_user = 0;
 	mode = S_NONE;
 	was_space = false;
 	for (p = s = line; ; s++) {
@@ -362,6 +380,9 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 		else if (plen == sizeof("post:") - 1 &&
 				memcmp(p, "post:", sizeof("post:") - 1) == 0)
 			mode = S_POST;
+		else if (plen == sizeof("user:") - 1 &&
+				memcmp(p, "user:", sizeof("user:") - 1) == 0)
+			mode = S_USER;
 		else if (*s != '\0' || (*s == '\0' && !was_space)) {
 			if (mode == S_PRE) {
 				dep->pre[n_pre] = itr;
@@ -375,6 +396,12 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 				itr[plen] = '\0';
 				itr += plen + 1;
 				n_post++;
+			} else if (mode == S_USER) {
+				dep->user[n_user] = itr;
+				memcpy(itr, p, plen);
+				itr[plen] = '\0';
+				itr += plen + 1;
+				n_user++;
 			}
 		}
 		p = s + 1;
@@ -395,14 +422,15 @@  static int kmod_config_add_softdep(struct kmod_config *config,
 static char *softdep_to_char(struct kmod_softdep *dep) {
 	const size_t sz_preprefix = sizeof("pre: ") - 1;
 	const size_t sz_postprefix = sizeof("post: ") - 1;
+	const size_t sz_userprefix = sizeof("user: ") - 1;
 	size_t sz = 1; /* at least '\0' */
-	size_t sz_pre, sz_post;
+	size_t sz_pre, sz_post, sz_user;
 	const char *start, *end;
 	char *s, *itr;
 
 	/*
-	 * Rely on the fact that dep->pre[] and dep->post[] are strv's that
-	 * point to a contiguous buffer
+	 * Rely on the fact that dep->pre[] dep->post[] and dep->user[]
+	 * are strv's that point to a contiguous buffer
 	 */
 	if (dep->n_pre > 0) {
 		start = dep->pre[0];
@@ -422,6 +450,15 @@  static char *softdep_to_char(struct kmod_softdep *dep) {
 	} else
 		sz_post = 0;
 
+	if (dep->n_user > 0) {
+		start = dep->user[0];
+		end = dep->user[dep->n_user - 1]
+					+ strlen(dep->user[dep->n_user - 1]);
+		sz_user = end - start;
+		sz += sz_user + sz_userprefix;
+	} else
+		sz_user = 0;
+
 	itr = s = malloc(sz);
 	if (s == NULL)
 		return NULL;
@@ -456,6 +493,21 @@  static char *softdep_to_char(struct kmod_softdep *dep) {
 		itr = p;
 	}
 
+	if (sz_user) {
+		char *p;
+
+		memcpy(itr, "user: ", sz_userprefix);
+		itr += sz_userprefix;
+
+		/* include last '\0' */
+		memcpy(itr, dep->user[0], sz_user + 1);
+		for (p = itr; p < itr + sz_user; p++) {
+			if (*p == '\0')
+				*p = ' ';
+		}
+		itr = p;
+	}
+
 	*itr = '\0';
 
 	return s;
diff --git a/libkmod/libkmod-internal.h b/libkmod/libkmod-internal.h
index 26a7e28..8e4f112 100644
--- a/libkmod/libkmod-internal.h
+++ b/libkmod/libkmod-internal.h
@@ -145,6 +145,7 @@  const char *kmod_command_get_modname(const struct kmod_list *l) __attribute__((n
 const char *kmod_softdep_get_name(const struct kmod_list *l) __attribute__((nonnull(1)));
 const char * const *kmod_softdep_get_pre(const struct kmod_list *l, unsigned int *count) __attribute__((nonnull(1, 2)));
 const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned int *count);
+const char * const *kmod_softdep_get_user(const struct kmod_list *l, unsigned int *count);
 
 
 /* libkmod-module.c */
diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c
index 585da41..dbe676c 100644
--- a/libkmod/libkmod-module.c
+++ b/libkmod/libkmod-module.c
@@ -1664,6 +1664,56 @@  KMOD_EXPORT int kmod_module_get_softdeps(const struct kmod_module *mod,
 	return 0;
 }
 
+/**
+ * kmod_module_get_user_softdeps:
+ * @mod: kmod module
+ * @user: where to save the list of user soft dependencies.
+ *
+ * Get user dependencies for this kmod module. Soft dependencies come
+ * from configuration file and are not cached in @mod because it may include
+ * dependency cycles that would make we leak kmod_module. Any call
+ * to this function will search for this module in configuration, allocate a
+ * list and return the result.
+ *
+ * @user is newly created list of kmod_module and
+ * should be unreferenced with kmod_module_unref_list().
+ *
+ * Returns: 0 on success or < 0 otherwise.
+ */
+KMOD_EXPORT int kmod_module_get_user_softdeps(const struct kmod_module *mod,
+						struct kmod_list **user)
+{
+	const struct kmod_list *l;
+	const struct kmod_config *config;
+
+	if (mod == NULL || user == NULL)
+		return -ENOENT;
+
+	assert(*user == NULL);
+
+	config = kmod_get_config(mod->ctx);
+
+	kmod_list_foreach(l, config->softdeps) {
+		const char *modname = kmod_softdep_get_name(l);
+		const char * const *array;
+		unsigned count;
+
+		if (fnmatch(modname, mod->name, 0) != 0)
+			continue;
+
+		array = kmod_softdep_get_user(l, &count);
+		*user = lookup_softdep(mod->ctx, array, count);
+
+		/*
+		 * find only the first command, as modprobe from
+		 * module-init-tools does
+		 */
+		break;
+	}
+
+	return 0;
+}
+
 /**
  * kmod_module_get_remove_commands:
  * @mod: kmod module
diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h
index 7251aa7..ec6d270 100644
--- a/libkmod/libkmod.h
+++ b/libkmod/libkmod.h
@@ -196,6 +196,8 @@  const char *kmod_module_get_remove_commands(const struct kmod_module *mod);
 struct kmod_list *kmod_module_get_dependencies(const struct kmod_module *mod);
 int kmod_module_get_softdeps(const struct kmod_module *mod,
 				struct kmod_list **pre, struct kmod_list **post);
+int kmod_module_get_user_softdeps(const struct kmod_module *mod,
+					struct kmod_list **user);
 int kmod_module_get_filtered_blacklist(const struct kmod_ctx *ctx,
 					const struct kmod_list *input,
 					struct kmod_list **output) __attribute__ ((deprecated));
diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym
index 0c04fda..26c3eef 100644
--- a/libkmod/libkmod.sym
+++ b/libkmod/libkmod.sym
@@ -42,6 +42,7 @@  global:
 
 	kmod_module_get_dependencies;
 	kmod_module_get_softdeps;
+	kmod_module_get_user_softdeps;
 	kmod_module_get_filtered_blacklist;
 
 	kmod_module_get_name;