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