-
Notifications
You must be signed in to change notification settings - Fork 7
/
nbd.c
364 lines (307 loc) · 8.56 KB
/
nbd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/* virt-p2v
* Copyright (C) 2009-2019 Red Hat Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* This file handles running L<nbdkit(1)>. */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <netdb.h>
#include <errno.h>
#include <error.h>
#include <libintl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>
#include "p2v.h"
/* How long to wait for nbdkit to start (seconds). */
#define WAIT_NBD_TIMEOUT 10
/* The local port that nbdkit listens on (incremented for each server which is
* started).
*/
static int nbd_local_port;
/* Whether nbdkit recognizes "--exit-with-parent". */
static bool nbd_exit_with_parent;
static pid_t start_nbdkit (const char *device, int *fds, size_t nr_fds);
static int open_listening_socket (int **fds, size_t *nr_fds);
static int bind_tcpip_socket (const char *port, int **fds, size_t *nr_fds);
static char *nbd_error;
static void set_nbd_error (const char *fs, ...)
__attribute__((format(printf,1,2)));
static void
set_nbd_error (const char *fs, ...)
{
va_list args;
char *msg;
int len;
va_start (args, fs);
len = vasprintf (&msg, fs, args);
va_end (args);
if (len < 0)
error (EXIT_FAILURE, errno,
"vasprintf (original error format string: %s)", fs);
free (nbd_error);
nbd_error = msg;
}
const char *
get_nbd_error (void)
{
return nbd_error;
}
/**
* Check for nbdkit.
*/
void
test_nbd_server (void)
{
int r;
/* Initialize nbd_local_port. */
if (is_iso_environment)
/* The p2v ISO should allow us to open up just about any port, so
* we can fix a port number in that case. Using a predictable
* port number in this case should avoid rare errors if the port
* colides with another (ie. it'll either always fail or never
* fail).
*/
nbd_local_port = 50123;
else
/* When testing on the local machine, choose a random port. */
nbd_local_port = 50000 + (random () % 10000);
#if DEBUG_STDERR
fprintf (stderr, "checking for nbdkit ...\n");
#endif
r = system ("nbdkit file --version"
#ifndef DEBUG_STDERR
" >/dev/null 2>&1"
#endif
);
if (r != 0) {
fprintf (stderr, _("%s: nbdkit was not found, cannot continue.\n"),
g_get_prgname ());
exit (EXIT_FAILURE);
}
r = system ("nbdkit --exit-with-parent --version"
#ifndef DEBUG_STDERR
" >/dev/null 2>&1"
#endif
);
nbd_exit_with_parent = (r == 0);
#if DEBUG_STDERR
fprintf (stderr, "found nbdkit (%s exit with parent)\n",
nbd_exit_with_parent ? "can" : "cannot");
#endif
}
/**
* Start nbdkit.
*
* We previously tested nbdkit (see C<test_nbd_server>).
*
* Returns the process ID (E<gt> 0) or C<0> if there is an error.
*/
pid_t
start_nbd_server (int *port, const char *device)
{
int *fds = NULL;
size_t i, nr_fds;
pid_t pid;
*port = open_listening_socket (&fds, &nr_fds);
if (*port == -1) return -1;
pid = start_nbdkit (device, fds, nr_fds);
for (i = 0; i < nr_fds; ++i)
close (fds[i]);
free (fds);
return pid;
}
#define FIRST_SOCKET_ACTIVATION_FD 3
/**
* Set up file descriptors and environment variables for
* socket activation.
*
* Note this function runs in the child between fork and exec.
*/
static inline void
socket_activation (int *fds, size_t nr_fds)
{
size_t i;
char nr_fds_str[20];
char pid_str[16];
if (fds == NULL) return;
for (i = 0; i < nr_fds; ++i) {
int fd = FIRST_SOCKET_ACTIVATION_FD + i;
if (fds[i] != fd) {
dup2 (fds[i], fd);
close (fds[i]);
}
}
snprintf (nr_fds_str, sizeof nr_fds_str, "%zu", nr_fds);
setenv ("LISTEN_FDS", nr_fds_str, 1);
snprintf (pid_str, sizeof pid_str, "%d", (int) getpid ());
setenv ("LISTEN_PID", pid_str, 1);
}
/**
* Start a local L<nbdkit(1)> process using the
* L<nbdkit-file-plugin(1)>.
*
* C<fds> and C<nr_fds> will contain the locally pre-opened file descriptors
* for this.
*
* Returns the process ID (E<gt> 0) or C<0> if there is an error.
*/
static pid_t
start_nbdkit (const char *device, int *fds, size_t nr_fds)
{
pid_t pid;
CLEANUP_FREE char *file_str = NULL;
#if DEBUG_STDERR
fprintf (stderr, "starting nbdkit for %s using socket activation\n", device);
#endif
if (asprintf (&file_str, "file=%s", device) == -1)
error (EXIT_FAILURE, errno, "asprintf");
pid = fork ();
if (pid == -1) {
set_nbd_error ("fork: %m");
return 0;
}
if (pid == 0) { /* Child. */
const char *nofork_opt;
close (0);
if (open ("/dev/null", O_RDONLY) == -1) {
perror ("open: /dev/null");
_exit (EXIT_FAILURE);
}
socket_activation (fds, nr_fds);
nofork_opt = nbd_exit_with_parent ?
"--exit-with-parent" : /* don't fork, and exit when the parent
* thread does */
"-f"; /* don't fork */
execlp ("nbdkit",
"nbdkit",
"-r", /* readonly (vital!) */
nofork_opt,
"file", /* file plugin */
file_str, /* a device like file=/dev/sda */
NULL);
perror ("nbdkit");
_exit (EXIT_FAILURE);
}
/* Parent. */
return pid;
}
/**
* Open a listening socket on an unused local port and return it.
*
* Returns the port number on success or C<-1> on error.
*
* The file descriptor(s) bound are returned in the array *fds, *nr_fds.
* The caller must free the array.
*/
static int
open_listening_socket (int **fds, size_t *nr_fds)
{
int port;
char port_str[16];
/* This just ensures we don't try the port we previously bound to. */
port = nbd_local_port;
/* Search for a free port. */
for (; port < 60000; ++port) {
snprintf (port_str, sizeof port_str, "%d", port);
if (bind_tcpip_socket (port_str, fds, nr_fds) == 0) {
/* See above. */
nbd_local_port = port + 1;
return port;
}
}
set_nbd_error ("cannot find a free local port");
return -1;
}
static int
bind_tcpip_socket (const char *port, int **fds_rtn, size_t *nr_fds_rtn)
{
struct addrinfo *ai = NULL;
struct addrinfo hints;
struct addrinfo *a;
int err;
int *fds = NULL;
size_t nr_fds;
int addr_in_use = 0;
memset (&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
err = getaddrinfo ("localhost", port, &hints, &ai);
if (err != 0) {
#if DEBUG_STDERR
fprintf (stderr, "%s: getaddrinfo: localhost: %s: %s", g_get_prgname (),
port, gai_strerror (err));
#endif
return -1;
}
nr_fds = 0;
for (a = ai; a != NULL; a = a->ai_next) {
int sock, opt;
sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
if (sock == -1)
error (EXIT_FAILURE, errno, "socket");
opt = 1;
if (setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt) == -1)
perror ("setsockopt: SO_REUSEADDR");
#ifdef IPV6_V6ONLY
if (a->ai_family == PF_INET6) {
if (setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof opt) == -1)
perror ("setsockopt: IPv6 only");
}
#endif
if (bind (sock, a->ai_addr, a->ai_addrlen) == -1) {
if (errno == EADDRINUSE) {
addr_in_use = 1;
close (sock);
continue;
}
perror ("bind");
close (sock);
continue;
}
if (listen (sock, SOMAXCONN) == -1) {
perror ("listen");
close (sock);
continue;
}
nr_fds++;
fds = realloc (fds, sizeof (int) * nr_fds);
if (!fds)
error (EXIT_FAILURE, errno, "realloc");
fds[nr_fds-1] = sock;
}
freeaddrinfo (ai);
if (nr_fds == 0 && addr_in_use) {
#if DEBUG_STDERR
fprintf (stderr, "%s: unable to bind to localhost:%s: %s\n",
g_get_prgname (), port, strerror (EADDRINUSE));
#endif
return -1;
}
#if DEBUG_STDERR
fprintf (stderr, "%s: bound to localhost:%s (%zu socket(s))\n",
g_get_prgname (), port, nr_fds);
#endif
*fds_rtn = fds;
*nr_fds_rtn = nr_fds;
return 0;
}