root / block / nbd.c @ 87ea75d5
History | View | Annotate | Download (10.4 kB)
1 |
/*
|
---|---|
2 |
* QEMU Block driver for NBD
|
3 |
*
|
4 |
* Copyright (C) 2008 Bull S.A.S.
|
5 |
* Author: Laurent Vivier <Laurent.Vivier@bull.net>
|
6 |
*
|
7 |
* Some parts:
|
8 |
* Copyright (C) 2007 Anthony Liguori <anthony@codemonkey.ws>
|
9 |
*
|
10 |
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
11 |
* of this software and associated documentation files (the "Software"), to deal
|
12 |
* in the Software without restriction, including without limitation the rights
|
13 |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
14 |
* copies of the Software, and to permit persons to whom the Software is
|
15 |
* furnished to do so, subject to the following conditions:
|
16 |
*
|
17 |
* The above copyright notice and this permission notice shall be included in
|
18 |
* all copies or substantial portions of the Software.
|
19 |
*
|
20 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
21 |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
22 |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
23 |
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
24 |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
25 |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
26 |
* THE SOFTWARE.
|
27 |
*/
|
28 |
|
29 |
#include "block/nbd-client.h" |
30 |
#include "qemu/uri.h" |
31 |
#include "block/block_int.h" |
32 |
#include "qemu/module.h" |
33 |
#include "qemu/sockets.h" |
34 |
#include "qapi/qmp/qjson.h" |
35 |
#include "qapi/qmp/qint.h" |
36 |
|
37 |
#include <sys/types.h> |
38 |
#include <unistd.h> |
39 |
|
40 |
#define EN_OPTSTR ":exportname=" |
41 |
|
42 |
typedef struct BDRVNBDState { |
43 |
NbdClientSession client; |
44 |
QemuOpts *socket_opts; |
45 |
} BDRVNBDState; |
46 |
|
47 |
static int nbd_parse_uri(const char *filename, QDict *options) |
48 |
{ |
49 |
URI *uri; |
50 |
const char *p; |
51 |
QueryParams *qp = NULL;
|
52 |
int ret = 0; |
53 |
bool is_unix;
|
54 |
|
55 |
uri = uri_parse(filename); |
56 |
if (!uri) {
|
57 |
return -EINVAL;
|
58 |
} |
59 |
|
60 |
/* transport */
|
61 |
if (!strcmp(uri->scheme, "nbd")) { |
62 |
is_unix = false;
|
63 |
} else if (!strcmp(uri->scheme, "nbd+tcp")) { |
64 |
is_unix = false;
|
65 |
} else if (!strcmp(uri->scheme, "nbd+unix")) { |
66 |
is_unix = true;
|
67 |
} else {
|
68 |
ret = -EINVAL; |
69 |
goto out;
|
70 |
} |
71 |
|
72 |
p = uri->path ? uri->path : "/";
|
73 |
p += strspn(p, "/");
|
74 |
if (p[0]) { |
75 |
qdict_put(options, "export", qstring_from_str(p));
|
76 |
} |
77 |
|
78 |
qp = query_params_parse(uri->query); |
79 |
if (qp->n > 1 || (is_unix && !qp->n) || (!is_unix && qp->n)) { |
80 |
ret = -EINVAL; |
81 |
goto out;
|
82 |
} |
83 |
|
84 |
if (is_unix) {
|
85 |
/* nbd+unix:///export?socket=path */
|
86 |
if (uri->server || uri->port || strcmp(qp->p[0].name, "socket")) { |
87 |
ret = -EINVAL; |
88 |
goto out;
|
89 |
} |
90 |
qdict_put(options, "path", qstring_from_str(qp->p[0].value)); |
91 |
} else {
|
92 |
QString *host; |
93 |
/* nbd[+tcp]://host[:port]/export */
|
94 |
if (!uri->server) {
|
95 |
ret = -EINVAL; |
96 |
goto out;
|
97 |
} |
98 |
|
99 |
/* strip braces from literal IPv6 address */
|
100 |
if (uri->server[0] == '[') { |
101 |
host = qstring_from_substr(uri->server, 1,
|
102 |
strlen(uri->server) - 2);
|
103 |
} else {
|
104 |
host = qstring_from_str(uri->server); |
105 |
} |
106 |
|
107 |
qdict_put(options, "host", host);
|
108 |
if (uri->port) {
|
109 |
char* port_str = g_strdup_printf("%d", uri->port); |
110 |
qdict_put(options, "port", qstring_from_str(port_str));
|
111 |
g_free(port_str); |
112 |
} |
113 |
} |
114 |
|
115 |
out:
|
116 |
if (qp) {
|
117 |
query_params_free(qp); |
118 |
} |
119 |
uri_free(uri); |
120 |
return ret;
|
121 |
} |
122 |
|
123 |
static void nbd_parse_filename(const char *filename, QDict *options, |
124 |
Error **errp) |
125 |
{ |
126 |
char *file;
|
127 |
char *export_name;
|
128 |
const char *host_spec; |
129 |
const char *unixpath; |
130 |
|
131 |
if (qdict_haskey(options, "host") |
132 |
|| qdict_haskey(options, "port")
|
133 |
|| qdict_haskey(options, "path"))
|
134 |
{ |
135 |
error_setg(errp, "host/port/path and a file name may not be specified "
|
136 |
"at the same time");
|
137 |
return;
|
138 |
} |
139 |
|
140 |
if (strstr(filename, "://")) { |
141 |
int ret = nbd_parse_uri(filename, options);
|
142 |
if (ret < 0) { |
143 |
error_setg(errp, "No valid URL specified");
|
144 |
} |
145 |
return;
|
146 |
} |
147 |
|
148 |
file = g_strdup(filename); |
149 |
|
150 |
export_name = strstr(file, EN_OPTSTR); |
151 |
if (export_name) {
|
152 |
if (export_name[strlen(EN_OPTSTR)] == 0) { |
153 |
goto out;
|
154 |
} |
155 |
export_name[0] = 0; /* truncate 'file' */ |
156 |
export_name += strlen(EN_OPTSTR); |
157 |
|
158 |
qdict_put(options, "export", qstring_from_str(export_name));
|
159 |
} |
160 |
|
161 |
/* extract the host_spec - fail if it's not nbd:... */
|
162 |
if (!strstart(file, "nbd:", &host_spec)) { |
163 |
error_setg(errp, "File name string for NBD must start with 'nbd:'");
|
164 |
goto out;
|
165 |
} |
166 |
|
167 |
if (!*host_spec) {
|
168 |
goto out;
|
169 |
} |
170 |
|
171 |
/* are we a UNIX or TCP socket? */
|
172 |
if (strstart(host_spec, "unix:", &unixpath)) { |
173 |
qdict_put(options, "path", qstring_from_str(unixpath));
|
174 |
} else {
|
175 |
InetSocketAddress *addr = NULL;
|
176 |
|
177 |
addr = inet_parse(host_spec, errp); |
178 |
if (error_is_set(errp)) {
|
179 |
goto out;
|
180 |
} |
181 |
|
182 |
qdict_put(options, "host", qstring_from_str(addr->host));
|
183 |
qdict_put(options, "port", qstring_from_str(addr->port));
|
184 |
qapi_free_InetSocketAddress(addr); |
185 |
} |
186 |
|
187 |
out:
|
188 |
g_free(file); |
189 |
} |
190 |
|
191 |
static int nbd_config(BDRVNBDState *s, QDict *options, char **export) |
192 |
{ |
193 |
Error *local_err = NULL;
|
194 |
|
195 |
if (qdict_haskey(options, "path")) { |
196 |
if (qdict_haskey(options, "host")) { |
197 |
qerror_report(ERROR_CLASS_GENERIC_ERROR, "path and host may not "
|
198 |
"be used at the same time.");
|
199 |
return -EINVAL;
|
200 |
} |
201 |
s->client.is_unix = true;
|
202 |
} else if (qdict_haskey(options, "host")) { |
203 |
s->client.is_unix = false;
|
204 |
} else {
|
205 |
return -EINVAL;
|
206 |
} |
207 |
|
208 |
s->socket_opts = qemu_opts_create(&socket_optslist, NULL, 0, |
209 |
&error_abort); |
210 |
|
211 |
qemu_opts_absorb_qdict(s->socket_opts, options, &local_err); |
212 |
if (error_is_set(&local_err)) {
|
213 |
qerror_report_err(local_err); |
214 |
error_free(local_err); |
215 |
return -EINVAL;
|
216 |
} |
217 |
|
218 |
if (!qemu_opt_get(s->socket_opts, "port")) { |
219 |
qemu_opt_set_number(s->socket_opts, "port", NBD_DEFAULT_PORT);
|
220 |
} |
221 |
|
222 |
*export = g_strdup(qdict_get_try_str(options, "export"));
|
223 |
if (*export) {
|
224 |
qdict_del(options, "export");
|
225 |
} |
226 |
|
227 |
return 0; |
228 |
} |
229 |
|
230 |
static int nbd_establish_connection(BlockDriverState *bs) |
231 |
{ |
232 |
BDRVNBDState *s = bs->opaque; |
233 |
int sock;
|
234 |
|
235 |
if (s->client.is_unix) {
|
236 |
sock = unix_socket_outgoing(qemu_opt_get(s->socket_opts, "path"));
|
237 |
} else {
|
238 |
sock = tcp_socket_outgoing_opts(s->socket_opts); |
239 |
if (sock >= 0) { |
240 |
socket_set_nodelay(sock); |
241 |
} |
242 |
} |
243 |
|
244 |
/* Failed to establish connection */
|
245 |
if (sock < 0) { |
246 |
logout("Failed to establish connection to NBD server\n");
|
247 |
return -errno;
|
248 |
} |
249 |
|
250 |
return sock;
|
251 |
} |
252 |
|
253 |
static int nbd_open(BlockDriverState *bs, QDict *options, int flags, |
254 |
Error **errp) |
255 |
{ |
256 |
BDRVNBDState *s = bs->opaque; |
257 |
char *export = NULL; |
258 |
int result, sock;
|
259 |
|
260 |
/* Pop the config into our state object. Exit if invalid. */
|
261 |
result = nbd_config(s, options, &export); |
262 |
if (result != 0) { |
263 |
return result;
|
264 |
} |
265 |
|
266 |
/* establish TCP connection, return error if it fails
|
267 |
* TODO: Configurable retry-until-timeout behaviour.
|
268 |
*/
|
269 |
sock = nbd_establish_connection(bs); |
270 |
if (sock < 0) { |
271 |
return sock;
|
272 |
} |
273 |
|
274 |
/* NBD handshake */
|
275 |
result = nbd_client_session_init(&s->client, bs, sock, export); |
276 |
g_free(export); |
277 |
return result;
|
278 |
} |
279 |
|
280 |
static int nbd_co_readv(BlockDriverState *bs, int64_t sector_num, |
281 |
int nb_sectors, QEMUIOVector *qiov)
|
282 |
{ |
283 |
BDRVNBDState *s = bs->opaque; |
284 |
|
285 |
return nbd_client_session_co_readv(&s->client, sector_num,
|
286 |
nb_sectors, qiov); |
287 |
} |
288 |
|
289 |
static int nbd_co_writev(BlockDriverState *bs, int64_t sector_num, |
290 |
int nb_sectors, QEMUIOVector *qiov)
|
291 |
{ |
292 |
BDRVNBDState *s = bs->opaque; |
293 |
|
294 |
return nbd_client_session_co_writev(&s->client, sector_num,
|
295 |
nb_sectors, qiov); |
296 |
} |
297 |
|
298 |
static int nbd_co_flush(BlockDriverState *bs) |
299 |
{ |
300 |
BDRVNBDState *s = bs->opaque; |
301 |
|
302 |
return nbd_client_session_co_flush(&s->client);
|
303 |
} |
304 |
|
305 |
static int nbd_co_discard(BlockDriverState *bs, int64_t sector_num, |
306 |
int nb_sectors)
|
307 |
{ |
308 |
BDRVNBDState *s = bs->opaque; |
309 |
|
310 |
return nbd_client_session_co_discard(&s->client, sector_num,
|
311 |
nb_sectors); |
312 |
} |
313 |
|
314 |
static void nbd_close(BlockDriverState *bs) |
315 |
{ |
316 |
BDRVNBDState *s = bs->opaque; |
317 |
|
318 |
qemu_opts_del(s->socket_opts); |
319 |
nbd_client_session_close(&s->client); |
320 |
} |
321 |
|
322 |
static int64_t nbd_getlength(BlockDriverState *bs)
|
323 |
{ |
324 |
BDRVNBDState *s = bs->opaque; |
325 |
|
326 |
return s->client.size;
|
327 |
} |
328 |
|
329 |
static BlockDriver bdrv_nbd = {
|
330 |
.format_name = "nbd",
|
331 |
.protocol_name = "nbd",
|
332 |
.instance_size = sizeof(BDRVNBDState),
|
333 |
.bdrv_parse_filename = nbd_parse_filename, |
334 |
.bdrv_file_open = nbd_open, |
335 |
.bdrv_co_readv = nbd_co_readv, |
336 |
.bdrv_co_writev = nbd_co_writev, |
337 |
.bdrv_close = nbd_close, |
338 |
.bdrv_co_flush_to_os = nbd_co_flush, |
339 |
.bdrv_co_discard = nbd_co_discard, |
340 |
.bdrv_getlength = nbd_getlength, |
341 |
}; |
342 |
|
343 |
static BlockDriver bdrv_nbd_tcp = {
|
344 |
.format_name = "nbd",
|
345 |
.protocol_name = "nbd+tcp",
|
346 |
.instance_size = sizeof(BDRVNBDState),
|
347 |
.bdrv_parse_filename = nbd_parse_filename, |
348 |
.bdrv_file_open = nbd_open, |
349 |
.bdrv_co_readv = nbd_co_readv, |
350 |
.bdrv_co_writev = nbd_co_writev, |
351 |
.bdrv_close = nbd_close, |
352 |
.bdrv_co_flush_to_os = nbd_co_flush, |
353 |
.bdrv_co_discard = nbd_co_discard, |
354 |
.bdrv_getlength = nbd_getlength, |
355 |
}; |
356 |
|
357 |
static BlockDriver bdrv_nbd_unix = {
|
358 |
.format_name = "nbd",
|
359 |
.protocol_name = "nbd+unix",
|
360 |
.instance_size = sizeof(BDRVNBDState),
|
361 |
.bdrv_parse_filename = nbd_parse_filename, |
362 |
.bdrv_file_open = nbd_open, |
363 |
.bdrv_co_readv = nbd_co_readv, |
364 |
.bdrv_co_writev = nbd_co_writev, |
365 |
.bdrv_close = nbd_close, |
366 |
.bdrv_co_flush_to_os = nbd_co_flush, |
367 |
.bdrv_co_discard = nbd_co_discard, |
368 |
.bdrv_getlength = nbd_getlength, |
369 |
}; |
370 |
|
371 |
static void bdrv_nbd_init(void) |
372 |
{ |
373 |
bdrv_register(&bdrv_nbd); |
374 |
bdrv_register(&bdrv_nbd_tcp); |
375 |
bdrv_register(&bdrv_nbd_unix); |
376 |
} |
377 |
|
378 |
block_init(bdrv_nbd_init); |