]>
Commit | Line | Data |
---|---|---|
6dd844db PB |
1 | /* |
2 | * Serving QEMU block devices via NBD | |
3 | * | |
4 | * Copyright (c) 2012 Red Hat, Inc. | |
5 | * | |
6 | * Author: Paolo Bonzini <pbonzini@redhat.com> | |
7 | * | |
8 | * This work is licensed under the terms of the GNU GPL, version 2 or | |
9 | * later. See the COPYING file in the top-level directory. | |
10 | */ | |
11 | ||
d38ea87a | 12 | #include "qemu/osdep.h" |
9c17d615 | 13 | #include "sysemu/blockdev.h" |
e140177d | 14 | #include "sysemu/block-backend.h" |
0d09e41a | 15 | #include "hw/block/block.h" |
e688df6b | 16 | #include "qapi/error.h" |
5daa6bfd | 17 | #include "qapi/qapi-commands-block-export.h" |
737e150e | 18 | #include "block/nbd.h" |
ae398278 | 19 | #include "io/channel-socket.h" |
862172f4 | 20 | #include "io/net-listener.h" |
6dd844db | 21 | |
ddffee39 | 22 | typedef struct NBDServerData { |
862172f4 | 23 | QIONetListener *listener; |
ddffee39 | 24 | QCryptoTLSCreds *tlscreds; |
00019455 | 25 | char *tlsauthz; |
1c8222b0 KW |
26 | uint32_t max_connections; |
27 | uint32_t connections; | |
ddffee39 DB |
28 | } NBDServerData; |
29 | ||
30 | static NBDServerData *nbd_server; | |
00917172 | 31 | static bool is_qemu_nbd; |
ddffee39 | 32 | |
1c8222b0 KW |
33 | static void nbd_update_server_watch(NBDServerData *s); |
34 | ||
00917172 KW |
35 | void nbd_server_is_qemu_nbd(bool value) |
36 | { | |
37 | is_qemu_nbd = value; | |
38 | } | |
39 | ||
0c9390d9 EB |
40 | static void nbd_blockdev_client_closed(NBDClient *client, bool ignored) |
41 | { | |
42 | nbd_client_put(client); | |
1c8222b0 KW |
43 | assert(nbd_server->connections > 0); |
44 | nbd_server->connections--; | |
45 | nbd_update_server_watch(nbd_server); | |
0c9390d9 | 46 | } |
6dd844db | 47 | |
862172f4 DB |
48 | static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc, |
49 | gpointer opaque) | |
6dd844db | 50 | { |
1c8222b0 KW |
51 | nbd_server->connections++; |
52 | nbd_update_server_watch(nbd_server); | |
53 | ||
0d73f725 | 54 | qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server"); |
00019455 | 55 | nbd_client_new(cioc, nbd_server->tlscreds, nbd_server->tlsauthz, |
0c9390d9 | 56 | nbd_blockdev_client_closed); |
6dd844db PB |
57 | } |
58 | ||
1c8222b0 KW |
59 | static void nbd_update_server_watch(NBDServerData *s) |
60 | { | |
61 | if (!s->max_connections || s->connections < s->max_connections) { | |
62 | qio_net_listener_set_client_func(s->listener, nbd_accept, NULL, NULL); | |
63 | } else { | |
64 | qio_net_listener_set_client_func(s->listener, NULL, NULL, NULL); | |
65 | } | |
66 | } | |
ddffee39 DB |
67 | |
68 | static void nbd_server_free(NBDServerData *server) | |
6dd844db | 69 | { |
ddffee39 | 70 | if (!server) { |
6dd844db PB |
71 | return; |
72 | } | |
73 | ||
862172f4 DB |
74 | qio_net_listener_disconnect(server->listener); |
75 | object_unref(OBJECT(server->listener)); | |
ddffee39 DB |
76 | if (server->tlscreds) { |
77 | object_unref(OBJECT(server->tlscreds)); | |
78 | } | |
00019455 | 79 | g_free(server->tlsauthz); |
ddffee39 DB |
80 | |
81 | g_free(server); | |
82 | } | |
83 | ||
84 | static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp) | |
85 | { | |
86 | Object *obj; | |
87 | QCryptoTLSCreds *creds; | |
88 | ||
89 | obj = object_resolve_path_component( | |
90 | object_get_objects_root(), id); | |
91 | if (!obj) { | |
92 | error_setg(errp, "No TLS credentials with id '%s'", | |
93 | id); | |
94 | return NULL; | |
95 | } | |
96 | creds = (QCryptoTLSCreds *) | |
97 | object_dynamic_cast(obj, TYPE_QCRYPTO_TLS_CREDS); | |
98 | if (!creds) { | |
99 | error_setg(errp, "Object with id '%s' is not TLS credentials", | |
100 | id); | |
101 | return NULL; | |
102 | } | |
103 | ||
104 | if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { | |
105 | error_setg(errp, | |
106 | "Expecting TLS credentials with a server endpoint"); | |
107 | return NULL; | |
108 | } | |
109 | object_ref(obj); | |
110 | return creds; | |
111 | } | |
112 | ||
113 | ||
bd269ebc | 114 | void nbd_server_start(SocketAddress *addr, const char *tls_creds, |
1c8222b0 KW |
115 | const char *tls_authz, uint32_t max_connections, |
116 | Error **errp) | |
ddffee39 DB |
117 | { |
118 | if (nbd_server) { | |
119 | error_setg(errp, "NBD server already running"); | |
ae398278 | 120 | return; |
6dd844db | 121 | } |
ae398278 | 122 | |
ddffee39 | 123 | nbd_server = g_new0(NBDServerData, 1); |
1c8222b0 | 124 | nbd_server->max_connections = max_connections; |
862172f4 DB |
125 | nbd_server->listener = qio_net_listener_new(); |
126 | ||
127 | qio_net_listener_set_name(nbd_server->listener, | |
128 | "nbd-listener"); | |
129 | ||
fc8135c6 | 130 | if (qio_net_listener_open_sync(nbd_server->listener, addr, 1, errp) < 0) { |
ddffee39 DB |
131 | goto error; |
132 | } | |
133 | ||
bd269ebc | 134 | if (tls_creds) { |
ddffee39 DB |
135 | nbd_server->tlscreds = nbd_get_tls_creds(tls_creds, errp); |
136 | if (!nbd_server->tlscreds) { | |
137 | goto error; | |
138 | } | |
139 | ||
bd269ebc MA |
140 | /* TODO SOCKET_ADDRESS_TYPE_FD where fd has AF_INET or AF_INET6 */ |
141 | if (addr->type != SOCKET_ADDRESS_TYPE_INET) { | |
ddffee39 DB |
142 | error_setg(errp, "TLS is only supported with IPv4/IPv6"); |
143 | goto error; | |
144 | } | |
145 | } | |
146 | ||
00019455 DB |
147 | nbd_server->tlsauthz = g_strdup(tls_authz); |
148 | ||
1c8222b0 | 149 | nbd_update_server_watch(nbd_server); |
ddffee39 DB |
150 | |
151 | return; | |
152 | ||
153 | error: | |
154 | nbd_server_free(nbd_server); | |
155 | nbd_server = NULL; | |
6dd844db PB |
156 | } |
157 | ||
eed8b691 KW |
158 | void nbd_server_start_options(NbdServerOptions *arg, Error **errp) |
159 | { | |
1c8222b0 KW |
160 | nbd_server_start(arg->addr, arg->tls_creds, arg->tls_authz, |
161 | arg->max_connections, errp); | |
eed8b691 KW |
162 | } |
163 | ||
bd269ebc MA |
164 | void qmp_nbd_server_start(SocketAddressLegacy *addr, |
165 | bool has_tls_creds, const char *tls_creds, | |
00019455 | 166 | bool has_tls_authz, const char *tls_authz, |
1c8222b0 | 167 | bool has_max_connections, uint32_t max_connections, |
bd269ebc MA |
168 | Error **errp) |
169 | { | |
170 | SocketAddress *addr_flat = socket_address_flatten(addr); | |
171 | ||
1c8222b0 | 172 | nbd_server_start(addr_flat, tls_creds, tls_authz, max_connections, errp); |
bd269ebc MA |
173 | qapi_free_SocketAddress(addr_flat); |
174 | } | |
175 | ||
56ee8626 | 176 | BlockExport *nbd_export_create(BlockExportOptions *exp_args, Error **errp) |
6dd844db | 177 | { |
56ee8626 | 178 | BlockExportOptionsNbd *arg = &exp_args->u.nbd; |
094138d0 | 179 | BlockDriverState *bs = NULL; |
56ee8626 | 180 | NBDExport *exp = NULL; |
61bc846d | 181 | AioContext *aio_context; |
6dd844db | 182 | |
56ee8626 KW |
183 | assert(exp_args->type == BLOCK_EXPORT_TYPE_NBD); |
184 | ||
00917172 | 185 | if (!nbd_server && !is_qemu_nbd) { |
17b6be4a | 186 | error_setg(errp, "NBD server not running"); |
56ee8626 | 187 | return NULL; |
17b6be4a PB |
188 | } |
189 | ||
c62d24e9 KW |
190 | if (!arg->has_name) { |
191 | arg->name = arg->device; | |
902a1f94 VSO |
192 | } |
193 | ||
c62d24e9 KW |
194 | if (strlen(arg->name) > NBD_MAX_STRING_SIZE) { |
195 | error_setg(errp, "export name '%s' too long", arg->name); | |
56ee8626 | 196 | return NULL; |
93676c88 EB |
197 | } |
198 | ||
c62d24e9 KW |
199 | if (arg->description && strlen(arg->description) > NBD_MAX_STRING_SIZE) { |
200 | error_setg(errp, "description '%s' too long", arg->description); | |
56ee8626 | 201 | return NULL; |
deb6ccb0 EB |
202 | } |
203 | ||
c62d24e9 KW |
204 | if (nbd_export_find(arg->name)) { |
205 | error_setg(errp, "NBD server already has export named '%s'", arg->name); | |
56ee8626 | 206 | return NULL; |
6dd844db PB |
207 | } |
208 | ||
c62d24e9 | 209 | bs = bdrv_lookup_bs(arg->device, arg->device, errp); |
094138d0 | 210 | if (!bs) { |
56ee8626 | 211 | return NULL; |
60fe4fac | 212 | } |
6dd844db | 213 | |
61bc846d EB |
214 | aio_context = bdrv_get_aio_context(bs); |
215 | aio_context_acquire(aio_context); | |
7596bbb3 | 216 | |
c62d24e9 KW |
217 | if (!arg->has_writable) { |
218 | arg->writable = false; | |
e6444734 | 219 | } |
9b562c64 KW |
220 | if (bdrv_is_read_only(bs) && arg->writable) { |
221 | error_setg(errp, "Cannot export read-only node as writable"); | |
222 | goto out; | |
e6444734 PB |
223 | } |
224 | ||
fefee85d KW |
225 | if (!exp_args->has_writethrough) { |
226 | exp_args->writethrough = false; | |
227 | } | |
228 | ||
b57e4de0 | 229 | exp = nbd_export_new(bs, arg->name, arg->description, arg->bitmap, |
c62d24e9 | 230 | !arg->writable, !arg->writable, |
d794f7f3 | 231 | exp_args->writethrough, errp); |
98f44bbe | 232 | if (!exp) { |
61bc846d | 233 | goto out; |
98f44bbe | 234 | } |
6dd844db | 235 | |
741cc431 HR |
236 | /* The list of named exports has a strong reference to this export now and |
237 | * our only way of accessing it is through nbd_export_find(), so we can drop | |
238 | * the strong reference that is @exp. */ | |
c69de1be | 239 | blk_exp_unref((BlockExport*) exp); |
61bc846d EB |
240 | |
241 | out: | |
242 | aio_context_release(aio_context); | |
56ee8626 KW |
243 | /* TODO Remove the cast: nbd_export_new() will return a BlockExport. */ |
244 | return (BlockExport*) exp; | |
245 | } | |
246 | ||
247 | void qmp_nbd_server_add(BlockExportOptionsNbd *arg, Error **errp) | |
248 | { | |
9b562c64 KW |
249 | BlockExport *export; |
250 | BlockDriverState *bs; | |
251 | BlockBackend *on_eject_blk; | |
252 | BlockExportOptions export_opts; | |
253 | ||
254 | bs = bdrv_lookup_bs(arg->device, arg->device, errp); | |
255 | if (!bs) { | |
256 | return; | |
257 | } | |
258 | ||
259 | export_opts = (BlockExportOptions) { | |
56ee8626 KW |
260 | .type = BLOCK_EXPORT_TYPE_NBD, |
261 | .u.nbd = *arg, | |
262 | }; | |
9b562c64 KW |
263 | |
264 | /* | |
265 | * nbd-server-add doesn't complain when a read-only device should be | |
266 | * exported as writable, but simply downgrades it. This is an error with | |
267 | * block-export-add. | |
268 | */ | |
269 | if (bdrv_is_read_only(bs)) { | |
270 | export_opts.u.nbd.has_writable = true; | |
271 | export_opts.u.nbd.writable = false; | |
272 | } | |
273 | ||
274 | export = blk_exp_add(&export_opts, errp); | |
275 | if (!export) { | |
276 | return; | |
277 | } | |
278 | ||
279 | /* | |
280 | * nbd-server-add removes the export when the named BlockBackend used for | |
281 | * @device goes away. | |
282 | */ | |
283 | on_eject_blk = blk_by_name(arg->device); | |
284 | if (on_eject_blk) { | |
285 | nbd_export_set_on_eject_blk(export, on_eject_blk); | |
286 | } | |
6dd844db PB |
287 | } |
288 | ||
a3b0dc75 VSO |
289 | void qmp_nbd_server_remove(const char *name, |
290 | bool has_mode, NbdServerRemoveMode mode, | |
291 | Error **errp) | |
292 | { | |
293 | NBDExport *exp; | |
61bc846d | 294 | AioContext *aio_context; |
a3b0dc75 VSO |
295 | |
296 | if (!nbd_server) { | |
297 | error_setg(errp, "NBD server not running"); | |
298 | return; | |
299 | } | |
300 | ||
301 | exp = nbd_export_find(name); | |
302 | if (exp == NULL) { | |
303 | error_setg(errp, "Export '%s' is not found", name); | |
304 | return; | |
305 | } | |
306 | ||
307 | if (!has_mode) { | |
308 | mode = NBD_SERVER_REMOVE_MODE_SAFE; | |
309 | } | |
310 | ||
61bc846d EB |
311 | aio_context = nbd_export_aio_context(exp); |
312 | aio_context_acquire(aio_context); | |
a3b0dc75 | 313 | nbd_export_remove(exp, mode, errp); |
61bc846d | 314 | aio_context_release(aio_context); |
a3b0dc75 VSO |
315 | } |
316 | ||
6dd844db PB |
317 | void qmp_nbd_server_stop(Error **errp) |
318 | { | |
7801c3a7 EB |
319 | if (!nbd_server) { |
320 | error_setg(errp, "NBD server not running"); | |
321 | return; | |
322 | } | |
323 | ||
741cc431 | 324 | nbd_export_close_all(); |
6dd844db | 325 | |
ddffee39 DB |
326 | nbd_server_free(nbd_server); |
327 | nbd_server = NULL; | |
6dd844db | 328 | } |