Skip to content

Commit 34d7e83

Browse files
behlendorfdasjoe
authored andcommitted
Wait in libzfs_init() for the /dev/zfs device
While module loading itself is synchronous the creation of the /dev/zfs device is not. This is because /dev/zfs is typically created by a udev rule after the module is registered and presented to user space through sysfs. This small window between module loading and device creation can result in spurious failures of libzfs_init(). This patch closes that race by extending libzfs_init() so it can detect that the modules are loaded and only if required wait for the /dev/zfs device to be created. This allows scripts to reliably use the following shell construct without the need for additional error handling. $ /sbin/modprobe zfs && /sbin/zpool import -a To minimize the potential time waiting in libzfs_init() a strategy similar to adaptive mutexes is employed. The function will busy-wait for up to 10ms based on the expectation that the modules were just loaded and therefore the /dev/zfs will be created imminently. If it takes longer than this it will fall back to polling for up to 10 seconds. This behavior can be customized to some degree by setting the following new environment variables. This functionality is provided for backwards compatibility with existing scripts which depend on the module auto-load behavior. By default module auto-loading is now disabled. * ZFS_MODULE_LOADING="YES|yes|ON|on" - Attempt to load modules. * ZFS_MODULE_TIMEOUT="<seconds>" - Seconds to wait for /dev/zfs The zfs-import-* systemd service files have been updated to call '/sbin/modprobe zfs' so they no longer rely on the legacy auto-loading behavior. Signed-off-by: Brian Behlendorf <[email protected]> Signed-off-by: Chris Dunlap <[email protected]> Signed-off-by: Richard Yao <[email protected]> Closes openzfs#2556
1 parent bc4861c commit 34d7e83

File tree

3 files changed

+67
-6
lines changed

3 files changed

+67
-6
lines changed

etc/systemd/system/zfs-import-cache.service.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ ConditionPathExists=@sysconfdir@/zfs/zpool.cache
99
[Service]
1010
Type=oneshot
1111
RemainAfterExit=yes
12-
ExecStart=@sbindir@/zpool import -c @sysconfdir@/zfs/zpool.cache -aN
12+
ExecStart=/sbin/modprobe zfs && @sbindir@/zpool import -c @sysconfdir@/zfs/zpool.cache -aN

etc/systemd/system/zfs-import-scan.service.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ ConditionPathExists=!@sysconfdir@/zfs/zpool.cache
99
[Service]
1010
Type=oneshot
1111
RemainAfterExit=yes
12-
ExecStart=@sbindir@/zpool import -d /dev/disk/by-id -aN
12+
ExecStart=/sbin/modprobe zfs && @sbindir@/zpool import -d /dev/disk/by-id -aN

lib/libzfs/libzfs_util.c

+65-4
Original file line numberDiff line numberDiff line change
@@ -661,23 +661,84 @@ libzfs_run_process(const char *path, char *argv[], int flags)
661661
return (-1);
662662
}
663663

664+
/*
665+
* Verify the required ZFS_DEV device is available and optionally attempt
666+
* to load the ZFS modules. Under normal circumstances the modules
667+
* should already have been loaded by some external mechanism.
668+
*
669+
* Environment variables:
670+
* - ZFS_MODULE_LOADING="YES|yes|ON|on" - Attempt to load modules.
671+
* - ZFS_MODULE_TIMEOUT="<seconds>" - Seconds to wait for ZFS_DEV
672+
*/
664673
int
665674
libzfs_load_module(const char *module)
666675
{
667676
char *argv[4] = {"/sbin/modprobe", "-q", (char *)module, (char *)0};
677+
char *load_str, *timeout_str;
678+
long timeout = 10; /* seconds */
679+
long busy_timeout = 10; /* milliseconds */
680+
int load = 0, fd;
681+
hrtime_t start;
682+
683+
/* Optionally request module loading */
684+
if (!libzfs_module_loaded(module)) {
685+
load_str = getenv("ZFS_MODULE_LOADING");
686+
if (load_str) {
687+
if (!strncasecmp(load_str, "YES", strlen("YES")) ||
688+
!strncasecmp(load_str, "ON", strlen("ON")))
689+
load = 1;
690+
else
691+
load = 0;
692+
}
668693

669-
if (libzfs_module_loaded(module))
670-
return (0);
694+
if (load && libzfs_run_process("/sbin/modprobe", argv, 0))
695+
return (ENOEXEC);
696+
}
697+
698+
/* Module loading is synchronous it must be available */
699+
if (!libzfs_module_loaded(module))
700+
return (ENXIO);
701+
702+
/*
703+
* Device creation by udev is asynchronous and waiting may be
704+
* required. Busy wait for 10ms and then fall back to polling every
705+
* 10ms for the allowed timeout (default 10s, max 10m). This is
706+
* done to optimize for the common case where the device is
707+
* immediately available and to avoid penalizing the possible
708+
* case where udev is slow or unable to create the device.
709+
*/
710+
timeout_str = getenv("ZFS_MODULE_TIMEOUT");
711+
if (timeout_str) {
712+
timeout = strtol(timeout_str, NULL, 0);
713+
timeout = MAX(MIN(timeout, (10 * 60)), 0); /* 0 <= N <= 600 */
714+
}
715+
716+
start = gethrtime();
717+
do {
718+
fd = open(ZFS_DEV, O_RDWR);
719+
if (fd >= 0) {
720+
(void) close(fd);
721+
return (0);
722+
} else if (errno != ENOENT) {
723+
return (errno);
724+
} else if (NSEC2MSEC(gethrtime() - start) < busy_timeout) {
725+
sched_yield();
726+
} else {
727+
usleep(10 * MILLISEC);
728+
}
729+
} while (NSEC2MSEC(gethrtime() - start) < (timeout * MILLISEC));
671730

672-
return (libzfs_run_process("/sbin/modprobe", argv, 0));
731+
return (ENOENT);
673732
}
674733

675734
libzfs_handle_t *
676735
libzfs_init(void)
677736
{
678737
libzfs_handle_t *hdl;
738+
int error;
679739

680-
if (libzfs_load_module("zfs") != 0) {
740+
error = libzfs_load_module(ZFS_DRIVER);
741+
if (error) {
681742
(void) fprintf(stderr, gettext("Failed to load ZFS module "
682743
"stack.\nLoad the module manually by running "
683744
"'insmod <location>/zfs.ko' as root.\n"));

0 commit comments

Comments
 (0)