Page MenuHomeFreeBSD

D60993.1793436012.diff
No OneTemporary

Authored By
Unknown
Size
14 KB
Referenced Files
None
Subscribers
None

D60993.1793436012.diff

diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8
--- a/usr.sbin/bhyve/bhyve.8
+++ b/usr.sbin/bhyve/bhyve.8
@@ -660,9 +660,45 @@
.Ar /filename Op Cm \&, Ar block-device-options
.It
.Ar /dev/xxx Op Cm \&, Ar block-device-options
+.It
+.Ar name Cm \&: Ar spec Op Cm \&, Ar block-device-options
.Sm on
.El
.Pp
+If the path has the form
+.Ar name Ns Cm \&: Ns Ar spec ,
+and no file with that path exists,
+the disk is provided by an external block backend.
+The
+.Ar name
+consists of two to 32 lower case letters, digits and underscores.
+.Nm
+loads the shared object
+.Pa /usr/local/lib/bhyve/block_ Ns Ao Ar name Ac Ns Pa .so
+and passes
+.Ar spec
+to the block backend it contains.
+The meaning of
+.Ar spec
+is defined by the backend.
+A backend is loaded when the disk is opened,
+before
+.Nm
+enters capability mode.
+A backend that needs resources which are not available in capability mode,
+such as network sockets,
+can only be used with a
+.Nm
+that was built without capability mode support.
+The
+.Cm nocache
+and
+.Cm direct
+options are ignored for a backend.
+Requests that are in progress in a backend cannot be cancelled,
+and the size of a disk provided by a backend cannot be changed while
+the guest is running.
+.Pp
The
.Ar block-device-options
are:
diff --git a/usr.sbin/bhyve/bhyve_config.5 b/usr.sbin/bhyve/bhyve_config.5
--- a/usr.sbin/bhyve/bhyve_config.5
+++ b/usr.sbin/bhyve/bhyve_config.5
@@ -400,6 +400,10 @@
.It Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
.It path Ta string Ta Ta
The path of the file or disk device to use as the backing store.
+Alternatively,
+.Ar name Ns Cm \&: Ns Ar spec
+selects an external block backend, see
+.Xr bhyve 8 .
.It nocache Ta bool Ta false Ta
Disable caching on the backing file by opening the backing file with
.Dv O_DIRECT .
diff --git a/usr.sbin/bhyve/block_backend.h b/usr.sbin/bhyve/block_backend.h
new file mode 100644
--- /dev/null
+++ b/usr.sbin/bhyve/block_backend.h
@@ -0,0 +1,102 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Willem Jan Withagen <wjw@digiware.nl>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Interface for block backends that live outside of bhyve(8).
+ *
+ * A disk path of the form "<name>:<spec>" selects a backend when no file
+ * with that path exists. bhyve then loads the shared object
+ * BLOCKIF_PLUGIN_DIR/block_<name>.so and looks up the symbol
+ * "bhyve_block_backend" in it, which must be a struct block_backend.
+ * The object is loaded while the disks are opened, so before bhyve enters
+ * capability mode.
+ *
+ * The interface deliberately uses no bhyve symbols, so a backend can be
+ * built and packaged without bhyve's sources.
+ *
+ * The bb_read, bb_write, bb_flush and bb_delete methods are called from
+ * the blockif worker threads (BLOCKIF_NUMTHR of them) and must be thread
+ * safe. They run synchronously: the request is complete when they return.
+ */
+
+#ifndef _BLOCK_BACKEND_H_
+#define _BLOCK_BACKEND_H_
+
+#include <sys/types.h>
+
+#include "block_if.h"
+
+#define BLOCK_BACKEND_VERSION 1
+#define BLOCK_BACKEND_SYMBOL "bhyve_block_backend"
+
+/*
+ * Geometry and properties of an opened backend, filled in by bb_open.
+ * The sector size can still be overridden by the "sectorsize" option.
+ */
+struct block_backend_info {
+ off_t bbi_size; /* size in bytes */
+ int bbi_sectsz; /* logical sector size, 0 means DEV_BSIZE */
+ int bbi_psectsz; /* physical sector size, 0 means bbi_sectsz */
+ int bbi_psectoff; /* physical sector offset */
+ int bbi_candelete; /* bb_delete is supported */
+ int bbi_rdonly; /* backend can only be read */
+};
+
+struct block_backend {
+ unsigned int bb_version; /* BLOCK_BACKEND_VERSION */
+ const char *bb_name; /* "<name>" in "<name>:<spec>" */
+
+ /*
+ * Open the device described by spec, the part of the path after
+ * the first colon. rdonly is set when the "ro" option was given.
+ * Returns 0 and a private pointer for the other methods in *privp,
+ * or an errno value.
+ */
+ int (*bb_open)(const char *spec, int rdonly,
+ struct block_backend_info *bbi, void **privp);
+
+ /*
+ * Transfer the data described by br->br_iov at br->br_offset and
+ * subtract the number of bytes transferred from br->br_resid.
+ * Return 0 or an errno value.
+ */
+ int (*bb_read)(void *priv, struct blockif_req *br);
+ int (*bb_write)(void *priv, struct blockif_req *br);
+
+ /* Optional. A missing bb_flush is a no-op. */
+ int (*bb_flush)(void *priv);
+
+ /*
+ * Optional, only used when bbi_candelete is set. Discard
+ * br->br_resid bytes at br->br_offset and set br->br_resid to 0.
+ */
+ int (*bb_delete)(void *priv, struct blockif_req *br);
+
+ void (*bb_close)(void *priv);
+};
+
+#endif /* _BLOCK_BACKEND_H_ */
diff --git a/usr.sbin/bhyve/block_if.c b/usr.sbin/bhyve/block_if.c
--- a/usr.sbin/bhyve/block_if.c
+++ b/usr.sbin/bhyve/block_if.c
@@ -41,6 +41,8 @@
#ifndef WITHOUT_CAPSICUM
#include <capsicum_helpers.h>
#endif
+#include <ctype.h>
+#include <dlfcn.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
@@ -62,9 +64,14 @@
#include "mevent.h"
#include "pci_emul.h"
#include "block_if.h"
+#include "block_backend.h"
#define BLOCKIF_SIG 0xb109b109
+#ifndef BLOCKIF_PLUGIN_DIR
+#define BLOCKIF_PLUGIN_DIR "/usr/local/lib/bhyve"
+#endif
+
#define BLOCKIF_NUMTHR 8
#define BLOCKIF_MAXREQ (BLOCKIF_RING_MAX + BLOCKIF_NUMTHR)
@@ -135,6 +142,10 @@
TAILQ_HEAD(, blockif_elem) bc_busyq;
struct blockif_elem bc_reqs[BLOCKIF_MAXREQ];
int bc_bootindex;
+
+ /* Set when the disk is served by an external block backend */
+ const struct block_backend *bc_backend;
+ void *bc_priv;
};
static pthread_once_t blockif_once = PTHREAD_ONCE_INIT;
@@ -234,6 +245,12 @@
static int
blockif_flush_bc(struct blockif_ctxt *bc)
{
+ if (bc->bc_backend != NULL) {
+ if (bc->bc_backend->bb_flush == NULL)
+ return (0);
+ return (bc->bc_backend->bb_flush(bc->bc_priv));
+ }
+
if (bc->bc_ischr) {
if (ioctl(bc->bc_fd, DIOCGFLUSH))
return (errno);
@@ -243,6 +260,37 @@
return (0);
}
+/*
+ * Process a request on a disk that is served by an external backend.
+ * Returns an errno value.
+ */
+static int
+blockif_proc_backend(struct blockif_ctxt *bc, enum blockop op,
+ struct blockif_req *br)
+{
+ const struct block_backend *bb;
+
+ bb = bc->bc_backend;
+ switch (op) {
+ case BOP_READ:
+ return (bb->bb_read(bc->bc_priv, br));
+ case BOP_WRITE:
+ if (bc->bc_rdonly)
+ return (EROFS);
+ return (bb->bb_write(bc->bc_priv, br));
+ case BOP_FLUSH:
+ return (blockif_flush_bc(bc));
+ case BOP_DELETE:
+ if (!bc->bc_candelete || bb->bb_delete == NULL)
+ return (EOPNOTSUPP);
+ if (bc->bc_rdonly)
+ return (EROFS);
+ return (bb->bb_delete(bc->bc_priv, br));
+ default:
+ return (EINVAL);
+ }
+}
+
static void
blockif_proc(struct blockif_ctxt *bc, struct blockif_elem *be, uint8_t *buf)
{
@@ -259,6 +307,10 @@
if (br->br_iovcnt <= 1)
buf = NULL;
err = 0;
+ if (bc->bc_backend != NULL) {
+ err = blockif_proc_backend(bc, be->be_op, br);
+ goto done;
+ }
switch (be->be_op) {
case BOP_READ:
if (buf == NULL) {
@@ -374,6 +426,7 @@
break;
}
+done:
be->be_status = BST_DONE;
(*br->br_callback)(br, err);
@@ -489,6 +542,79 @@
return (pci_emul_add_boot_device(pi, bc->bc_bootindex));
}
+/*
+ * Check if path has the form "<name>:<spec>" and names an external block
+ * backend. A path that exists as a file is always a plain disk image.
+ *
+ * Returns 0 and sets *bbp and *specp if the backend was found and loaded,
+ * ENOENT if the path does not refer to a backend, or another errno value
+ * after reporting why the backend could not be used.
+ */
+static int
+blockif_backend_find(const char *path, const struct block_backend **bbp,
+ const char **specp)
+{
+ char sofile[MAXPATHLEN];
+ const struct block_backend *bb;
+ struct stat sb;
+ const char *colon;
+ size_t i, len;
+ void *handle;
+
+ colon = strchr(path, ':');
+ if (colon == NULL)
+ return (ENOENT);
+ len = colon - path;
+ if (len < 2 || len > 32)
+ return (ENOENT);
+ for (i = 0; i < len; i++) {
+ if (!islower((unsigned char)path[i]) &&
+ !isdigit((unsigned char)path[i]) && path[i] != '_')
+ return (ENOENT);
+ }
+ if (stat(path, &sb) == 0)
+ return (ENOENT);
+
+ snprintf(sofile, sizeof(sofile), "%s/block_%.*s.so",
+ BLOCKIF_PLUGIN_DIR, (int)len, path);
+ if (access(sofile, R_OK) != 0) {
+ /* The open of path will fail too, say what else was tried. */
+ EPRINTLN("%s: no such file and no block backend %s", path,
+ sofile);
+ return (ENOENT);
+ }
+
+ handle = dlopen(sofile, RTLD_NOW | RTLD_LOCAL);
+ if (handle == NULL) {
+ EPRINTLN("Cannot load block backend %s: %s", sofile, dlerror());
+ return (ENXIO);
+ }
+ bb = dlsym(handle, BLOCK_BACKEND_SYMBOL);
+ if (bb == NULL) {
+ EPRINTLN("%s does not provide a block backend", sofile);
+ goto err;
+ }
+ if (bb->bb_version != BLOCK_BACKEND_VERSION) {
+ EPRINTLN("%s: block backend version %u, expected %u", sofile,
+ bb->bb_version, BLOCK_BACKEND_VERSION);
+ goto err;
+ }
+ if (bb->bb_name == NULL || strlen(bb->bb_name) != len ||
+ strncmp(bb->bb_name, path, len) != 0 || bb->bb_open == NULL ||
+ bb->bb_read == NULL || bb->bb_write == NULL ||
+ bb->bb_close == NULL) {
+ EPRINTLN("%s: incomplete block backend", sofile);
+ goto err;
+ }
+
+ *bbp = bb;
+ *specp = colon + 1;
+ return (0);
+err:
+ dlclose(handle);
+ return (EINVAL);
+}
+
struct blockif_ctxt *
blockif_open(nvlist_t *nvl, const char *ident)
{
@@ -499,9 +625,13 @@
struct blockif_ctxt *bc;
struct stat sbuf;
struct diocgattr_arg arg;
+ const struct block_backend *bb;
+ struct block_backend_info bbi;
+ const char *spec;
+ void *bpriv;
off_t size, psectsz, psectoff;
int extra, fd, i, sectsz;
- int ro, candelete, geom, ssopt, pssopt;
+ int ro, candelete, geom, ssopt, pssopt, ischr;
int nodelete;
int bootindex;
@@ -513,11 +643,14 @@
pthread_once(&blockif_once, blockif_init);
bc = NULL;
+ bb = NULL;
+ bpriv = NULL;
fd = -1;
extra = 0;
ssopt = 0;
ro = 0;
nodelete = 0;
+ ischr = 0;
bootindex = -1;
if (get_config_bool_node_default(nvl, "nocache", false))
@@ -562,6 +695,32 @@
goto err;
}
+ /*
+ * An external block backend provides its own geometry, in place of
+ * opening and probing a file or device.
+ */
+ i = blockif_backend_find(path, &bb, &spec);
+ if (i == 0) {
+ memset(&bbi, 0, sizeof(bbi));
+ if (bb->bb_open(spec, ro, &bbi, &bpriv) != 0) {
+ EPRINTLN("Could not open %s device \"%s\"", bb->bb_name,
+ spec);
+ bb = NULL;
+ goto err;
+ }
+ size = bbi.bbi_size;
+ sectsz = bbi.bbi_sectsz != 0 ? bbi.bbi_sectsz : DEV_BSIZE;
+ psectsz = bbi.bbi_psectsz != 0 ? bbi.bbi_psectsz : sectsz;
+ psectoff = bbi.bbi_psectoff;
+ candelete = !nodelete && bbi.bbi_candelete &&
+ bb->bb_delete != NULL;
+ if (bbi.bbi_rdonly)
+ ro = 1;
+ geom = 0;
+ goto geometry;
+ } else if (i != ENOENT)
+ goto err;
+
fd = open(path, (ro ? O_RDONLY : O_RDWR) | extra);
if (fd < 0 && !ro) {
/* Attempt a r/w fail with a r/o open */
@@ -592,11 +751,12 @@
/*
* Deal with raw devices
*/
- size = sbuf.st_size;
+ ischr = S_ISCHR(sbuf.st_mode);
+ size = sbuf.st_size;
sectsz = DEV_BSIZE;
psectsz = psectoff = 0;
candelete = geom = 0;
- if (S_ISCHR(sbuf.st_mode)) {
+ if (ischr) {
if (ioctl(fd, DIOCGMEDIASIZE, &size) < 0 ||
ioctl(fd, DIOCGSECTORSIZE, &sectsz)) {
perror("Could not fetch dev blk/sector size");
@@ -623,6 +783,7 @@
errx(EX_OSERR, "Unable to apply rights for sandbox");
#endif
+geometry:
if (ssopt != 0) {
if (!powerof2(ssopt) || !powerof2(pssopt) || ssopt < 512 ||
ssopt > pssopt) {
@@ -638,7 +799,7 @@
* Validate that the emulated sector size complies with this
* requirement.
*/
- if (S_ISCHR(sbuf.st_mode)) {
+ if (ischr) {
if (ssopt < sectsz || (ssopt % sectsz) != 0) {
EPRINTLN("Sector size %d incompatible "
"with underlying device sector size %d",
@@ -660,7 +821,7 @@
bc->bc_magic = BLOCKIF_SIG;
bc->bc_fd = fd;
- bc->bc_ischr = S_ISCHR(sbuf.st_mode);
+ bc->bc_ischr = ischr;
bc->bc_isgeom = geom;
bc->bc_candelete = candelete;
bc->bc_rdonly = ro;
@@ -668,6 +829,8 @@
bc->bc_sectsz = sectsz;
bc->bc_psectsz = psectsz;
bc->bc_psectoff = psectoff;
+ bc->bc_backend = bb;
+ bc->bc_priv = bpriv;
if (pthread_mutex_init(&bc->bc_mtx, NULL) != 0)
goto err;
if (pthread_cond_init(&bc->bc_cond, NULL) != 0)
@@ -713,6 +876,8 @@
pthread_cond_destroy(&bc->bc_work_done_cond);
free(bc);
}
+ if (bb != NULL && bpriv != NULL)
+ bb->bb_close(bpriv);
if (fd >= 0)
close(fd);
return (NULL);
@@ -756,6 +921,10 @@
if (cb == NULL)
return (EINVAL);
+ /* Resizes are detected by watching the backing file or device */
+ if (bc->bc_backend != NULL)
+ return (EOPNOTSUPP);
+
err = 0;
pthread_mutex_lock(&bc->bc_mtx);
@@ -888,6 +1057,15 @@
return (EINVAL);
}
+ /*
+ * An external backend cannot be interrupted with a signal. The
+ * request completes through its normal callback path.
+ */
+ if (bc->bc_backend != NULL) {
+ pthread_mutex_unlock(&bc->bc_mtx);
+ return (EBUSY);
+ }
+
/*
* Interrupt the processing thread to force it return
* prematurely via it's normal callback path.
@@ -951,7 +1129,10 @@
* Release resources
*/
bc->bc_magic = 0;
- close(bc->bc_fd);
+ if (bc->bc_backend != NULL)
+ bc->bc_backend->bb_close(bc->bc_priv);
+ else
+ close(bc->bc_fd);
pthread_mutex_destroy(&bc->bc_mtx);
pthread_cond_destroy(&bc->bc_cond);
pthread_cond_destroy(&bc->bc_work_done_cond);

File Metadata

Mime Type
text/plain
Expires
Sat, Oct 31, 8:40 AM (1 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41253137
Default Alt Text
D60993.1793436012.diff (14 KB)

Event Timeline