|  | From fa2ff1a0da263362a2c98c8cfe68bcac3a323ac4 Mon Sep 17 00:00:00 2001 | 
|  | From: David Howells <dhowells@redhat.com> | 
|  | Date: Tue, 3 Mar 2020 14:29:27 +0000 | 
|  | Subject: [PATCH 4/4] fsinfo: Allow retrieval of superblock devname, options | 
|  | and stats | 
|  |  | 
|  | Provide fsinfo() attributes to retrieve superblock device name, options, | 
|  | and statistics in string form.  The following attributes are defined: | 
|  |  | 
|  | FSINFO_ATTR_SOURCE		- Mount-specific device name | 
|  | FSINFO_ATTR_CONFIGURATION	- Mount options | 
|  | FSINFO_ATTR_FS_STATISTICS	- Filesystem statistics | 
|  |  | 
|  | FSINFO_ATTR_SOURCE could be made indexable by params->Nth to handle the | 
|  | case where there is more than one source (e.g. the bcachefs filesystem). | 
|  |  | 
|  | Signed-off-by: David Howells <dhowells@redhat.com> | 
|  | --- | 
|  | fs/fsinfo.c                 | 41 +++++++++++++++++++++++++++++++++++++ | 
|  | fs/internal.h               |  2 ++ | 
|  | fs/namespace.c              | 39 +++++++++++++++++++++++++++++++++++ | 
|  | include/uapi/linux/fsinfo.h |  3 +++ | 
|  | samples/vfs/test-fsinfo.c   |  4 ++++ | 
|  | 5 files changed, 89 insertions(+) | 
|  |  | 
|  | diff --git a/fs/fsinfo.c b/fs/fsinfo.c | 
|  | index 1830c73f37a7..3ed43c5a10e8 100644 | 
|  | --- a/fs/fsinfo.c | 
|  | +++ b/fs/fsinfo.c | 
|  | @@ -188,6 +188,44 @@ static int fsinfo_generic_volume_id(struct path *path, struct fsinfo_context *ct | 
|  | return fsinfo_string(path->dentry->d_sb->s_id, ctx); | 
|  | } | 
|  |  | 
|  | +/* | 
|  | + * Retrieve the superblock configuration (mount options) as a comma-separated | 
|  | + * string.  The initial comma is stripped off. | 
|  | + */ | 
|  | +static int fsinfo_generic_seq_read(struct path *path, struct fsinfo_context *ctx) | 
|  | +{ | 
|  | +	struct super_block *sb = path->dentry->d_sb; | 
|  | +	struct seq_file m = { | 
|  | +		.buf	= ctx->buffer, | 
|  | +		.size	= ctx->buf_size, | 
|  | +	}; | 
|  | +	int ret = 0; | 
|  | + | 
|  | +	switch (ctx->requested_attr) { | 
|  | +	case FSINFO_ATTR_CONFIGURATION: | 
|  | +		if (sb->s_op->show_options) | 
|  | +			ret = sb->s_op->show_options(&m, path->mnt->mnt_root); | 
|  | +		break; | 
|  | + | 
|  | +	case FSINFO_ATTR_FS_STATISTICS: | 
|  | +		if (sb->s_op->show_stats) | 
|  | +			ret = sb->s_op->show_stats(&m, path->mnt->mnt_root); | 
|  | +		break; | 
|  | +	} | 
|  | + | 
|  | +	if (ret < 0) | 
|  | +		return ret; | 
|  | +	if (seq_has_overflowed(&m)) | 
|  | +		return ctx->buf_size + PAGE_SIZE; | 
|  | +	if (ctx->requested_attr == FSINFO_ATTR_CONFIGURATION) { | 
|  | +		if (m.count > 0 && ((char *)ctx->buffer)[0] == ',') { | 
|  | +			m.count--; | 
|  | +			ctx->skip = 1; | 
|  | +		} | 
|  | +	} | 
|  | +	return m.count; | 
|  | +} | 
|  | + | 
|  | static const struct fsinfo_attribute fsinfo_common_attributes[] = { | 
|  | FSINFO_VSTRUCT	(FSINFO_ATTR_STATFS,		fsinfo_generic_statfs), | 
|  | FSINFO_VSTRUCT	(FSINFO_ATTR_IDS,		fsinfo_generic_ids), | 
|  | @@ -196,6 +234,9 @@ static const struct fsinfo_attribute fsinfo_common_attributes[] = { | 
|  | FSINFO_VSTRUCT	(FSINFO_ATTR_TIMESTAMP_INFO,	fsinfo_generic_timestamp_info), | 
|  | FSINFO_STRING	(FSINFO_ATTR_VOLUME_ID,		fsinfo_generic_volume_id), | 
|  | FSINFO_VSTRUCT	(FSINFO_ATTR_VOLUME_UUID,	fsinfo_generic_volume_uuid), | 
|  | +	FSINFO_STRING	(FSINFO_ATTR_SOURCE,		fsinfo_generic_mount_source), | 
|  | +	FSINFO_STRING	(FSINFO_ATTR_CONFIGURATION,	fsinfo_generic_seq_read), | 
|  | +	FSINFO_STRING	(FSINFO_ATTR_FS_STATISTICS,	fsinfo_generic_seq_read), | 
|  |  | 
|  | FSINFO_LIST	(FSINFO_ATTR_FSINFO_ATTRIBUTES,	(void *)123UL), | 
|  | FSINFO_VSTRUCT_N(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO, (void *)123UL), | 
|  | diff --git a/fs/internal.h b/fs/internal.h | 
|  | index a0d90f23593c..6f2cc77bf38d 100644 | 
|  | --- a/fs/internal.h | 
|  | +++ b/fs/internal.h | 
|  | @@ -91,6 +91,8 @@ extern int __mnt_want_write_file(struct file *); | 
|  | extern void __mnt_drop_write_file(struct file *); | 
|  |  | 
|  | extern void dissolve_on_fput(struct vfsmount *); | 
|  | +extern int fsinfo_generic_mount_source(struct path *, struct fsinfo_context *); | 
|  | + | 
|  | /* | 
|  | * fs_struct.c | 
|  | */ | 
|  | diff --git a/fs/namespace.c b/fs/namespace.c | 
|  | index 85b5f7bea82e..b4951e2afae1 100644 | 
|  | --- a/fs/namespace.c | 
|  | +++ b/fs/namespace.c | 
|  | @@ -30,6 +30,7 @@ | 
|  | #include <uapi/linux/mount.h> | 
|  | #include <linux/fs_context.h> | 
|  | #include <linux/shmem_fs.h> | 
|  | +#include <linux/fsinfo.h> | 
|  |  | 
|  | #include "pnode.h" | 
|  | #include "internal.h" | 
|  | @@ -3975,3 +3976,41 @@ const struct proc_ns_operations mntns_operations = { | 
|  | .install	= mntns_install, | 
|  | .owner		= mntns_owner, | 
|  | }; | 
|  | + | 
|  | +#ifdef CONFIG_FSINFO | 
|  | +static inline void mangle(struct seq_file *m, const char *s) | 
|  | +{ | 
|  | +	seq_escape(m, s, " \t\n\\"); | 
|  | +} | 
|  | + | 
|  | +/* | 
|  | + * Return the mount source/device name as seen from this mountpoint.  Shared | 
|  | + * mounts may vary here and the filesystem is permitted to substitute its own | 
|  | + * rendering. | 
|  | + */ | 
|  | +int fsinfo_generic_mount_source(struct path *path, struct fsinfo_context *ctx) | 
|  | +{ | 
|  | +	struct super_block *sb = path->mnt->mnt_sb; | 
|  | +	struct mount *mnt = real_mount(path->mnt); | 
|  | +	struct seq_file m = { | 
|  | +		.buf	= ctx->buffer, | 
|  | +		.size	= ctx->buf_size, | 
|  | +	}; | 
|  | +	int ret; | 
|  | + | 
|  | +	if (sb->s_op->show_devname) { | 
|  | +		ret = sb->s_op->show_devname(&m, mnt->mnt.mnt_root); | 
|  | +		if (ret < 0) | 
|  | +			return ret; | 
|  | +	} else { | 
|  | +		if (!mnt->mnt_devname) | 
|  | +			return fsinfo_string("none", ctx); | 
|  | +		mangle(&m, mnt->mnt_devname); | 
|  | +	} | 
|  | + | 
|  | +	if (seq_has_overflowed(&m)) | 
|  | +		return ctx->buf_size + PAGE_SIZE; | 
|  | +	return m.count; | 
|  | +} | 
|  | + | 
|  | +#endif /* CONFIG_FSINFO */ | 
|  | diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h | 
|  | index e9b35b9b7629..d3722c7142a6 100644 | 
|  | --- a/include/uapi/linux/fsinfo.h | 
|  | +++ b/include/uapi/linux/fsinfo.h | 
|  | @@ -23,6 +23,9 @@ | 
|  | #define FSINFO_ATTR_VOLUME_ID		0x05	/* Volume ID (string) */ | 
|  | #define FSINFO_ATTR_VOLUME_UUID		0x06	/* Volume UUID (LE uuid) */ | 
|  | #define FSINFO_ATTR_VOLUME_NAME		0x07	/* Volume name (string) */ | 
|  | +#define FSINFO_ATTR_SOURCE		0x09	/* Superblock source/device name (string) */ | 
|  | +#define FSINFO_ATTR_CONFIGURATION	0x0a	/* Superblock configuration/options (string) */ | 
|  | +#define FSINFO_ATTR_FS_STATISTICS	0x0b	/* Superblock filesystem statistics (string) */ | 
|  |  | 
|  | #define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO 0x100	/* Information about attr N (for path) */ | 
|  | #define FSINFO_ATTR_FSINFO_ATTRIBUTES	0x101	/* List of supported attrs (for path) */ | 
|  | diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c | 
|  | index 2b53c735d330..1797f7b8d08a 100644 | 
|  | --- a/samples/vfs/test-fsinfo.c | 
|  | +++ b/samples/vfs/test-fsinfo.c | 
|  | @@ -289,6 +289,10 @@ static const struct fsinfo_attribute fsinfo_attributes[] = { | 
|  | FSINFO_STRING	(FSINFO_ATTR_VOLUME_ID,		string), | 
|  | FSINFO_VSTRUCT	(FSINFO_ATTR_VOLUME_UUID,	fsinfo_generic_volume_uuid), | 
|  | FSINFO_STRING	(FSINFO_ATTR_VOLUME_NAME,	string), | 
|  | +	FSINFO_STRING	(FSINFO_ATTR_SOURCE,		string), | 
|  | +	FSINFO_STRING	(FSINFO_ATTR_CONFIGURATION,	string), | 
|  | +	FSINFO_STRING	(FSINFO_ATTR_FS_STATISTICS,	string), | 
|  | + | 
|  | FSINFO_VSTRUCT_N(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO, fsinfo_meta_attribute_info), | 
|  | FSINFO_LIST	(FSINFO_ATTR_FSINFO_ATTRIBUTES,	fsinfo_meta_attributes), | 
|  | {} | 
|  | -- | 
|  | 2.20.1 | 
|  |  |