diff options
author | Jeremy Kerr <jk@ozlabs.org> | 2018-08-10 11:16:29 +0800 |
---|---|---|
committer | Jeremy Kerr <jk@ozlabs.org> | 2018-08-10 12:44:47 +0800 |
commit | 4ac9246f8391ac5af736046fa6001632f91c1173 (patch) | |
tree | 3f1e2e131beda1e027bf568cff77e2ebb0b0db1e | |
parent | 9b00deb26b4cd03bc1e33afe7a5e2c5e0e87f06c (diff) | |
download | jsnbd-4ac9246f8391ac5af736046fa6001632f91c1173.tar.gz jsnbd-4ac9246f8391ac5af736046fa6001632f91c1173.zip |
nbd-proxy: listen for udev change event before running start hook
Currently, we run the start state-change hook as soon as the nbd session
has been established. However, at that point the nbd device isn't
connected, as we haven't processed any read/write operations on the
block device.
This change defers running the start script until we know that the
device is initialised - when we see a udev change event occur. To do
this, we establish a udev monitor.
Once we see that state change, we run the state change hooks and shut
down the udev monitor.
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | nbd-proxy.c | 134 |
3 files changed, 133 insertions, 6 deletions
diff --git a/Makefile.am b/Makefile.am index e494d27..ffeb594 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,8 +2,10 @@ sbin_PROGRAMS = nbd-proxy nbd_proxy_CPPFLAGS = \ $(JSON_CFLAGS) \ + $(UDEV_CFLAGS) \ -DRUNSTATEDIR=\"$(localstatedir)/run\" \ -DSYSCONFDIR=\"$(sysconfdir)\" nbd_proxy_LDADD = \ - $(JSON_LIBS) + $(JSON_LIBS) \ + $(UDEV_LIBS) diff --git a/configure.ac b/configure.ac index ace06b4..4ff9874 100644 --- a/configure.ac +++ b/configure.ac @@ -15,6 +15,7 @@ AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CFLAGS]) AC_CHECK_FUNCS(splice) PKG_CHECK_MODULES(JSON, [json-c]) +PKG_CHECK_MODULES(UDEV, [libudev]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT diff --git a/nbd-proxy.c b/nbd-proxy.c index 84702b4..6e932ad 100644 --- a/nbd-proxy.c +++ b/nbd-proxy.c @@ -39,6 +39,7 @@ #include <sys/wait.h> #include <json.h> +#include <libudev.h> #include "config.h" @@ -56,12 +57,15 @@ struct ctx { char *sock_path; pid_t nbd_client_pid; int nbd_timeout; + dev_t nbd_devno; uint8_t *buf; size_t bufsize; struct config *configs; int n_configs; struct config *default_config; struct config *config; + struct udev *udev; + struct udev_monitor *monitor; }; static const char *conf_path = SYSCONFDIR "/nbd-proxy/config.json"; @@ -454,11 +458,94 @@ static int run_state_hooks(struct ctx *ctx, const char *action) return rc; } +static int udev_init(struct ctx *ctx) +{ + int rc; + + ctx->udev = udev_new(); + if (!ctx) { + warn("can't create udev object"); + return -1; + } + + ctx->monitor = udev_monitor_new_from_netlink(ctx->udev, "kernel"); + if (!ctx->monitor) { + warn("can't create udev monitor"); + goto out_unref_udev; + } + + rc = udev_monitor_filter_add_match_subsystem_devtype( + ctx->monitor, "block", "disk"); + if (rc) { + warn("can't create udev monitor filter"); + goto out_unref_monitor; + } + + rc = udev_monitor_enable_receiving(ctx->monitor); + if (rc) { + warn("can't start udev monitor"); + goto out_unref_monitor; + } + + return 0; + +out_unref_monitor: + udev_monitor_unref(ctx->monitor); +out_unref_udev: + udev_unref(ctx->udev); + return -1; +} + +static void udev_free(struct ctx *ctx) +{ + udev_monitor_unref(ctx->monitor); + udev_unref(ctx->udev); +} + +/* Check for the change event on our nbd device, signifying that the kernel + * has finished initialising the block device. Once we see the event, we run + * the "start" state hook, and close the udev monitor. + * + * Returns: + * 0 if no processing was performed + * -1 on state hook error (and the nbd session should be closed) + */ +static int udev_process(struct ctx *ctx) +{ + struct udev_device *dev; + bool action_is_change; + dev_t devno; + int rc; + + dev = udev_monitor_receive_device(ctx->monitor); + if (!dev) + return 0; + + devno = udev_device_get_devnum(dev); + action_is_change = !strcmp(udev_device_get_action(dev), "change"); + udev_device_unref(dev); + + if (devno != ctx->nbd_devno) + return 0; + + if (!action_is_change) + return 0; + + udev_monitor_unref(ctx->monitor); + udev_unref(ctx->udev); + ctx->monitor = NULL; + ctx->udev = NULL; + + rc = run_state_hooks(ctx, "start"); + + return rc; +} + static int run_proxy(struct ctx *ctx) { - struct pollfd pollfds[3]; + struct pollfd pollfds[4]; bool exit = false; - int rc; + int rc, n_fd; /* main proxy: forward data between stdio & socket */ pollfds[0].fd = ctx->sock_client; @@ -467,10 +554,14 @@ static int run_proxy(struct ctx *ctx) pollfds[1].events = POLLIN; pollfds[2].fd = ctx->signal_pipe[0]; pollfds[2].events = POLLIN; + pollfds[3].fd = udev_monitor_get_fd(ctx->monitor); + pollfds[3].events = POLLIN; + + n_fd = 4; for (;;) { errno = 0; - rc = poll(pollfds, 3, -1); + rc = poll(pollfds, n_fd, -1); if (rc < 0) { if (errno == EINTR) continue; @@ -495,6 +586,20 @@ static int run_proxy(struct ctx *ctx) if (rc || exit) break; } + + if (pollfds[3].revents) { + rc = udev_process(ctx); + if (rc) + break; + + /* udev_process may have closed the udev connection, + * in which case we can stop polling on its fd */ + if (!ctx->udev) { + pollfds[3].fd = 0; + pollfds[3].revents = 0; + n_fd = 3; + } + } } return rc ? -1 : 0; @@ -644,7 +749,8 @@ err_free: static int config_select(struct ctx *ctx, const char *name) { struct config *config; - int i; + struct stat statbuf; + int i, rc; config = NULL; @@ -671,8 +777,23 @@ static int config_select(struct ctx *ctx, const char *name) } } + /* check that the device exists */ + rc = stat(config->nbd_device, &statbuf); + if (rc) { + warn("can't stat nbd device %s", config->nbd_device); + return -1; + } + + if (!S_ISBLK(statbuf.st_mode)) { + warn("specified nbd path %s isn't a block device", + config->nbd_device); + return -1; + } + /* ... and apply it */ ctx->config = config; + ctx->nbd_devno = statbuf.st_rdev; + return 0; } @@ -756,12 +877,15 @@ int main(int argc, char **argv) if (rc) goto out_stop_client; - rc = run_state_hooks(ctx, "start"); + rc = udev_init(ctx); if (rc) goto out_stop_client; rc = run_proxy(ctx); + if (ctx->udev) + udev_free(ctx); + run_state_hooks(ctx, "stop"); out_stop_client: |