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>
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ /* 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 @@
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: