docs: Fix typo in nbdkit-protocol(1)
[nbdkit.git] / filters / multi-conn / multi-conn.c
blob99ae51c6895c04d0e0dc2333412aa9c7f12c3ddc
1 /* nbdkit
2 * Copyright (C) 2021-2022 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdint.h>
38 #include <string.h>
39 #include <stdbool.h>
40 #include <assert.h>
41 #include <pthread.h>
43 #include <nbdkit-filter.h>
45 #include "cleanup.h"
46 #include "vector.h"
48 /* Track results of .config */
49 static enum MultiConnMode {
50 AUTO,
51 EMULATE,
52 PLUGIN,
53 UNSAFE,
54 DISABLE,
55 } mode;
57 static enum TrackDirtyMode {
58 CONN,
59 FAST,
60 OFF,
61 } track;
63 static bool byname = false;
65 enum dirty {
66 WRITE = 1, /* A write may have populated a cache */
67 READ = 2, /* A read may have populated a cache */
70 /* Coordination between connections. */
71 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
73 /* The list of handles to active connections. */
74 struct handle {
75 nbdkit_next *next;
76 enum MultiConnMode mode; /* Runtime resolution of mode==AUTO */
77 enum dirty dirty; /* What aspects of this connection are dirty */
78 char *name; /* Used when byname==true to assign group */
79 struct group *group; /* All connections grouped with this one */
81 DEFINE_VECTOR_TYPE(conns_vector, struct handle *);
82 struct group {
83 conns_vector conns;
84 char *name;
85 bool dirty; /* True if any connection in group is dirty */
87 DEFINE_VECTOR_TYPE(group_vector, struct group *);
88 static group_vector groups = empty_vector;
90 /* Accept 'multi-conn-mode=mode', 'multi-conn-track-dirty=level', and
91 * 'multi-conn-exportname=bool'.
93 static int
94 multi_conn_config (nbdkit_next_config *next, nbdkit_backend *nxdata,
95 const char *key, const char *value)
97 if (strcmp (key, "multi-conn-mode") == 0) {
98 if (strcmp (value, "auto") == 0)
99 mode = AUTO;
100 else if (strcmp (value, "emulate") == 0)
101 mode = EMULATE;
102 else if (strcmp (value, "plugin") == 0)
103 mode = PLUGIN;
104 else if (strcmp (value, "disable") == 0)
105 mode = DISABLE;
106 else if (strcmp (value, "unsafe") == 0)
107 mode = UNSAFE;
108 else {
109 nbdkit_error ("unknown multi-conn mode '%s'", value);
110 return -1;
112 return 0;
114 else if (strcmp (key, "multi-conn-track-dirty") == 0) {
115 if (strcmp (value, "connection") == 0 ||
116 strcmp (value, "conn") == 0)
117 track = CONN;
118 else if (strcmp (value, "fast") == 0)
119 track = FAST;
120 else if (strcmp (value, "off") == 0)
121 track = OFF;
122 else {
123 nbdkit_error ("unknown multi-conn track-dirty setting '%s'", value);
124 return -1;
126 return 0;
128 else if (strcmp (key, "multi-conn-exportname") == 0 ||
129 strcmp (key, "multi-conn-export-name") == 0) {
130 int r;
132 r = nbdkit_parse_bool (value);
133 if (r == -1)
134 return -1;
135 byname = r;
136 return 0;
138 return next (nxdata, key, value);
141 #define multi_conn_config_help \
142 "multi-conn-mode=<MODE> 'auto' (default), 'emulate', 'plugin',\n" \
143 " 'disable', or 'unsafe'.\n" \
144 "multi-conn-track-dirty=<LEVEL> 'conn' (default), 'fast', or 'off'.\n" \
145 "multi-conn-exportname=<BOOL> true to limit emulation by export name.\n"
147 static int
148 multi_conn_get_ready (int thread_model)
150 if (thread_model == NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS &&
151 mode == AUTO)
152 mode = DISABLE;
153 return 0;
156 static void
157 multi_conn_unload (void)
159 assert (groups.len == 0);
160 group_vector_reset (&groups);
163 static void *
164 multi_conn_open (nbdkit_next_open *next, nbdkit_context *nxdata,
165 int readonly, const char *exportname, int is_tls)
167 struct handle *h;
169 if (next (nxdata, readonly, exportname) == -1)
170 return NULL;
172 /* Allocate here, but populate and insert into list in .prepare */
173 h = calloc (1, sizeof *h);
174 if (h == NULL) {
175 nbdkit_error ("calloc: %m");
176 return NULL;
178 if (byname) {
179 h->name = strdup (exportname);
180 if (h->name == NULL) {
181 nbdkit_error ("strdup: %m");
182 free (h);
183 return NULL;
186 return h;
189 static int
190 multi_conn_prepare (nbdkit_next *next, void *handle, int readonly)
192 struct handle *h = handle;
193 struct group *g;
194 bool new_group = false;
195 int r;
196 size_t i;
198 h->next = next;
199 if (mode == AUTO) { /* See also .get_ready turning AUTO into DISABLE */
200 r = next->can_multi_conn (next);
201 if (r == -1)
202 return -1;
203 if (r == 0)
204 h->mode = EMULATE;
205 else
206 h->mode = PLUGIN;
208 else
209 h->mode = mode;
210 if (h->mode == EMULATE && next->can_flush (next) != 1) {
211 nbdkit_error ("emulating multi-conn requires working flush");
212 return -1;
215 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
216 if (byname) {
217 g = NULL;
218 for (i = 0; i < groups.len; i++)
219 if (strcmp (groups.ptr[i]->name, h->name) == 0) {
220 g = groups.ptr[i];
221 break;
224 else
225 g = groups.len ? groups.ptr[0] : NULL;
227 if (!g) {
228 g = calloc (1, sizeof *g);
229 if (g == NULL) {
230 nbdkit_error ("calloc: %m");
231 return -1;
233 if (group_vector_append (&groups, g) == -1) {
234 free (g);
235 return -1;
237 g->name = h->name;
238 h->name = NULL;
239 new_group = true;
241 if (conns_vector_append (&g->conns, h) == -1) {
242 if (new_group) {
243 group_vector_remove (&groups, groups.len - 1);
244 free (g->name);
245 free (g);
247 return -1;
249 h->group = g;
250 return 0;
253 static int
254 multi_conn_finalize (nbdkit_next *next, void *handle)
256 struct handle *h = handle;
257 size_t i;
259 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
260 assert (h->next == next);
261 assert (h->group);
263 /* XXX should we add a config param to flush if the client forgot? */
264 for (i = 0; i < h->group->conns.len; i++) {
265 if (h->group->conns.ptr[i] == h) {
266 conns_vector_remove (&h->group->conns, i);
267 break;
270 if (h->group->conns.len == 0) {
271 for (i = 0; i < groups.len; i++)
272 if (groups.ptr[i] == h->group) {
273 group_vector_remove (&groups, i);
274 free (h->group->name);
275 free (h->group);
276 break;
279 h->group = NULL;
280 return 0;
283 static void
284 multi_conn_close (void *handle)
286 struct handle *h = handle;
288 assert (h->group == NULL);
289 free (h->name);
290 free (h);
293 static int
294 multi_conn_can_fua (nbdkit_next *next, void *handle)
296 /* If the backend has native FUA support but is not multi-conn
297 * consistent, and we have to flush on every connection, then we are
298 * better off advertising emulated fua rather than native.
300 struct handle *h = handle;
301 int fua = next->can_fua (next);
303 assert (h->mode != AUTO);
304 if (fua == NBDKIT_FUA_NATIVE && h->mode == EMULATE)
305 return NBDKIT_FUA_EMULATE;
306 return fua;
309 static int
310 multi_conn_can_multi_conn (nbdkit_next *next, void *handle)
312 struct handle *h = handle;
314 switch (h->mode) {
315 case EMULATE:
316 return 1;
317 case PLUGIN:
318 return next->can_multi_conn (next);
319 case DISABLE:
320 return 0;
321 case UNSAFE:
322 return 1;
323 case AUTO: /* Not possible, see .prepare */
324 default:
325 abort ();
329 static void
330 mark_dirty (struct handle *h, bool is_write)
332 /* No need to grab lock here: the NBD spec is clear that a client
333 * must wait for the response to a flush before sending the next
334 * command that expects to see the result of that flush, so any race
335 * in accessing dirty can be traced back to the client improperly
336 * sending a flush in parallel with other live commands.
338 switch (track) {
339 case CONN:
340 h->dirty |= is_write ? WRITE : READ;
341 /* fallthrough */
342 case FAST:
343 if (is_write)
344 h->group->dirty = true;
345 break;
346 case OFF:
347 break;
348 default:
349 abort ();
353 static int
354 multi_conn_flush (nbdkit_next *next,
355 void *handle, uint32_t flags, int *err);
357 static int
358 multi_conn_pread (nbdkit_next *next,
359 void *handle, void *buf, uint32_t count, uint64_t offs,
360 uint32_t flags, int *err)
362 struct handle *h = handle;
364 mark_dirty (h, false);
365 return next->pread (next, buf, count, offs, flags, err);
368 static int
369 multi_conn_pwrite (nbdkit_next *next,
370 void *handle, const void *buf, uint32_t count,
371 uint64_t offs, uint32_t flags, int *err)
373 struct handle *h = handle;
374 bool need_flush = false;
376 if (flags & NBDKIT_FLAG_FUA) {
377 if (h->mode == EMULATE) {
378 mark_dirty (h, true);
379 need_flush = true;
380 flags &= ~NBDKIT_FLAG_FUA;
383 else
384 mark_dirty (h, true);
386 if (next->pwrite (next, buf, count, offs, flags, err) == -1)
387 return -1;
388 if (need_flush)
389 return multi_conn_flush (next, h, 0, err);
390 return 0;
393 static int
394 multi_conn_zero (nbdkit_next *next,
395 void *handle, uint32_t count, uint64_t offs, uint32_t flags,
396 int *err)
398 struct handle *h = handle;
399 bool need_flush = false;
401 if (flags & NBDKIT_FLAG_FUA) {
402 if (h->mode == EMULATE) {
403 mark_dirty (h, true);
404 need_flush = true;
405 flags &= ~NBDKIT_FLAG_FUA;
408 else
409 mark_dirty (h, true);
411 if (next->zero (next, count, offs, flags, err) == -1)
412 return -1;
413 if (need_flush)
414 return multi_conn_flush (next, h, 0, err);
415 return 0;
418 static int
419 multi_conn_trim (nbdkit_next *next,
420 void *handle, uint32_t count, uint64_t offs, uint32_t flags,
421 int *err)
423 struct handle *h = handle;
424 bool need_flush = false;
426 if (flags & NBDKIT_FLAG_FUA) {
427 if (h->mode == EMULATE) {
428 mark_dirty (h, true);
429 need_flush = true;
430 flags &= ~NBDKIT_FLAG_FUA;
433 else
434 mark_dirty (h, true);
436 if (next->trim (next, count, offs, flags, err) == -1)
437 return -1;
438 if (need_flush)
439 return multi_conn_flush (next, h, 0, err);
440 return 0;
443 static int
444 multi_conn_cache (nbdkit_next *next,
445 void *handle, uint32_t count, uint64_t offs, uint32_t flags,
446 int *err)
448 struct handle *h = handle;
450 mark_dirty (h, false);
451 return next->cache (next, count, offs, flags, err);
454 static int
455 multi_conn_flush (nbdkit_next *next,
456 void *handle, uint32_t flags, int *err)
458 struct handle *h = handle, *h2;
459 size_t i;
461 assert (h->group);
462 if (h->mode == EMULATE) {
463 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
464 for (i = 0; i < h->group->conns.len; i++) {
465 h2 = h->group->conns.ptr[i];
466 if (track == OFF || (h->group->dirty &&
467 (track == FAST || h2->dirty & READ)) ||
468 h2->dirty & WRITE) {
469 if (h2->next->flush (h2->next, flags, err) == -1)
470 return -1;
471 h2->dirty = 0;
474 h->group->dirty = 0;
476 else {
477 /* !EMULATE: Check if the image is clean, allowing us to skip a flush. */
478 if (track != OFF && !h->group->dirty)
479 return 0;
480 /* Perform the flush, then update dirty tracking. */
481 if (next->flush (next, flags, err) == -1)
482 return -1;
483 switch (track) {
484 case CONN:
485 if (next->can_multi_conn (next) == 1) {
486 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
487 for (i = 0; i < h->group->conns.len; i++)
488 h->group->conns.ptr[i]->dirty = 0;
489 h->group->dirty = 0;
491 else
492 h->dirty = 0;
493 break;
494 case FAST:
495 h->group->dirty = false;
496 break;
497 case OFF:
498 break;
499 default:
500 abort ();
503 return 0;
506 static struct nbdkit_filter filter = {
507 .name = "multi-conn",
508 .longname = "nbdkit multi-conn filter",
509 .config = multi_conn_config,
510 .config_help = multi_conn_config_help,
511 .get_ready = multi_conn_get_ready,
512 .unload = multi_conn_unload,
513 .open = multi_conn_open,
514 .prepare = multi_conn_prepare,
515 .finalize = multi_conn_finalize,
516 .close = multi_conn_close,
517 .can_fua = multi_conn_can_fua,
518 .can_multi_conn = multi_conn_can_multi_conn,
519 .pread = multi_conn_pread,
520 .pwrite = multi_conn_pwrite,
521 .trim = multi_conn_trim,
522 .zero = multi_conn_zero,
523 .cache = multi_conn_cache,
524 .flush = multi_conn_flush,
527 NBDKIT_REGISTER_FILTER(filter)