Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F176823811
D60993.1793436012.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
14 KB
Referenced Files
None
Subscribers
None
D60993.1793436012.diff
View Options
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, §sz)) {
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
Details
Attached
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)
Attached To
Mode
D60993: bhyve: Allow block devices to be provided by external backends
Attached
Detach File
Event Timeline