nbd-proxy: Add state hook facility
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
diff --git a/README b/README
index d5ab82e..0d3030e 100644
--- a/README
+++ b/README
@@ -53,3 +53,13 @@
There is no authentication or authorisation implemented in the nbd proxy. Your
websocket proxy should implement proper authentication before nbd-proxy is
connected to the websocket endpoint.
+
+State hooks
+-----------
+
+The nbd-proxy has a facility to run hooks on state change. When a
+nbd session is established or shut down, the proxy will run any executables
+found under the hook path (by default, /etc/nbd-proxy/state.d/).
+
+These hooks are called with two arguments: the action ("start" or "stop"),
+and the name of the configuration (as specified in the config.json file).
diff --git a/nbd-proxy.c b/nbd-proxy.c
index 6b23d6a..2fcae12 100644
--- a/nbd-proxy.c
+++ b/nbd-proxy.c
@@ -17,10 +17,12 @@
#define _GNU_SOURCE
+#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
+#include <limits.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
@@ -32,6 +34,7 @@
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
@@ -51,7 +54,6 @@
int sock_client;
int signal_pipe[2];
char *sock_path;
- char *dev_path;
pid_t nbd_client_pid;
int nbd_timeout;
uint8_t *buf;
@@ -59,13 +61,18 @@
struct config *configs;
int n_configs;
struct config *default_config;
+ struct config *config;
};
static const char *conf_path = SYSCONFDIR "/nbd-proxy/config.json";
+static const char *state_hook_path = SYSCONFDIR "/nbd-proxy/state.d";
static const char *sockpath_tmpl = RUNSTATEDIR "/nbd.%d.sock";
+
static const size_t bufsize = 0x20000;
static const int nbd_timeout_default = 30;
+#define BUILD_ASSERT_OR_ZERO(c) (sizeof(struct {int:-!(c);}))
+
static int open_nbd_socket(struct ctx *ctx)
{
struct sockaddr_un addr;
@@ -148,7 +155,7 @@
"-u", ctx->sock_path,
"-n",
"-t", timeout_str,
- ctx->dev_path,
+ ctx->config->nbd_device,
NULL);
err(EXIT_FAILURE, "can't start ndb client");
}
@@ -342,6 +349,111 @@
return 0;
}
+#define join_paths(p1, p2, r) \
+ (BUILD_ASSERT_OR_ZERO(sizeof(r) > PATH_MAX) + __join_paths(p1, p2, r))
+static int __join_paths(const char *p1, const char *p2, char res[])
+{
+ size_t len;
+ char *pos;
+
+ len = strlen(p1) + 1 + strlen(p2);
+ if (len > PATH_MAX)
+ return -1;
+
+ pos = res;
+ strcpy(pos, p1);
+ pos += strlen(p1);
+ *pos = '/';
+ pos++;
+ strcpy(pos, p2);
+
+ return 0;
+}
+
+static int run_state_hook(struct ctx *ctx,
+ const char *path, const char *name, const char *action)
+{
+ int status, rc, fd;
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0) {
+ warn("can't fork to execute hook %s", name);
+ return -1;
+ }
+
+ if (!pid) {
+ close(ctx->sock);
+ close(ctx->sock_client);
+ close(ctx->signal_pipe[0]);
+ close(ctx->signal_pipe[1]);
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0)
+ exit(EXIT_FAILURE);
+
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ execl(path, name, action, ctx->config->name, NULL);
+ exit(EXIT_FAILURE);
+ }
+
+ rc = waitpid(pid, &status, 0);
+ if (rc < 0) {
+ warn("wait");
+ return -1;
+ }
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ warnx("hook %s failed", name);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int run_state_hooks(struct ctx *ctx, const char *action)
+{
+ struct dirent *dirent;
+ int rc;
+ DIR *dir;
+
+ dir = opendir(state_hook_path);
+ if (!dir)
+ return 0;
+
+ rc = 0;
+
+ for (dirent = readdir(dir); dirent; dirent = readdir(dir)) {
+ char full_path[PATH_MAX+1];
+ struct stat statbuf;
+
+ if (dirent->d_name[0] == '.')
+ continue;
+
+ rc = fstatat(dirfd(dir), dirent->d_name, &statbuf, 0);
+ if (rc)
+ continue;
+
+ if (!(S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)))
+ continue;
+
+ if (faccessat(dirfd(dir), dirent->d_name, X_OK, 0))
+ continue;
+
+ rc = join_paths(state_hook_path, dirent->d_name, full_path);
+ if (rc)
+ continue;
+
+ rc = run_state_hook(ctx, full_path, dirent->d_name, action);
+ if (rc)
+ break;
+ }
+
+ closedir(dir);
+
+ return rc;
+}
static int run_proxy(struct ctx *ctx)
{
@@ -561,7 +673,7 @@
}
/* ... and apply it */
- ctx->dev_path = config->nbd_device;
+ ctx->config = config;
return 0;
}
@@ -645,8 +757,14 @@
if (rc)
goto out_stop_client;
+ rc = run_state_hooks(ctx, "start");
+ if (rc)
+ goto out_stop_client;
+
rc = run_proxy(ctx);
+ run_state_hooks(ctx, "stop");
+
out_stop_client:
/* we cleanup signals before stopping the client, because we
* no longer care about SIGCHLD from the stopping nbd-client