]>
Commit | Line | Data |
---|---|---|
5276c87c VSO |
1 | /* |
2 | * QEMU Block driver for NBD | |
3 | * | |
4 | * Copyright (c) 2021 Virtuozzo International GmbH. | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | * THE SOFTWARE. | |
23 | */ | |
24 | ||
25 | #include "qemu/osdep.h" | |
8bb100c9 | 26 | #include "trace.h" |
5276c87c VSO |
27 | |
28 | #include "block/nbd.h" | |
29 | ||
30 | #include "qapi/qapi-visit-sockets.h" | |
31 | #include "qapi/clone-visitor.h" | |
32 | ||
33 | struct NBDClientConnection { | |
130d49ba | 34 | /* Initialization constants, never change */ |
5276c87c | 35 | SocketAddress *saddr; /* address to connect to */ |
130d49ba | 36 | QCryptoTLSCreds *tlscreds; |
046f98d0 | 37 | char *tlshostname; |
130d49ba VSO |
38 | NBDExportInfo initial_info; |
39 | bool do_negotiation; | |
e0e67cbe | 40 | bool do_retry; |
5276c87c VSO |
41 | |
42 | QemuMutex mutex; | |
43 | ||
169b9a94 | 44 | NBDExportInfo updated_info; |
5276c87c | 45 | /* |
169b9a94 VSO |
46 | * @sioc represents a successful result. While thread is running, @sioc is |
47 | * used only by thread and not protected by mutex. When thread is not | |
48 | * running, @sioc is stolen by nbd_co_establish_connection() under mutex. | |
5276c87c VSO |
49 | */ |
50 | QIOChannelSocket *sioc; | |
130d49ba | 51 | QIOChannel *ioc; |
169b9a94 VSO |
52 | /* |
53 | * @err represents previous attempt. It may be copied by | |
54 | * nbd_co_establish_connection() when it reports failure. | |
55 | */ | |
5276c87c VSO |
56 | Error *err; |
57 | ||
58 | /* All further fields are accessed only under mutex */ | |
59 | bool running; /* thread is running now */ | |
60 | bool detached; /* thread is detached and should cleanup the state */ | |
61 | ||
62 | /* | |
63 | * wait_co: if non-NULL, which coroutine to wake in | |
64 | * nbd_co_establish_connection() after yield() | |
65 | */ | |
66 | Coroutine *wait_co; | |
67 | }; | |
68 | ||
e0e67cbe VSO |
69 | /* |
70 | * The function isn't protected by any mutex, only call it when the client | |
71 | * connection attempt has not yet started. | |
72 | */ | |
73 | void nbd_client_connection_enable_retry(NBDClientConnection *conn) | |
74 | { | |
75 | conn->do_retry = true; | |
76 | } | |
77 | ||
130d49ba VSO |
78 | NBDClientConnection *nbd_client_connection_new(const SocketAddress *saddr, |
79 | bool do_negotiation, | |
80 | const char *export_name, | |
81 | const char *x_dirty_bitmap, | |
046f98d0 DB |
82 | QCryptoTLSCreds *tlscreds, |
83 | const char *tlshostname) | |
5276c87c VSO |
84 | { |
85 | NBDClientConnection *conn = g_new(NBDClientConnection, 1); | |
86 | ||
130d49ba | 87 | object_ref(OBJECT(tlscreds)); |
5276c87c VSO |
88 | *conn = (NBDClientConnection) { |
89 | .saddr = QAPI_CLONE(SocketAddress, saddr), | |
130d49ba | 90 | .tlscreds = tlscreds, |
046f98d0 | 91 | .tlshostname = g_strdup(tlshostname), |
130d49ba VSO |
92 | .do_negotiation = do_negotiation, |
93 | ||
94 | .initial_info.request_sizes = true, | |
95 | .initial_info.structured_reply = true, | |
96 | .initial_info.base_allocation = true, | |
97 | .initial_info.x_dirty_bitmap = g_strdup(x_dirty_bitmap), | |
98 | .initial_info.name = g_strdup(export_name ?: "") | |
5276c87c VSO |
99 | }; |
100 | ||
101 | qemu_mutex_init(&conn->mutex); | |
102 | ||
103 | return conn; | |
104 | } | |
105 | ||
106 | static void nbd_client_connection_do_free(NBDClientConnection *conn) | |
107 | { | |
108 | if (conn->sioc) { | |
109 | qio_channel_close(QIO_CHANNEL(conn->sioc), NULL); | |
110 | object_unref(OBJECT(conn->sioc)); | |
111 | } | |
112 | error_free(conn->err); | |
113 | qapi_free_SocketAddress(conn->saddr); | |
046f98d0 | 114 | g_free(conn->tlshostname); |
130d49ba VSO |
115 | object_unref(OBJECT(conn->tlscreds)); |
116 | g_free(conn->initial_info.x_dirty_bitmap); | |
117 | g_free(conn->initial_info.name); | |
5276c87c VSO |
118 | g_free(conn); |
119 | } | |
120 | ||
130d49ba VSO |
121 | /* |
122 | * Connect to @addr and do NBD negotiation if @info is not null. If @tlscreds | |
123 | * are given @outioc is returned. @outioc is provided only on success. The call | |
124 | * may be cancelled from other thread by simply qio_channel_shutdown(sioc). | |
125 | */ | |
126 | static int nbd_connect(QIOChannelSocket *sioc, SocketAddress *addr, | |
127 | NBDExportInfo *info, QCryptoTLSCreds *tlscreds, | |
046f98d0 | 128 | const char *tlshostname, |
130d49ba VSO |
129 | QIOChannel **outioc, Error **errp) |
130 | { | |
131 | int ret; | |
132 | ||
133 | if (outioc) { | |
134 | *outioc = NULL; | |
135 | } | |
136 | ||
137 | ret = qio_channel_socket_connect_sync(sioc, addr, errp); | |
138 | if (ret < 0) { | |
139 | return ret; | |
140 | } | |
141 | ||
142 | qio_channel_set_delay(QIO_CHANNEL(sioc), false); | |
143 | ||
144 | if (!info) { | |
145 | return 0; | |
146 | } | |
147 | ||
148 | ret = nbd_receive_negotiate(NULL, QIO_CHANNEL(sioc), tlscreds, | |
046f98d0 | 149 | tlshostname, |
130d49ba VSO |
150 | outioc, info, errp); |
151 | if (ret < 0) { | |
152 | /* | |
153 | * nbd_receive_negotiate() may setup tls ioc and return it even on | |
154 | * failure path. In this case we should use it instead of original | |
155 | * channel. | |
156 | */ | |
157 | if (outioc && *outioc) { | |
158 | qio_channel_close(QIO_CHANNEL(*outioc), NULL); | |
159 | object_unref(OBJECT(*outioc)); | |
160 | *outioc = NULL; | |
161 | } else { | |
162 | qio_channel_close(QIO_CHANNEL(sioc), NULL); | |
163 | } | |
164 | ||
165 | return ret; | |
166 | } | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
5276c87c VSO |
171 | static void *connect_thread_func(void *opaque) |
172 | { | |
173 | NBDClientConnection *conn = opaque; | |
174 | int ret; | |
175 | bool do_free; | |
e0e67cbe VSO |
176 | uint64_t timeout = 1; |
177 | uint64_t max_timeout = 16; | |
5276c87c | 178 | |
f58b2dfe VSO |
179 | qemu_mutex_lock(&conn->mutex); |
180 | while (!conn->detached) { | |
169b9a94 VSO |
181 | Error *local_err = NULL; |
182 | ||
f58b2dfe | 183 | assert(!conn->sioc); |
e0e67cbe | 184 | conn->sioc = qio_channel_socket_new(); |
5276c87c | 185 | |
f58b2dfe VSO |
186 | qemu_mutex_unlock(&conn->mutex); |
187 | ||
e0e67cbe | 188 | conn->updated_info = conn->initial_info; |
130d49ba | 189 | |
e0e67cbe VSO |
190 | ret = nbd_connect(conn->sioc, conn->saddr, |
191 | conn->do_negotiation ? &conn->updated_info : NULL, | |
046f98d0 DB |
192 | conn->tlscreds, conn->tlshostname, |
193 | &conn->ioc, &local_err); | |
5276c87c | 194 | |
e0e67cbe VSO |
195 | /* |
196 | * conn->updated_info will finally be returned to the user. Clear the | |
197 | * pointers to our internally allocated strings, which are IN parameters | |
198 | * of nbd_receive_negotiate() and therefore nbd_connect(). Caller | |
199 | * shoudn't be interested in these fields. | |
200 | */ | |
201 | conn->updated_info.x_dirty_bitmap = NULL; | |
202 | conn->updated_info.name = NULL; | |
203 | ||
f58b2dfe VSO |
204 | qemu_mutex_lock(&conn->mutex); |
205 | ||
169b9a94 VSO |
206 | error_free(conn->err); |
207 | conn->err = NULL; | |
208 | error_propagate(&conn->err, local_err); | |
209 | ||
e0e67cbe VSO |
210 | if (ret < 0) { |
211 | object_unref(OBJECT(conn->sioc)); | |
212 | conn->sioc = NULL; | |
f58b2dfe | 213 | if (conn->do_retry && !conn->detached) { |
8bb100c9 | 214 | trace_nbd_connect_thread_sleep(timeout); |
f58b2dfe VSO |
215 | qemu_mutex_unlock(&conn->mutex); |
216 | ||
e0e67cbe VSO |
217 | sleep(timeout); |
218 | if (timeout < max_timeout) { | |
219 | timeout *= 2; | |
220 | } | |
f58b2dfe VSO |
221 | |
222 | qemu_mutex_lock(&conn->mutex); | |
e0e67cbe VSO |
223 | continue; |
224 | } | |
225 | } | |
226 | ||
227 | break; | |
228 | } | |
5276c87c | 229 | |
f58b2dfe | 230 | /* mutex is locked */ |
5276c87c VSO |
231 | |
232 | assert(conn->running); | |
233 | conn->running = false; | |
234 | if (conn->wait_co) { | |
235 | aio_co_wake(conn->wait_co); | |
236 | conn->wait_co = NULL; | |
237 | } | |
238 | do_free = conn->detached; | |
239 | ||
240 | qemu_mutex_unlock(&conn->mutex); | |
241 | ||
242 | if (do_free) { | |
243 | nbd_client_connection_do_free(conn); | |
244 | } | |
245 | ||
246 | return NULL; | |
247 | } | |
248 | ||
249 | void nbd_client_connection_release(NBDClientConnection *conn) | |
250 | { | |
251 | bool do_free = false; | |
252 | ||
253 | if (!conn) { | |
254 | return; | |
255 | } | |
256 | ||
e70da5ff VSO |
257 | WITH_QEMU_LOCK_GUARD(&conn->mutex) { |
258 | assert(!conn->detached); | |
259 | if (conn->running) { | |
260 | conn->detached = true; | |
261 | } else { | |
262 | do_free = true; | |
263 | } | |
f58b2dfe VSO |
264 | if (conn->sioc) { |
265 | qio_channel_shutdown(QIO_CHANNEL(conn->sioc), | |
266 | QIO_CHANNEL_SHUTDOWN_BOTH, NULL); | |
267 | } | |
5276c87c | 268 | } |
5276c87c VSO |
269 | |
270 | if (do_free) { | |
271 | nbd_client_connection_do_free(conn); | |
272 | } | |
273 | } | |
274 | ||
275 | /* | |
276 | * Get a new connection in context of @conn: | |
277 | * if the thread is running, wait for completion | |
278 | * if the thread already succeeded in the background, and user didn't get the | |
279 | * result, just return it now | |
280 | * otherwise the thread is not running, so start a thread and wait for | |
281 | * completion | |
130d49ba | 282 | * |
97cf8925 VSO |
283 | * If @blocking is false, don't wait for the thread, return immediately. |
284 | * | |
130d49ba VSO |
285 | * If @info is not NULL, also do nbd-negotiation after successful connection. |
286 | * In this case info is used only as out parameter, and is fully initialized by | |
287 | * nbd_co_establish_connection(). "IN" fields of info as well as related only to | |
288 | * nbd_receive_export_list() would be zero (see description of NBDExportInfo in | |
289 | * include/block/nbd.h). | |
5276c87c | 290 | */ |
43cb34de | 291 | QIOChannel *coroutine_fn |
130d49ba | 292 | nbd_co_establish_connection(NBDClientConnection *conn, NBDExportInfo *info, |
97cf8925 | 293 | bool blocking, Error **errp) |
5276c87c | 294 | { |
5276c87c VSO |
295 | QemuThread thread; |
296 | ||
130d49ba VSO |
297 | if (conn->do_negotiation) { |
298 | assert(info); | |
130d49ba VSO |
299 | } |
300 | ||
e70da5ff VSO |
301 | WITH_QEMU_LOCK_GUARD(&conn->mutex) { |
302 | /* | |
303 | * Don't call nbd_co_establish_connection() in several coroutines in | |
304 | * parallel. Only one call at once is supported. | |
305 | */ | |
306 | assert(!conn->wait_co); | |
307 | ||
308 | if (!conn->running) { | |
309 | if (conn->sioc) { | |
310 | /* Previous attempt finally succeeded in background */ | |
130d49ba | 311 | if (conn->do_negotiation) { |
130d49ba | 312 | memcpy(info, &conn->updated_info, sizeof(*info)); |
43cb34de VSO |
313 | if (conn->ioc) { |
314 | /* TLS channel now has own reference to parent */ | |
315 | object_unref(OBJECT(conn->sioc)); | |
316 | conn->sioc = NULL; | |
317 | ||
318 | return g_steal_pointer(&conn->ioc); | |
319 | } | |
130d49ba | 320 | } |
43cb34de VSO |
321 | |
322 | assert(!conn->ioc); | |
323 | ||
324 | return QIO_CHANNEL(g_steal_pointer(&conn->sioc)); | |
e70da5ff VSO |
325 | } |
326 | ||
327 | conn->running = true; | |
e70da5ff VSO |
328 | qemu_thread_create(&thread, "nbd-connect", |
329 | connect_thread_func, conn, QEMU_THREAD_DETACHED); | |
5276c87c VSO |
330 | } |
331 | ||
97cf8925 | 332 | if (!blocking) { |
169b9a94 VSO |
333 | if (conn->err) { |
334 | error_propagate(errp, error_copy(conn->err)); | |
335 | } else { | |
336 | error_setg(errp, "No connection at the moment"); | |
337 | } | |
338 | ||
97cf8925 VSO |
339 | return NULL; |
340 | } | |
341 | ||
e70da5ff | 342 | conn->wait_co = qemu_coroutine_self(); |
5276c87c VSO |
343 | } |
344 | ||
5276c87c VSO |
345 | /* |
346 | * We are going to wait for connect-thread finish, but | |
347 | * nbd_co_establish_connection_cancel() can interrupt. | |
348 | */ | |
349 | qemu_coroutine_yield(); | |
350 | ||
e70da5ff VSO |
351 | WITH_QEMU_LOCK_GUARD(&conn->mutex) { |
352 | if (conn->running) { | |
353 | /* | |
354 | * The connection attempt was canceled and the coroutine resumed | |
355 | * before the connection thread finished its job. Report the | |
356 | * attempt as failed, but leave the connection thread running, | |
357 | * to reuse it for the next connection attempt. | |
358 | */ | |
169b9a94 VSO |
359 | if (conn->err) { |
360 | error_propagate(errp, error_copy(conn->err)); | |
361 | } else { | |
9e14491a VSO |
362 | /* |
363 | * The only possible case here is cancelling by open_timer | |
364 | * during nbd_open(). So, the error message is for that case. | |
365 | * If we have more use cases, we can refactor | |
366 | * nbd_co_establish_connection_cancel() to take an additional | |
367 | * parameter cancel_reason, that would be passed than to the | |
368 | * caller of cancelled nbd_co_establish_connection(). | |
369 | */ | |
370 | error_setg(errp, "Connection attempt cancelled by timeout"); | |
169b9a94 VSO |
371 | } |
372 | ||
e70da5ff VSO |
373 | return NULL; |
374 | } else { | |
169b9a94 VSO |
375 | /* Thread finished. There must be either error or sioc */ |
376 | assert(!conn->err != !conn->sioc); | |
377 | ||
378 | if (conn->err) { | |
379 | error_propagate(errp, error_copy(conn->err)); | |
43cb34de VSO |
380 | return NULL; |
381 | } | |
169b9a94 | 382 | |
43cb34de | 383 | if (conn->do_negotiation) { |
130d49ba | 384 | memcpy(info, &conn->updated_info, sizeof(*info)); |
43cb34de VSO |
385 | if (conn->ioc) { |
386 | /* TLS channel now has own reference to parent */ | |
387 | object_unref(OBJECT(conn->sioc)); | |
388 | conn->sioc = NULL; | |
389 | ||
390 | return g_steal_pointer(&conn->ioc); | |
391 | } | |
130d49ba | 392 | } |
43cb34de VSO |
393 | |
394 | assert(!conn->ioc); | |
395 | ||
396 | return QIO_CHANNEL(g_steal_pointer(&conn->sioc)); | |
e70da5ff | 397 | } |
5276c87c VSO |
398 | } |
399 | ||
e70da5ff | 400 | abort(); /* unreachable */ |
5276c87c VSO |
401 | } |
402 | ||
403 | /* | |
404 | * nbd_co_establish_connection_cancel | |
405 | * Cancel nbd_co_establish_connection() asynchronously. | |
406 | * | |
407 | * Note that this function neither directly stops the thread nor closes the | |
408 | * socket, but rather safely wakes nbd_co_establish_connection() which is | |
409 | * sleeping in yield() | |
410 | */ | |
411 | void nbd_co_establish_connection_cancel(NBDClientConnection *conn) | |
412 | { | |
413 | Coroutine *wait_co; | |
414 | ||
e70da5ff VSO |
415 | WITH_QEMU_LOCK_GUARD(&conn->mutex) { |
416 | wait_co = g_steal_pointer(&conn->wait_co); | |
417 | } | |
5276c87c VSO |
418 | |
419 | if (wait_co) { | |
420 | aio_co_wake(wait_co); | |
421 | } | |
422 | } |