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