blob: 6b23d6ab1d514ee03f4322c5adb18c1272a82aca [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>
Jeremy Kerr6f9c4332018-08-03 17:11:15 +080023#include <getopt.h>
Jeremy Kerrf403c422018-07-26 12:14:56 +080024#include <signal.h>
25#include <stdbool.h>
26#include <stdint.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include <sys/poll.h>
33#include <sys/socket.h>
34#include <sys/stat.h>
35#include <sys/un.h>
36#include <sys/wait.h>
37
Jeremy Kerr19527352018-08-03 15:04:38 +080038#include <json.h>
39
Jeremy Kerrf403c422018-07-26 12:14:56 +080040#include "config.h"
41
Jeremy Kerr19527352018-08-03 15:04:38 +080042struct config {
43 char *name;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +080044 bool is_default;
Jeremy Kerr19527352018-08-03 15:04:38 +080045 char *nbd_device;
46 struct json_object *metadata;
47};
48
Jeremy Kerrf403c422018-07-26 12:14:56 +080049struct ctx {
50 int sock;
51 int sock_client;
52 int signal_pipe[2];
53 char *sock_path;
Jeremy Kerr19527352018-08-03 15:04:38 +080054 char *dev_path;
Jeremy Kerrf403c422018-07-26 12:14:56 +080055 pid_t nbd_client_pid;
Jeremy Kerr19527352018-08-03 15:04:38 +080056 int nbd_timeout;
Jeremy Kerrf403c422018-07-26 12:14:56 +080057 uint8_t *buf;
58 size_t bufsize;
Jeremy Kerr19527352018-08-03 15:04:38 +080059 struct config *configs;
60 int n_configs;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +080061 struct config *default_config;
Jeremy Kerrf403c422018-07-26 12:14:56 +080062};
63
Jeremy Kerr19527352018-08-03 15:04:38 +080064static const char *conf_path = SYSCONFDIR "/nbd-proxy/config.json";
Jeremy Kerrf403c422018-07-26 12:14:56 +080065static const char *sockpath_tmpl = RUNSTATEDIR "/nbd.%d.sock";
Jeremy Kerrf403c422018-07-26 12:14:56 +080066static const size_t bufsize = 0x20000;
Jeremy Kerr19527352018-08-03 15:04:38 +080067static const int nbd_timeout_default = 30;
Jeremy Kerrf403c422018-07-26 12:14:56 +080068
69static int open_nbd_socket(struct ctx *ctx)
70{
71 struct sockaddr_un addr;
72 char *path;
73 int sd, rc;
74
75 rc = asprintf(&path, sockpath_tmpl, getpid());
76 if (rc < 0)
77 return -1;
78
79 sd = socket(AF_UNIX, SOCK_STREAM, 0);
80 if (sd < 0) {
81 warn("can't create socket");
82 goto err_free;
83 }
84
85 rc = fchmod(sd, S_IRUSR | S_IWUSR);
86 if (rc) {
87 warn("can't set permissions on socket");
88 goto err_close;
89 }
90
91 addr.sun_family = AF_UNIX;
92 strncpy(addr.sun_path, path, sizeof(addr.sun_path));
93
94 rc = bind(sd, (struct sockaddr *)&addr, sizeof(addr));
95 if (rc) {
96 warn("can't bind to path %s", path);
97 goto err_close;
98 }
99
100 rc = listen(sd, 1);
101 if (rc) {
102 warn("can't listen on socket %s", path);
103 goto err_unlink;
104 }
105
106 ctx->sock = sd;
107 ctx->sock_path = path;
108 return 0;
109
110err_unlink:
111 unlink(path);
112err_close:
113 close(sd);
114err_free:
115 free(path);
116 return -1;
117}
118
119static int start_nbd_client(struct ctx *ctx)
120{
121 pid_t pid;
122
123 pid = fork();
124 if (pid < 0) {
125 warn("can't create client process");
126 return -1;
127 }
128
129 /* child process: run nbd-client in non-fork mode */
130 if (pid == 0) {
Jeremy Kerr09295f22018-07-27 16:29:38 +0800131 char timeout_str[10];
Jeremy Kerrf403c422018-07-26 12:14:56 +0800132 int fd;
133
Jeremy Kerr19527352018-08-03 15:04:38 +0800134 snprintf(timeout_str, sizeof(timeout_str),
135 "%d", ctx->nbd_timeout);
Jeremy Kerr09295f22018-07-27 16:29:38 +0800136
Jeremy Kerrf403c422018-07-26 12:14:56 +0800137 fd = open("/dev/null", O_RDWR);
138 if (fd < 0)
139 err(EXIT_FAILURE, "can't open /dev/null");
140
141 dup2(fd, STDIN_FILENO);
142 dup2(fd, STDOUT_FILENO);
143 dup2(fd, STDERR_FILENO);
144 close(fd);
145 close(ctx->sock);
146
147 execlp("nbd-client", "nbd-client",
148 "-u", ctx->sock_path,
149 "-n",
Jeremy Kerr09295f22018-07-27 16:29:38 +0800150 "-t", timeout_str,
Jeremy Kerr19527352018-08-03 15:04:38 +0800151 ctx->dev_path,
Jeremy Kerrf403c422018-07-26 12:14:56 +0800152 NULL);
153 err(EXIT_FAILURE, "can't start ndb client");
154 }
155
156 ctx->nbd_client_pid = pid;
157 return 0;
158}
159
160static void stop_nbd_client(struct ctx *ctx)
161{
162 int rc;
163
164 if (!ctx->nbd_client_pid)
165 return;
166
167 rc = kill(ctx->nbd_client_pid, SIGTERM);
168 if (rc)
169 return;
170
171 waitpid(ctx->nbd_client_pid, NULL, 0);
172 ctx->nbd_client_pid = 0;
173}
174
175static int copy_fd(struct ctx *ctx, int fd_in, int fd_out)
176{
177#ifdef HAVE_SPLICE
178 int rc;
179
180 rc = splice(fd_in, NULL, fd_out, NULL, ctx->bufsize, 0);
181 if (rc < 0)
182 warn("splice");
183
184 return rc;
185#else
186 size_t len, pos;
187 ssize_t rc;
188
189 for (;;) {
190 errno = 0;
191 rc = read(fd_in, ctx->buf, ctx->bufsize);
192 if (rc < 0) {
193 if (errno == EINTR)
194 continue;
195 warn("read failure");
196 return -1;
197 }
198 if (rc == 0)
199 return 0;
200 break;
201 }
202
203 len = rc;
204
205 for (pos = 0; pos < len;) {
206 errno = 0;
207 rc = write(fd_out, ctx->buf + pos, len - pos);
208 if (rc < 0) {
209 if (errno == EINTR)
210 continue;
211 warn("write failure");
212 return -1;
213 }
214 if (rc == 0)
215 break;
216 pos += rc;
217 }
218
219 return pos;
220#endif
221}
222
223static int signal_pipe_fd = -1;
224
225static void signal_handler(int signal)
226{
227 int rc;
228
229 rc = write(signal_pipe_fd, &signal, sizeof(signal));
230
231 /* not a lot we can do here but exit... */
232 if (rc != sizeof(signal))
233 exit(EXIT_FAILURE);
234}
235
236static int setup_signals(struct ctx *ctx)
237{
238 struct sigaction sa;
239 int rc;
240
241 rc = pipe(ctx->signal_pipe);
242 if (rc) {
243 warn("cant setup signal pipe");
244 return -1;
245 }
246
247 signal_pipe_fd = ctx->signal_pipe[1];
248
249 memset(&sa, 0, sizeof(sa));
250 sa.sa_handler = signal_handler;
251
252 sigaction(SIGINT, &sa, NULL);
253 sigaction(SIGTERM, &sa, NULL);
254 sigaction(SIGCHLD, &sa, NULL);
255
256 return 0;
257}
258
259static void cleanup_signals(struct ctx *ctx)
260{
261 struct sigaction sa;
262 memset(&sa, 0, sizeof(sa));
263 sa.sa_handler = SIG_DFL;
264
265 sigaction(SIGINT, &sa, NULL);
266 sigaction(SIGTERM, &sa, NULL);
267 sigaction(SIGCHLD, &sa, NULL);
268
269 close(ctx->signal_pipe[0]);
270 close(ctx->signal_pipe[1]);
271}
272
273static int process_signal_pipe(struct ctx *ctx, bool *exit)
274{
275 int buf, rc, status;
276
277 rc = read(ctx->signal_pipe[0], &buf, sizeof(buf));
278 if (rc != sizeof(buf))
279 return -1;
280
281 *exit = false;
282
283 switch (buf) {
284 case SIGCHLD:
285 rc = waitpid(ctx->nbd_client_pid, &status, WNOHANG);
286 if (rc > 0) {
287 warnx("nbd client stopped (%s: %d); exiting",
288 WIFEXITED(status) ? "rc" : "sig",
289 WIFEXITED(status) ?
290 WEXITSTATUS(status) :
291 WTERMSIG(status));
292 ctx->nbd_client_pid = 0;
293 }
294 break;
295 case SIGINT:
296 case SIGTERM:
297 *exit = true;
298 break;
299 }
300
301 return 0;
302}
303
304static int wait_for_nbd_client(struct ctx *ctx)
305{
306 struct pollfd pollfds[2];
307 int rc;
308
309 pollfds[0].fd = ctx->sock;
310 pollfds[0].events = POLLIN;
311 pollfds[1].fd = ctx->signal_pipe[0];
312 pollfds[1].events = POLLIN;
313
314 for (;;) {
315 errno = 0;
316 rc = poll(pollfds, 2, -1);
317 if (rc < 0) {
318 if (errno == EINTR)
319 continue;
320 warn("poll failed");
321 return -1;
322 }
323
324 if (pollfds[0].revents) {
325 rc = accept(ctx->sock, NULL, NULL);
326 if (rc < 0) {
327 warn("can't create connection");
328 return -1;
329 }
330 ctx->sock_client = rc;
331 break;
332 }
333
334 if (pollfds[1].revents) {
335 bool exit;
336 rc = process_signal_pipe(ctx, &exit);
337 if (rc || exit)
338 return -1;
339 }
340 }
341
342 return 0;
343}
344
345
346static int run_proxy(struct ctx *ctx)
347{
348 struct pollfd pollfds[3];
349 bool exit = false;
350 int rc;
351
352 /* main proxy: forward data between stdio & socket */
353 pollfds[0].fd = ctx->sock_client;
354 pollfds[0].events = POLLIN;
355 pollfds[1].fd = STDIN_FILENO;
356 pollfds[1].events = POLLIN;
357 pollfds[2].fd = ctx->signal_pipe[0];
358 pollfds[2].events = POLLIN;
359
360 for (;;) {
361 errno = 0;
362 rc = poll(pollfds, 3, -1);
363 if (rc < 0) {
364 if (errno == EINTR)
365 continue;
366 warn("poll failed");
367 break;
368 }
369
370 if (pollfds[0].revents) {
371 rc = copy_fd(ctx, ctx->sock_client, STDOUT_FILENO);
372 if (rc <= 0)
373 break;
374 }
375
376 if (pollfds[1].revents) {
377 rc = copy_fd(ctx, STDIN_FILENO, ctx->sock_client);
378 if (rc <= 0)
379 break;
380 }
381
382 if (pollfds[2].revents) {
383 rc = process_signal_pipe(ctx, &exit);
384 if (rc || exit)
385 break;
386 }
387 }
388
389 return rc ? -1 : 0;
390}
391
Jeremy Kerr13bb28f2018-08-09 10:41:10 +0800392static void print_metadata(struct ctx *ctx)
393{
394 struct json_object *md;
395 int i;
396
397 md = json_object_new_object();
398
399 for (i = 0; i < ctx->n_configs; i++) {
400 struct config *config = &ctx->configs[i];
401 json_object_object_add(md, config->name,
402 config->metadata);
403 }
404
405 puts(json_object_get_string(md));
406
407 json_object_put(md);
408}
409
Jeremy Kerr19527352018-08-03 15:04:38 +0800410static void config_free_one(struct config *config)
Jeremy Kerrf403c422018-07-26 12:14:56 +0800411{
Jeremy Kerr19527352018-08-03 15:04:38 +0800412 if (config->metadata)
413 json_object_put(config->metadata);
414 free(config->nbd_device);
415 free(config->name);
416}
417
418static int config_parse_one(struct config *config, const char *name,
419 json_object *obj)
420{
421 struct json_object *tmp, *meta;
422 json_bool jrc;
423
424 jrc = json_object_object_get_ex(obj, "nbd-device", &tmp);
425 if (!jrc) {
426 warnx("config %s doesn't specify a nbd-device", name);
427 return -1;
428 }
429
430 if (!json_object_is_type(tmp, json_type_string)) {
431 warnx("config %s has invalid nbd-device", name);
432 return -1;
433 }
434
435 config->nbd_device = strdup(json_object_get_string(tmp));
436 config->name = strdup(name);
437
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800438 jrc = json_object_object_get_ex(obj, "default", &tmp);
439 config->is_default = jrc && json_object_get_boolean(tmp);
440
Jeremy Kerr19527352018-08-03 15:04:38 +0800441 jrc = json_object_object_get_ex(obj, "metadata", &meta);
442 if (jrc && json_object_is_type(meta, json_type_object))
443 config->metadata = json_object_get(meta);
444 else
445 config->metadata = NULL;
446
447 return 0;
448}
449
450static void config_free(struct ctx *ctx)
451{
452 int i;
453
454 for (i = 0; i < ctx->n_configs; i++)
455 config_free_one(&ctx->configs[i]);
456
457 free(ctx->configs);
458 ctx->n_configs = 0;
459}
460
461static int config_init(struct ctx *ctx)
462{
463 struct json_object *obj, *tmp;
464 json_bool jrc;
465 int i, rc;
466
467 /* apply defaults */
468 ctx->nbd_timeout = nbd_timeout_default;
469
470 obj = json_object_from_file(conf_path);
471 if (!obj) {
472 warnx("can't read configuration from %s\n", conf_path);
473 return -1;
474 }
475
476 /* global configuration */
477 jrc = json_object_object_get_ex(obj, "timeout", &tmp);
478 if (jrc) {
479 errno = 0;
480 ctx->nbd_timeout = json_object_get_int(tmp);
481 if (ctx->nbd_timeout == 0 && errno) {
482 warnx("can't parse timeout value");
483 goto err_free;
484 }
485 }
486
487 /* per-config configuration */
488 jrc = json_object_object_get_ex(obj, "configurations", &tmp);
489 if (!jrc) {
490 warnx("no configurations specified");
491 goto err_free;
492 }
493
494 if (!json_object_is_type(tmp, json_type_object)) {
495 warnx("invalid configurations format");
496 goto err_free;
497 }
498
499 ctx->n_configs = json_object_object_length(tmp);
500 ctx->configs = calloc(ctx->n_configs, sizeof(*ctx->configs));
501
502 i = 0;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800503 json_object_object_foreach(tmp, name, config_json) {
504 struct config *config = &ctx->configs[i];
505
506 rc = config_parse_one(config, name, config_json);
Jeremy Kerr19527352018-08-03 15:04:38 +0800507 if (rc)
508 goto err_free;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800509
510 if (config->is_default) {
511 if (ctx->default_config) {
512 warn("multiple configs flagged as default");
513 goto err_free;
514 }
515 ctx->default_config = config;
516 }
Jeremy Kerr19527352018-08-03 15:04:38 +0800517 i++;
518 }
519
520 json_object_put(obj);
521
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800522 if (ctx->n_configs == 1)
523 ctx->default_config = &ctx->configs[0];
524
Jeremy Kerr19527352018-08-03 15:04:38 +0800525 return 0;
526
527err_free:
528 warnx("failed to load config from %s", conf_path);
529 json_object_put(obj);
530 return -1;
531}
532
533static int config_select(struct ctx *ctx, const char *name)
534{
535 struct config *config;
536 int i;
537
538 config = NULL;
539
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800540 if (!name) {
541 /* no config specified: use the default */
542 if (!ctx->default_config) {
543 warnx("no config specified, and no default");
544 return -1;
Jeremy Kerr19527352018-08-03 15:04:38 +0800545 }
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800546 config = ctx->default_config;
Jeremy Kerr19527352018-08-03 15:04:38 +0800547
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800548 } else {
549 /* find a matching config... */
550 for (i = 0; i < ctx->n_configs; i++) {
551 if (!strcmp(ctx->configs[i].name, name)) {
552 config = &ctx->configs[i];
553 break;
554 }
555 }
556
557 if (!config) {
558 warnx("no such configuration '%s'", name);
559 return -1;
560 }
Jeremy Kerr19527352018-08-03 15:04:38 +0800561 }
562
563 /* ... and apply it */
564 ctx->dev_path = config->nbd_device;
565 return 0;
566}
567
Jeremy Kerr6f9c4332018-08-03 17:11:15 +0800568static const struct option options[] = {
Jeremy Kerr13bb28f2018-08-09 10:41:10 +0800569 { .name = "help", .val = 'h' },
570 { .name = "metadata", .val = 'm' },
Jeremy Kerr6f9c4332018-08-03 17:11:15 +0800571 { 0 },
572};
573
Jeremy Kerr13bb28f2018-08-09 10:41:10 +0800574enum action {
575 ACTION_PROXY,
576 ACTION_METADATA,
577};
578
Jeremy Kerr6f9c4332018-08-03 17:11:15 +0800579static void print_usage(const char *progname)
580{
Jeremy Kerr13bb28f2018-08-09 10:41:10 +0800581 fprintf(stderr, "usage:\n");
582 fprintf(stderr, "\t%s [configuration]\n", progname);
583 fprintf(stderr, "\t%s --metadata\n", progname);
Jeremy Kerr6f9c4332018-08-03 17:11:15 +0800584}
585
Jeremy Kerr19527352018-08-03 15:04:38 +0800586int main(int argc, char **argv)
587{
Jeremy Kerr13bb28f2018-08-09 10:41:10 +0800588 enum action action = ACTION_PROXY;
Jeremy Kerr19527352018-08-03 15:04:38 +0800589 const char *config_name;
Jeremy Kerrf403c422018-07-26 12:14:56 +0800590 struct ctx _ctx, *ctx;
591 int rc;
592
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800593 config_name = NULL;
Jeremy Kerr19527352018-08-03 15:04:38 +0800594
Jeremy Kerr6f9c4332018-08-03 17:11:15 +0800595 for (;;) {
596 int c = getopt_long(argc, argv, "h", options, NULL);
597 if (c == -1)
598 break;
599
600 switch (c) {
Jeremy Kerr13bb28f2018-08-09 10:41:10 +0800601 case 'm':
602 action = ACTION_METADATA;
603 break;
Jeremy Kerr6f9c4332018-08-03 17:11:15 +0800604 case 'h':
605 case '?':
606 print_usage(argv[0]);
607 return c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE;
608 }
609 }
610
611 if (optind < argc)
612 config_name = argv[optind];
Jeremy Kerr19527352018-08-03 15:04:38 +0800613
Jeremy Kerrf403c422018-07-26 12:14:56 +0800614 ctx = &_ctx;
Jeremy Kerr19527352018-08-03 15:04:38 +0800615 memset(ctx, 0, sizeof(*ctx));
Jeremy Kerrf403c422018-07-26 12:14:56 +0800616 ctx->bufsize = bufsize;
617 ctx->buf = malloc(ctx->bufsize);
Jeremy Kerr19527352018-08-03 15:04:38 +0800618
619 rc = config_init(ctx);
620 if (rc)
621 goto out_free;
622
Jeremy Kerr13bb28f2018-08-09 10:41:10 +0800623 if (action == ACTION_METADATA) {
624 print_metadata(ctx);
625 goto out_free;
626 }
627
Jeremy Kerr19527352018-08-03 15:04:38 +0800628 rc = config_select(ctx, config_name);
629 if (rc)
630 goto out_free;
Jeremy Kerrf403c422018-07-26 12:14:56 +0800631
632 rc = open_nbd_socket(ctx);
633 if (rc)
634 goto out_free;
635
636 rc = setup_signals(ctx);
637 if (rc)
638 goto out_close;
639
640 rc = start_nbd_client(ctx);
641 if (rc)
642 goto out_stop_client;
643
644 rc = wait_for_nbd_client(ctx);
645 if (rc)
646 goto out_stop_client;
647
648 rc = run_proxy(ctx);
649
650out_stop_client:
651 /* we cleanup signals before stopping the client, because we
652 * no longer care about SIGCHLD from the stopping nbd-client
653 * process. stop_nbd_client will be a no-op if the client hasn't
654 * been started. */
655 cleanup_signals(ctx);
656
657 stop_nbd_client(ctx);
658 close(ctx->sock_client);
659
660out_close:
661 if (ctx->sock_path) {
662 unlink(ctx->sock_path);
663 free(ctx->sock_path);
664 }
665 close(ctx->sock);
666out_free:
Jeremy Kerr19527352018-08-03 15:04:38 +0800667 config_free(ctx);
Jeremy Kerrf403c422018-07-26 12:14:56 +0800668 free(ctx->buf);
669 return rc ? EXIT_FAILURE : EXIT_SUCCESS;
670}