[PATCH 4/4] erofs-utils: mount: integrate ublk backend

Chengyu hudson at cyzhu.com
Mon Mar 16 01:27:45 AEDT 2026


From: Chengyu Zhu <hudsonzhu at tencent.com>

Wire up the ublk userspace block device backend into mount.erofs,
providing an alternative to nbd for block device exposure.

Signed-off-by: Chengyu Zhu <hudsonzhu at tencent.com>
---
 mount/main.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 242 insertions(+), 26 deletions(-)

diff --git a/mount/main.c b/mount/main.c
index 93c8444..be650f5 100644
--- a/mount/main.c
+++ b/mount/main.c
@@ -18,6 +18,7 @@
 #include "../lib/liberofs_nbd.h"
 #include "../lib/liberofs_oci.h"
 #include "../lib/liberofs_gzran.h"
+#include "../lib/liberofs_ublk.h"
 
 #ifdef HAVE_LINUX_LOOP_H
 #include <linux/loop.h>
@@ -43,12 +44,14 @@ struct loop_info {
 
 #define EROFSMOUNT_RUNDIR		"/var/run/erofs"
 #define EROFSMOUNT_NBD_REC_FMT		EROFSMOUNT_RUNDIR "/mountnbd_nbd%d"
+#define EROFSMOUNT_UBLK_REC_FMT	EROFSMOUNT_RUNDIR "/mountublk_ublkb%d"
 
 enum erofs_backend_drv {
 	EROFSAUTO,
 	EROFSLOCAL,
 	EROFSFUSE,
 	EROFSNBD,
+	EROFSUBLK,
 };
 
 enum erofsmount_mode {
@@ -98,10 +101,10 @@ static void usage(int argc, char **argv)
 		" -d <0-9>              set output verbosity; 0=quiet, 9=verbose (default=%i)\n"
 		" -o options            comma-separated list of mount options\n"
 		" -t type[.subtype]     filesystem type (and optional subtype)\n"
-		"                       subtypes: fuse, local, nbd\n"
+		"                       subtypes: fuse, local, nbd, ublk\n"
 		" -u                    unmount the filesystem\n"
 		"    --disconnect       abort an existing NBD device forcibly\n"
-		"    --reattach         reattach to an existing NBD device\n"
+		"    --reattach         reattach to an existing NBD or ublk device\n"
 #ifdef OCIEROFS_ENABLED
 		"\n"
 		"OCI-specific options (EXPERIMENTAL, with -o):\n"
@@ -327,6 +330,8 @@ static int erofsmount_parse_options(int argc, char **argv)
 					mountcfg.backend = EROFSLOCAL;
 				} else if (!strcmp(dot + 1, "nbd")) {
 					mountcfg.backend = EROFSNBD;
+				} else if (!strcmp(dot + 1, "ublk")) {
+					mountcfg.backend = EROFSUBLK;
 				} else {
 					erofs_err("invalid filesystem subtype `%s`", dot + 1);
 					return -EINVAL;
@@ -1136,11 +1141,30 @@ out_fork:
 	return num;
 }
 
+static int erofsmount_ublk_handler(void *ctx, struct erofs_ublk_request *req)
+{
+	struct erofs_vfile *vf = ctx;
+	ssize_t ret;
+
+	if (req->op != EROFS_UBLK_OP_READ)
+		return -EOPNOTSUPP;
+
+	ret = erofs_io_pread(vf, req->buf, req->nr_sectors << 9,
+			     req->start_sector << 9);
+	if (ret < 0)
+		return (int)ret;
+
+	req->result = ret;
+	return 0;
+}
+
 static int erofsmount_reattach(const char *target)
 {
-	char *identifier;
 	struct erofsmount_nbd_ctx ctx = {};
-	int nbdnum, err;
+	char *identifier = NULL;
+	char ublk_recp[64];
+	int ublk_dev_id = -1;
+	int nbdnum = -1, err;
 	struct stat st;
 	FILE *f;
 
@@ -1148,7 +1172,48 @@ static int erofsmount_reattach(const char *target)
 	if (err < 0)
 		return -errno;
 
-	if (!S_ISBLK(st.st_mode) || major(st.st_rdev) != EROFS_NBD_MAJOR)
+	if (!S_ISBLK(st.st_mode))
+		return -ENOTBLK;
+
+	if (sscanf(target, "/dev/ublkb%d", &ublk_dev_id) == 1) {
+		if (!erofs_ublk_is_recoverable(ublk_dev_id)) {
+			erofs_err("ublk device %d is not recoverable",
+				  ublk_dev_id);
+			return -ENODEV;
+		}
+		snprintf(ublk_recp, sizeof(ublk_recp),
+			 EROFSMOUNT_UBLK_REC_FMT, ublk_dev_id);
+		f = fopen(ublk_recp, "r");
+		if (!f) {
+			erofs_err("cannot open recovery file %s: %s",
+				  ublk_recp, strerror(errno));
+			return -errno;
+		}
+		err = erofsmount_open_recovery_source(f, &ctx.vd);
+		if (err)
+			return err;
+		if (fork() == 0) {
+			if (erofs_ublk_init() < 0)
+				exit(EXIT_FAILURE);
+			err = erofs_ublk_recover_dev(ublk_dev_id,
+						     erofsmount_ublk_handler,
+						     &ctx.vd);
+			if (err) {
+				erofs_err("erofs_ublk_recover_dev: %s",
+					  strerror(-err));
+				exit(EXIT_FAILURE);
+			}
+			erofs_ublk_start(ublk_dev_id, -1);
+			unlink(ublk_recp);
+			erofs_ublk_destroy(ublk_dev_id);
+			erofs_io_close(&ctx.vd);
+			exit(EXIT_SUCCESS);
+		}
+		erofs_io_close(&ctx.vd);
+		return 0;
+	}
+
+	if (major(st.st_rdev) != EROFS_NBD_MAJOR)
 		return -ENOTBLK;
 
 	nbdnum = erofs_nbd_get_index_from_minor(minor(st.st_rdev));
@@ -1165,7 +1230,8 @@ static int erofsmount_reattach(const char *target)
 	if (!identifier) {
 		char *recp;
 
-		if (asprintf(&recp, EROFSMOUNT_NBD_REC_FMT, nbdnum) <= 0) {
+		if (asprintf(&recp, EROFSMOUNT_NBD_REC_FMT,
+			     nbdnum) <= 0) {
 			err = -ENOMEM;
 			goto err_out;
 		}
@@ -1342,6 +1408,131 @@ out_err:
 	return -errno;
 }
 
+static int erofsmount_ublk(struct erofsmount_source *source,
+			   const char *mountpoint, const char *fstype,
+			   int flags, const char *options)
+{
+	int pipefd[2];
+	char dev_path[64];
+	pid_t pid;
+	int dev_id, err;
+	char ready;
+
+	err = erofs_ublk_init();
+	if (err) {
+		erofs_err("ublk not supported");
+		return err;
+	}
+
+	if (pipe(pipefd) < 0)
+		return -errno;
+
+	pid = fork();
+	if (pid < 0) {
+		close(pipefd[0]);
+		close(pipefd[1]);
+		return -errno;
+	}
+
+	if (pid == 0) {
+		struct erofs_vfile vf = {};
+		struct erofs_ublk_dev_info info = {};
+		char ublk_recp[64], *recp;
+		struct stat st;
+
+		close(pipefd[0]);
+
+		err = erofsmount_open_source(&vf, source);
+		if (err)
+			exit(EXIT_FAILURE);
+
+		info.nr_hw_queues = 1;
+		info.queue_depth = 64;
+		info.max_io_buf_bytes = 65536;
+		info.dev_id = -1;
+		info.blkbits = 12;
+		info.flags = EROFS_UBLK_F_USER_RECOVERY;
+
+		if (source->type == EROFSMOUNT_SOURCE_LOCAL &&
+		    fstat(vf.fd, &st) == 0)
+			info.dev_size = st.st_size;
+		else
+			info.dev_size = INT64_MAX;
+
+		dev_id = erofs_ublk_create_dev(&info,
+				erofsmount_ublk_handler, &vf);
+		if (dev_id < 0) {
+			erofs_err("erofs_ublk_create_dev failed: %s",
+				  strerror(-dev_id));
+			exit(EXIT_FAILURE);
+		}
+
+		snprintf(ublk_recp, sizeof(ublk_recp),
+			 EROFSMOUNT_UBLK_REC_FMT, dev_id);
+		recp = erofsmount_write_recovery_info(source);
+		if (IS_ERR(recp)) {
+			erofs_err("write_recovery_info: %s",
+				  strerror(-(int)PTR_ERR(recp)));
+		} else {
+			if (rename(recp, ublk_recp))
+				erofs_err("rename recovery: %s",
+					  strerror(errno));
+			free(recp);
+		}
+
+		if (write(pipefd[1], &dev_id,
+			  sizeof(dev_id)) != sizeof(dev_id))
+			exit(EXIT_FAILURE);
+
+		err = erofs_ublk_start(dev_id, pipefd[1]);
+		if (err)
+			erofs_err("erofs_ublk_start: %s",
+				  strerror(-err));
+
+		unlink(ublk_recp);
+		erofs_ublk_destroy(dev_id);
+		if (vf.fd > 0)
+			close(vf.fd);
+		exit(EXIT_SUCCESS);
+	}
+
+	close(pipefd[1]);
+	if (read(pipefd[0], &dev_id, sizeof(dev_id)) !=
+	    sizeof(dev_id)) {
+		waitpid(pid, NULL, 0);
+		close(pipefd[0]);
+		return -EIO;
+	}
+
+	snprintf(dev_path, sizeof(dev_path),
+		 "/dev/ublkb%d", dev_id);
+
+	if (read(pipefd[0], &ready, 1) != 1) {
+		waitpid(pid, NULL, 0);
+		close(pipefd[0]);
+		return -EIO;
+	}
+	close(pipefd[0]);
+
+	err = mount(dev_path, mountpoint, fstype, flags, options);
+	if (err < 0) {
+		err = -errno;
+		kill(pid, SIGTERM);
+		waitpid(pid, NULL, 0);
+		return err;
+	}
+	return 0;
+}
+
+static int ublk_dev_id_from_path(const char *path)
+{
+	int dev_id;
+
+	if (sscanf(path, "/dev/ublkb%d", &dev_id) == 1)
+		return dev_id;
+	return -1;
+}
+
 int erofsmount_umount(char *target)
 {
 	char *device = NULL, *mountpoint = NULL;
@@ -1379,7 +1570,7 @@ int erofsmount_umount(char *target)
 
 	for (s = NULL; (getline(&s, &n, mounts)) > 0;) {
 		bool hit = false;
-		char *f1, *f2, *end;
+		char *f1, *f2 = NULL, *end;
 
 		f1 = s;
 		end = strchr(f1, ' ');
@@ -1396,31 +1587,48 @@ int erofsmount_umount(char *target)
 				hit = true;
 		}
 		if (hit) {
-			if (isblk) {
-				err = -EBUSY;
-				free(s);
-				fclose(mounts);
-				goto err_out;
-			}
 			free(device);
 			device = strdup(f1);
-			if (!mountpoint)
-				mountpoint = strdup(f2);
+			free(mountpoint);
+			mountpoint = f2 ? strdup(f2) : NULL;
 		}
 	}
 	free(s);
 	fclose(mounts);
+
+	if (isblk && !device) {
+		if (S_ISBLK(st.st_mode) && major(st.st_rdev) == EROFS_NBD_MAJOR) {
+			nbdnum = erofs_nbd_get_index_from_minor(minor(st.st_rdev));
+			err = erofs_nbd_nl_disconnect(nbdnum);
+			if (err != -EOPNOTSUPP)
+				goto err_out;
+		}
+		err = ublk_dev_id_from_path(target);
+		if (err >= 0) {
+			err = erofs_ublk_del_dev_by_id(err);
+			goto err_out;
+		}
+		err = -ENOENT;
+		goto err_out;
+	}
+
 	if (!isblk && !device) {
 		err = -ENOENT;
 		goto err_out;
 	}
 
-	if (isblk && !mountpoint &&
-	    S_ISBLK(st.st_mode) && major(st.st_rdev) == EROFS_NBD_MAJOR) {
-		nbdnum = erofs_nbd_get_index_from_minor(minor(st.st_rdev));
-		err = erofs_nbd_nl_disconnect(nbdnum);
-		if (err != -EOPNOTSUPP)
-			return err;
+	err = ublk_dev_id_from_path(device);
+	if (err >= 0) {
+		if (mountpoint) {
+			int ret = umount(mountpoint);
+
+			if (ret) {
+				err = -errno;
+				goto err_out;
+			}
+		}
+		err = erofs_ublk_del_dev_by_id(err);
+		goto err_out;
 	}
 
 	/* Avoid TOCTOU issue with NBD_CFLAG_DISCONNECT_ON_CLOSE */
@@ -1438,15 +1646,16 @@ int erofsmount_umount(char *target)
 		}
 	}
 	err = fstat(fd, &st);
-	if (err < 0)
+	if (err < 0) {
 		err = -errno;
-	else if (S_ISBLK(st.st_mode) && major(st.st_rdev) == EROFS_NBD_MAJOR) {
+	} else if (S_ISBLK(st.st_mode) && major(st.st_rdev) == EROFS_NBD_MAJOR) {
 		nbdnum = erofs_nbd_get_index_from_minor(minor(st.st_rdev));
 		err = erofs_nbd_nl_disconnect(nbdnum);
 		if (err == -EOPNOTSUPP)
 			err = erofs_nbd_disconnect(fd);
 	}
 	close(fd);
+
 err_out:
 	free(device);
 	free(mountpoint);
@@ -1523,13 +1732,20 @@ int main(int argc, char *argv[])
 		goto exit;
 	}
 
-	if (mountcfg.backend == EROFSNBD) {
+	if (mountcfg.backend == EROFSNBD || mountcfg.backend == EROFSUBLK) {
 		if (mountsrc.type == EROFSMOUNT_SOURCE_OCI)
 			mountsrc.ocicfg.image_ref = mountcfg.device;
 		else
 			mountsrc.device_path = mountcfg.device;
-		err = erofsmount_nbd(&mountsrc, mountcfg.target,
-				     mountcfg.fstype, mountcfg.flags, mountcfg.options);
+
+		if (mountcfg.backend == EROFSNBD)
+			err = erofsmount_nbd(&mountsrc, mountcfg.target,
+					     mountcfg.fstype, mountcfg.flags,
+					     mountcfg.options);
+		else
+			err = erofsmount_ublk(&mountsrc, mountcfg.target,
+					      mountcfg.fstype, mountcfg.flags,
+					      mountcfg.options);
 		goto exit;
 	}
 
-- 
2.47.1



More information about the Linux-erofs mailing list