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
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
43 #include <nbdkit-filter.h>
48 /* Track results of .config */
49 static enum MultiConnMode
{
57 static enum TrackDirtyMode
{
63 static bool byname
= false;
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. */
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
*);
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'.
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)
100 else if (strcmp (value
, "emulate") == 0)
102 else if (strcmp (value
, "plugin") == 0)
104 else if (strcmp (value
, "disable") == 0)
106 else if (strcmp (value
, "unsafe") == 0)
109 nbdkit_error ("unknown multi-conn mode '%s'", value
);
114 else if (strcmp (key
, "multi-conn-track-dirty") == 0) {
115 if (strcmp (value
, "connection") == 0 ||
116 strcmp (value
, "conn") == 0)
118 else if (strcmp (value
, "fast") == 0)
120 else if (strcmp (value
, "off") == 0)
123 nbdkit_error ("unknown multi-conn track-dirty setting '%s'", value
);
128 else if (strcmp (key
, "multi-conn-exportname") == 0 ||
129 strcmp (key
, "multi-conn-export-name") == 0) {
132 r
= nbdkit_parse_bool (value
);
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"
148 multi_conn_get_ready (int thread_model
)
150 if (thread_model
== NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS
&&
157 multi_conn_unload (void)
159 assert (groups
.len
== 0);
160 group_vector_reset (&groups
);
164 multi_conn_open (nbdkit_next_open
*next
, nbdkit_context
*nxdata
,
165 int readonly
, const char *exportname
, int is_tls
)
169 if (next (nxdata
, readonly
, exportname
) == -1)
172 /* Allocate here, but populate and insert into list in .prepare */
173 h
= calloc (1, sizeof *h
);
175 nbdkit_error ("calloc: %m");
179 h
->name
= strdup (exportname
);
180 if (h
->name
== NULL
) {
181 nbdkit_error ("strdup: %m");
190 multi_conn_prepare (nbdkit_next
*next
, void *handle
, int readonly
)
192 struct handle
*h
= handle
;
194 bool new_group
= false;
199 if (mode
== AUTO
) { /* See also .get_ready turning AUTO into DISABLE */
200 r
= next
->can_multi_conn (next
);
210 if (h
->mode
== EMULATE
&& next
->can_flush (next
) != 1) {
211 nbdkit_error ("emulating multi-conn requires working flush");
215 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
218 for (i
= 0; i
< groups
.len
; i
++)
219 if (strcmp (groups
.ptr
[i
]->name
, h
->name
) == 0) {
225 g
= groups
.len
? groups
.ptr
[0] : NULL
;
228 g
= calloc (1, sizeof *g
);
230 nbdkit_error ("calloc: %m");
233 if (group_vector_append (&groups
, g
) == -1) {
241 if (conns_vector_append (&g
->conns
, h
) == -1) {
243 group_vector_remove (&groups
, groups
.len
- 1);
254 multi_conn_finalize (nbdkit_next
*next
, void *handle
)
256 struct handle
*h
= handle
;
259 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
260 assert (h
->next
== next
);
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
);
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
);
284 multi_conn_close (void *handle
)
286 struct handle
*h
= handle
;
288 assert (h
->group
== NULL
);
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
;
310 multi_conn_can_multi_conn (nbdkit_next
*next
, void *handle
)
312 struct handle
*h
= handle
;
318 return next
->can_multi_conn (next
);
323 case AUTO
: /* Not possible, see .prepare */
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.
340 h
->dirty
|= is_write
? WRITE
: READ
;
344 h
->group
->dirty
= true;
354 multi_conn_flush (nbdkit_next
*next
,
355 void *handle
, uint32_t flags
, int *err
);
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
);
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);
380 flags
&= ~NBDKIT_FLAG_FUA
;
384 mark_dirty (h
, true);
386 if (next
->pwrite (next
, buf
, count
, offs
, flags
, err
) == -1)
389 return multi_conn_flush (next
, h
, 0, err
);
394 multi_conn_zero (nbdkit_next
*next
,
395 void *handle
, uint32_t count
, uint64_t offs
, uint32_t flags
,
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);
405 flags
&= ~NBDKIT_FLAG_FUA
;
409 mark_dirty (h
, true);
411 if (next
->zero (next
, count
, offs
, flags
, err
) == -1)
414 return multi_conn_flush (next
, h
, 0, err
);
419 multi_conn_trim (nbdkit_next
*next
,
420 void *handle
, uint32_t count
, uint64_t offs
, uint32_t flags
,
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);
430 flags
&= ~NBDKIT_FLAG_FUA
;
434 mark_dirty (h
, true);
436 if (next
->trim (next
, count
, offs
, flags
, err
) == -1)
439 return multi_conn_flush (next
, h
, 0, err
);
444 multi_conn_cache (nbdkit_next
*next
,
445 void *handle
, uint32_t count
, uint64_t offs
, uint32_t flags
,
448 struct handle
*h
= handle
;
450 mark_dirty (h
, false);
451 return next
->cache (next
, count
, offs
, flags
, err
);
455 multi_conn_flush (nbdkit_next
*next
,
456 void *handle
, uint32_t flags
, int *err
)
458 struct handle
*h
= handle
, *h2
;
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
)) ||
469 if (h2
->next
->flush (h2
->next
, flags
, err
) == -1)
477 /* !EMULATE: Check if the image is clean, allowing us to skip a flush. */
478 if (track
!= OFF
&& !h
->group
->dirty
)
480 /* Perform the flush, then update dirty tracking. */
481 if (next
->flush (next
, flags
, err
) == -1)
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;
495 h
->group
->dirty
= false;
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
)