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