blob: 3ce887905c303d2753e740c62451f08c0b19c004 [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 Kerr19527352018-08-03 15:04:38 +0800392static void config_free_one(struct config *config)
Jeremy Kerrf403c422018-07-26 12:14:56 +0800393{
Jeremy Kerr19527352018-08-03 15:04:38 +0800394 if (config->metadata)
395 json_object_put(config->metadata);
396 free(config->nbd_device);
397 free(config->name);
398}
399
400static int config_parse_one(struct config *config, const char *name,
401 json_object *obj)
402{
403 struct json_object *tmp, *meta;
404 json_bool jrc;
405
406 jrc = json_object_object_get_ex(obj, "nbd-device", &tmp);
407 if (!jrc) {
408 warnx("config %s doesn't specify a nbd-device", name);
409 return -1;
410 }
411
412 if (!json_object_is_type(tmp, json_type_string)) {
413 warnx("config %s has invalid nbd-device", name);
414 return -1;
415 }
416
417 config->nbd_device = strdup(json_object_get_string(tmp));
418 config->name = strdup(name);
419
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800420 jrc = json_object_object_get_ex(obj, "default", &tmp);
421 config->is_default = jrc && json_object_get_boolean(tmp);
422
Jeremy Kerr19527352018-08-03 15:04:38 +0800423 jrc = json_object_object_get_ex(obj, "metadata", &meta);
424 if (jrc && json_object_is_type(meta, json_type_object))
425 config->metadata = json_object_get(meta);
426 else
427 config->metadata = NULL;
428
429 return 0;
430}
431
432static void config_free(struct ctx *ctx)
433{
434 int i;
435
436 for (i = 0; i < ctx->n_configs; i++)
437 config_free_one(&ctx->configs[i]);
438
439 free(ctx->configs);
440 ctx->n_configs = 0;
441}
442
443static int config_init(struct ctx *ctx)
444{
445 struct json_object *obj, *tmp;
446 json_bool jrc;
447 int i, rc;
448
449 /* apply defaults */
450 ctx->nbd_timeout = nbd_timeout_default;
451
452 obj = json_object_from_file(conf_path);
453 if (!obj) {
454 warnx("can't read configuration from %s\n", conf_path);
455 return -1;
456 }
457
458 /* global configuration */
459 jrc = json_object_object_get_ex(obj, "timeout", &tmp);
460 if (jrc) {
461 errno = 0;
462 ctx->nbd_timeout = json_object_get_int(tmp);
463 if (ctx->nbd_timeout == 0 && errno) {
464 warnx("can't parse timeout value");
465 goto err_free;
466 }
467 }
468
469 /* per-config configuration */
470 jrc = json_object_object_get_ex(obj, "configurations", &tmp);
471 if (!jrc) {
472 warnx("no configurations specified");
473 goto err_free;
474 }
475
476 if (!json_object_is_type(tmp, json_type_object)) {
477 warnx("invalid configurations format");
478 goto err_free;
479 }
480
481 ctx->n_configs = json_object_object_length(tmp);
482 ctx->configs = calloc(ctx->n_configs, sizeof(*ctx->configs));
483
484 i = 0;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800485 json_object_object_foreach(tmp, name, config_json) {
486 struct config *config = &ctx->configs[i];
487
488 rc = config_parse_one(config, name, config_json);
Jeremy Kerr19527352018-08-03 15:04:38 +0800489 if (rc)
490 goto err_free;
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800491
492 if (config->is_default) {
493 if (ctx->default_config) {
494 warn("multiple configs flagged as default");
495 goto err_free;
496 }
497 ctx->default_config = config;
498 }
Jeremy Kerr19527352018-08-03 15:04:38 +0800499 i++;
500 }
501
502 json_object_put(obj);
503
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800504 if (ctx->n_configs == 1)
505 ctx->default_config = &ctx->configs[0];
506
Jeremy Kerr19527352018-08-03 15:04:38 +0800507 return 0;
508
509err_free:
510 warnx("failed to load config from %s", conf_path);
511 json_object_put(obj);
512 return -1;
513}
514
515static int config_select(struct ctx *ctx, const char *name)
516{
517 struct config *config;
518 int i;
519
520 config = NULL;
521
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800522 if (!name) {
523 /* no config specified: use the default */
524 if (!ctx->default_config) {
525 warnx("no config specified, and no default");
526 return -1;
Jeremy Kerr19527352018-08-03 15:04:38 +0800527 }
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800528 config = ctx->default_config;
Jeremy Kerr19527352018-08-03 15:04:38 +0800529
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800530 } else {
531 /* find a matching config... */
532 for (i = 0; i < ctx->n_configs; i++) {
533 if (!strcmp(ctx->configs[i].name, name)) {
534 config = &ctx->configs[i];
535 break;
536 }
537 }
538
539 if (!config) {
540 warnx("no such configuration '%s'", name);
541 return -1;
542 }
Jeremy Kerr19527352018-08-03 15:04:38 +0800543 }
544
545 /* ... and apply it */
546 ctx->dev_path = config->nbd_device;
547 return 0;
548}
549
Jeremy Kerr6f9c4332018-08-03 17:11:15 +0800550static const struct option options[] = {
551 { .name = "help", .val = 'h' },
552 { 0 },
553};
554
555static void print_usage(const char *progname)
556{
557 fprintf(stderr, "usage: %s [configuration]\n", progname);
558}
559
Jeremy Kerr19527352018-08-03 15:04:38 +0800560int main(int argc, char **argv)
561{
562 const char *config_name;
Jeremy Kerrf403c422018-07-26 12:14:56 +0800563 struct ctx _ctx, *ctx;
564 int rc;
565
Jeremy Kerr0f1a49c2018-08-03 15:41:38 +0800566 config_name = NULL;
Jeremy Kerr19527352018-08-03 15:04:38 +0800567
Jeremy Kerr6f9c4332018-08-03 17:11:15 +0800568 for (;;) {
569 int c = getopt_long(argc, argv, "h", options, NULL);
570 if (c == -1)
571 break;
572
573 switch (c) {
574 case 'h':
575 case '?':
576 print_usage(argv[0]);
577 return c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE;
578 }
579 }
580
581 if (optind < argc)
582 config_name = argv[optind];
Jeremy Kerr19527352018-08-03 15:04:38 +0800583
Jeremy Kerrf403c422018-07-26 12:14:56 +0800584 ctx = &_ctx;
Jeremy Kerr19527352018-08-03 15:04:38 +0800585 memset(ctx, 0, sizeof(*ctx));
Jeremy Kerrf403c422018-07-26 12:14:56 +0800586 ctx->bufsize = bufsize;
587 ctx->buf = malloc(ctx->bufsize);
Jeremy Kerr19527352018-08-03 15:04:38 +0800588
589 rc = config_init(ctx);
590 if (rc)
591 goto out_free;
592
593 rc = config_select(ctx, config_name);
594 if (rc)
595 goto out_free;
Jeremy Kerrf403c422018-07-26 12:14:56 +0800596
597 rc = open_nbd_socket(ctx);
598 if (rc)
599 goto out_free;
600
601 rc = setup_signals(ctx);
602 if (rc)
603 goto out_close;
604
605 rc = start_nbd_client(ctx);
606 if (rc)
607 goto out_stop_client;
608
609 rc = wait_for_nbd_client(ctx);
610 if (rc)
611 goto out_stop_client;
612
613 rc = run_proxy(ctx);
614
615out_stop_client:
616 /* we cleanup signals before stopping the client, because we
617 * no longer care about SIGCHLD from the stopping nbd-client
618 * process. stop_nbd_client will be a no-op if the client hasn't
619 * been started. */
620 cleanup_signals(ctx);
621
622 stop_nbd_client(ctx);
623 close(ctx->sock_client);
624
625out_close:
626 if (ctx->sock_path) {
627 unlink(ctx->sock_path);
628 free(ctx->sock_path);
629 }
630 close(ctx->sock);
631out_free:
Jeremy Kerr19527352018-08-03 15:04:38 +0800632 config_free(ctx);
Jeremy Kerrf403c422018-07-26 12:14:56 +0800633 free(ctx->buf);
634 return rc ? EXIT_FAILURE : EXIT_SUCCESS;
635}