blob: e251a1e6d4a4b3eafebd6bcce5c54ae8024af212 [file] [log] [blame]
Jeremy Kerrf403c422018-07-26 12:14:56 +08001/* Copyright 2018 IBM Corp.
2 *
3 * Author: Jeremy Kerr <jk@ozlabs.org>
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations
15 * under the License.
16 */
17
18#define _GNU_SOURCE
19
20#include <err.h>
21#include <errno.h>
22#include <fcntl.h>
23#include <signal.h>
24#include <stdbool.h>
25#include <stdint.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <unistd.h>
30
31#include <sys/poll.h>
32#include <sys/socket.h>
33#include <sys/stat.h>
34#include <sys/un.h>
35#include <sys/wait.h>
36
Jeremy Kerr19527352018-08-03 15:04:38 +080037#include <json.h>
38
Jeremy Kerrf403c422018-07-26 12:14:56 +080039#include "config.h"
40
Jeremy Kerr19527352018-08-03 15:04:38 +080041struct config {
42 char *name;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +080043 bool is_default;
Jeremy Kerr19527352018-08-03 15:04:38 +080044 char *nbd_device;
45 struct json_object *metadata;
46};
47
Jeremy Kerrf403c422018-07-26 12:14:56 +080048struct ctx {
49 int sock;
50 int sock_client;
51 int signal_pipe[2];
52 char *sock_path;
Jeremy Kerr19527352018-08-03 15:04:38 +080053 char *dev_path;
Jeremy Kerrf403c422018-07-26 12:14:56 +080054 pid_t nbd_client_pid;
Jeremy Kerr19527352018-08-03 15:04:38 +080055 int nbd_timeout;
Jeremy Kerrf403c422018-07-26 12:14:56 +080056 uint8_t *buf;
57 size_t bufsize;
Jeremy Kerr19527352018-08-03 15:04:38 +080058 struct config *configs;
59 int n_configs;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +080060 struct config *default_config;
Jeremy Kerrf403c422018-07-26 12:14:56 +080061};
62
Jeremy Kerr19527352018-08-03 15:04:38 +080063static const char *conf_path = SYSCONFDIR "/nbd-proxy/config.json";
Jeremy Kerrf403c422018-07-26 12:14:56 +080064static const char *sockpath_tmpl = RUNSTATEDIR "/nbd.%d.sock";
Jeremy Kerrf403c422018-07-26 12:14:56 +080065static const size_t bufsize = 0x20000;
Jeremy Kerr19527352018-08-03 15:04:38 +080066static const int nbd_timeout_default = 30;
Jeremy Kerrf403c422018-07-26 12:14:56 +080067
68static int open_nbd_socket(struct ctx *ctx)
69{
70 struct sockaddr_un addr;
71 char *path;
72 int sd, rc;
73
74 rc = asprintf(&path, sockpath_tmpl, getpid());
75 if (rc < 0)
76 return -1;
77
78 sd = socket(AF_UNIX, SOCK_STREAM, 0);
79 if (sd < 0) {
80 warn("can't create socket");
81 goto err_free;
82 }
83
84 rc = fchmod(sd, S_IRUSR | S_IWUSR);
85 if (rc) {
86 warn("can't set permissions on socket");
87 goto err_close;
88 }
89
90 addr.sun_family = AF_UNIX;
91 strncpy(addr.sun_path, path, sizeof(addr.sun_path));
92
93 rc = bind(sd, (struct sockaddr *)&addr, sizeof(addr));
94 if (rc) {
95 warn("can't bind to path %s", path);
96 goto err_close;
97 }
98
99 rc = listen(sd, 1);
100 if (rc) {
101 warn("can't listen on socket %s", path);
102 goto err_unlink;
103 }
104
105 ctx->sock = sd;
106 ctx->sock_path = path;
107 return 0;
108
109err_unlink:
110 unlink(path);
111err_close:
112 close(sd);
113err_free:
114 free(path);
115 return -1;
116}
117
118static int start_nbd_client(struct ctx *ctx)
119{
120 pid_t pid;
121
122 pid = fork();
123 if (pid < 0) {
124 warn("can't create client process");
125 return -1;
126 }
127
128 /* child process: run nbd-client in non-fork mode */
129 if (pid == 0) {
Jeremy Kerr09295f22018-07-27 16:29:38 +0800130 char timeout_str[10];
Jeremy Kerrf403c422018-07-26 12:14:56 +0800131 int fd;
132
Jeremy Kerr19527352018-08-03 15:04:38 +0800133 snprintf(timeout_str, sizeof(timeout_str),
134 "%d", ctx->nbd_timeout);
Jeremy Kerr09295f22018-07-27 16:29:38 +0800135
Jeremy Kerrf403c422018-07-26 12:14:56 +0800136 fd = open("/dev/null", O_RDWR);
137 if (fd < 0)
138 err(EXIT_FAILURE, "can't open /dev/null");
139
140 dup2(fd, STDIN_FILENO);
141 dup2(fd, STDOUT_FILENO);
142 dup2(fd, STDERR_FILENO);
143 close(fd);
144 close(ctx->sock);
145
146 execlp("nbd-client", "nbd-client",
147 "-u", ctx->sock_path,
148 "-n",
Jeremy Kerr09295f22018-07-27 16:29:38 +0800149 "-t", timeout_str,
Jeremy Kerr19527352018-08-03 15:04:38 +0800150 ctx->dev_path,
Jeremy Kerrf403c422018-07-26 12:14:56 +0800151 NULL);
152 err(EXIT_FAILURE, "can't start ndb client");
153 }
154
155 ctx->nbd_client_pid = pid;
156 return 0;
157}
158
159static void stop_nbd_client(struct ctx *ctx)
160{
161 int rc;
162
163 if (!ctx->nbd_client_pid)
164 return;
165
166 rc = kill(ctx->nbd_client_pid, SIGTERM);
167 if (rc)
168 return;
169
170 waitpid(ctx->nbd_client_pid, NULL, 0);
171 ctx->nbd_client_pid = 0;
172}
173
174static int copy_fd(struct ctx *ctx, int fd_in, int fd_out)
175{
176#ifdef HAVE_SPLICE
177 int rc;
178
179 rc = splice(fd_in, NULL, fd_out, NULL, ctx->bufsize, 0);
180 if (rc < 0)
181 warn("splice");
182
183 return rc;
184#else
185 size_t len, pos;
186 ssize_t rc;
187
188 for (;;) {
189 errno = 0;
190 rc = read(fd_in, ctx->buf, ctx->bufsize);
191 if (rc < 0) {
192 if (errno == EINTR)
193 continue;
194 warn("read failure");
195 return -1;
196 }
197 if (rc == 0)
198 return 0;
199 break;
200 }
201
202 len = rc;
203
204 for (pos = 0; pos < len;) {
205 errno = 0;
206 rc = write(fd_out, ctx->buf + pos, len - pos);
207 if (rc < 0) {
208 if (errno == EINTR)
209 continue;
210 warn("write failure");
211 return -1;
212 }
213 if (rc == 0)
214 break;
215 pos += rc;
216 }
217
218 return pos;
219#endif
220}
221
222static int signal_pipe_fd = -1;
223
224static void signal_handler(int signal)
225{
226 int rc;
227
228 rc = write(signal_pipe_fd, &signal, sizeof(signal));
229
230 /* not a lot we can do here but exit... */
231 if (rc != sizeof(signal))
232 exit(EXIT_FAILURE);
233}
234
235static int setup_signals(struct ctx *ctx)
236{
237 struct sigaction sa;
238 int rc;
239
240 rc = pipe(ctx->signal_pipe);
241 if (rc) {
242 warn("cant setup signal pipe");
243 return -1;
244 }
245
246 signal_pipe_fd = ctx->signal_pipe[1];
247
248 memset(&sa, 0, sizeof(sa));
249 sa.sa_handler = signal_handler;
250
251 sigaction(SIGINT, &sa, NULL);
252 sigaction(SIGTERM, &sa, NULL);
253 sigaction(SIGCHLD, &sa, NULL);
254
255 return 0;
256}
257
258static void cleanup_signals(struct ctx *ctx)
259{
260 struct sigaction sa;
261 memset(&sa, 0, sizeof(sa));
262 sa.sa_handler = SIG_DFL;
263
264 sigaction(SIGINT, &sa, NULL);
265 sigaction(SIGTERM, &sa, NULL);
266 sigaction(SIGCHLD, &sa, NULL);
267
268 close(ctx->signal_pipe[0]);
269 close(ctx->signal_pipe[1]);
270}
271
272static int process_signal_pipe(struct ctx *ctx, bool *exit)
273{
274 int buf, rc, status;
275
276 rc = read(ctx->signal_pipe[0], &buf, sizeof(buf));
277 if (rc != sizeof(buf))
278 return -1;
279
280 *exit = false;
281
282 switch (buf) {
283 case SIGCHLD:
284 rc = waitpid(ctx->nbd_client_pid, &status, WNOHANG);
285 if (rc > 0) {
286 warnx("nbd client stopped (%s: %d); exiting",
287 WIFEXITED(status) ? "rc" : "sig",
288 WIFEXITED(status) ?
289 WEXITSTATUS(status) :
290 WTERMSIG(status));
291 ctx->nbd_client_pid = 0;
292 }
293 break;
294 case SIGINT:
295 case SIGTERM:
296 *exit = true;
297 break;
298 }
299
300 return 0;
301}
302
303static int wait_for_nbd_client(struct ctx *ctx)
304{
305 struct pollfd pollfds[2];
306 int rc;
307
308 pollfds[0].fd = ctx->sock;
309 pollfds[0].events = POLLIN;
310 pollfds[1].fd = ctx->signal_pipe[0];
311 pollfds[1].events = POLLIN;
312
313 for (;;) {
314 errno = 0;
315 rc = poll(pollfds, 2, -1);
316 if (rc < 0) {
317 if (errno == EINTR)
318 continue;
319 warn("poll failed");
320 return -1;
321 }
322
323 if (pollfds[0].revents) {
324 rc = accept(ctx->sock, NULL, NULL);
325 if (rc < 0) {
326 warn("can't create connection");
327 return -1;
328 }
329 ctx->sock_client = rc;
330 break;
331 }
332
333 if (pollfds[1].revents) {
334 bool exit;
335 rc = process_signal_pipe(ctx, &exit);
336 if (rc || exit)
337 return -1;
338 }
339 }
340
341 return 0;
342}
343
344
345static int run_proxy(struct ctx *ctx)
346{
347 struct pollfd pollfds[3];
348 bool exit = false;
349 int rc;
350
351 /* main proxy: forward data between stdio & socket */
352 pollfds[0].fd = ctx->sock_client;
353 pollfds[0].events = POLLIN;
354 pollfds[1].fd = STDIN_FILENO;
355 pollfds[1].events = POLLIN;
356 pollfds[2].fd = ctx->signal_pipe[0];
357 pollfds[2].events = POLLIN;
358
359 for (;;) {
360 errno = 0;
361 rc = poll(pollfds, 3, -1);
362 if (rc < 0) {
363 if (errno == EINTR)
364 continue;
365 warn("poll failed");
366 break;
367 }
368
369 if (pollfds[0].revents) {
370 rc = copy_fd(ctx, ctx->sock_client, STDOUT_FILENO);
371 if (rc <= 0)
372 break;
373 }
374
375 if (pollfds[1].revents) {
376 rc = copy_fd(ctx, STDIN_FILENO, ctx->sock_client);
377 if (rc <= 0)
378 break;
379 }
380
381 if (pollfds[2].revents) {
382 rc = process_signal_pipe(ctx, &exit);
383 if (rc || exit)
384 break;
385 }
386 }
387
388 return rc ? -1 : 0;
389}
390
Jeremy Kerr19527352018-08-03 15:04:38 +0800391static void config_free_one(struct config *config)
Jeremy Kerrf403c422018-07-26 12:14:56 +0800392{
Jeremy Kerr19527352018-08-03 15:04:38 +0800393 if (config->metadata)
394 json_object_put(config->metadata);
395 free(config->nbd_device);
396 free(config->name);
397}
398
399static int config_parse_one(struct config *config, const char *name,
400 json_object *obj)
401{
402 struct json_object *tmp, *meta;
403 json_bool jrc;
404
405 jrc = json_object_object_get_ex(obj, "nbd-device", &tmp);
406 if (!jrc) {
407 warnx("config %s doesn't specify a nbd-device", name);
408 return -1;
409 }
410
411 if (!json_object_is_type(tmp, json_type_string)) {
412 warnx("config %s has invalid nbd-device", name);
413 return -1;
414 }
415
416 config->nbd_device = strdup(json_object_get_string(tmp));
417 config->name = strdup(name);
418
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800419 jrc = json_object_object_get_ex(obj, "default", &tmp);
420 config->is_default = jrc && json_object_get_boolean(tmp);
421
Jeremy Kerr19527352018-08-03 15:04:38 +0800422 jrc = json_object_object_get_ex(obj, "metadata", &meta);
423 if (jrc && json_object_is_type(meta, json_type_object))
424 config->metadata = json_object_get(meta);
425 else
426 config->metadata = NULL;
427
428 return 0;
429}
430
431static void config_free(struct ctx *ctx)
432{
433 int i;
434
435 for (i = 0; i < ctx->n_configs; i++)
436 config_free_one(&ctx->configs[i]);
437
438 free(ctx->configs);
439 ctx->n_configs = 0;
440}
441
442static int config_init(struct ctx *ctx)
443{
444 struct json_object *obj, *tmp;
445 json_bool jrc;
446 int i, rc;
447
448 /* apply defaults */
449 ctx->nbd_timeout = nbd_timeout_default;
450
451 obj = json_object_from_file(conf_path);
452 if (!obj) {
453 warnx("can't read configuration from %s\n", conf_path);
454 return -1;
455 }
456
457 /* global configuration */
458 jrc = json_object_object_get_ex(obj, "timeout", &tmp);
459 if (jrc) {
460 errno = 0;
461 ctx->nbd_timeout = json_object_get_int(tmp);
462 if (ctx->nbd_timeout == 0 && errno) {
463 warnx("can't parse timeout value");
464 goto err_free;
465 }
466 }
467
468 /* per-config configuration */
469 jrc = json_object_object_get_ex(obj, "configurations", &tmp);
470 if (!jrc) {
471 warnx("no configurations specified");
472 goto err_free;
473 }
474
475 if (!json_object_is_type(tmp, json_type_object)) {
476 warnx("invalid configurations format");
477 goto err_free;
478 }
479
480 ctx->n_configs = json_object_object_length(tmp);
481 ctx->configs = calloc(ctx->n_configs, sizeof(*ctx->configs));
482
483 i = 0;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800484 json_object_object_foreach(tmp, name, config_json) {
485 struct config *config = &ctx->configs[i];
486
487 rc = config_parse_one(config, name, config_json);
Jeremy Kerr19527352018-08-03 15:04:38 +0800488 if (rc)
489 goto err_free;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800490
491 if (config->is_default) {
492 if (ctx->default_config) {
493 warn("multiple configs flagged as default");
494 goto err_free;
495 }
496 ctx->default_config = config;
497 }
Jeremy Kerr19527352018-08-03 15:04:38 +0800498 i++;
499 }
500
501 json_object_put(obj);
502
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800503 if (ctx->n_configs == 1)
504 ctx->default_config = &ctx->configs[0];
505
Jeremy Kerr19527352018-08-03 15:04:38 +0800506 return 0;
507
508err_free:
509 warnx("failed to load config from %s", conf_path);
510 json_object_put(obj);
511 return -1;
512}
513
514static int config_select(struct ctx *ctx, const char *name)
515{
516 struct config *config;
517 int i;
518
519 config = NULL;
520
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800521 if (!name) {
522 /* no config specified: use the default */
523 if (!ctx->default_config) {
524 warnx("no config specified, and no default");
525 return -1;
Jeremy Kerr19527352018-08-03 15:04:38 +0800526 }
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800527 config = ctx->default_config;
Jeremy Kerr19527352018-08-03 15:04:38 +0800528
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800529 } else {
530 /* find a matching config... */
531 for (i = 0; i < ctx->n_configs; i++) {
532 if (!strcmp(ctx->configs[i].name, name)) {
533 config = &ctx->configs[i];
534 break;
535 }
536 }
537
538 if (!config) {
539 warnx("no such configuration '%s'", name);
540 return -1;
541 }
Jeremy Kerr19527352018-08-03 15:04:38 +0800542 }
543
544 /* ... and apply it */
545 ctx->dev_path = config->nbd_device;
546 return 0;
547}
548
549int main(int argc, char **argv)
550{
551 const char *config_name;
Jeremy Kerrf403c422018-07-26 12:14:56 +0800552 struct ctx _ctx, *ctx;
553 int rc;
554
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800555 config_name = NULL;
Jeremy Kerr19527352018-08-03 15:04:38 +0800556
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800557 if (argc > 1)
558 config_name = argv[1];
Jeremy Kerr19527352018-08-03 15:04:38 +0800559
Jeremy Kerrf403c422018-07-26 12:14:56 +0800560 ctx = &_ctx;
Jeremy Kerr19527352018-08-03 15:04:38 +0800561 memset(ctx, 0, sizeof(*ctx));
Jeremy Kerrf403c422018-07-26 12:14:56 +0800562 ctx->bufsize = bufsize;
563 ctx->buf = malloc(ctx->bufsize);
Jeremy Kerr19527352018-08-03 15:04:38 +0800564
565 rc = config_init(ctx);
566 if (rc)
567 goto out_free;
568
569 rc = config_select(ctx, config_name);
570 if (rc)
571 goto out_free;
Jeremy Kerrf403c422018-07-26 12:14:56 +0800572
573 rc = open_nbd_socket(ctx);
574 if (rc)
575 goto out_free;
576
577 rc = setup_signals(ctx);
578 if (rc)
579 goto out_close;
580
581 rc = start_nbd_client(ctx);
582 if (rc)
583 goto out_stop_client;
584
585 rc = wait_for_nbd_client(ctx);
586 if (rc)
587 goto out_stop_client;
588
589 rc = run_proxy(ctx);
590
591out_stop_client:
592 /* we cleanup signals before stopping the client, because we
593 * no longer care about SIGCHLD from the stopping nbd-client
594 * process. stop_nbd_client will be a no-op if the client hasn't
595 * been started. */
596 cleanup_signals(ctx);
597
598 stop_nbd_client(ctx);
599 close(ctx->sock_client);
600
601out_close:
602 if (ctx->sock_path) {
603 unlink(ctx->sock_path);
604 free(ctx->sock_path);
605 }
606 close(ctx->sock);
607out_free:
Jeremy Kerr19527352018-08-03 15:04:38 +0800608 config_free(ctx);
Jeremy Kerrf403c422018-07-26 12:14:56 +0800609 free(ctx->buf);
610 return rc ? EXIT_FAILURE : EXIT_SUCCESS;
611}