Currently, kernel and FUSE mounts are supported for local images. Support for remote images will be added later.
Signed-off-by: Gao Xiang <hsiang...@linux.alibaba.com> --- v2: - fix missing <fcntl.h> Makefile.am | 2 +- configure.ac | 2 + include/erofs/err.h | 10 ++ include/erofs/internal.h | 11 -- mount/Makefile.am | 11 ++ mount/main.c | 289 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 313 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..0f7538a --- /dev/null +++ b/mount/main.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0+ +#define _GNU_SOURCE +#include <fcntl.h> +#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.5