linux: libzfs: simplify module-loaded check

The short-path is now one access() call,
we always modprobe zfs (ZFS_MODULE_LOADING which doesn't use the libzfs
boolean parsing is gone),
and we use a simple inotify IN_CREATE loop with a timerfd timeout
rather than 10ms kernel-style polling

There's one substantial difference: ZFS_MODULE_TIMEOUT=-1
now means "never give up", rather than "wait 10 minutes"

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Ahelenia Ziemiańska <nabijaczleweli@nabijaczleweli.xyz>
Closes #13330
This commit is contained in:
наб 2022-04-14 23:30:41 +02:00 committed by Brian Behlendorf
parent 89e81bc6ad
commit 38f4d99f76
4 changed files with 109 additions and 89 deletions

View File

@ -1292,6 +1292,7 @@ typedef struct ddt_histogram {
#define ZVOL_DRIVER "zvol" #define ZVOL_DRIVER "zvol"
#define ZFS_DRIVER "zfs" #define ZFS_DRIVER "zfs"
#define ZFS_DEV "/dev/zfs" #define ZFS_DEV "/dev/zfs"
#define ZFS_DEVDIR "/dev"
#define ZFS_SUPER_MAGIC 0x2fc12fc1 #define ZFS_SUPER_MAGIC 0x2fc12fc1

View File

@ -20,20 +20,24 @@
*/ */
#include <alloca.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <libintl.h> #include <libintl.h>
#include <math.h>
#include <poll.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <strings.h> #include <strings.h>
#include <unistd.h> #include <sys/inotify.h>
#include <math.h>
#include <sys/stat.h>
#include <sys/mnttab.h>
#include <sys/mntent.h> #include <sys/mntent.h>
#include <sys/mnttab.h>
#include <sys/stat.h>
#include <sys/timerfd.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h>
#include <libzfs.h> #include <libzfs.h>
#include <libzfs_core.h> #include <libzfs_core.h>
@ -57,7 +61,7 @@ libzfs_error_init(int error)
switch (error) { switch (error) {
case ENXIO: case ENXIO:
return (dgettext(TEXT_DOMAIN, "The ZFS modules are not " return (dgettext(TEXT_DOMAIN, "The ZFS modules are not "
"loaded.\nTry running '/sbin/modprobe zfs' as root " "loaded.\nTry running 'modprobe zfs' as root "
"to load them.")); "to load them."));
case ENOENT: case ENOENT:
return (dgettext(TEXT_DOMAIN, "/dev/zfs and /proc/self/mounts " return (dgettext(TEXT_DOMAIN, "/dev/zfs and /proc/self/mounts "
@ -65,7 +69,7 @@ libzfs_error_init(int error)
"-t proc proc /proc' as root.")); "-t proc proc /proc' as root."));
case ENOEXEC: case ENOEXEC:
return (dgettext(TEXT_DOMAIN, "The ZFS modules cannot be " return (dgettext(TEXT_DOMAIN, "The ZFS modules cannot be "
"auto-loaded.\nTry running '/sbin/modprobe zfs' as " "auto-loaded.\nTry running 'modprobe zfs' as "
"root to manually load them.")); "root to manually load them."));
case EACCES: case EACCES:
return (dgettext(TEXT_DOMAIN, "Permission denied the " return (dgettext(TEXT_DOMAIN, "Permission denied the "
@ -76,93 +80,80 @@ libzfs_error_init(int error)
} }
} }
static int
libzfs_module_loaded(const char *module)
{
const char path_prefix[] = "/sys/module/";
char path[256];
memcpy(path, path_prefix, sizeof (path_prefix) - 1);
strcpy(path + sizeof (path_prefix) - 1, module);
return (access(path, F_OK) == 0);
}
/* /*
* Verify the required ZFS_DEV device is available and optionally attempt * zfs(4) is loaded by udev if there's a fstype=zfs device present,
* to load the ZFS modules. Under normal circumstances the modules * but if there isn't, load them automatically;
* should already have been loaded by some external mechanism. * always wait for ZFS_DEV to appear via udev.
* *
* Environment variables: * Environment variables:
* - ZFS_MODULE_LOADING="YES|yes|ON|on" - Attempt to load modules. * - ZFS_MODULE_TIMEOUT="<seconds>" - Seconds to wait for ZFS_DEV,
* - ZFS_MODULE_TIMEOUT="<seconds>" - Seconds to wait for ZFS_DEV * defaults to 10, max. 10 min.
*/ */
static int
libzfs_load_module_impl(const char *module)
{
char *argv[4] = {"/sbin/modprobe", "-q", (char *)module, (char *)0};
char *load_str, *timeout_str;
long timeout = 10; /* seconds */
long busy_timeout = 10; /* milliseconds */
int load = 0, fd;
hrtime_t start;
/* Optionally request module loading */
if (!libzfs_module_loaded(module)) {
load_str = getenv("ZFS_MODULE_LOADING");
if (load_str) {
if (!strncasecmp(load_str, "YES", strlen("YES")) ||
!strncasecmp(load_str, "ON", strlen("ON")))
load = 1;
else
load = 0;
}
if (load) {
if (libzfs_run_process("/sbin/modprobe", argv, 0))
return (ENOEXEC);
}
if (!libzfs_module_loaded(module))
return (ENXIO);
}
/*
* Device creation by udev is asynchronous and waiting may be
* required. Busy wait for 10ms and then fall back to polling every
* 10ms for the allowed timeout (default 10s, max 10m). This is
* done to optimize for the common case where the device is
* immediately available and to avoid penalizing the possible
* case where udev is slow or unable to create the device.
*/
timeout_str = getenv("ZFS_MODULE_TIMEOUT");
if (timeout_str) {
timeout = strtol(timeout_str, NULL, 0);
timeout = MAX(MIN(timeout, (10 * 60)), 0); /* 0 <= N <= 600 */
}
start = gethrtime();
do {
fd = open(ZFS_DEV, O_RDWR | O_CLOEXEC);
if (fd >= 0) {
(void) close(fd);
return (0);
} else if (errno != ENOENT) {
return (errno);
} else if (NSEC2MSEC(gethrtime() - start) < busy_timeout) {
sched_yield();
} else {
usleep(10 * MILLISEC);
}
} while (NSEC2MSEC(gethrtime() - start) < (timeout * MILLISEC));
return (ENOENT);
}
int int
libzfs_load_module(void) libzfs_load_module(void)
{ {
return (libzfs_load_module_impl(ZFS_DRIVER)); if (access(ZFS_DEV, F_OK) == 0)
return (0);
if (access(ZFS_SYSFS_DIR, F_OK) != 0) {
char *argv[] = {"modprobe", "zfs", NULL};
if (libzfs_run_process("modprobe", argv, 0))
return (ENOEXEC);
if (access(ZFS_SYSFS_DIR, F_OK) != 0)
return (ENXIO);
}
const char *timeout_str = getenv("ZFS_MODULE_TIMEOUT");
int seconds = 10;
if (timeout_str)
seconds = MIN(strtol(timeout_str, NULL, 0), 600);
struct itimerspec timeout = {.it_value.tv_sec = MAX(seconds, 0)};
int ino = inotify_init1(IN_CLOEXEC);
if (ino == -1)
return (ENOENT);
inotify_add_watch(ino, ZFS_DEVDIR, IN_CREATE);
if (access(ZFS_DEV, F_OK) == 0) {
close(ino);
return (0);
} else if (seconds == 0) {
close(ino);
return (ENOENT);
}
size_t evsz = sizeof (struct inotify_event) + NAME_MAX + 1;
struct inotify_event *ev = alloca(evsz);
int tout = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (tout == -1) {
close(ino);
return (ENOENT);
}
timerfd_settime(tout, 0, &timeout, NULL);
int ret = ENOENT;
struct pollfd pfds[] = {
{.fd = ino, .events = POLLIN},
{.fd = tout, .events = POLLIN},
};
while (poll(pfds, ARRAY_SIZE(pfds), -1) != -1) {
if (pfds[0].revents & POLLIN) {
verify(read(ino, ev, evsz) >
sizeof (struct inotify_event));
if (strcmp(ev->name, &ZFS_DEV[sizeof (ZFS_DEVDIR)])
== 0) {
ret = 0;
break;
}
}
if (pfds[1].revents & POLLIN)
break;
}
close(tout);
close(ino);
return (ret);
} }
int int

View File

@ -736,7 +736,7 @@ Do note that any changes done with the
command will be undone if the share is ever unshared (like via a reboot). command will be undone if the share is ever unshared (like via a reboot).
. .
.Sh ENVIRONMENT VARIABLES .Sh ENVIRONMENT VARIABLES
.Bl -tag -width "ZFS_MOUNT_HELPER" .Bl -tag -width "ZFS_MODULE_TIMEOUT"
.It Sy ZFS_MOUNT_HELPER .It Sy ZFS_MOUNT_HELPER
Cause Cause
.Nm zfs Cm mount .Nm zfs Cm mount
@ -744,14 +744,28 @@ to use
.Xr mount 8 .Xr mount 8
to mount ZFS datasets. to mount ZFS datasets.
This option is provided for backwards compatibility with older ZFS versions. This option is provided for backwards compatibility with older ZFS versions.
.El .
.Bl -tag -width "ZFS_SET_PIPE_MAX"
.It Sy ZFS_SET_PIPE_MAX .It Sy ZFS_SET_PIPE_MAX
Tells Tells
.Nm zfs .Nm zfs
to set the maximum pipe size for sends/recieves. to set the maximum pipe size for sends/recieves.
Disabled by default on Linux Disabled by default on Linux
due to an unfixed deadlock in Linux's pipe size handling code. due to an unfixed deadlock in Linux's pipe size handling code.
.
.\" Shared with zpool.8
.It Sy ZFS_MODULE_TIMEOUT
Time, in seconds, to wait for
.Pa /dev/zfs
to appear.
Defaults to
.Sy 10 ,
max
.Sy 600 Pq 10 minutes .
If
.Pf < Sy 0 ,
wait forever; if
.Sy 0 ,
don't wait.
.El .El
. .
.Sh INTERFACE STABILITY .Sh INTERFACE STABILITY

View File

@ -524,6 +524,20 @@ If
.Sy ZPOOL_SCRIPTS_ENABLED .Sy ZPOOL_SCRIPTS_ENABLED
is not set, it is assumed that the user is allowed to run is not set, it is assumed that the user is allowed to run
.Nm zpool Cm status Ns / Ns Cm iostat Fl c . .Nm zpool Cm status Ns / Ns Cm iostat Fl c .
.\" Shared with zfs.8
.It Sy ZFS_MODULE_TIMEOUT
Time, in seconds, to wait for
.Pa /dev/zfs
to appear.
Defaults to
.Sy 10 ,
max
.Sy 600 Pq 10 minutes .
If
.Pf < Sy 0 ,
wait forever; if
.Sy 0 ,
don't wait.
.El .El
. .
.Sh INTERFACE STABILITY .Sh INTERFACE STABILITY