[PATCH] erofs-utils: add a dedicated mount helper

Gao Xiang hsiangkao at linux.alibaba.com
Thu Aug 21 16:12:05 AEST 2025


Currently, kernel and FUSE mounts are supported for local images.
Support for remote images will be added later.

Signed-off-by: Gao Xiang <hsiangkao at linux.alibaba.com>
---
 Makefile.am              |   2 +-
 configure.ac             |   2 +
 include/erofs/err.h      |  10 ++
 include/erofs/internal.h |  11 --
 mount/Makefile.am        |  11 ++
 mount/main.c             | 287 +++++++++++++++++++++++++++++++++++++++
 6 files changed, 311 insertions(+), 12 deletions(-)
 create mode 100644 mount/Makefile.am
 create mode 100644 mount/main.c

diff --git a/Makefile.am b/Makefile.am
index f8a967f..7cb93a6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,4 +6,4 @@ SUBDIRS = man lib mkfs dump fsck
 if ENABLE_FUSE
 SUBDIRS += fuse
 endif
-SUBDIRS += contrib
+SUBDIRS += mount contrib
diff --git a/configure.ac b/configure.ac
index 1efb57a..c0174ce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -217,6 +217,7 @@ AC_CHECK_HEADERS(m4_flatten([
 	linux/aufs_type.h
 	linux/falloc.h
 	linux/fs.h
+	linux/loop.h
 	linux/types.h
 	linux/xattr.h
 	limits.h
@@ -799,5 +800,6 @@ AC_CONFIG_FILES([Makefile
 		 dump/Makefile
 		 fuse/Makefile
 		 fsck/Makefile
+		 mount/Makefile
 		 contrib/Makefile])
 AC_OUTPUT
diff --git a/include/erofs/err.h b/include/erofs/err.h
index 2ae9e21..ff488dd 100644
--- a/include/erofs/err.h
+++ b/include/erofs/err.h
@@ -13,6 +13,16 @@ extern "C"
 #endif
 
 #include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+static inline const char *erofs_strerror(int err)
+{
+	static char msg[256];
+
+	sprintf(msg, "[Error %d] %s", -err, strerror(-err));
+	return msg;
+}
 
 #define MAX_ERRNO (4095)
 #define IS_ERR_VALUE(x)                                                        \
diff --git a/include/erofs/internal.h b/include/erofs/internal.h
index ea58584..9a82e06 100644
--- a/include/erofs/internal.h
+++ b/include/erofs/internal.h
@@ -362,17 +362,6 @@ static inline bool is_dot_dotdot(const char *name)
 	return name[1] == '\0' || (name[1] == '.' && name[2] == '\0');
 }
 
-#include <stdio.h>
-#include <string.h>
-
-static inline const char *erofs_strerror(int err)
-{
-	static char msg[256];
-
-	sprintf(msg, "[Error %d] %s", -err, strerror(-err));
-	return msg;
-}
-
 enum {
 	BH_Meta,
 	BH_Mapped,
diff --git a/mount/Makefile.am b/mount/Makefile.am
new file mode 100644
index 0000000..d901c20
--- /dev/null
+++ b/mount/Makefile.am
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Makefile.am
+
+AUTOMAKE_OPTIONS = foreign
+sbin_PROGRAMS    = mount.erofs
+AM_CPPFLAGS = ${libuuid_CFLAGS}
+mount_erofs_SOURCES = main.c
+mount_erofs_CFLAGS = -Wall -I$(top_srcdir)/include
+mount_erofs_LDADD = $(top_builddir)/lib/liberofs.la ${libselinux_LIBS} \
+	${liblz4_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libdeflate_LIBS} \
+	${libzstd_LIBS} ${libqpl_LIBS} ${libxxhash_LIBS}
diff --git a/mount/main.c b/mount/main.c
new file mode 100644
index 0000000..096602a
--- /dev/null
+++ b/mount/main.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0+
+#define _GNU_SOURCE
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <unistd.h>
+#include "erofs/config.h"
+#include "erofs/print.h"
+#include "erofs/err.h"
+#ifdef HAVE_LINUX_LOOP_H
+#include <linux/loop.h>
+#else
+#define LOOP_CTL_GET_FREE	0x4C82
+#define LOOP_SET_FD		0x4C00
+#define LOOP_SET_STATUS		0x4C02
+enum {
+	LO_FLAGS_AUTOCLEAR = 4,
+};
+struct loop_info {
+	char	pad[44];
+        int	lo_flags;
+	char    pad1[120];
+};
+#endif
+
+enum erofs_backend_drv {
+	EROFSAUTO,
+	EROFSLOCAL,
+	EROFSFUSE,
+};
+
+static struct erofsmount_cfg {
+	char *device;
+	char *mountpoint;
+	char *options;
+	char *full_options;		/* used for erofsfuse */
+	char *fstype;
+	long flags;
+	enum erofs_backend_drv backend;
+} mountcfg = {
+	.full_options = "ro",
+	.flags = MS_RDONLY,		/* default mountflags */
+	.fstype = "erofs",
+};
+
+static long erofsmount_parse_flagopts(char *s, long flags, char **more)
+{
+	static const struct {
+		char *name;
+		long flags;
+	} opts[] = {
+		{"defaults", 0}, {"quiet", 0}, // NOPs
+		{"user", 0}, {"nouser", 0}, // checked in fstab, ignored in -o
+		{"ro", MS_RDONLY}, {"rw", ~MS_RDONLY},
+		{"nosuid", MS_NOSUID}, {"suid", ~MS_NOSUID},
+		{"nodev", MS_NODEV}, {"dev", ~MS_NODEV},
+		{"noexec", MS_NOEXEC}, {"exec", ~MS_NOEXEC},
+		{"sync", MS_SYNCHRONOUS}, {"async", ~MS_SYNCHRONOUS},
+		{"noatime", MS_NOATIME}, {"atime", ~MS_NOATIME},
+		{"norelatime", ~MS_RELATIME}, {"relatime", MS_RELATIME},
+		{"nodiratime", MS_NODIRATIME}, {"diratime", ~MS_NODIRATIME},
+		{"loud", ~MS_SILENT},
+		{"remount", MS_REMOUNT}, {"move", MS_MOVE},
+		// mand dirsync rec iversion strictatime
+	};
+
+	for (;;) {
+		char *comma;
+		int i;
+
+		comma = strchr(s, ',');
+		if (comma)
+			*comma = '\0';
+		for (i = 0; i < ARRAY_SIZE(opts); ++i) {
+			if (!strcasecmp(s, opts[i].name)) {
+				if (opts[i].flags < 0)
+					flags &= opts[i].flags;
+				else
+					flags |= opts[i].flags;
+				break;
+			}
+		}
+
+		if (more && i >= ARRAY_SIZE(opts)) {
+			int sl = strlen(s);
+			char *new = *more;
+
+			i = new ? strlen(new) : 0;
+			new = realloc(new, i + strlen(s) + 2);
+			if (!new)
+				return -ENOMEM;
+			if (i) new[i++] = ',';
+			memcpy(new + i, s, sl);
+			new[i + sl] = '\0';
+			*more = new;
+		}
+
+		if (!comma)
+			break;
+		*comma = ',';
+		s = comma + 1;
+	}
+	return flags;
+}
+
+static int erofsmount_parse_options(int argc, char **argv)
+{
+	static const struct option long_options[] = {
+		{"help", no_argument, 0, 'h'},
+		{0, 0, 0, 0},
+	};
+	char *dot;
+	int opt;
+
+	while ((opt = getopt_long(argc, argv, "Nfno:st:v",
+				  long_options, NULL)) != -1) {
+		switch (opt) {
+		case 'o':
+			mountcfg.full_options = optarg;
+			mountcfg.flags =
+				erofsmount_parse_flagopts(optarg, mountcfg.flags,
+							  &mountcfg.options);
+			break;
+		case 't':
+			dot = strchr(optarg, '.');
+			if (dot) {
+				if (!strcmp(dot + 1, "fuse")) {
+					mountcfg.backend = EROFSFUSE;
+				} else if (!strcmp(dot + 1, "local")) {
+					mountcfg.backend = EROFSLOCAL;
+				} else {
+					erofs_err("invalid filesystem subtype `%s`", dot + 1);
+					return -EINVAL;
+				}
+				*dot = '\0';
+			}
+			mountcfg.fstype = optarg;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (optind >= argc) {
+		erofs_err("missing argument: DEVICE");
+		return -EINVAL;
+	}
+
+	mountcfg.device = strdup(argv[optind++]);
+	if (!mountcfg.device)
+		return -ENOMEM;
+
+	if (optind >= argc) {
+		erofs_err("missing argument: MOUNTPOINT");
+		return -EINVAL;
+	}
+
+	mountcfg.mountpoint = strdup(argv[optind++]);
+	if (!mountcfg.mountpoint)
+		return -ENOMEM;
+
+	if (optind < argc) {
+		erofs_err("unexpected argument: %s\n", argv[optind]);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int erofsmount_fuse(const char *source, const char *mountpoint,
+			   const char *fstype, const char *options)
+{
+	char *command;
+	int err;
+
+	if (strcmp(fstype, "erofs")) {
+		fprintf(stderr, "unsupported filesystem type `%s`\n",
+			mountcfg.fstype);
+		return -ENODEV;
+	}
+
+	err = asprintf(&command, "erofsfuse -o%s %s %s", options,
+		       source, mountpoint);
+	if (err < 0)
+		return -ENOMEM;
+
+	/* execvp() doesn't work for external mount helpers here */
+	err = execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+	if (err < 0) {
+		perror("failed to execute /bin/sh");
+		return -errno;
+	}
+	return 0;
+}
+
+#define EROFSMOUNT_LOOPDEV_RETRIES	3
+
+int erofsmount_loopmount(const char *source, const char *mountpoint,
+			 const char *fstype, int flags,
+			 const char *options)
+{
+	int fd, dfd, num;
+	struct loop_info li = {};
+	bool ro = flags & MS_RDONLY;
+	char device[32];
+
+	fd = open("/dev/loop-control", O_RDWR | O_CLOEXEC);
+	if (fd < 0)
+		return -errno;
+
+	num = ioctl(fd, LOOP_CTL_GET_FREE);
+	if (num < 0)
+		return -errno;
+	close(fd);
+
+	snprintf(device, sizeof(device), "/dev/loop%d", num);
+	for (num = 0; num < EROFSMOUNT_LOOPDEV_RETRIES; ++num) {
+		fd = open(device, (ro ? O_RDONLY : O_RDWR) | O_CLOEXEC);
+		if (fd >= 0)
+			break;
+		usleep(50000);
+	}
+	if (fd < 0)
+		return -errno;
+
+	dfd = open(source, (ro ? O_RDONLY : O_RDWR));
+	if (dfd < 0)
+		goto out_err;
+
+	num = ioctl(fd, LOOP_SET_FD, dfd);
+	if (num < 0) {
+		close(dfd);
+		goto out_err;
+	}
+	close(dfd);
+
+	li.lo_flags = LO_FLAGS_AUTOCLEAR;
+	num = ioctl(fd, LOOP_SET_STATUS, &li);
+	if (num < 0)
+		goto out_err;
+	num = mount(device, mountpoint, fstype, flags, options);
+	if (num < 0)
+		goto out_err;
+	close(fd);
+	return 0;
+out_err:
+	close(fd);
+	return -errno;
+}
+
+int main(int argc, char *argv[])
+{
+	int err;
+
+	erofs_init_configure();
+	err = erofsmount_parse_options(argc, argv);
+	if (err) {
+		if (err == -EINVAL)
+			fprintf(stderr, "Try '%s --help' for more information.\n", argv[0]);
+		return EXIT_FAILURE;
+	}
+
+	if (mountcfg.backend == EROFSFUSE) {
+		err = erofsmount_fuse(mountcfg.device, mountcfg.mountpoint,
+				      mountcfg.fstype, mountcfg.full_options);
+		goto exit;
+	}
+
+	err = mount(mountcfg.device, mountcfg.mountpoint, mountcfg.fstype,
+		    mountcfg.flags, mountcfg.options);
+	if (err < 0)
+		err = -errno;
+
+	if ((err == -ENODEV || err == -EPERM) && mountcfg.backend == EROFSAUTO)
+		err = erofsmount_fuse(mountcfg.device, mountcfg.mountpoint,
+				      mountcfg.fstype, mountcfg.full_options);
+	else if (err == -ENOTBLK)
+		err = erofsmount_loopmount(mountcfg.device, mountcfg.mountpoint,
+					   mountcfg.fstype, mountcfg.flags,
+					   mountcfg.options);
+exit:
+	if (err < 0)
+		fprintf(stderr, "Failed to mount %s: %s\n",
+			mountcfg.fstype, erofs_strerror(err));
+	return err ? EXIT_FAILURE : EXIT_SUCCESS;
+}
-- 
2.43.0



More information about the Linux-erofs mailing list