* [PATCH v3 0/7] virtiofsd: Announce submounts to the guest
@ 2020-11-02 16:18 Max Reitz
2020-11-02 16:18 ` [PATCH v3 1/7] virtiofsd: Check FUSE_SUBMOUNTS Max Reitz
` (7 more replies)
0 siblings, 8 replies; 13+ messages in thread
From: Max Reitz @ 2020-11-02 16:18 UTC (permalink / raw)
To: qemu-devel
Cc: Miklos Szeredi, Eduardo Habkost, Dr . David Alan Gilbert,
Max Reitz, virtio-fs, Stefan Hajnoczi
RFC: https://www.redhat.com/archives/virtio-fs/2020-May/msg00024.html
v1: https://lists.nongnu.org/archive/html/qemu-devel/2020-09/msg03598.html
v2: https://lists.nongnu.org/archive/html/qemu-devel/2020-10/msg09117.html
Branch: https://github.com/XanClic/qemu.git virtiofs-submounts-v4
Branch: https://git.xanclic.moe/XanClic/qemu.git virtiofs-submounts-v4
Hi,
In an effort to cut this cover letter shorter, I’ll defer to the cover
letter of v2 linked above concerning the general description of this
series, and limit myself to describing the differences from v2:
v3:
- Patch 7:
- Replace specific has_passwordless_sudo() function by a generic
has_cmd() function that can check for any shell command whether it
works or not
- Let this function catch exceptions, too, so if sudo is not present
altogether, the test doesn’t fail but is skipped as intended
[Eduardo]
- (Add has_cmds() to run multiple instances of has_cmd() in a
decorator)
- Skip the test not only if “sudo -n true” doesn’t work, but also
check for ssh-keygen, bash, losetup, mkfs.xfs, and mount, which are
some other tools we need that might not be present on the host
git-backport-diff against v2:
Key:
[----] : patches are identical
[####] : number of functional differences between upstream/downstream patch
[down] : patch is downstream-only
The flags [FC] indicate (F)unctional and (C)ontextual differences, respectively
001/7:[----] [--] 'virtiofsd: Check FUSE_SUBMOUNTS'
002/7:[----] [--] 'virtiofsd: Add attr_flags to fuse_entry_param'
003/7:[----] [--] 'meson.build: Check for statx()'
004/7:[----] [--] 'virtiofsd: Add mount ID to the lo_inode key'
005/7:[----] [--] 'virtiofsd: Announce sub-mount points'
006/7:[----] [--] 'tests/acceptance/boot_linux: Accept SSH pubkey'
007/7:[0042] [FC] 'tests/acceptance: Add virtiofs_submounts.py'
Max Reitz (7):
virtiofsd: Check FUSE_SUBMOUNTS
virtiofsd: Add attr_flags to fuse_entry_param
meson.build: Check for statx()
virtiofsd: Add mount ID to the lo_inode key
virtiofsd: Announce sub-mount points
tests/acceptance/boot_linux: Accept SSH pubkey
tests/acceptance: Add virtiofs_submounts.py
meson.build | 16 +
tools/virtiofsd/fuse_common.h | 7 +
tools/virtiofsd/fuse_lowlevel.h | 5 +
tools/virtiofsd/fuse_lowlevel.c | 5 +
tools/virtiofsd/helper.c | 1 +
tools/virtiofsd/passthrough_ll.c | 117 ++++++-
tools/virtiofsd/passthrough_seccomp.c | 1 +
tests/acceptance/boot_linux.py | 13 +-
tests/acceptance/virtiofs_submounts.py | 321 ++++++++++++++++++
.../virtiofs_submounts.py.data/cleanup.sh | 46 +++
.../guest-cleanup.sh | 30 ++
.../virtiofs_submounts.py.data/guest.sh | 138 ++++++++
.../virtiofs_submounts.py.data/host.sh | 127 +++++++
13 files changed, 811 insertions(+), 16 deletions(-)
create mode 100644 tests/acceptance/virtiofs_submounts.py
create mode 100644 tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
create mode 100644 tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
create mode 100644 tests/acceptance/virtiofs_submounts.py.data/guest.sh
create mode 100644 tests/acceptance/virtiofs_submounts.py.data/host.sh
--
2.26.2
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH v3 1/7] virtiofsd: Check FUSE_SUBMOUNTS
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
@ 2020-11-02 16:18 ` Max Reitz
2020-11-02 16:18 ` [PATCH v3 2/7] virtiofsd: Add attr_flags to fuse_entry_param Max Reitz
` (6 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Max Reitz @ 2020-11-02 16:18 UTC (permalink / raw)
To: qemu-devel
Cc: Miklos Szeredi, Eduardo Habkost, Dr . David Alan Gilbert,
Max Reitz, virtio-fs, Stefan Hajnoczi
FUSE_SUBMOUNTS is a pure indicator by the kernel to signal that it
supports submounts. It does not check its state in the init reply, so
there is nothing for fuse_lowlevel.c to do but to check its existence
and copy it into fuse_conn_info.capable.
Signed-off-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
tools/virtiofsd/fuse_common.h | 7 +++++++
tools/virtiofsd/fuse_lowlevel.c | 3 +++
2 files changed, 10 insertions(+)
diff --git a/tools/virtiofsd/fuse_common.h b/tools/virtiofsd/fuse_common.h
index 686c42c0a5..5aee5193eb 100644
--- a/tools/virtiofsd/fuse_common.h
+++ b/tools/virtiofsd/fuse_common.h
@@ -352,6 +352,13 @@ struct fuse_file_info {
*/
#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
+/**
+ * Indicates that the kernel supports the FUSE_ATTR_SUBMOUNT flag.
+ *
+ * Setting (or unsetting) this flag in the `want` field has *no effect*.
+ */
+#define FUSE_CAP_SUBMOUNTS (1 << 27)
+
/**
* Ioctl flags
*
diff --git a/tools/virtiofsd/fuse_lowlevel.c b/tools/virtiofsd/fuse_lowlevel.c
index 4d1ba2925d..370222339b 100644
--- a/tools/virtiofsd/fuse_lowlevel.c
+++ b/tools/virtiofsd/fuse_lowlevel.c
@@ -1988,6 +1988,9 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid,
bufsize = max_bufsize;
}
}
+ if (arg->flags & FUSE_SUBMOUNTS) {
+ se->conn.capable |= FUSE_CAP_SUBMOUNTS;
+ }
#ifdef HAVE_SPLICE
#ifdef HAVE_VMSPLICE
se->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE;
--
2.26.2
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 2/7] virtiofsd: Add attr_flags to fuse_entry_param
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
2020-11-02 16:18 ` [PATCH v3 1/7] virtiofsd: Check FUSE_SUBMOUNTS Max Reitz
@ 2020-11-02 16:18 ` Max Reitz
2020-11-02 16:18 ` [PATCH v3 3/7] meson.build: Check for statx() Max Reitz
` (5 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Max Reitz @ 2020-11-02 16:18 UTC (permalink / raw)
To: qemu-devel
Cc: Miklos Szeredi, Eduardo Habkost, Dr . David Alan Gilbert,
Max Reitz, virtio-fs, Stefan Hajnoczi
fuse_entry_param is converted to fuse_attr on the line (by
fill_entry()), so it should have a member that mirrors fuse_attr.flags.
fill_entry() should then copy this fuse_entry_param.attr_flags to
fuse_attr.flags.
Signed-off-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
tools/virtiofsd/fuse_lowlevel.h | 5 +++++
tools/virtiofsd/fuse_lowlevel.c | 2 ++
2 files changed, 7 insertions(+)
diff --git a/tools/virtiofsd/fuse_lowlevel.h b/tools/virtiofsd/fuse_lowlevel.h
index 562fd5241e..9c06240f9e 100644
--- a/tools/virtiofsd/fuse_lowlevel.h
+++ b/tools/virtiofsd/fuse_lowlevel.h
@@ -102,6 +102,11 @@ struct fuse_entry_param {
* large value.
*/
double entry_timeout;
+
+ /**
+ * Flags for fuse_attr.flags that do not fit into attr.
+ */
+ uint32_t attr_flags;
};
/**
diff --git a/tools/virtiofsd/fuse_lowlevel.c b/tools/virtiofsd/fuse_lowlevel.c
index 370222339b..c70fb16a9a 100644
--- a/tools/virtiofsd/fuse_lowlevel.c
+++ b/tools/virtiofsd/fuse_lowlevel.c
@@ -341,6 +341,8 @@ static void fill_entry(struct fuse_entry_out *arg,
.attr_valid_nsec = calc_timeout_nsec(e->attr_timeout),
};
convert_stat(&e->attr, &arg->attr);
+
+ arg->attr.flags = e->attr_flags;
}
/*
--
2.26.2
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 3/7] meson.build: Check for statx()
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
2020-11-02 16:18 ` [PATCH v3 1/7] virtiofsd: Check FUSE_SUBMOUNTS Max Reitz
2020-11-02 16:18 ` [PATCH v3 2/7] virtiofsd: Add attr_flags to fuse_entry_param Max Reitz
@ 2020-11-02 16:18 ` Max Reitz
2020-11-02 16:18 ` [PATCH v3 4/7] virtiofsd: Add mount ID to the lo_inode key Max Reitz
` (4 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Max Reitz @ 2020-11-02 16:18 UTC (permalink / raw)
To: qemu-devel
Cc: Miklos Szeredi, Eduardo Habkost, Dr . David Alan Gilbert,
Max Reitz, virtio-fs, Stefan Hajnoczi
Check whether the glibc provides statx() and if so, define CONFIG_STATX.
Signed-off-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
meson.build | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/meson.build b/meson.build
index 47e32e1fcb..39ac5cf6d8 100644
--- a/meson.build
+++ b/meson.build
@@ -736,6 +736,21 @@ if not has_malloc_trim and get_option('malloc_trim').enabled()
endif
endif
+# Check whether the glibc provides statx()
+
+statx_test = '''
+ #ifndef _GNU_SOURCE
+ #define _GNU_SOURCE
+ #endif
+ #include <sys/stat.h>
+ int main(void) {
+ struct statx statxbuf;
+ statx(0, "", 0, STATX_BASIC_STATS, &statxbuf);
+ return 0;
+ }'''
+
+has_statx = cc.links(statx_test)
+
#################
# config-host.h #
#################
@@ -768,6 +783,7 @@ config_host_data.set('CONFIG_XKBCOMMON', xkbcommon.found())
config_host_data.set('CONFIG_KEYUTILS', keyutils.found())
config_host_data.set('CONFIG_GETTID', has_gettid)
config_host_data.set('CONFIG_MALLOC_TRIM', has_malloc_trim)
+config_host_data.set('CONFIG_STATX', has_statx)
config_host_data.set('QEMU_VERSION', '"@0@"'.format(meson.project_version()))
config_host_data.set('QEMU_VERSION_MAJOR', meson.project_version().split('.')[0])
config_host_data.set('QEMU_VERSION_MINOR', meson.project_version().split('.')[1])
--
2.26.2
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 4/7] virtiofsd: Add mount ID to the lo_inode key
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
` (2 preceding siblings ...)
2020-11-02 16:18 ` [PATCH v3 3/7] meson.build: Check for statx() Max Reitz
@ 2020-11-02 16:18 ` Max Reitz
2020-11-02 16:18 ` [PATCH v3 5/7] virtiofsd: Announce sub-mount points Max Reitz
` (3 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Max Reitz @ 2020-11-02 16:18 UTC (permalink / raw)
To: qemu-devel
Cc: Miklos Szeredi, Eduardo Habkost, Dr . David Alan Gilbert,
Max Reitz, virtio-fs, Stefan Hajnoczi
Using st_dev is not sufficient to uniquely identify a mount: You can
mount the same device twice, but those are still separate trees, and
e.g. by mounting something else inside one of them, they may differ.
Using statx(), we can get a mount ID that uniquely identifies a mount.
If that is available, add it to the lo_inode key.
Most of this patch is taken from Miklos's mail here:
https://marc.info/?l=fuse-devel&m=160062521827983
(virtiofsd-use-mount-id.patch attachment)
Suggested-by: Miklos Szeredi <mszeredi@redhat.com>
Signed-off-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
tools/virtiofsd/passthrough_ll.c | 95 ++++++++++++++++++++++++---
tools/virtiofsd/passthrough_seccomp.c | 1 +
2 files changed, 86 insertions(+), 10 deletions(-)
diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
index a0beb986f3..34d107975f 100644
--- a/tools/virtiofsd/passthrough_ll.c
+++ b/tools/virtiofsd/passthrough_ll.c
@@ -94,6 +94,7 @@ struct lo_map {
struct lo_key {
ino_t ino;
dev_t dev;
+ uint64_t mnt_id;
};
struct lo_inode {
@@ -166,6 +167,7 @@ struct lo_data {
int readdirplus_set;
int readdirplus_clear;
int allow_direct_io;
+ bool use_statx;
struct lo_inode root;
GHashTable *inodes; /* protected by lo->mutex */
struct lo_map ino_map; /* protected by lo->mutex */
@@ -219,7 +221,8 @@ static struct {
/* That we loaded cap-ng in the current thread from the saved */
static __thread bool cap_loaded = 0;
-static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st);
+static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st,
+ uint64_t mnt_id);
static int is_dot_or_dotdot(const char *name)
{
@@ -741,12 +744,14 @@ out_err:
fuse_reply_err(req, saverr);
}
-static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st,
+ uint64_t mnt_id)
{
struct lo_inode *p;
struct lo_key key = {
.ino = st->st_ino,
.dev = st->st_dev,
+ .mnt_id = mnt_id,
};
pthread_mutex_lock(&lo->mutex);
@@ -774,6 +779,60 @@ static void posix_locks_value_destroy(gpointer data)
free(plock);
}
+static int do_statx(struct lo_data *lo, int dirfd, const char *pathname,
+ struct stat *statbuf, int flags, uint64_t *mnt_id)
+{
+ int res;
+
+#if defined(CONFIG_STATX) && defined(STATX_MNT_ID)
+ if (lo->use_statx) {
+ struct statx statxbuf;
+
+ res = statx(dirfd, pathname, flags, STATX_BASIC_STATS | STATX_MNT_ID,
+ &statxbuf);
+ if (!res) {
+ memset(statbuf, 0, sizeof(*statbuf));
+ statbuf->st_dev = makedev(statxbuf.stx_dev_major,
+ statxbuf.stx_dev_minor);
+ statbuf->st_ino = statxbuf.stx_ino;
+ statbuf->st_mode = statxbuf.stx_mode;
+ statbuf->st_nlink = statxbuf.stx_nlink;
+ statbuf->st_uid = statxbuf.stx_uid;
+ statbuf->st_gid = statxbuf.stx_gid;
+ statbuf->st_rdev = makedev(statxbuf.stx_rdev_major,
+ statxbuf.stx_rdev_minor);
+ statbuf->st_size = statxbuf.stx_size;
+ statbuf->st_blksize = statxbuf.stx_blksize;
+ statbuf->st_blocks = statxbuf.stx_blocks;
+ statbuf->st_atim.tv_sec = statxbuf.stx_atime.tv_sec;
+ statbuf->st_atim.tv_nsec = statxbuf.stx_atime.tv_nsec;
+ statbuf->st_mtim.tv_sec = statxbuf.stx_mtime.tv_sec;
+ statbuf->st_mtim.tv_nsec = statxbuf.stx_mtime.tv_nsec;
+ statbuf->st_ctim.tv_sec = statxbuf.stx_ctime.tv_sec;
+ statbuf->st_ctim.tv_nsec = statxbuf.stx_ctime.tv_nsec;
+
+ if (statxbuf.stx_mask & STATX_MNT_ID) {
+ *mnt_id = statxbuf.stx_mnt_id;
+ } else {
+ *mnt_id = 0;
+ }
+ return 0;
+ } else if (errno != ENOSYS) {
+ return -1;
+ }
+ lo->use_statx = false;
+ /* fallback */
+ }
+#endif
+ res = fstatat(dirfd, pathname, statbuf, flags);
+ if (res == -1) {
+ return -1;
+ }
+ *mnt_id = 0;
+
+ return 0;
+}
+
/*
* Increments nlookup and caller must release refcount using
* lo_inode_put(&parent).
@@ -784,6 +843,7 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
int newfd;
int res;
int saverr;
+ uint64_t mnt_id;
struct lo_data *lo = lo_data(req);
struct lo_inode *inode = NULL;
struct lo_inode *dir = lo_inode(req, parent);
@@ -811,12 +871,13 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
goto out_err;
}
- res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+ res = do_statx(lo, newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
+ &mnt_id);
if (res == -1) {
goto out_err;
}
- inode = lo_find(lo, &e->attr);
+ inode = lo_find(lo, &e->attr, mnt_id);
if (inode) {
close(newfd);
} else {
@@ -838,6 +899,7 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
inode->fd = newfd;
inode->key.ino = e->attr.st_ino;
inode->key.dev = e->attr.st_dev;
+ inode->key.mnt_id = mnt_id;
pthread_mutex_init(&inode->plock_mutex, NULL);
inode->posix_locks = g_hash_table_new_full(
g_direct_hash, g_direct_equal, NULL, posix_locks_value_destroy);
@@ -1090,15 +1152,23 @@ static struct lo_inode *lookup_name(fuse_req_t req, fuse_ino_t parent,
const char *name)
{
int res;
+ uint64_t mnt_id;
struct stat attr;
+ struct lo_data *lo = lo_data(req);
+ struct lo_inode *dir = lo_inode(req, parent);
- res = fstatat(lo_fd(req, parent), name, &attr,
- AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+ if (!dir) {
+ return NULL;
+ }
+
+ res = do_statx(lo, dir->fd, name, &attr,
+ AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW, &mnt_id);
+ lo_inode_put(lo, &dir);
if (res == -1) {
return NULL;
}
- return lo_find(lo_data(req), &attr);
+ return lo_find(lo, &attr, mnt_id);
}
static void lo_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
@@ -3266,6 +3336,7 @@ static void setup_root(struct lo_data *lo, struct lo_inode *root)
{
int fd, res;
struct stat stat;
+ uint64_t mnt_id;
fd = open("/", O_PATH);
if (fd == -1) {
@@ -3273,7 +3344,8 @@ static void setup_root(struct lo_data *lo, struct lo_inode *root)
exit(1);
}
- res = fstatat(fd, "", &stat, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+ res = do_statx(lo, fd, "", &stat, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW,
+ &mnt_id);
if (res == -1) {
fuse_log(FUSE_LOG_ERR, "fstatat(%s): %m\n", lo->source);
exit(1);
@@ -3283,6 +3355,7 @@ static void setup_root(struct lo_data *lo, struct lo_inode *root)
root->fd = fd;
root->key.ino = stat.st_ino;
root->key.dev = stat.st_dev;
+ root->key.mnt_id = mnt_id;
root->nlookup = 2;
g_atomic_int_set(&root->refcount, 2);
}
@@ -3291,7 +3364,7 @@ static guint lo_key_hash(gconstpointer key)
{
const struct lo_key *lkey = key;
- return (guint)lkey->ino + (guint)lkey->dev;
+ return (guint)lkey->ino + (guint)lkey->dev + (guint)lkey->mnt_id;
}
static gboolean lo_key_equal(gconstpointer a, gconstpointer b)
@@ -3299,7 +3372,7 @@ static gboolean lo_key_equal(gconstpointer a, gconstpointer b)
const struct lo_key *la = a;
const struct lo_key *lb = b;
- return la->ino == lb->ino && la->dev == lb->dev;
+ return la->ino == lb->ino && la->dev == lb->dev && la->mnt_id == lb->mnt_id;
}
static void fuse_lo_data_cleanup(struct lo_data *lo)
@@ -3445,6 +3518,8 @@ int main(int argc, char *argv[])
exit(1);
}
+ lo.use_statx = true;
+
se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
if (se == NULL) {
goto err_out1;
diff --git a/tools/virtiofsd/passthrough_seccomp.c b/tools/virtiofsd/passthrough_seccomp.c
index eb9af8265f..751e15fa39 100644
--- a/tools/virtiofsd/passthrough_seccomp.c
+++ b/tools/virtiofsd/passthrough_seccomp.c
@@ -76,6 +76,7 @@ static const int syscall_whitelist[] = {
SCMP_SYS(mremap),
SCMP_SYS(munmap),
SCMP_SYS(newfstatat),
+ SCMP_SYS(statx),
SCMP_SYS(open),
SCMP_SYS(openat),
SCMP_SYS(ppoll),
--
2.26.2
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 5/7] virtiofsd: Announce sub-mount points
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
` (3 preceding siblings ...)
2020-11-02 16:18 ` [PATCH v3 4/7] virtiofsd: Add mount ID to the lo_inode key Max Reitz
@ 2020-11-02 16:18 ` Max Reitz
2020-11-03 8:10 ` Miklos Szeredi
2020-11-02 16:18 ` [PATCH v3 6/7] tests/acceptance/boot_linux: Accept SSH pubkey Max Reitz
` (2 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Max Reitz @ 2020-11-02 16:18 UTC (permalink / raw)
To: qemu-devel
Cc: Miklos Szeredi, Eduardo Habkost, Dr . David Alan Gilbert,
Max Reitz, virtio-fs, Stefan Hajnoczi
Whenever we encounter a directory with an st_dev or mount ID that
differs from that of its parent, we set the FUSE_ATTR_SUBMOUNT flag so
the guest can create a submount for it.
We only need to do so in lo_do_lookup(). The following functions return
a fuse_attr object:
- lo_create(), though fuse_reply_create(): Calls lo_do_lookup().
- lo_lookup(), though fuse_reply_entry(): Calls lo_do_lookup().
- lo_mknod_symlink(), through fuse_reply_entry(): Calls lo_do_lookup().
- lo_link(), through fuse_reply_entry(): Creating a link cannot create a
submount, so there is no need to check for it.
- lo_getattr(), through fuse_reply_attr(): Announcing submounts when the
node is first detected (at lookup) is sufficient. We do not need to
return the submount attribute later.
- lo_do_readdir(), through fuse_add_direntry_plus(): Calls
lo_do_lookup().
Make announcing submounts optional, so submounts are only announced to
the guest with the announce_submounts option. Some users may prefer the
current behavior, so that the guest learns nothing about the host mount
structure.
(announce_submounts is force-disabled when the guest does not present
the FUSE_SUBMOUNTS capability, or when there is no statx().)
Signed-off-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
tools/virtiofsd/helper.c | 1 +
tools/virtiofsd/passthrough_ll.c | 22 ++++++++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/tools/virtiofsd/helper.c b/tools/virtiofsd/helper.c
index 2e181a49b5..4433724d3a 100644
--- a/tools/virtiofsd/helper.c
+++ b/tools/virtiofsd/helper.c
@@ -190,6 +190,7 @@ void fuse_cmdline_help(void)
" retain/discard O_DIRECT flags passed down\n"
" to virtiofsd from guest applications.\n"
" default: no_allow_direct_io\n"
+ " -o announce_submounts Announce sub-mount points to the guest\n"
);
}
diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
index 34d107975f..ec1008bceb 100644
--- a/tools/virtiofsd/passthrough_ll.c
+++ b/tools/virtiofsd/passthrough_ll.c
@@ -40,6 +40,7 @@
#include "fuse_virtio.h"
#include "fuse_log.h"
#include "fuse_lowlevel.h"
+#include "standard-headers/linux/fuse.h"
#include <assert.h>
#include <cap-ng.h>
#include <dirent.h>
@@ -167,6 +168,7 @@ struct lo_data {
int readdirplus_set;
int readdirplus_clear;
int allow_direct_io;
+ int announce_submounts;
bool use_statx;
struct lo_inode root;
GHashTable *inodes; /* protected by lo->mutex */
@@ -207,6 +209,7 @@ static const struct fuse_opt lo_opts[] = {
{ "no_readdirplus", offsetof(struct lo_data, readdirplus_clear), 1 },
{ "allow_direct_io", offsetof(struct lo_data, allow_direct_io), 1 },
{ "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 },
+ { "announce_submounts", offsetof(struct lo_data, announce_submounts), 1 },
FUSE_OPT_END
};
static bool use_syslog = false;
@@ -601,6 +604,20 @@ static void lo_init(void *userdata, struct fuse_conn_info *conn)
fuse_log(FUSE_LOG_DEBUG, "lo_init: disabling readdirplus\n");
conn->want &= ~FUSE_CAP_READDIRPLUS;
}
+
+ if (!(conn->capable & FUSE_CAP_SUBMOUNTS) && lo->announce_submounts) {
+ fuse_log(FUSE_LOG_WARNING, "lo_init: Cannot announce submounts, client "
+ "does not support it\n");
+ lo->announce_submounts = false;
+ }
+
+#ifndef CONFIG_STATX
+ if (lo->announce_submounts) {
+ fuse_log(FUSE_LOG_WARNING, "lo_init: Cannot announce submounts, there "
+ "is no statx()\n");
+ lo->announce_submounts = false;
+ }
+#endif
}
static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
@@ -877,6 +894,11 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
goto out_err;
}
+ if (S_ISDIR(e->attr.st_mode) && lo->announce_submounts &&
+ (e->attr.st_dev != dir->key.dev || mnt_id != dir->key.mnt_id)) {
+ e->attr_flags |= FUSE_ATTR_SUBMOUNT;
+ }
+
inode = lo_find(lo, &e->attr, mnt_id);
if (inode) {
close(newfd);
--
2.26.2
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 6/7] tests/acceptance/boot_linux: Accept SSH pubkey
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
` (4 preceding siblings ...)
2020-11-02 16:18 ` [PATCH v3 5/7] virtiofsd: Announce sub-mount points Max Reitz
@ 2020-11-02 16:18 ` Max Reitz
2020-11-02 16:18 ` [PATCH v3 7/7] tests/acceptance: Add virtiofs_submounts.py Max Reitz
2020-11-03 8:13 ` [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Miklos Szeredi
7 siblings, 0 replies; 13+ messages in thread
From: Max Reitz @ 2020-11-02 16:18 UTC (permalink / raw)
To: qemu-devel
Cc: Miklos Szeredi, Eduardo Habkost, Dr . David Alan Gilbert,
Max Reitz, virtio-fs, Stefan Hajnoczi
Let download_cloudinit() take an optional pubkey, which subclasses of
BootLinux can pass through setUp().
Signed-off-by: Max Reitz <mreitz@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Willian Rampazzo <willianr@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
tests/acceptance/boot_linux.py | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/tests/acceptance/boot_linux.py b/tests/acceptance/boot_linux.py
index c743e231f4..1da4a53d6a 100644
--- a/tests/acceptance/boot_linux.py
+++ b/tests/acceptance/boot_linux.py
@@ -57,7 +57,7 @@ class BootLinuxBase(Test):
self.cancel('Failed to download/prepare boot image')
return boot.path
- def download_cloudinit(self):
+ def download_cloudinit(self, ssh_pubkey=None):
self.log.info('Preparing cloudinit image')
try:
cloudinit_iso = os.path.join(self.workdir, 'cloudinit.iso')
@@ -67,7 +67,8 @@ class BootLinuxBase(Test):
password='password',
# QEMU's hard coded usermode router address
phone_home_host='10.0.2.2',
- phone_home_port=self.phone_home_port)
+ phone_home_port=self.phone_home_port,
+ authorized_key=ssh_pubkey)
except Exception:
self.cancel('Failed to prepared cloudinit image')
return cloudinit_iso
@@ -80,19 +81,19 @@ class BootLinux(BootLinuxBase):
timeout = 900
chksum = None
- def setUp(self):
+ def setUp(self, ssh_pubkey=None):
super(BootLinux, self).setUp()
self.vm.add_args('-smp', '2')
self.vm.add_args('-m', '1024')
self.prepare_boot()
- self.prepare_cloudinit()
+ self.prepare_cloudinit(ssh_pubkey)
def prepare_boot(self):
path = self.download_boot()
self.vm.add_args('-drive', 'file=%s' % path)
- def prepare_cloudinit(self):
- cloudinit_iso = self.download_cloudinit()
+ def prepare_cloudinit(self, ssh_pubkey=None):
+ cloudinit_iso = self.download_cloudinit(ssh_pubkey)
self.vm.add_args('-drive', 'file=%s,format=raw' % cloudinit_iso)
def launch_and_wait(self):
--
2.26.2
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH v3 7/7] tests/acceptance: Add virtiofs_submounts.py
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
` (5 preceding siblings ...)
2020-11-02 16:18 ` [PATCH v3 6/7] tests/acceptance/boot_linux: Accept SSH pubkey Max Reitz
@ 2020-11-02 16:18 ` Max Reitz
2020-11-02 18:55 ` Eduardo Habkost
2020-11-03 8:13 ` [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Miklos Szeredi
7 siblings, 1 reply; 13+ messages in thread
From: Max Reitz @ 2020-11-02 16:18 UTC (permalink / raw)
To: qemu-devel
Cc: Miklos Szeredi, Eduardo Habkost, Dr . David Alan Gilbert,
Max Reitz, virtio-fs, Stefan Hajnoczi
This test invokes several shell scripts to create a random directory
tree full of submounts, and then check in the VM whether every submount
has its own ID and the structure looks as expected.
(Note that the test scripts must be non-executable, so Avocado will not
try to execute them as if they were tests on their own, too.)
Because at this commit's date it is unlikely that the Linux kernel on
the image provided by boot_linux.py supports submounts in virtio-fs, the
test will be cancelled if no custom Linux binary is provided through the
vmlinuz parameter. (The on-image kernel can be used by providing an
empty string via vmlinuz=.)
So, invoking the test can be done as follows:
$ avocado run \
tests/acceptance/virtiofs_submounts.py \
-p vmlinuz=/path/to/linux/build/arch/x86/boot/bzImage
This test requires root privileges (through passwordless sudo -n),
because at this point, virtiofsd requires them. (If you have a
timestamp_timeout period for sudoers (e.g. the default of 5 min), you
can provide this by executing something like "sudo true" before invoking
Avocado.)
Signed-off-by: Max Reitz <mreitz@redhat.com>
---
tests/acceptance/virtiofs_submounts.py | 321 ++++++++++++++++++
.../virtiofs_submounts.py.data/cleanup.sh | 46 +++
.../guest-cleanup.sh | 30 ++
.../virtiofs_submounts.py.data/guest.sh | 138 ++++++++
.../virtiofs_submounts.py.data/host.sh | 127 +++++++
5 files changed, 662 insertions(+)
create mode 100644 tests/acceptance/virtiofs_submounts.py
create mode 100644 tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
create mode 100644 tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
create mode 100644 tests/acceptance/virtiofs_submounts.py.data/guest.sh
create mode 100644 tests/acceptance/virtiofs_submounts.py.data/host.sh
diff --git a/tests/acceptance/virtiofs_submounts.py b/tests/acceptance/virtiofs_submounts.py
new file mode 100644
index 0000000000..361e5990b6
--- /dev/null
+++ b/tests/acceptance/virtiofs_submounts.py
@@ -0,0 +1,321 @@
+import logging
+import re
+import os
+import subprocess
+import time
+
+from avocado import skipUnless
+from avocado_qemu import Test, BUILD_DIR
+from avocado_qemu import wait_for_console_pattern
+from avocado.utils import ssh
+
+from qemu.accel import kvm_available
+
+from boot_linux import BootLinux
+
+
+def run_cmd(args):
+ subp = subprocess.Popen(args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True)
+ stdout, stderr = subp.communicate()
+ ret = subp.returncode
+
+ return (stdout, stderr, ret)
+
+def has_cmd(name, args=None):
+ """
+ This function is for use in a @avocado.skipUnless decorator, e.g.:
+
+ @skipUnless(*has_cmd('sudo -n', ('sudo', '-n', 'true')))
+ def test_something_that_needs_sudo(self):
+ ...
+ """
+
+ if args is None:
+ args = ('which', name)
+
+ try:
+ _, stderr, exitcode = run_cmd(args)
+ except Exception as e:
+ exitcode = -1
+ stderr = str(e)
+
+ if exitcode != 0:
+ cmd_line = ' '.join(args)
+ err = f'{name} required, but "{cmd_line}" failed: {stderr.strip()}'
+ return (False, err)
+ else:
+ return (True, '')
+
+def has_cmds(*cmds):
+ """
+ This function is for use in a @avocado.skipUnless decorator and
+ allows checking for the availability of multiple commands, e.g.:
+
+ @skipUnless(*has_cmds(('cmd1', ('cmd1', '--some-parameter')),
+ 'cmd2', 'cmd3'))
+ def test_something_that_needs_cmd1_and_cmd2(self):
+ ...
+ """
+
+ for cmd in cmds:
+ if isinstance(cmd, str):
+ cmd = (cmd,)
+
+ ok, errstr = has_cmd(*cmd)
+ if not ok:
+ return (False, errstr)
+
+ return (True, '')
+
+
+class VirtiofsSubmountsTest(BootLinux):
+ """
+ :avocado: tags=arch:x86_64
+ """
+
+ def get_portfwd(self):
+ port = None
+
+ res = self.vm.command('human-monitor-command',
+ command_line='info usernet')
+ for line in res.split('\r\n'):
+ match = \
+ re.search(r'TCP.HOST_FORWARD.*127\.0\.0\.1\s*(\d+)\s+10\.',
+ line)
+ if match is not None:
+ port = match[1]
+ break
+
+ self.assertIsNotNone(port)
+ self.log.debug('sshd listening on port: ' + port)
+ return port
+
+ def ssh_connect(self, username, keyfile):
+ self.ssh_logger = logging.getLogger('ssh')
+ port = self.get_portfwd()
+ self.ssh_session = ssh.Session('127.0.0.1', port=int(port),
+ user=username, key=keyfile)
+ for i in range(10):
+ try:
+ self.ssh_session.connect()
+ return
+ except:
+ time.sleep(4)
+ pass
+ self.fail('sshd timeout')
+
+ def ssh_command(self, command):
+ self.ssh_logger.info(command)
+ result = self.ssh_session.cmd(command)
+ stdout_lines = [line.rstrip() for line
+ in result.stdout_text.splitlines()]
+ for line in stdout_lines:
+ self.ssh_logger.info(line)
+ stderr_lines = [line.rstrip() for line
+ in result.stderr_text.splitlines()]
+ for line in stderr_lines:
+ self.ssh_logger.warning(line)
+
+ self.assertEqual(result.exit_status, 0,
+ f'Guest command failed: {command}')
+ return stdout_lines, stderr_lines
+
+ def run(self, args, ignore_error=False):
+ stdout, stderr, ret = run_cmd(args)
+
+ if ret != 0:
+ cmdline = ' '.join(args)
+ if not ignore_error:
+ self.fail(f'{cmdline}: Returned {ret}: {stderr}')
+ else:
+ self.log.warn(f'{cmdline}: Returned {ret}: {stderr}')
+
+ return (stdout, stderr, ret)
+
+ def set_up_shared_dir(self):
+ atwd = os.getenv('AVOCADO_TEST_WORKDIR')
+ self.shared_dir = os.path.join(atwd, 'virtiofs-shared')
+
+ os.mkdir(self.shared_dir)
+
+ self.run(('cp', self.get_data('guest.sh'),
+ os.path.join(self.shared_dir, 'check.sh')))
+
+ self.run(('cp', self.get_data('guest-cleanup.sh'),
+ os.path.join(self.shared_dir, 'cleanup.sh')))
+
+ def set_up_virtiofs(self):
+ attmp = os.getenv('AVOCADO_TESTS_COMMON_TMPDIR')
+ self.vfsdsock = os.path.join(attmp, 'vfsdsock')
+
+ self.run(('sudo', '-n', 'rm', '-f', self.vfsdsock), ignore_error=True)
+
+ self.virtiofsd = \
+ subprocess.Popen(('sudo', '-n',
+ 'tools/virtiofsd/virtiofsd',
+ f'--socket-path={self.vfsdsock}',
+ '-o', f'source={self.shared_dir}',
+ '-o', 'cache=always',
+ '-o', 'xattr',
+ '-o', 'announce_submounts',
+ '-f'),
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.PIPE,
+ universal_newlines=True)
+
+ while not os.path.exists(self.vfsdsock):
+ if self.virtiofsd.poll() is not None:
+ self.fail('virtiofsd exited prematurely: ' +
+ self.virtiofsd.communicate()[1])
+ time.sleep(0.1)
+
+ self.run(('sudo', '-n', 'chmod', 'go+rw', self.vfsdsock))
+
+ self.vm.add_args('-chardev',
+ f'socket,id=vfsdsock,path={self.vfsdsock}',
+ '-device',
+ 'vhost-user-fs-pci,queue-size=1024,chardev=vfsdsock' \
+ ',tag=host',
+ '-object',
+ 'memory-backend-file,id=mem,size=1G,' \
+ 'mem-path=/dev/shm,share=on',
+ '-numa',
+ 'node,memdev=mem')
+
+ def launch_vm(self):
+ self.launch_and_wait()
+ self.ssh_connect('root', self.ssh_key)
+
+ def set_up_nested_mounts(self):
+ scratch_dir = os.path.join(self.shared_dir, 'scratch')
+ try:
+ os.mkdir(scratch_dir)
+ except FileExistsError:
+ pass
+
+ args = ['bash', self.get_data('host.sh'), scratch_dir]
+ if self.seed:
+ args += [self.seed]
+
+ out, _, _ = self.run(args)
+ seed = re.search(r'^Seed: \d+', out)
+ self.log.info(seed[0])
+
+ def mount_in_guest(self):
+ self.ssh_command('mkdir -p /mnt/host')
+ self.ssh_command('mount -t virtiofs host /mnt/host')
+
+ def check_in_guest(self):
+ self.ssh_command('bash /mnt/host/check.sh /mnt/host/scratch/share')
+
+ def live_cleanup(self):
+ self.ssh_command('bash /mnt/host/cleanup.sh /mnt/host/scratch')
+
+ # It would be nice if the above was sufficient to make virtiofsd clear
+ # all references to the mounted directories (so they can be unmounted
+ # on the host), but unfortunately it is not. To do so, we have to
+ # resort to a remount.
+ self.ssh_command('mount -o remount /mnt/host')
+
+ scratch_dir = os.path.join(self.shared_dir, 'scratch')
+ self.run(('bash', self.get_data('cleanup.sh'), scratch_dir))
+
+ @skipUnless(*has_cmds(('sudo -n', ('sudo', '-n', 'true')),
+ 'ssh-keygen', 'bash', 'losetup', 'mkfs.xfs', 'mount'))
+ def setUp(self):
+ vmlinuz = self.params.get('vmlinuz')
+ if vmlinuz is None:
+ self.cancel('vmlinuz parameter not set; you must point it to a '
+ 'Linux kernel binary to test (to run this test with ' \
+ 'the on-image kernel, set it to an empty string)')
+
+ self.seed = self.params.get('seed')
+
+ atwd = os.getenv('AVOCADO_TEST_WORKDIR')
+ self.ssh_key = os.path.join(atwd, 'id_ed25519')
+
+ self.run(('ssh-keygen', '-t', 'ed25519', '-f', self.ssh_key))
+
+ pubkey = open(self.ssh_key + '.pub').read()
+
+ super(VirtiofsSubmountsTest, self).setUp(pubkey)
+
+ if len(vmlinuz) > 0:
+ self.vm.add_args('-kernel', vmlinuz,
+ '-append', 'console=ttyS0 root=/dev/sda1')
+
+ # Allow us to connect to SSH
+ self.vm.add_args('-netdev', 'user,id=vnet,hostfwd=:127.0.0.1:0-:22',
+ '-device', 'e1000,netdev=vnet')
+
+ if not kvm_available(self.arch, self.qemu_bin):
+ self.cancel(KVM_NOT_AVAILABLE)
+ self.vm.add_args('-accel', 'kvm')
+
+ def tearDown(self):
+ try:
+ self.vm.shutdown()
+ except:
+ pass
+
+ scratch_dir = os.path.join(self.shared_dir, 'scratch')
+ self.run(('bash', self.get_data('cleanup.sh'), scratch_dir),
+ ignore_error=True)
+
+ def test_pre_virtiofsd_set_up(self):
+ self.set_up_shared_dir()
+
+ self.set_up_nested_mounts()
+
+ self.set_up_virtiofs()
+ self.launch_vm()
+ self.mount_in_guest()
+ self.check_in_guest()
+
+ def test_pre_launch_set_up(self):
+ self.set_up_shared_dir()
+ self.set_up_virtiofs()
+
+ self.set_up_nested_mounts()
+
+ self.launch_vm()
+ self.mount_in_guest()
+ self.check_in_guest()
+
+ def test_post_launch_set_up(self):
+ self.set_up_shared_dir()
+ self.set_up_virtiofs()
+ self.launch_vm()
+
+ self.set_up_nested_mounts()
+
+ self.mount_in_guest()
+ self.check_in_guest()
+
+ def test_post_mount_set_up(self):
+ self.set_up_shared_dir()
+ self.set_up_virtiofs()
+ self.launch_vm()
+ self.mount_in_guest()
+
+ self.set_up_nested_mounts()
+
+ self.check_in_guest()
+
+ def test_two_runs(self):
+ self.set_up_shared_dir()
+
+ self.set_up_nested_mounts()
+
+ self.set_up_virtiofs()
+ self.launch_vm()
+ self.mount_in_guest()
+ self.check_in_guest()
+
+ self.live_cleanup()
+ self.set_up_nested_mounts()
+
+ self.check_in_guest()
diff --git a/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh b/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
new file mode 100644
index 0000000000..2a6579a0fe
--- /dev/null
+++ b/tests/acceptance/virtiofs_submounts.py.data/cleanup.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+function print_usage()
+{
+ if [ -n "$2" ]; then
+ echo "Error: $2"
+ echo
+ fi
+ echo "Usage: $1 <scratch dir>"
+}
+
+scratch_dir=$1
+if [ -z "$scratch_dir" ]; then
+ print_usage "$0" 'Scratch dir not given' >&2
+ exit 1
+fi
+
+cd "$scratch_dir/share" || exit 1
+mps=(mnt*)
+mp_i=0
+for mp in "${mps[@]}"; do
+ mp_i=$((mp_i + 1))
+ printf "Unmounting %i/%i...\r" "$mp_i" "${#mps[@]}"
+
+ sudo umount -R "$mp"
+ rm -rf "$mp"
+done
+echo
+
+rm some-file
+cd ..
+rmdir share
+
+imgs=(fs*.img)
+img_i=0
+for img in "${imgs[@]}"; do
+ img_i=$((img_i + 1))
+ printf "Detaching and deleting %i/%i...\r" "$img_i" "${#imgs[@]}"
+
+ dev=$(losetup -j "$img" | sed -e 's/:.*//')
+ sudo losetup -d "$dev"
+ rm -f "$img"
+done
+echo
+
+echo 'Done.'
diff --git a/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh b/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
new file mode 100644
index 0000000000..729cb2d1a5
--- /dev/null
+++ b/tests/acceptance/virtiofs_submounts.py.data/guest-cleanup.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+function print_usage()
+{
+ if [ -n "$2" ]; then
+ echo "Error: $2"
+ echo
+ fi
+ echo "Usage: $1 <scratch dir>"
+}
+
+scratch_dir=$1
+if [ -z "$scratch_dir" ]; then
+ print_usage "$0" 'Scratch dir not given' >&2
+ exit 1
+fi
+
+cd "$scratch_dir/share" || exit 1
+
+mps=(mnt*)
+mp_i=0
+for mp in "${mps[@]}"; do
+ mp_i=$((mp_i + 1))
+ printf "Unmounting %i/%i...\r" "$mp_i" "${#mps[@]}"
+
+ sudo umount -R "$mp"
+done
+echo
+
+echo 'Done.'
diff --git a/tests/acceptance/virtiofs_submounts.py.data/guest.sh b/tests/acceptance/virtiofs_submounts.py.data/guest.sh
new file mode 100644
index 0000000000..59ba40fde1
--- /dev/null
+++ b/tests/acceptance/virtiofs_submounts.py.data/guest.sh
@@ -0,0 +1,138 @@
+#!/bin/bash
+
+function print_usage()
+{
+ if [ -n "$2" ]; then
+ echo "Error: $2"
+ echo
+ fi
+ echo "Usage: $1 <shared dir>"
+ echo '(The shared directory is the "share" directory in the scratch' \
+ 'directory)'
+}
+
+shared_dir=$1
+if [ -z "$shared_dir" ]; then
+ print_usage "$0" 'Shared dir not given' >&2
+ exit 1
+fi
+
+cd "$shared_dir"
+
+# FIXME: This should not be necessary, but it is. In order for all
+# submounts to be proper mount points, we need to visit them.
+# (Before we visit them, they will not be auto-mounted, and so just
+# appear as normal directories, with the catch that their st_ino will
+# be the st_ino of the filesystem they host, while the st_dev will
+# still be the st_dev of the parent.)
+# `find` does not work, because it will refuse to touch the mount
+# points as long as they are not mounted; their st_dev being shared
+# with the parent and st_ino just being the root node's inode ID
+# will practically ensure that this node exists elsewhere on the
+# filesystem, and `find` is required to recognize loops and not to
+# follow them.
+# Thus, we have to manually visit all nodes first.
+
+mnt_i=0
+
+function recursively_visit()
+{
+ pushd "$1" >/dev/null
+ for entry in *; do
+ if [[ "$entry" == mnt* ]]; then
+ mnt_i=$((mnt_i + 1))
+ printf "Triggering auto-mount $mnt_i...\r"
+ fi
+
+ if [ -d "$entry" ]; then
+ recursively_visit "$entry"
+ fi
+ done
+ popd >/dev/null
+}
+
+recursively_visit .
+echo
+
+
+if [ -n "$(find -name not-mounted)" ]; then
+ echo "Error: not-mounted files visible on mount points:" >&2
+ find -name not-mounted >&2
+ exit 1
+fi
+
+if [ ! -f some-file -o "$(cat some-file)" != 'root' ]; then
+ echo "Error: Bad file in the share root" >&2
+ exit 1
+fi
+
+shopt -s nullglob
+
+function check_submounts()
+{
+ local base_path=$1
+
+ for mp in mnt*; do
+ printf "Checking submount %i...\r" "$((${#devs[@]} + 1))"
+
+ mp_i=$(echo "$mp" | sed -e 's/mnt//')
+ dev=$(stat -c '%D' "$mp")
+
+ if [ -n "${devs[mp_i]}" ]; then
+ echo "Error: $mp encountered twice" >&2
+ exit 1
+ fi
+ devs[mp_i]=$dev
+
+ pushd "$mp" >/dev/null
+ path="$base_path$mp"
+ while true; do
+ expected_content="$(printf '%s\n%s\n' "$mp_i" "$path")"
+ if [ ! -f some-file ]; then
+ echo "Error: $PWD/some-file does not exist" >&2
+ exit 1
+ fi
+
+ if [ "$(cat some-file)" != "$expected_content" ]; then
+ echo "Error: Bad content in $PWD/some-file:" >&2
+ echo '--- found ---'
+ cat some-file
+ echo '--- expected ---'
+ echo "$expected_content"
+ exit 1
+ fi
+ if [ "$(stat -c '%D' some-file)" != "$dev" ]; then
+ echo "Error: $PWD/some-file has the wrong device ID" >&2
+ exit 1
+ fi
+
+ if [ -d sub ]; then
+ if [ "$(stat -c '%D' sub)" != "$dev" ]; then
+ echo "Error: $PWD/some-file has the wrong device ID" >&2
+ exit 1
+ fi
+ cd sub
+ path="$path/sub"
+ else
+ if [ -n "$(echo mnt*)" ]; then
+ check_submounts "$path/"
+ fi
+ break
+ fi
+ done
+ popd >/dev/null
+ done
+}
+
+root_dev=$(stat -c '%D' some-file)
+devs=()
+check_submounts ''
+echo
+
+reused_devs=$(echo "$root_dev ${devs[@]}" | tr ' ' '\n' | sort | uniq -d)
+if [ -n "$reused_devs" ]; then
+ echo "Error: Reused device IDs: $reused_devs" >&2
+ exit 1
+fi
+
+echo "Test passed for ${#devs[@]} submounts."
diff --git a/tests/acceptance/virtiofs_submounts.py.data/host.sh b/tests/acceptance/virtiofs_submounts.py.data/host.sh
new file mode 100644
index 0000000000..d8a9afebdb
--- /dev/null
+++ b/tests/acceptance/virtiofs_submounts.py.data/host.sh
@@ -0,0 +1,127 @@
+#!/bin/bash
+
+mount_count=128
+
+function print_usage()
+{
+ if [ -n "$2" ]; then
+ echo "Error: $2"
+ echo
+ fi
+ echo "Usage: $1 <scratch dir> [seed]"
+ echo "(If no seed is given, it will be randomly generated.)"
+}
+
+scratch_dir=$1
+if [ -z "$scratch_dir" ]; then
+ print_usage "$0" 'No scratch dir given' >&2
+ exit 1
+fi
+
+if [ ! -d "$scratch_dir" ]; then
+ print_usage "$0" "$scratch_dir is not a directory" >&2
+ exit 1
+fi
+
+seed=$2
+if [ -z "$seed" ]; then
+ seed=$RANDOM
+fi
+RANDOM=$seed
+
+echo "Seed: $seed"
+
+set -e
+shopt -s nullglob
+
+cd "$scratch_dir"
+if [ -d share ]; then
+ echo 'Error: This directory seems to be in use already' >&2
+ exit 1
+fi
+
+for ((i = 0; i < $mount_count; i++)); do
+ printf "Setting up fs %i/%i...\r" "$((i + 1))" "$mount_count"
+
+ rm -f fs$i.img
+ truncate -s 512M fs$i.img
+ mkfs.xfs -q fs$i.img
+ devs[i]=$(sudo losetup -f --show fs$i.img)
+done
+echo
+
+top_level_mounts=$((RANDOM % mount_count + 1))
+
+mkdir -p share
+echo 'root' > share/some-file
+
+for ((i = 0; i < $top_level_mounts; i++)); do
+ printf "Mounting fs %i/%i...\r" "$((i + 1))" "$mount_count"
+
+ mkdir -p share/mnt$i
+ touch share/mnt$i/not-mounted
+ sudo mount "${devs[i]}" share/mnt$i
+ sudo chown "$(id -u):$(id -g)" share/mnt$i
+
+ pushd share/mnt$i >/dev/null
+ path=mnt$i
+ nesting=$((RANDOM % 4))
+ for ((j = 0; j < $nesting; j++)); do
+ cat > some-file <<EOF
+$i
+$path
+EOF
+ mkdir sub
+ cd sub
+ path="$path/sub"
+ done
+cat > some-file <<EOF
+$i
+$path
+EOF
+ popd >/dev/null
+done
+
+for ((; i < $mount_count; i++)); do
+ printf "Mounting fs %i/%i...\r" "$((i + 1))" "$mount_count"
+
+ mp_i=$((i % top_level_mounts))
+
+ pushd share/mnt$mp_i >/dev/null
+ path=mnt$mp_i
+ while true; do
+ sub_mp="$(echo mnt*)"
+ if cd sub 2>/dev/null; then
+ path="$path/sub"
+ elif [ -n "$sub_mp" ] && cd "$sub_mp" 2>/dev/null; then
+ path="$path/$sub_mp"
+ else
+ break
+ fi
+ done
+ mkdir mnt$i
+ touch mnt$i/not-mounted
+ sudo mount "${devs[i]}" mnt$i
+ sudo chown "$(id -u):$(id -g)" mnt$i
+
+ cd mnt$i
+ path="$path/mnt$i"
+ nesting=$((RANDOM % 4))
+ for ((j = 0; j < $nesting; j++)); do
+ cat > some-file <<EOF
+$i
+$path
+EOF
+ mkdir sub
+ cd sub
+ path="$path/sub"
+ done
+ cat > some-file <<EOF
+$i
+$path
+EOF
+ popd >/dev/null
+done
+echo
+
+echo 'Done.'
--
2.26.2
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH v3 7/7] tests/acceptance: Add virtiofs_submounts.py
2020-11-02 16:18 ` [PATCH v3 7/7] tests/acceptance: Add virtiofs_submounts.py Max Reitz
@ 2020-11-02 18:55 ` Eduardo Habkost
0 siblings, 0 replies; 13+ messages in thread
From: Eduardo Habkost @ 2020-11-02 18:55 UTC (permalink / raw)
To: Max Reitz
Cc: virtio-fs, Miklos Szeredi, qemu-devel, Stefan Hajnoczi,
Dr . David Alan Gilbert
On Mon, Nov 02, 2020 at 05:18:59PM +0100, Max Reitz wrote:
> This test invokes several shell scripts to create a random directory
> tree full of submounts, and then check in the VM whether every submount
> has its own ID and the structure looks as expected.
>
> (Note that the test scripts must be non-executable, so Avocado will not
> try to execute them as if they were tests on their own, too.)
>
> Because at this commit's date it is unlikely that the Linux kernel on
> the image provided by boot_linux.py supports submounts in virtio-fs, the
> test will be cancelled if no custom Linux binary is provided through the
> vmlinuz parameter. (The on-image kernel can be used by providing an
> empty string via vmlinuz=.)
>
> So, invoking the test can be done as follows:
> $ avocado run \
> tests/acceptance/virtiofs_submounts.py \
> -p vmlinuz=/path/to/linux/build/arch/x86/boot/bzImage
>
> This test requires root privileges (through passwordless sudo -n),
> because at this point, virtiofsd requires them. (If you have a
> timestamp_timeout period for sudoers (e.g. the default of 5 min), you
> can provide this by executing something like "sudo true" before invoking
> Avocado.)
>
> Signed-off-by: Max Reitz <mreitz@redhat.com>
Fixes the issue detected in v3.
Tested-by: Eduardo Habkost <ehabkost@redhat.com>
--
Eduardo
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 5/7] virtiofsd: Announce sub-mount points
2020-11-02 16:18 ` [PATCH v3 5/7] virtiofsd: Announce sub-mount points Max Reitz
@ 2020-11-03 8:10 ` Miklos Szeredi
2020-11-03 9:00 ` Max Reitz
0 siblings, 1 reply; 13+ messages in thread
From: Miklos Szeredi @ 2020-11-03 8:10 UTC (permalink / raw)
To: Max Reitz
Cc: virtio-fs-list, Eduardo Habkost, QEMU Developers, Stefan Hajnoczi,
Dr . David Alan Gilbert
On Mon, Nov 2, 2020 at 5:19 PM Max Reitz <mreitz@redhat.com> wrote:
>
> Whenever we encounter a directory with an st_dev or mount ID that
> differs from that of its parent, we set the FUSE_ATTR_SUBMOUNT flag so
> the guest can create a submount for it.
>
> We only need to do so in lo_do_lookup(). The following functions return
> a fuse_attr object:
> - lo_create(), though fuse_reply_create(): Calls lo_do_lookup().
> - lo_lookup(), though fuse_reply_entry(): Calls lo_do_lookup().
> - lo_mknod_symlink(), through fuse_reply_entry(): Calls lo_do_lookup().
> - lo_link(), through fuse_reply_entry(): Creating a link cannot create a
> submount, so there is no need to check for it.
> - lo_getattr(), through fuse_reply_attr(): Announcing submounts when the
> node is first detected (at lookup) is sufficient. We do not need to
> return the submount attribute later.
> - lo_do_readdir(), through fuse_add_direntry_plus(): Calls
> lo_do_lookup().
>
> Make announcing submounts optional, so submounts are only announced to
> the guest with the announce_submounts option. Some users may prefer the
> current behavior, so that the guest learns nothing about the host mount
> structure.
>
> (announce_submounts is force-disabled when the guest does not present
> the FUSE_SUBMOUNTS capability, or when there is no statx().)
>
> Signed-off-by: Max Reitz <mreitz@redhat.com>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
> tools/virtiofsd/helper.c | 1 +
> tools/virtiofsd/passthrough_ll.c | 22 ++++++++++++++++++++++
> 2 files changed, 23 insertions(+)
>
> diff --git a/tools/virtiofsd/helper.c b/tools/virtiofsd/helper.c
> index 2e181a49b5..4433724d3a 100644
> --- a/tools/virtiofsd/helper.c
> +++ b/tools/virtiofsd/helper.c
> @@ -190,6 +190,7 @@ void fuse_cmdline_help(void)
> " retain/discard O_DIRECT flags passed down\n"
> " to virtiofsd from guest applications.\n"
> " default: no_allow_direct_io\n"
> + " -o announce_submounts Announce sub-mount points to the guest\n"
> );
> }
>
> diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
> index 34d107975f..ec1008bceb 100644
> --- a/tools/virtiofsd/passthrough_ll.c
> +++ b/tools/virtiofsd/passthrough_ll.c
> @@ -40,6 +40,7 @@
> #include "fuse_virtio.h"
> #include "fuse_log.h"
> #include "fuse_lowlevel.h"
> +#include "standard-headers/linux/fuse.h"
> #include <assert.h>
> #include <cap-ng.h>
> #include <dirent.h>
> @@ -167,6 +168,7 @@ struct lo_data {
> int readdirplus_set;
> int readdirplus_clear;
> int allow_direct_io;
> + int announce_submounts;
> bool use_statx;
> struct lo_inode root;
> GHashTable *inodes; /* protected by lo->mutex */
> @@ -207,6 +209,7 @@ static const struct fuse_opt lo_opts[] = {
> { "no_readdirplus", offsetof(struct lo_data, readdirplus_clear), 1 },
> { "allow_direct_io", offsetof(struct lo_data, allow_direct_io), 1 },
> { "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 },
> + { "announce_submounts", offsetof(struct lo_data, announce_submounts), 1 },
> FUSE_OPT_END
> };
> static bool use_syslog = false;
> @@ -601,6 +604,20 @@ static void lo_init(void *userdata, struct fuse_conn_info *conn)
> fuse_log(FUSE_LOG_DEBUG, "lo_init: disabling readdirplus\n");
> conn->want &= ~FUSE_CAP_READDIRPLUS;
> }
> +
> + if (!(conn->capable & FUSE_CAP_SUBMOUNTS) && lo->announce_submounts) {
> + fuse_log(FUSE_LOG_WARNING, "lo_init: Cannot announce submounts, client "
> + "does not support it\n");
> + lo->announce_submounts = false;
> + }
> +
> +#ifndef CONFIG_STATX
> + if (lo->announce_submounts) {
> + fuse_log(FUSE_LOG_WARNING, "lo_init: Cannot announce submounts, there "
> + "is no statx()\n");
> + lo->announce_submounts = false;
Minor issue: this warns only when libc doesn't have statx, and not
when kernel doesn't have it.
> + }
> +#endif
> }
>
> static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
> @@ -877,6 +894,11 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
> goto out_err;
> }
>
> + if (S_ISDIR(e->attr.st_mode) && lo->announce_submounts &&
> + (e->attr.st_dev != dir->key.dev || mnt_id != dir->key.mnt_id)) {
> + e->attr_flags |= FUSE_ATTR_SUBMOUNT;
> + }
... and since announce_submounts isn't turned off in case the kernel
doesn't have STATX_MNT_ID, this will trigger a submount when crossing
into a different filesystem.
Possible solutions:
a) test and warn at startup in case kernel doesn't have statx
b) do not test st_dev, only mnt_id (which will always be zero if not supported)
c) merge announce_submounts and use_statx, which are basically doing
the same thing
> +
> inode = lo_find(lo, &e->attr, mnt_id);
> if (inode) {
> close(newfd);
> --
> 2.26.2
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 0/7] virtiofsd: Announce submounts to the guest
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
` (6 preceding siblings ...)
2020-11-02 16:18 ` [PATCH v3 7/7] tests/acceptance: Add virtiofs_submounts.py Max Reitz
@ 2020-11-03 8:13 ` Miklos Szeredi
7 siblings, 0 replies; 13+ messages in thread
From: Miklos Szeredi @ 2020-11-03 8:13 UTC (permalink / raw)
To: Max Reitz
Cc: virtio-fs-list, Eduardo Habkost, QEMU Developers, Stefan Hajnoczi,
Dr . David Alan Gilbert
On Mon, Nov 2, 2020 at 5:19 PM Max Reitz <mreitz@redhat.com> wrote:
>
> RFC: https://www.redhat.com/archives/virtio-fs/2020-May/msg00024.html
> v1: https://lists.nongnu.org/archive/html/qemu-devel/2020-09/msg03598.html
> v2: https://lists.nongnu.org/archive/html/qemu-devel/2020-10/msg09117.html
>
> Branch: https://github.com/XanClic/qemu.git virtiofs-submounts-v4
> Branch: https://git.xanclic.moe/XanClic/qemu.git virtiofs-submounts-v4
>
>
> Hi,
>
> In an effort to cut this cover letter shorter, I’ll defer to the cover
> letter of v2 linked above concerning the general description of this
> series, and limit myself to describing the differences from v2:
Other than the issues in 5/7:
Reviewed-by: Miklos Szeredi <mszeredi@redhat.com>
Thanks,
Miklos
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 5/7] virtiofsd: Announce sub-mount points
2020-11-03 8:10 ` Miklos Szeredi
@ 2020-11-03 9:00 ` Max Reitz
2020-11-03 9:14 ` Miklos Szeredi
0 siblings, 1 reply; 13+ messages in thread
From: Max Reitz @ 2020-11-03 9:00 UTC (permalink / raw)
To: Miklos Szeredi
Cc: virtio-fs-list, Eduardo Habkost, QEMU Developers, Stefan Hajnoczi,
Dr . David Alan Gilbert
On 03.11.20 09:10, Miklos Szeredi wrote:
> On Mon, Nov 2, 2020 at 5:19 PM Max Reitz <mreitz@redhat.com> wrote:
>>
>> Whenever we encounter a directory with an st_dev or mount ID that
>> differs from that of its parent, we set the FUSE_ATTR_SUBMOUNT flag so
>> the guest can create a submount for it.
>>
>> We only need to do so in lo_do_lookup(). The following functions return
>> a fuse_attr object:
>> - lo_create(), though fuse_reply_create(): Calls lo_do_lookup().
>> - lo_lookup(), though fuse_reply_entry(): Calls lo_do_lookup().
>> - lo_mknod_symlink(), through fuse_reply_entry(): Calls lo_do_lookup().
>> - lo_link(), through fuse_reply_entry(): Creating a link cannot create a
>> submount, so there is no need to check for it.
>> - lo_getattr(), through fuse_reply_attr(): Announcing submounts when the
>> node is first detected (at lookup) is sufficient. We do not need to
>> return the submount attribute later.
>> - lo_do_readdir(), through fuse_add_direntry_plus(): Calls
>> lo_do_lookup().
>>
>> Make announcing submounts optional, so submounts are only announced to
>> the guest with the announce_submounts option. Some users may prefer the
>> current behavior, so that the guest learns nothing about the host mount
>> structure.
>>
>> (announce_submounts is force-disabled when the guest does not present
>> the FUSE_SUBMOUNTS capability, or when there is no statx().)
>>
>> Signed-off-by: Max Reitz <mreitz@redhat.com>
>> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
>> ---
>> tools/virtiofsd/helper.c | 1 +
>> tools/virtiofsd/passthrough_ll.c | 22 ++++++++++++++++++++++
>> 2 files changed, 23 insertions(+)
>>
>> diff --git a/tools/virtiofsd/helper.c b/tools/virtiofsd/helper.c
>> index 2e181a49b5..4433724d3a 100644
>> --- a/tools/virtiofsd/helper.c
>> +++ b/tools/virtiofsd/helper.c
>> @@ -190,6 +190,7 @@ void fuse_cmdline_help(void)
>> " retain/discard O_DIRECT flags passed down\n"
>> " to virtiofsd from guest applications.\n"
>> " default: no_allow_direct_io\n"
>> + " -o announce_submounts Announce sub-mount points to the guest\n"
>> );
>> }
>>
>> diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
>> index 34d107975f..ec1008bceb 100644
>> --- a/tools/virtiofsd/passthrough_ll.c
>> +++ b/tools/virtiofsd/passthrough_ll.c
>> @@ -40,6 +40,7 @@
>> #include "fuse_virtio.h"
>> #include "fuse_log.h"
>> #include "fuse_lowlevel.h"
>> +#include "standard-headers/linux/fuse.h"
>> #include <assert.h>
>> #include <cap-ng.h>
>> #include <dirent.h>
>> @@ -167,6 +168,7 @@ struct lo_data {
>> int readdirplus_set;
>> int readdirplus_clear;
>> int allow_direct_io;
>> + int announce_submounts;
>> bool use_statx;
>> struct lo_inode root;
>> GHashTable *inodes; /* protected by lo->mutex */
>> @@ -207,6 +209,7 @@ static const struct fuse_opt lo_opts[] = {
>> { "no_readdirplus", offsetof(struct lo_data, readdirplus_clear), 1 },
>> { "allow_direct_io", offsetof(struct lo_data, allow_direct_io), 1 },
>> { "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 },
>> + { "announce_submounts", offsetof(struct lo_data, announce_submounts), 1 },
>> FUSE_OPT_END
>> };
>> static bool use_syslog = false;
>> @@ -601,6 +604,20 @@ static void lo_init(void *userdata, struct fuse_conn_info *conn)
>> fuse_log(FUSE_LOG_DEBUG, "lo_init: disabling readdirplus\n");
>> conn->want &= ~FUSE_CAP_READDIRPLUS;
>> }
>> +
>> + if (!(conn->capable & FUSE_CAP_SUBMOUNTS) && lo->announce_submounts) {
>> + fuse_log(FUSE_LOG_WARNING, "lo_init: Cannot announce submounts, client "
>> + "does not support it\n");
>> + lo->announce_submounts = false;
>> + }
>> +
>> +#ifndef CONFIG_STATX
>> + if (lo->announce_submounts) {
>> + fuse_log(FUSE_LOG_WARNING, "lo_init: Cannot announce submounts, there "
>> + "is no statx()\n");
>> + lo->announce_submounts = false;
>
> Minor issue: this warns only when libc doesn't have statx, and not
> when kernel doesn't have it.
>
>> + }
>> +#endif
>> }
>>
>> static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
>> @@ -877,6 +894,11 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
>> goto out_err;
>> }
>>
>> + if (S_ISDIR(e->attr.st_mode) && lo->announce_submounts &&
>> + (e->attr.st_dev != dir->key.dev || mnt_id != dir->key.mnt_id)) {
>> + e->attr_flags |= FUSE_ATTR_SUBMOUNT;
>> + }
>
> ... and since announce_submounts isn't turned off in case the kernel
> doesn't have STATX_MNT_ID, this will trigger a submount when crossing
> into a different filesystem.
Oh. Yes. I totally forgot that when writing the warning.
> Possible solutions:
>
> a) test and warn at startup in case kernel doesn't have statx
>
> b) do not test st_dev, only mnt_id (which will always be zero if not supported)
>
> c) merge announce_submounts and use_statx, which are basically doing
> the same thing
Isn’t that a single thing? I.e., if we decide not to test st_dev, then
we should do all of these, I think.
OTOH, we could also just drop the warning (that statx()) isn’t
available, because as the code is, we can still announce submounts. The
only problem is that we’ll suffer from the bug fixed by patch 4
(different mounts with the same st_dev being treated as the same tree),
but that’s unrelated to announcing submounts.
(Well, to be fair, not having statx() does break one thing about
submounts: I suppose you could mount a device inside of its own mount
(“mount $mount_opts $dir; mount $mount_opts $dir/sub” – then $dir/sub
wouldn’t be marked as a submount without statx()), but that probably
yields a host of other problems besides not announcing the nested mount
as a submount (virtiofsd would treat $dir/sub as the same node as $dir,
I think), so again, I wouldn’t worry too much about not getting the
FUSE_SUBMOUNT flag right.)
So I think I’d rather just drop the warning and leave the rest as it is.
Not least because STATX_MNT_ID is rather new.
Max
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH v3 5/7] virtiofsd: Announce sub-mount points
2020-11-03 9:00 ` Max Reitz
@ 2020-11-03 9:14 ` Miklos Szeredi
0 siblings, 0 replies; 13+ messages in thread
From: Miklos Szeredi @ 2020-11-03 9:14 UTC (permalink / raw)
To: Max Reitz
Cc: virtio-fs-list, Eduardo Habkost, QEMU Developers, Stefan Hajnoczi,
Dr . David Alan Gilbert
On Tue, Nov 3, 2020 at 10:00 AM Max Reitz <mreitz@redhat.com> wrote:
>
> On 03.11.20 09:10, Miklos Szeredi wrote:
> > On Mon, Nov 2, 2020 at 5:19 PM Max Reitz <mreitz@redhat.com> wrote:
> >>
> >> Whenever we encounter a directory with an st_dev or mount ID that
> >> differs from that of its parent, we set the FUSE_ATTR_SUBMOUNT flag so
> >> the guest can create a submount for it.
> >>
> >> We only need to do so in lo_do_lookup(). The following functions return
> >> a fuse_attr object:
> >> - lo_create(), though fuse_reply_create(): Calls lo_do_lookup().
> >> - lo_lookup(), though fuse_reply_entry(): Calls lo_do_lookup().
> >> - lo_mknod_symlink(), through fuse_reply_entry(): Calls lo_do_lookup().
> >> - lo_link(), through fuse_reply_entry(): Creating a link cannot create a
> >> submount, so there is no need to check for it.
> >> - lo_getattr(), through fuse_reply_attr(): Announcing submounts when the
> >> node is first detected (at lookup) is sufficient. We do not need to
> >> return the submount attribute later.
> >> - lo_do_readdir(), through fuse_add_direntry_plus(): Calls
> >> lo_do_lookup().
> >>
> >> Make announcing submounts optional, so submounts are only announced to
> >> the guest with the announce_submounts option. Some users may prefer the
> >> current behavior, so that the guest learns nothing about the host mount
> >> structure.
> >>
> >> (announce_submounts is force-disabled when the guest does not present
> >> the FUSE_SUBMOUNTS capability, or when there is no statx().)
> >>
> >> Signed-off-by: Max Reitz <mreitz@redhat.com>
> >> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> >> ---
> >> tools/virtiofsd/helper.c | 1 +
> >> tools/virtiofsd/passthrough_ll.c | 22 ++++++++++++++++++++++
> >> 2 files changed, 23 insertions(+)
> >>
> >> diff --git a/tools/virtiofsd/helper.c b/tools/virtiofsd/helper.c
> >> index 2e181a49b5..4433724d3a 100644
> >> --- a/tools/virtiofsd/helper.c
> >> +++ b/tools/virtiofsd/helper.c
> >> @@ -190,6 +190,7 @@ void fuse_cmdline_help(void)
> >> " retain/discard O_DIRECT flags passed down\n"
> >> " to virtiofsd from guest applications.\n"
> >> " default: no_allow_direct_io\n"
> >> + " -o announce_submounts Announce sub-mount points to the guest\n"
> >> );
> >> }
> >>
> >> diff --git a/tools/virtiofsd/passthrough_ll.c b/tools/virtiofsd/passthrough_ll.c
> >> index 34d107975f..ec1008bceb 100644
> >> --- a/tools/virtiofsd/passthrough_ll.c
> >> +++ b/tools/virtiofsd/passthrough_ll.c
> >> @@ -40,6 +40,7 @@
> >> #include "fuse_virtio.h"
> >> #include "fuse_log.h"
> >> #include "fuse_lowlevel.h"
> >> +#include "standard-headers/linux/fuse.h"
> >> #include <assert.h>
> >> #include <cap-ng.h>
> >> #include <dirent.h>
> >> @@ -167,6 +168,7 @@ struct lo_data {
> >> int readdirplus_set;
> >> int readdirplus_clear;
> >> int allow_direct_io;
> >> + int announce_submounts;
> >> bool use_statx;
> >> struct lo_inode root;
> >> GHashTable *inodes; /* protected by lo->mutex */
> >> @@ -207,6 +209,7 @@ static const struct fuse_opt lo_opts[] = {
> >> { "no_readdirplus", offsetof(struct lo_data, readdirplus_clear), 1 },
> >> { "allow_direct_io", offsetof(struct lo_data, allow_direct_io), 1 },
> >> { "no_allow_direct_io", offsetof(struct lo_data, allow_direct_io), 0 },
> >> + { "announce_submounts", offsetof(struct lo_data, announce_submounts), 1 },
> >> FUSE_OPT_END
> >> };
> >> static bool use_syslog = false;
> >> @@ -601,6 +604,20 @@ static void lo_init(void *userdata, struct fuse_conn_info *conn)
> >> fuse_log(FUSE_LOG_DEBUG, "lo_init: disabling readdirplus\n");
> >> conn->want &= ~FUSE_CAP_READDIRPLUS;
> >> }
> >> +
> >> + if (!(conn->capable & FUSE_CAP_SUBMOUNTS) && lo->announce_submounts) {
> >> + fuse_log(FUSE_LOG_WARNING, "lo_init: Cannot announce submounts, client "
> >> + "does not support it\n");
> >> + lo->announce_submounts = false;
> >> + }
> >> +
> >> +#ifndef CONFIG_STATX
> >> + if (lo->announce_submounts) {
> >> + fuse_log(FUSE_LOG_WARNING, "lo_init: Cannot announce submounts, there "
> >> + "is no statx()\n");
> >> + lo->announce_submounts = false;
> >
> > Minor issue: this warns only when libc doesn't have statx, and not
> > when kernel doesn't have it.
> >
> >> + }
> >> +#endif
> >> }
> >>
> >> static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
> >> @@ -877,6 +894,11 @@ static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
> >> goto out_err;
> >> }
> >>
> >> + if (S_ISDIR(e->attr.st_mode) && lo->announce_submounts &&
> >> + (e->attr.st_dev != dir->key.dev || mnt_id != dir->key.mnt_id)) {
> >> + e->attr_flags |= FUSE_ATTR_SUBMOUNT;
> >> + }
> >
> > ... and since announce_submounts isn't turned off in case the kernel
> > doesn't have STATX_MNT_ID, this will trigger a submount when crossing
> > into a different filesystem.
>
> Oh. Yes. I totally forgot that when writing the warning.
>
> > Possible solutions:
> >
> > a) test and warn at startup in case kernel doesn't have statx
> >
> > b) do not test st_dev, only mnt_id (which will always be zero if not supported)
> >
> > c) merge announce_submounts and use_statx, which are basically doing
> > the same thing
>
> Isn’t that a single thing? I.e., if we decide not to test st_dev, then
> we should do all of these, I think.
>
> OTOH, we could also just drop the warning (that statx()) isn’t
> available, because as the code is, we can still announce submounts. The
> only problem is that we’ll suffer from the bug fixed by patch 4
> (different mounts with the same st_dev being treated as the same tree),
> but that’s unrelated to announcing submounts.
>
> (Well, to be fair, not having statx() does break one thing about
> submounts: I suppose you could mount a device inside of its own mount
> (“mount $mount_opts $dir; mount $mount_opts $dir/sub” – then $dir/sub
> wouldn’t be marked as a submount without statx()), but that probably
> yields a host of other problems besides not announcing the nested mount
> as a submount (virtiofsd would treat $dir/sub as the same node as $dir,
> I think), so again, I wouldn’t worry too much about not getting the
> FUSE_SUBMOUNT flag right.)
>
> So I think I’d rather just drop the warning and leave the rest as it is.
> Not least because STATX_MNT_ID is rather new.
Okay, makes sense. The nested bind mount case is likely not very
likely to turn up in real life, and if it does, submounts can still be
disabled explicitly.
Thanks,
Miklos
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2020-11-03 9:15 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2020-11-02 16:18 [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Max Reitz
2020-11-02 16:18 ` [PATCH v3 1/7] virtiofsd: Check FUSE_SUBMOUNTS Max Reitz
2020-11-02 16:18 ` [PATCH v3 2/7] virtiofsd: Add attr_flags to fuse_entry_param Max Reitz
2020-11-02 16:18 ` [PATCH v3 3/7] meson.build: Check for statx() Max Reitz
2020-11-02 16:18 ` [PATCH v3 4/7] virtiofsd: Add mount ID to the lo_inode key Max Reitz
2020-11-02 16:18 ` [PATCH v3 5/7] virtiofsd: Announce sub-mount points Max Reitz
2020-11-03 8:10 ` Miklos Szeredi
2020-11-03 9:00 ` Max Reitz
2020-11-03 9:14 ` Miklos Szeredi
2020-11-02 16:18 ` [PATCH v3 6/7] tests/acceptance/boot_linux: Accept SSH pubkey Max Reitz
2020-11-02 16:18 ` [PATCH v3 7/7] tests/acceptance: Add virtiofs_submounts.py Max Reitz
2020-11-02 18:55 ` Eduardo Habkost
2020-11-03 8:13 ` [PATCH v3 0/7] virtiofsd: Announce submounts to the guest Miklos Szeredi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).