blob: cb3c33cd32f762a0cdb3b410c77af4adfe193d57 [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;
43 char *nbd_device;
44 struct json_object *metadata;
45};
46
Jeremy Kerrf403c422018-07-26 12:14:56 +080047struct ctx {
48 int sock;
49 int sock_client;
50 int signal_pipe[2];
51 char *sock_path;
Jeremy Kerr19527352018-08-03 15:04:38 +080052 char *dev_path;
Jeremy Kerrf403c422018-07-26 12:14:56 +080053 pid_t nbd_client_pid;
Jeremy Kerr19527352018-08-03 15:04:38 +080054 int nbd_timeout;
Jeremy Kerrf403c422018-07-26 12:14:56 +080055 uint8_t *buf;
56 size_t bufsize;
Jeremy Kerr19527352018-08-03 15:04:38 +080057 struct config *configs;
58 int n_configs;
Jeremy Kerrf403c422018-07-26 12:14:56 +080059};
60
Jeremy Kerr19527352018-08-03 15:04:38 +080061static const char *conf_path = SYSCONFDIR "/nbd-proxy/config.json";
Jeremy Kerrf403c422018-07-26 12:14:56 +080062static const char *sockpath_tmpl = RUNSTATEDIR "/nbd.%d.sock";
Jeremy Kerrf403c422018-07-26 12:14:56 +080063static const size_t bufsize = 0x20000;
Jeremy Kerr19527352018-08-03 15:04:38 +080064static const int nbd_timeout_default = 30;
Jeremy Kerrf403c422018-07-26 12:14:56 +080065
66static int open_nbd_socket(struct ctx *ctx)
67{
68 struct sockaddr_un addr;
69 char *path;
70 int sd, rc;
71
72 rc = asprintf(&path, sockpath_tmpl, getpid());
73 if (rc < 0)
74 return -1;
75
76 sd = socket(AF_UNIX, SOCK_STREAM, 0);
77 if (sd < 0) {
78 warn("can't create socket");
79 goto err_free;
80 }
81
82 rc = fchmod(sd, S_IRUSR | S_IWUSR);
83 if (rc) {
84 warn("can't set permissions on socket");
85 goto err_close;
86 }
87
88 addr.sun_family = AF_UNIX;
89 strncpy(addr.sun_path, path, sizeof(addr.sun_path));
90
91 rc = bind(sd, (struct sockaddr *)&addr, sizeof(addr));
92 if (rc) {
93 warn("can't bind to path %s", path);
94 goto err_close;
95 }
96
97 rc = listen(sd, 1);
98 if (rc) {
99 warn("can't listen on socket %s", path);
100 goto err_unlink;
101 }
102
103 ctx->sock = sd;
104 ctx->sock_path = path;
105 return 0;
106
107err_unlink:
108 unlink(path);
109err_close:
110 close(sd);
111err_free:
112 free(path);
113 return -1;
114}
115
116static int start_nbd_client(struct ctx *ctx)
117{
118 pid_t pid;
119
120 pid = fork();
121 if (pid < 0) {
122 warn("can't create client process");
123 return -1;
124 }
125
126 /* child process: run nbd-client in non-fork mode */
127 if (pid == 0) {
Jeremy Kerr09295f22018-07-27 16:29:38 +0800128 char timeout_str[10];
Jeremy Kerrf403c422018-07-26 12:14:56 +0800129 int fd;
130
Jeremy Kerr19527352018-08-03 15:04:38 +0800131 snprintf(timeout_str, sizeof(timeout_str),
132 "%d", ctx->nbd_timeout);
Jeremy Kerr09295f22018-07-27 16:29:38 +0800133
Jeremy Kerrf403c422018-07-26 12:14:56 +0800134 fd = open("/dev/null", O_RDWR);
135 if (fd < 0)
136 err(EXIT_FAILURE, "can't open /dev/null");
137
138 dup2(fd, STDIN_FILENO);
139 dup2(fd, STDOUT_FILENO);
140 dup2(fd, STDERR_FILENO);
141 close(fd);
142 close(ctx->sock);
143
144 execlp("nbd-client", "nbd-client",
145 "-u", ctx->sock_path,
146 "-n",
Jeremy Kerr09295f22018-07-27 16:29:38 +0800147 "-t", timeout_str,
Jeremy Kerr19527352018-08-03 15:04:38 +0800148 ctx->dev_path,
Jeremy Kerrf403c422018-07-26 12:14:56 +0800149 NULL);
150 err(EXIT_FAILURE, "can't start ndb client");
151 }
152
153 ctx->nbd_client_pid = pid;
154 return 0;
155}
156
157static void stop_nbd_client(struct ctx *ctx)
158{
159 int rc;
160
161 if (!ctx->nbd_client_pid)
162 return;
163
164 rc = kill(ctx->nbd_client_pid, SIGTERM);
165 if (rc)
166 return;
167
168 waitpid(ctx->nbd_client_pid, NULL, 0);
169 ctx->nbd_client_pid = 0;
170}
171
172static int copy_fd(struct ctx *ctx, int fd_in, int fd_out)
173{
174#ifdef HAVE_SPLICE
175 int rc;
176
177 rc = splice(fd_in, NULL, fd_out, NULL, ctx->bufsize, 0);
178 if (rc < 0)
179 warn("splice");
180
181 return rc;
182#else
183 size_t len, pos;
184 ssize_t rc;
185
186 for (;;) {
187 errno = 0;
188 rc = read(fd_in, ctx->buf, ctx->bufsize);
189 if (rc < 0) {
190 if (errno == EINTR)
191 continue;
192 warn("read failure");
193 return -1;
194 }
195 if (rc == 0)
196 return 0;
197 break;
198 }
199
200 len = rc;
201
202 for (pos = 0; pos < len;) {
203 errno = 0;
204 rc = write(fd_out, ctx->buf + pos, len - pos);
205 if (rc < 0) {
206 if (errno == EINTR)
207 continue;
208 warn("write failure");
209 return -1;
210 }
211 if (rc == 0)
212 break;
213 pos += rc;
214 }
215
216 return pos;
217#endif
218}
219
220static int signal_pipe_fd = -1;
221
222static void signal_handler(int signal)
223{
224 int rc;
225
226 rc = write(signal_pipe_fd, &signal, sizeof(signal));
227
228 /* not a lot we can do here but exit... */
229 if (rc != sizeof(signal))
230 exit(EXIT_FAILURE);
231}
232
233static int setup_signals(struct ctx *ctx)
234{
235 struct sigaction sa;
236 int rc;
237
238 rc = pipe(ctx->signal_pipe);
239 if (rc) {
240 warn("cant setup signal pipe");
241 return -1;
242 }
243
244 signal_pipe_fd = ctx->signal_pipe[1];
245
246 memset(&sa, 0, sizeof(sa));
247 sa.sa_handler = signal_handler;
248
249 sigaction(SIGINT, &sa, NULL);
250 sigaction(SIGTERM, &sa, NULL);
251 sigaction(SIGCHLD, &sa, NULL);
252
253 return 0;
254}
255
256static void cleanup_signals(struct ctx *ctx)
257{
258 struct sigaction sa;
259 memset(&sa, 0, sizeof(sa));
260 sa.sa_handler = SIG_DFL;
261
262 sigaction(SIGINT, &sa, NULL);
263 sigaction(SIGTERM, &sa, NULL);
264 sigaction(SIGCHLD, &sa, NULL);
265
266 close(ctx->signal_pipe[0]);
267 close(ctx->signal_pipe[1]);
268}
269
270static int process_signal_pipe(struct ctx *ctx, bool *exit)
271{
272 int buf, rc, status;
273
274 rc = read(ctx->signal_pipe[0], &buf, sizeof(buf));
275 if (rc != sizeof(buf))
276 return -1;
277
278 *exit = false;
279
280 switch (buf) {
281 case SIGCHLD:
282 rc = waitpid(ctx->nbd_client_pid, &status, WNOHANG);
283 if (rc > 0) {
284 warnx("nbd client stopped (%s: %d); exiting",
285 WIFEXITED(status) ? "rc" : "sig",
286 WIFEXITED(status) ?
287 WEXITSTATUS(status) :
288 WTERMSIG(status));
289 ctx->nbd_client_pid = 0;
290 }
291 break;
292 case SIGINT:
293 case SIGTERM:
294 *exit = true;
295 break;
296 }
297
298 return 0;
299}
300
301static int wait_for_nbd_client(struct ctx *ctx)
302{
303 struct pollfd pollfds[2];
304 int rc;
305
306 pollfds[0].fd = ctx->sock;
307 pollfds[0].events = POLLIN;
308 pollfds[1].fd = ctx->signal_pipe[0];
309 pollfds[1].events = POLLIN;
310
311 for (;;) {
312 errno = 0;
313 rc = poll(pollfds, 2, -1);
314 if (rc < 0) {
315 if (errno == EINTR)
316 continue;
317 warn("poll failed");
318 return -1;
319 }
320
321 if (pollfds[0].revents) {
322 rc = accept(ctx->sock, NULL, NULL);
323 if (rc < 0) {
324 warn("can't create connection");
325 return -1;
326 }
327 ctx->sock_client = rc;
328 break;
329 }
330
331 if (pollfds[1].revents) {
332 bool exit;
333 rc = process_signal_pipe(ctx, &exit);
334 if (rc || exit)
335 return -1;
336 }
337 }
338
339 return 0;
340}
341
342
343static int run_proxy(struct ctx *ctx)
344{
345 struct pollfd pollfds[3];
346 bool exit = false;
347 int rc;
348
349 /* main proxy: forward data between stdio & socket */
350 pollfds[0].fd = ctx->sock_client;
351 pollfds[0].events = POLLIN;
352 pollfds[1].fd = STDIN_FILENO;
353 pollfds[1].events = POLLIN;
354 pollfds[2].fd = ctx->signal_pipe[0];
355 pollfds[2].events = POLLIN;
356
357 for (;;) {
358 errno = 0;
359 rc = poll(pollfds, 3, -1);
360 if (rc < 0) {
361 if (errno == EINTR)
362 continue;
363 warn("poll failed");
364 break;
365 }
366
367 if (pollfds[0].revents) {
368 rc = copy_fd(ctx, ctx->sock_client, STDOUT_FILENO);
369 if (rc <= 0)
370 break;
371 }
372
373 if (pollfds[1].revents) {
374 rc = copy_fd(ctx, STDIN_FILENO, ctx->sock_client);
375 if (rc <= 0)
376 break;
377 }
378
379 if (pollfds[2].revents) {
380 rc = process_signal_pipe(ctx, &exit);
381 if (rc || exit)
382 break;
383 }
384 }
385
386 return rc ? -1 : 0;
387}
388
Jeremy Kerr19527352018-08-03 15:04:38 +0800389static void config_free_one(struct config *config)
Jeremy Kerrf403c422018-07-26 12:14:56 +0800390{
Jeremy Kerr19527352018-08-03 15:04:38 +0800391 if (config->metadata)
392 json_object_put(config->metadata);
393 free(config->nbd_device);
394 free(config->name);
395}
396
397static int config_parse_one(struct config *config, const char *name,
398 json_object *obj)
399{
400 struct json_object *tmp, *meta;
401 json_bool jrc;
402
403 jrc = json_object_object_get_ex(obj, "nbd-device", &tmp);
404 if (!jrc) {
405 warnx("config %s doesn't specify a nbd-device", name);
406 return -1;
407 }
408
409 if (!json_object_is_type(tmp, json_type_string)) {
410 warnx("config %s has invalid nbd-device", name);
411 return -1;
412 }
413
414 config->nbd_device = strdup(json_object_get_string(tmp));
415 config->name = strdup(name);
416
417 jrc = json_object_object_get_ex(obj, "metadata", &meta);
418 if (jrc && json_object_is_type(meta, json_type_object))
419 config->metadata = json_object_get(meta);
420 else
421 config->metadata = NULL;
422
423 return 0;
424}
425
426static void config_free(struct ctx *ctx)
427{
428 int i;
429
430 for (i = 0; i < ctx->n_configs; i++)
431 config_free_one(&ctx->configs[i]);
432
433 free(ctx->configs);
434 ctx->n_configs = 0;
435}
436
437static int config_init(struct ctx *ctx)
438{
439 struct json_object *obj, *tmp;
440 json_bool jrc;
441 int i, rc;
442
443 /* apply defaults */
444 ctx->nbd_timeout = nbd_timeout_default;
445
446 obj = json_object_from_file(conf_path);
447 if (!obj) {
448 warnx("can't read configuration from %s\n", conf_path);
449 return -1;
450 }
451
452 /* global configuration */
453 jrc = json_object_object_get_ex(obj, "timeout", &tmp);
454 if (jrc) {
455 errno = 0;
456 ctx->nbd_timeout = json_object_get_int(tmp);
457 if (ctx->nbd_timeout == 0 && errno) {
458 warnx("can't parse timeout value");
459 goto err_free;
460 }
461 }
462
463 /* per-config configuration */
464 jrc = json_object_object_get_ex(obj, "configurations", &tmp);
465 if (!jrc) {
466 warnx("no configurations specified");
467 goto err_free;
468 }
469
470 if (!json_object_is_type(tmp, json_type_object)) {
471 warnx("invalid configurations format");
472 goto err_free;
473 }
474
475 ctx->n_configs = json_object_object_length(tmp);
476 ctx->configs = calloc(ctx->n_configs, sizeof(*ctx->configs));
477
478 i = 0;
479 json_object_object_foreach(tmp, name, config) {
480 rc = config_parse_one(&ctx->configs[i], name, config);
481 if (rc)
482 goto err_free;
483 i++;
484 }
485
486 json_object_put(obj);
487
488 return 0;
489
490err_free:
491 warnx("failed to load config from %s", conf_path);
492 json_object_put(obj);
493 return -1;
494}
495
496static int config_select(struct ctx *ctx, const char *name)
497{
498 struct config *config;
499 int i;
500
501 config = NULL;
502
503 /* find a matching config... */
504 for (i = 0; i < ctx->n_configs; i++) {
505 if (!strcmp(ctx->configs[i].name, name)) {
506 config = &ctx->configs[i];
507 break;
508 }
509 }
510
511 if (!config) {
512 warnx("no such configuration '%s'", name);
513 return -1;
514 }
515
516 /* ... and apply it */
517 ctx->dev_path = config->nbd_device;
518 return 0;
519}
520
521int main(int argc, char **argv)
522{
523 const char *config_name;
Jeremy Kerrf403c422018-07-26 12:14:56 +0800524 struct ctx _ctx, *ctx;
525 int rc;
526
Jeremy Kerr19527352018-08-03 15:04:38 +0800527 if (argc != 2) {
528 fprintf(stderr, "usage: %s <configuration>\n", argv[0]);
529 return EXIT_FAILURE;
530 }
531
532 config_name = argv[1];
533
Jeremy Kerrf403c422018-07-26 12:14:56 +0800534 ctx = &_ctx;
Jeremy Kerr19527352018-08-03 15:04:38 +0800535 memset(ctx, 0, sizeof(*ctx));
Jeremy Kerrf403c422018-07-26 12:14:56 +0800536 ctx->bufsize = bufsize;
537 ctx->buf = malloc(ctx->bufsize);
Jeremy Kerr19527352018-08-03 15:04:38 +0800538
539 rc = config_init(ctx);
540 if (rc)
541 goto out_free;
542
543 rc = config_select(ctx, config_name);
544 if (rc)
545 goto out_free;
Jeremy Kerrf403c422018-07-26 12:14:56 +0800546
547 rc = open_nbd_socket(ctx);
548 if (rc)
549 goto out_free;
550
551 rc = setup_signals(ctx);
552 if (rc)
553 goto out_close;
554
555 rc = start_nbd_client(ctx);
556 if (rc)
557 goto out_stop_client;
558
559 rc = wait_for_nbd_client(ctx);
560 if (rc)
561 goto out_stop_client;
562
563 rc = run_proxy(ctx);
564
565out_stop_client:
566 /* we cleanup signals before stopping the client, because we
567 * no longer care about SIGCHLD from the stopping nbd-client
568 * process. stop_nbd_client will be a no-op if the client hasn't
569 * been started. */
570 cleanup_signals(ctx);
571
572 stop_nbd_client(ctx);
573 close(ctx->sock_client);
574
575out_close:
576 if (ctx->sock_path) {
577 unlink(ctx->sock_path);
578 free(ctx->sock_path);
579 }
580 close(ctx->sock);
581out_free:
Jeremy Kerr19527352018-08-03 15:04:38 +0800582 config_free(ctx);
Jeremy Kerrf403c422018-07-26 12:14:56 +0800583 free(ctx->buf);
584 return rc ? EXIT_FAILURE : EXIT_SUCCESS;
585}