]>
Commit | Line | Data |
---|---|---|
2302c1ca MAL |
1 | /* |
2 | * QEMU Block driver for NBD | |
3 | * | |
b626b51a | 4 | * Copyright (C) 2016 Red Hat, Inc. |
2302c1ca MAL |
5 | * Copyright (C) 2008 Bull S.A.S. |
6 | * Author: Laurent Vivier <[email protected]> | |
7 | * | |
8 | * Some parts: | |
9 | * Copyright (C) 2007 Anthony Liguori <[email protected]> | |
10 | * | |
11 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
12 | * of this software and associated documentation files (the "Software"), to deal | |
13 | * in the Software without restriction, including without limitation the rights | |
14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
15 | * copies of the Software, and to permit persons to whom the Software is | |
16 | * furnished to do so, subject to the following conditions: | |
17 | * | |
18 | * The above copyright notice and this permission notice shall be included in | |
19 | * all copies or substantial portions of the Software. | |
20 | * | |
21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
24 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
27 | * THE SOFTWARE. | |
28 | */ | |
29 | ||
80c71a24 | 30 | #include "qemu/osdep.h" |
2302c1ca | 31 | #include "nbd-client.h" |
2302c1ca MAL |
32 | |
33 | #define HANDLE_TO_INDEX(bs, handle) ((handle) ^ ((uint64_t)(intptr_t)bs)) | |
34 | #define INDEX_TO_HANDLE(bs, index) ((index) ^ ((uint64_t)(intptr_t)bs)) | |
35 | ||
69152c09 MAL |
36 | static void nbd_recv_coroutines_enter_all(NbdClientSession *s) |
37 | { | |
38 | int i; | |
39 | ||
40 | for (i = 0; i < MAX_NBD_REQUESTS; i++) { | |
41 | if (s->recv_coroutine[i]) { | |
0b8b8753 | 42 | qemu_coroutine_enter(s->recv_coroutine[i]); |
69152c09 MAL |
43 | } |
44 | } | |
45 | } | |
46 | ||
f53a829b | 47 | static void nbd_teardown_connection(BlockDriverState *bs) |
4a41a2d6 | 48 | { |
f53a829b HR |
49 | NbdClientSession *client = nbd_get_client_session(bs); |
50 | ||
064097d9 DB |
51 | if (!client->ioc) { /* Already closed */ |
52 | return; | |
53 | } | |
54 | ||
4a41a2d6 | 55 | /* finish any pending coroutines */ |
064097d9 DB |
56 | qio_channel_shutdown(client->ioc, |
57 | QIO_CHANNEL_SHUTDOWN_BOTH, | |
58 | NULL); | |
4a41a2d6 SH |
59 | nbd_recv_coroutines_enter_all(client); |
60 | ||
f53a829b | 61 | nbd_client_detach_aio_context(bs); |
064097d9 DB |
62 | object_unref(OBJECT(client->sioc)); |
63 | client->sioc = NULL; | |
64 | object_unref(OBJECT(client->ioc)); | |
65 | client->ioc = NULL; | |
4a41a2d6 SH |
66 | } |
67 | ||
2302c1ca MAL |
68 | static void nbd_reply_ready(void *opaque) |
69 | { | |
f53a829b HR |
70 | BlockDriverState *bs = opaque; |
71 | NbdClientSession *s = nbd_get_client_session(bs); | |
2302c1ca MAL |
72 | uint64_t i; |
73 | int ret; | |
74 | ||
064097d9 DB |
75 | if (!s->ioc) { /* Already closed */ |
76 | return; | |
77 | } | |
78 | ||
2302c1ca MAL |
79 | if (s->reply.handle == 0) { |
80 | /* No reply already in flight. Fetch a header. It is possible | |
81 | * that another thread has done the same thing in parallel, so | |
82 | * the socket is not readable anymore. | |
83 | */ | |
1c778ef7 | 84 | ret = nbd_receive_reply(s->ioc, &s->reply); |
2302c1ca MAL |
85 | if (ret == -EAGAIN) { |
86 | return; | |
87 | } | |
88 | if (ret < 0) { | |
89 | s->reply.handle = 0; | |
90 | goto fail; | |
91 | } | |
92 | } | |
93 | ||
94 | /* There's no need for a mutex on the receive side, because the | |
95 | * handler acts as a synchronization point and ensures that only | |
96 | * one coroutine is called until the reply finishes. */ | |
97 | i = HANDLE_TO_INDEX(s, s->reply.handle); | |
98 | if (i >= MAX_NBD_REQUESTS) { | |
99 | goto fail; | |
100 | } | |
101 | ||
102 | if (s->recv_coroutine[i]) { | |
0b8b8753 | 103 | qemu_coroutine_enter(s->recv_coroutine[i]); |
2302c1ca MAL |
104 | return; |
105 | } | |
106 | ||
107 | fail: | |
f53a829b | 108 | nbd_teardown_connection(bs); |
2302c1ca MAL |
109 | } |
110 | ||
111 | static void nbd_restart_write(void *opaque) | |
112 | { | |
f53a829b | 113 | BlockDriverState *bs = opaque; |
2302c1ca | 114 | |
0b8b8753 | 115 | qemu_coroutine_enter(nbd_get_client_session(bs)->send_coroutine); |
2302c1ca MAL |
116 | } |
117 | ||
f53a829b HR |
118 | static int nbd_co_send_request(BlockDriverState *bs, |
119 | struct nbd_request *request, | |
1e2a77a8 | 120 | QEMUIOVector *qiov) |
2302c1ca | 121 | { |
f53a829b | 122 | NbdClientSession *s = nbd_get_client_session(bs); |
69447cd8 | 123 | AioContext *aio_context; |
141cabe6 | 124 | int rc, ret, i; |
2302c1ca MAL |
125 | |
126 | qemu_co_mutex_lock(&s->send_mutex); | |
141cabe6 BW |
127 | |
128 | for (i = 0; i < MAX_NBD_REQUESTS; i++) { | |
129 | if (s->recv_coroutine[i] == NULL) { | |
130 | s->recv_coroutine[i] = qemu_coroutine_self(); | |
131 | break; | |
132 | } | |
133 | } | |
134 | ||
1c778ef7 | 135 | g_assert(qemu_in_coroutine()); |
141cabe6 BW |
136 | assert(i < MAX_NBD_REQUESTS); |
137 | request->handle = INDEX_TO_HANDLE(s, i); | |
064097d9 DB |
138 | |
139 | if (!s->ioc) { | |
140 | qemu_co_mutex_unlock(&s->send_mutex); | |
141 | return -EPIPE; | |
142 | } | |
143 | ||
2302c1ca | 144 | s->send_coroutine = qemu_coroutine_self(); |
f53a829b | 145 | aio_context = bdrv_get_aio_context(bs); |
141cabe6 | 146 | |
064097d9 | 147 | aio_set_fd_handler(aio_context, s->sioc->fd, false, |
f53a829b | 148 | nbd_reply_ready, nbd_restart_write, bs); |
2302c1ca | 149 | if (qiov) { |
064097d9 | 150 | qio_channel_set_cork(s->ioc, true); |
1c778ef7 | 151 | rc = nbd_send_request(s->ioc, request); |
2302c1ca | 152 | if (rc >= 0) { |
1e2a77a8 EB |
153 | ret = nbd_wr_syncv(s->ioc, qiov->iov, qiov->niov, request->len, |
154 | false); | |
2302c1ca MAL |
155 | if (ret != request->len) { |
156 | rc = -EIO; | |
157 | } | |
158 | } | |
064097d9 | 159 | qio_channel_set_cork(s->ioc, false); |
2302c1ca | 160 | } else { |
1c778ef7 | 161 | rc = nbd_send_request(s->ioc, request); |
2302c1ca | 162 | } |
064097d9 | 163 | aio_set_fd_handler(aio_context, s->sioc->fd, false, |
dca21ef2 | 164 | nbd_reply_ready, NULL, bs); |
2302c1ca MAL |
165 | s->send_coroutine = NULL; |
166 | qemu_co_mutex_unlock(&s->send_mutex); | |
167 | return rc; | |
168 | } | |
169 | ||
170 | static void nbd_co_receive_reply(NbdClientSession *s, | |
1e2a77a8 EB |
171 | struct nbd_request *request, |
172 | struct nbd_reply *reply, | |
173 | QEMUIOVector *qiov) | |
2302c1ca MAL |
174 | { |
175 | int ret; | |
176 | ||
177 | /* Wait until we're woken up by the read handler. TODO: perhaps | |
178 | * peek at the next reply and avoid yielding if it's ours? */ | |
179 | qemu_coroutine_yield(); | |
180 | *reply = s->reply; | |
064097d9 DB |
181 | if (reply->handle != request->handle || |
182 | !s->ioc) { | |
2302c1ca MAL |
183 | reply->error = EIO; |
184 | } else { | |
185 | if (qiov && reply->error == 0) { | |
1e2a77a8 EB |
186 | ret = nbd_wr_syncv(s->ioc, qiov->iov, qiov->niov, request->len, |
187 | true); | |
2302c1ca MAL |
188 | if (ret != request->len) { |
189 | reply->error = EIO; | |
190 | } | |
191 | } | |
192 | ||
193 | /* Tell the read handler to read another header. */ | |
194 | s->reply.handle = 0; | |
195 | } | |
196 | } | |
197 | ||
198 | static void nbd_coroutine_start(NbdClientSession *s, | |
199 | struct nbd_request *request) | |
200 | { | |
2302c1ca MAL |
201 | /* Poor man semaphore. The free_sema is locked when no other request |
202 | * can be accepted, and unlocked after receiving one reply. */ | |
9bc9732f CX |
203 | if (s->in_flight == MAX_NBD_REQUESTS) { |
204 | qemu_co_queue_wait(&s->free_sema); | |
2302c1ca MAL |
205 | assert(s->in_flight < MAX_NBD_REQUESTS); |
206 | } | |
207 | s->in_flight++; | |
208 | ||
141cabe6 | 209 | /* s->recv_coroutine[i] is set as soon as we get the send_lock. */ |
2302c1ca MAL |
210 | } |
211 | ||
212 | static void nbd_coroutine_end(NbdClientSession *s, | |
213 | struct nbd_request *request) | |
214 | { | |
215 | int i = HANDLE_TO_INDEX(s, request->handle); | |
216 | s->recv_coroutine[i] = NULL; | |
217 | if (s->in_flight-- == MAX_NBD_REQUESTS) { | |
9bc9732f | 218 | qemu_co_queue_next(&s->free_sema); |
2302c1ca MAL |
219 | } |
220 | } | |
221 | ||
70c4fb26 EB |
222 | int nbd_client_co_preadv(BlockDriverState *bs, uint64_t offset, |
223 | uint64_t bytes, QEMUIOVector *qiov, int flags) | |
2302c1ca | 224 | { |
f53a829b | 225 | NbdClientSession *client = nbd_get_client_session(bs); |
70c4fb26 EB |
226 | struct nbd_request request = { |
227 | .type = NBD_CMD_READ, | |
228 | .from = offset, | |
229 | .len = bytes, | |
230 | }; | |
2302c1ca MAL |
231 | struct nbd_reply reply; |
232 | ssize_t ret; | |
233 | ||
70c4fb26 EB |
234 | assert(bytes <= NBD_MAX_BUFFER_SIZE); |
235 | assert(!flags); | |
2302c1ca MAL |
236 | |
237 | nbd_coroutine_start(client, &request); | |
1e2a77a8 | 238 | ret = nbd_co_send_request(bs, &request, NULL); |
2302c1ca MAL |
239 | if (ret < 0) { |
240 | reply.error = -ret; | |
241 | } else { | |
1e2a77a8 | 242 | nbd_co_receive_reply(client, &request, &reply, qiov); |
2302c1ca MAL |
243 | } |
244 | nbd_coroutine_end(client, &request); | |
245 | return -reply.error; | |
2302c1ca MAL |
246 | } |
247 | ||
70c4fb26 EB |
248 | int nbd_client_co_pwritev(BlockDriverState *bs, uint64_t offset, |
249 | uint64_t bytes, QEMUIOVector *qiov, int flags) | |
2302c1ca | 250 | { |
f53a829b | 251 | NbdClientSession *client = nbd_get_client_session(bs); |
70c4fb26 EB |
252 | struct nbd_request request = { |
253 | .type = NBD_CMD_WRITE, | |
254 | .from = offset, | |
255 | .len = bytes, | |
256 | }; | |
2302c1ca MAL |
257 | struct nbd_reply reply; |
258 | ssize_t ret; | |
259 | ||
52a46505 EB |
260 | if (flags & BDRV_REQ_FUA) { |
261 | assert(client->nbdflags & NBD_FLAG_SEND_FUA); | |
b626b51a | 262 | request.flags |= NBD_CMD_FLAG_FUA; |
2302c1ca MAL |
263 | } |
264 | ||
70c4fb26 | 265 | assert(bytes <= NBD_MAX_BUFFER_SIZE); |
2302c1ca MAL |
266 | |
267 | nbd_coroutine_start(client, &request); | |
1e2a77a8 | 268 | ret = nbd_co_send_request(bs, &request, qiov); |
2302c1ca MAL |
269 | if (ret < 0) { |
270 | reply.error = -ret; | |
271 | } else { | |
1e2a77a8 | 272 | nbd_co_receive_reply(client, &request, &reply, NULL); |
2302c1ca MAL |
273 | } |
274 | nbd_coroutine_end(client, &request); | |
275 | return -reply.error; | |
276 | } | |
277 | ||
f53a829b | 278 | int nbd_client_co_flush(BlockDriverState *bs) |
2302c1ca | 279 | { |
f53a829b | 280 | NbdClientSession *client = nbd_get_client_session(bs); |
b1b27b64 | 281 | struct nbd_request request = { .type = NBD_CMD_FLUSH }; |
2302c1ca MAL |
282 | struct nbd_reply reply; |
283 | ssize_t ret; | |
284 | ||
285 | if (!(client->nbdflags & NBD_FLAG_SEND_FLUSH)) { | |
286 | return 0; | |
287 | } | |
288 | ||
2302c1ca MAL |
289 | request.from = 0; |
290 | request.len = 0; | |
291 | ||
292 | nbd_coroutine_start(client, &request); | |
1e2a77a8 | 293 | ret = nbd_co_send_request(bs, &request, NULL); |
2302c1ca MAL |
294 | if (ret < 0) { |
295 | reply.error = -ret; | |
296 | } else { | |
1e2a77a8 | 297 | nbd_co_receive_reply(client, &request, &reply, NULL); |
2302c1ca MAL |
298 | } |
299 | nbd_coroutine_end(client, &request); | |
300 | return -reply.error; | |
301 | } | |
302 | ||
447e57c3 | 303 | int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int count) |
2302c1ca | 304 | { |
f53a829b | 305 | NbdClientSession *client = nbd_get_client_session(bs); |
447e57c3 EB |
306 | struct nbd_request request = { |
307 | .type = NBD_CMD_TRIM, | |
308 | .from = offset, | |
309 | .len = count, | |
310 | }; | |
2302c1ca MAL |
311 | struct nbd_reply reply; |
312 | ssize_t ret; | |
313 | ||
314 | if (!(client->nbdflags & NBD_FLAG_SEND_TRIM)) { | |
315 | return 0; | |
316 | } | |
2302c1ca MAL |
317 | |
318 | nbd_coroutine_start(client, &request); | |
1e2a77a8 | 319 | ret = nbd_co_send_request(bs, &request, NULL); |
2302c1ca MAL |
320 | if (ret < 0) { |
321 | reply.error = -ret; | |
322 | } else { | |
1e2a77a8 | 323 | nbd_co_receive_reply(client, &request, &reply, NULL); |
2302c1ca MAL |
324 | } |
325 | nbd_coroutine_end(client, &request); | |
326 | return -reply.error; | |
327 | ||
328 | } | |
329 | ||
f53a829b | 330 | void nbd_client_detach_aio_context(BlockDriverState *bs) |
69447cd8 | 331 | { |
f53a829b | 332 | aio_set_fd_handler(bdrv_get_aio_context(bs), |
064097d9 | 333 | nbd_get_client_session(bs)->sioc->fd, |
dca21ef2 | 334 | false, NULL, NULL, NULL); |
69447cd8 SH |
335 | } |
336 | ||
f53a829b HR |
337 | void nbd_client_attach_aio_context(BlockDriverState *bs, |
338 | AioContext *new_context) | |
69447cd8 | 339 | { |
064097d9 | 340 | aio_set_fd_handler(new_context, nbd_get_client_session(bs)->sioc->fd, |
dca21ef2 | 341 | false, nbd_reply_ready, NULL, bs); |
69447cd8 SH |
342 | } |
343 | ||
f53a829b | 344 | void nbd_client_close(BlockDriverState *bs) |
2302c1ca | 345 | { |
f53a829b | 346 | NbdClientSession *client = nbd_get_client_session(bs); |
b626b51a | 347 | struct nbd_request request = { .type = NBD_CMD_DISC }; |
2302c1ca | 348 | |
064097d9 | 349 | if (client->ioc == NULL) { |
4a41a2d6 SH |
350 | return; |
351 | } | |
352 | ||
1c778ef7 | 353 | nbd_send_request(client->ioc, &request); |
5ad283eb | 354 | |
f53a829b | 355 | nbd_teardown_connection(bs); |
2302c1ca MAL |
356 | } |
357 | ||
75822a12 DB |
358 | int nbd_client_init(BlockDriverState *bs, |
359 | QIOChannelSocket *sioc, | |
360 | const char *export, | |
361 | QCryptoTLSCreds *tlscreds, | |
362 | const char *hostname, | |
363 | Error **errp) | |
2302c1ca | 364 | { |
f53a829b | 365 | NbdClientSession *client = nbd_get_client_session(bs); |
2302c1ca MAL |
366 | int ret; |
367 | ||
368 | /* NBD handshake */ | |
e2bc625f | 369 | logout("session init %s\n", export); |
064097d9 DB |
370 | qio_channel_set_blocking(QIO_CHANNEL(sioc), true, NULL); |
371 | ||
1c778ef7 | 372 | ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export, |
f95910fe | 373 | &client->nbdflags, |
75822a12 | 374 | tlscreds, hostname, |
f95910fe DB |
375 | &client->ioc, |
376 | &client->size, errp); | |
2302c1ca MAL |
377 | if (ret < 0) { |
378 | logout("Failed to negotiate with the NBD server\n"); | |
2302c1ca MAL |
379 | return ret; |
380 | } | |
4df863f3 EB |
381 | if (client->nbdflags & NBD_FLAG_SEND_FUA) { |
382 | bs->supported_write_flags = BDRV_REQ_FUA; | |
383 | } | |
2302c1ca MAL |
384 | |
385 | qemu_co_mutex_init(&client->send_mutex); | |
9bc9732f | 386 | qemu_co_queue_init(&client->free_sema); |
064097d9 DB |
387 | client->sioc = sioc; |
388 | object_ref(OBJECT(client->sioc)); | |
f95910fe DB |
389 | |
390 | if (!client->ioc) { | |
391 | client->ioc = QIO_CHANNEL(sioc); | |
392 | object_ref(OBJECT(client->ioc)); | |
393 | } | |
2302c1ca MAL |
394 | |
395 | /* Now that we're connected, set the socket to be non-blocking and | |
396 | * kick the reply mechanism. */ | |
064097d9 DB |
397 | qio_channel_set_blocking(QIO_CHANNEL(sioc), false, NULL); |
398 | ||
f53a829b | 399 | nbd_client_attach_aio_context(bs, bdrv_get_aio_context(bs)); |
2302c1ca MAL |
400 | |
401 | logout("Established connection with NBD server\n"); | |
402 | return 0; | |
403 | } |