blob: 13a13667b4399079d4de2135d7a3ff9a1bf180d7 [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
37#include "config.h"
38
39struct ctx {
40 int sock;
41 int sock_client;
42 int signal_pipe[2];
43 char *sock_path;
44 pid_t nbd_client_pid;
45 uint8_t *buf;
46 size_t bufsize;
47};
48
49static const char *sockpath_tmpl = RUNSTATEDIR "/nbd.%d.sock";
50static const char *dev_path = "/dev/nbd0";
51static const size_t bufsize = 0x20000;
Jeremy Kerr09295f22018-07-27 16:29:38 +080052static const int nbd_timeout = 30;
Jeremy Kerrf403c422018-07-26 12:14:56 +080053
54static int open_nbd_socket(struct ctx *ctx)
55{
56 struct sockaddr_un addr;
57 char *path;
58 int sd, rc;
59
60 rc = asprintf(&path, sockpath_tmpl, getpid());
61 if (rc < 0)
62 return -1;
63
64 sd = socket(AF_UNIX, SOCK_STREAM, 0);
65 if (sd < 0) {
66 warn("can't create socket");
67 goto err_free;
68 }
69
70 rc = fchmod(sd, S_IRUSR | S_IWUSR);
71 if (rc) {
72 warn("can't set permissions on socket");
73 goto err_close;
74 }
75
76 addr.sun_family = AF_UNIX;
77 strncpy(addr.sun_path, path, sizeof(addr.sun_path));
78
79 rc = bind(sd, (struct sockaddr *)&addr, sizeof(addr));
80 if (rc) {
81 warn("can't bind to path %s", path);
82 goto err_close;
83 }
84
85 rc = listen(sd, 1);
86 if (rc) {
87 warn("can't listen on socket %s", path);
88 goto err_unlink;
89 }
90
91 ctx->sock = sd;
92 ctx->sock_path = path;
93 return 0;
94
95err_unlink:
96 unlink(path);
97err_close:
98 close(sd);
99err_free:
100 free(path);
101 return -1;
102}
103
104static int start_nbd_client(struct ctx *ctx)
105{
106 pid_t pid;
107
108 pid = fork();
109 if (pid < 0) {
110 warn("can't create client process");
111 return -1;
112 }
113
114 /* child process: run nbd-client in non-fork mode */
115 if (pid == 0) {
Jeremy Kerr09295f22018-07-27 16:29:38 +0800116 char timeout_str[10];
Jeremy Kerrf403c422018-07-26 12:14:56 +0800117 int fd;
118
Jeremy Kerr09295f22018-07-27 16:29:38 +0800119 snprintf(timeout_str, sizeof(timeout_str), "%d", nbd_timeout);
120
Jeremy Kerrf403c422018-07-26 12:14:56 +0800121 fd = open("/dev/null", O_RDWR);
122 if (fd < 0)
123 err(EXIT_FAILURE, "can't open /dev/null");
124
125 dup2(fd, STDIN_FILENO);
126 dup2(fd, STDOUT_FILENO);
127 dup2(fd, STDERR_FILENO);
128 close(fd);
129 close(ctx->sock);
130
131 execlp("nbd-client", "nbd-client",
132 "-u", ctx->sock_path,
133 "-n",
Jeremy Kerr09295f22018-07-27 16:29:38 +0800134 "-t", timeout_str,
Jeremy Kerrf403c422018-07-26 12:14:56 +0800135 dev_path,
136 NULL);
137 err(EXIT_FAILURE, "can't start ndb client");
138 }
139
140 ctx->nbd_client_pid = pid;
141 return 0;
142}
143
144static void stop_nbd_client(struct ctx *ctx)
145{
146 int rc;
147
148 if (!ctx->nbd_client_pid)
149 return;
150
151 rc = kill(ctx->nbd_client_pid, SIGTERM);
152 if (rc)
153 return;
154
155 waitpid(ctx->nbd_client_pid, NULL, 0);
156 ctx->nbd_client_pid = 0;
157}
158
159static int copy_fd(struct ctx *ctx, int fd_in, int fd_out)
160{
161#ifdef HAVE_SPLICE
162 int rc;
163
164 rc = splice(fd_in, NULL, fd_out, NULL, ctx->bufsize, 0);
165 if (rc < 0)
166 warn("splice");
167
168 return rc;
169#else
170 size_t len, pos;
171 ssize_t rc;
172
173 for (;;) {
174 errno = 0;
175 rc = read(fd_in, ctx->buf, ctx->bufsize);
176 if (rc < 0) {
177 if (errno == EINTR)
178 continue;
179 warn("read failure");
180 return -1;
181 }
182 if (rc == 0)
183 return 0;
184 break;
185 }
186
187 len = rc;
188
189 for (pos = 0; pos < len;) {
190 errno = 0;
191 rc = write(fd_out, ctx->buf + pos, len - pos);
192 if (rc < 0) {
193 if (errno == EINTR)
194 continue;
195 warn("write failure");
196 return -1;
197 }
198 if (rc == 0)
199 break;
200 pos += rc;
201 }
202
203 return pos;
204#endif
205}
206
207static int signal_pipe_fd = -1;
208
209static void signal_handler(int signal)
210{
211 int rc;
212
213 rc = write(signal_pipe_fd, &signal, sizeof(signal));
214
215 /* not a lot we can do here but exit... */
216 if (rc != sizeof(signal))
217 exit(EXIT_FAILURE);
218}
219
220static int setup_signals(struct ctx *ctx)
221{
222 struct sigaction sa;
223 int rc;
224
225 rc = pipe(ctx->signal_pipe);
226 if (rc) {
227 warn("cant setup signal pipe");
228 return -1;
229 }
230
231 signal_pipe_fd = ctx->signal_pipe[1];
232
233 memset(&sa, 0, sizeof(sa));
234 sa.sa_handler = signal_handler;
235
236 sigaction(SIGINT, &sa, NULL);
237 sigaction(SIGTERM, &sa, NULL);
238 sigaction(SIGCHLD, &sa, NULL);
239
240 return 0;
241}
242
243static void cleanup_signals(struct ctx *ctx)
244{
245 struct sigaction sa;
246 memset(&sa, 0, sizeof(sa));
247 sa.sa_handler = SIG_DFL;
248
249 sigaction(SIGINT, &sa, NULL);
250 sigaction(SIGTERM, &sa, NULL);
251 sigaction(SIGCHLD, &sa, NULL);
252
253 close(ctx->signal_pipe[0]);
254 close(ctx->signal_pipe[1]);
255}
256
257static int process_signal_pipe(struct ctx *ctx, bool *exit)
258{
259 int buf, rc, status;
260
261 rc = read(ctx->signal_pipe[0], &buf, sizeof(buf));
262 if (rc != sizeof(buf))
263 return -1;
264
265 *exit = false;
266
267 switch (buf) {
268 case SIGCHLD:
269 rc = waitpid(ctx->nbd_client_pid, &status, WNOHANG);
270 if (rc > 0) {
271 warnx("nbd client stopped (%s: %d); exiting",
272 WIFEXITED(status) ? "rc" : "sig",
273 WIFEXITED(status) ?
274 WEXITSTATUS(status) :
275 WTERMSIG(status));
276 ctx->nbd_client_pid = 0;
277 }
278 break;
279 case SIGINT:
280 case SIGTERM:
281 *exit = true;
282 break;
283 }
284
285 return 0;
286}
287
288static int wait_for_nbd_client(struct ctx *ctx)
289{
290 struct pollfd pollfds[2];
291 int rc;
292
293 pollfds[0].fd = ctx->sock;
294 pollfds[0].events = POLLIN;
295 pollfds[1].fd = ctx->signal_pipe[0];
296 pollfds[1].events = POLLIN;
297
298 for (;;) {
299 errno = 0;
300 rc = poll(pollfds, 2, -1);
301 if (rc < 0) {
302 if (errno == EINTR)
303 continue;
304 warn("poll failed");
305 return -1;
306 }
307
308 if (pollfds[0].revents) {
309 rc = accept(ctx->sock, NULL, NULL);
310 if (rc < 0) {
311 warn("can't create connection");
312 return -1;
313 }
314 ctx->sock_client = rc;
315 break;
316 }
317
318 if (pollfds[1].revents) {
319 bool exit;
320 rc = process_signal_pipe(ctx, &exit);
321 if (rc || exit)
322 return -1;
323 }
324 }
325
326 return 0;
327}
328
329
330static int run_proxy(struct ctx *ctx)
331{
332 struct pollfd pollfds[3];
333 bool exit = false;
334 int rc;
335
336 /* main proxy: forward data between stdio & socket */
337 pollfds[0].fd = ctx->sock_client;
338 pollfds[0].events = POLLIN;
339 pollfds[1].fd = STDIN_FILENO;
340 pollfds[1].events = POLLIN;
341 pollfds[2].fd = ctx->signal_pipe[0];
342 pollfds[2].events = POLLIN;
343
344 for (;;) {
345 errno = 0;
346 rc = poll(pollfds, 3, -1);
347 if (rc < 0) {
348 if (errno == EINTR)
349 continue;
350 warn("poll failed");
351 break;
352 }
353
354 if (pollfds[0].revents) {
355 rc = copy_fd(ctx, ctx->sock_client, STDOUT_FILENO);
356 if (rc <= 0)
357 break;
358 }
359
360 if (pollfds[1].revents) {
361 rc = copy_fd(ctx, STDIN_FILENO, ctx->sock_client);
362 if (rc <= 0)
363 break;
364 }
365
366 if (pollfds[2].revents) {
367 rc = process_signal_pipe(ctx, &exit);
368 if (rc || exit)
369 break;
370 }
371 }
372
373 return rc ? -1 : 0;
374}
375
376int main(void)
377{
378 struct ctx _ctx, *ctx;
379 int rc;
380
381 ctx = &_ctx;
382 ctx->bufsize = bufsize;
383 ctx->buf = malloc(ctx->bufsize);
384 ctx->sock_path = NULL;
385 ctx->nbd_client_pid = 0;
386
387 rc = open_nbd_socket(ctx);
388 if (rc)
389 goto out_free;
390
391 rc = setup_signals(ctx);
392 if (rc)
393 goto out_close;
394
395 rc = start_nbd_client(ctx);
396 if (rc)
397 goto out_stop_client;
398
399 rc = wait_for_nbd_client(ctx);
400 if (rc)
401 goto out_stop_client;
402
403 rc = run_proxy(ctx);
404
405out_stop_client:
406 /* we cleanup signals before stopping the client, because we
407 * no longer care about SIGCHLD from the stopping nbd-client
408 * process. stop_nbd_client will be a no-op if the client hasn't
409 * been started. */
410 cleanup_signals(ctx);
411
412 stop_nbd_client(ctx);
413 close(ctx->sock_client);
414
415out_close:
416 if (ctx->sock_path) {
417 unlink(ctx->sock_path);
418 free(ctx->sock_path);
419 }
420 close(ctx->sock);
421out_free:
422 free(ctx->buf);
423 return rc ? EXIT_FAILURE : EXIT_SUCCESS;
424}