* [PATCH 21/25] fsinfo: pstore - add sb operation fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:14 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
From: Ian Kent <raven@themaw.net>
The new fsinfo() system call adds a new super block operation
->fsinfo() which is used by file systems to provide file
system specific information for fsinfo() requests.
The fsinfo() request FSINFO_ATTR_PARAMETERS provides the same
function as sb operation ->show_options() so it needs to be
implemented by any file system that provides ->show_options()
as a minimum.
Signed-off-by: Ian Kent <raven@themaw.net>
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/pstore/inode.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/fs/pstore/inode.c b/fs/pstore/inode.c
index 4640debf8755..44f4ffc4436e 100644
--- a/fs/pstore/inode.c
+++ b/fs/pstore/inode.c
@@ -36,6 +36,7 @@
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
+#include <linux/fsinfo.h>
#include "internal.h"
@@ -281,6 +282,33 @@ static int pstore_show_options(struct seq_file *m, struct dentry *root)
return 0;
}
+#ifdef CONFIG_FSINFO
+/*
+ * Get filesystem information.
+ */
+static int pstore_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct fsinfo_capabilities *caps;
+
+ switch (params->request) {
+ case FSINFO_ATTR_CAPABILITIES:
+ caps = params->buffer;
+ fsinfo_set_cap(caps, FSINFO_CAP_IS_FLASH_FS);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_CTIME);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_MTIME);
+ return sizeof(*caps);
+
+ case FSINFO_ATTR_PARAMETERS:
+ if (kmsg_bytes != PSTORE_DEFAULT_KMSG_BYTES)
+ fsinfo_note_paramf(params, "kmsg_bytes", "%lu", kmsg_bytes);
+ return params->usage;
+
+ default:
+ return generic_fsinfo(path, params);
+ }
+}
+#endif /* CONFIG_FSINFO */
+
static int pstore_reconfigure(struct fs_context *fc)
{
sync_filesystem(fc->root->d_sb);
@@ -293,6 +321,9 @@ static const struct super_operations pstore_ops = {
.drop_inode = generic_delete_inode,
.evict_inode = pstore_evict_inode,
.show_options = pstore_show_options,
+#ifdef CONFIG_FSINFO
+ .fsinfo = pstore_fsinfo,
+#endif
};
static struct super_block *pstore_sb;
^ permalink raw reply related
* [PATCH 20/25] fsinfo: devpts - add sb operation fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:13 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
From: Ian Kent <raven@themaw.net>
The new fsinfo() system call adds a new super block operation
->fsinfo() which is used by file systems to provide file
system specific information for fsinfo() requests.
The fsinfo() request FSINFO_ATTR_PARAMETERS provides the same
function as sb operation ->show_options() so it needs to be
implemented by any file system that provides ->show_options()
as a minimum.
Also add a simple FSINFO_ATTR_CAPABILITIES implementation.
Signed-off-by: Ian Kent <raven@themaw.net>
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/devpts/inode.c | 42 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/fs/devpts/inode.c b/fs/devpts/inode.c
index 3aa73a2ec2f1..12fece8913d2 100644
--- a/fs/devpts/inode.c
+++ b/fs/devpts/inode.c
@@ -28,6 +28,7 @@
#include <linux/devpts_fs.h>
#include <linux/fsnotify.h>
#include <linux/seq_file.h>
+#include <linux/fsinfo.h>
#define DEVPTS_DEFAULT_MODE 0600
/*
@@ -401,9 +402,50 @@ static int devpts_show_options(struct seq_file *seq, struct dentry *root)
return 0;
}
+#ifdef CONFIG_FSINFO
+/*
+ * Get filesystem information.
+ */
+static int devpts_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct pts_fs_info *fsi = DEVPTS_SB(path->dentry->d_sb);
+ struct pts_mount_opts *opts = &fsi->mount_opts;
+ struct fsinfo_capabilities *caps;
+
+ switch (params->request) {
+ case FSINFO_ATTR_CAPABILITIES:
+ caps = params->buffer;
+ fsinfo_set_cap(caps, FSINFO_CAP_IS_KERNEL_FS);
+ fsinfo_set_cap(caps, FSINFO_CAP_NOT_PERSISTENT);
+ fsinfo_set_cap(caps, FSINFO_CAP_UIDS);
+ fsinfo_set_cap(caps, FSINFO_CAP_GIDS);
+ return sizeof(*caps);
+
+ case FSINFO_ATTR_PARAMETERS:
+ if (opts->setuid)
+ fsinfo_note_paramf(params, "uid", "%u",
+ from_kuid_munged(&init_user_ns, opts->uid));
+ if (opts->setgid)
+ fsinfo_note_paramf(params, "gid", "%u",
+ from_kgid_munged(&init_user_ns, opts->gid));
+ fsinfo_note_paramf(params, "mode", "%03o", opts->mode);
+ fsinfo_note_paramf(params, "ptmxmode", "%03o", opts->ptmxmode);
+ if (opts->max < NR_UNIX98_PTY_MAX)
+ fsinfo_note_paramf(params, "max", "%d", opts->max);
+ return params->usage;
+
+ default:
+ return generic_fsinfo(path, params);
+ }
+}
+#endif /* CONFIG_FSINFO */
+
static const struct super_operations devpts_sops = {
.statfs = simple_statfs,
.show_options = devpts_show_options,
+#ifdef CONFIG_FSINFO
+ .fsinfo = devpts_fsinfo,
+#endif
};
static int devpts_fill_super(struct super_block *s, struct fs_context *fc)
^ permalink raw reply related
* [PATCH 19/25] fsinfo: proc - add sb operation fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:13 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
From: Ian Kent <raven@themaw.net>
The new fsinfo() system call adds a new super block operation
->fsinfo() which is used by file systems to provide file
system specific information for fsinfo() requests.
The fsinfo() request FSINFO_ATTR_PARAMETERS provides the same
function as sb operation ->show_options() so it needs to be
implemented by any file system that provides ->show_options()
as a minimum.
Also add a simple FSINFO_ATTR_CAPABILITIES implementation.
Signed-off-by: Ian Kent <raven@themaw.net>
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/proc/inode.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 5f8d215b3fd0..0f6c122d22f0 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -24,6 +24,7 @@
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/mount.h>
+#include <linux/fsinfo.h>
#include <linux/uaccess.h>
@@ -115,6 +116,38 @@ static int proc_show_options(struct seq_file *seq, struct dentry *root)
return 0;
}
+#ifdef CONFIG_FSINFO
+/*
+ * Get filesystem information.
+ */
+static int proc_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct super_block *sb = path->dentry->d_sb;
+ struct pid_namespace *pid = sb->s_fs_info;
+ struct fsinfo_capabilities *caps;
+
+ switch (params->request) {
+ case FSINFO_ATTR_CAPABILITIES:
+ caps = params->buffer;
+ fsinfo_set_cap(caps, FSINFO_CAP_IS_KERNEL_FS);
+ fsinfo_set_cap(caps, FSINFO_CAP_NOT_PERSISTENT);
+ return sizeof(*caps);
+
+ case FSINFO_ATTR_PARAMETERS:
+ if (!gid_eq(pid->pid_gid, GLOBAL_ROOT_GID))
+ fsinfo_note_paramf(params, "gid", "%u",
+ from_kgid_munged(&init_user_ns, pid->pid_gid));
+ if (pid->hide_pid != HIDEPID_OFF)
+ fsinfo_note_paramf(params, "hidepid",
+ "%u", pid->hide_pid);
+ return params->usage;
+
+ default:
+ return generic_fsinfo(path, params);
+ }
+}
+#endif /* CONFIG_FSINFO */
+
const struct super_operations proc_sops = {
.alloc_inode = proc_alloc_inode,
.free_inode = proc_free_inode,
@@ -122,6 +155,9 @@ const struct super_operations proc_sops = {
.evict_inode = proc_evict_inode,
.statfs = simple_statfs,
.show_options = proc_show_options,
+#ifdef CONFIG_FSINFO
+ .fsinfo = proc_fsinfo,
+#endif
};
enum {BIAS = -1U<<31};
^ permalink raw reply related
* [PATCH 18/25] fsinfo: shmem - add tmpfs sb operation fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:13 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
From: Ian Kent <raven@themaw.net>
The new fsinfo() system call adds a new super block operation
->fsinfo() which is used by file systems to provide file
system specific information for fsinfo() requests.
The fsinfo() request FSINFO_ATTR_PARAMETERS provides the same
function as sb operation ->show_options() so it needs to be
implemented by any file system that provides ->show_options()
as a minimum.
Also add a simple FSINFO_ATTR_CAPABILITIES implementation.
Signed-off-by: Ian Kent <raven@themaw.net>
Signed-off-by: David Howells <dhowells@redhat.com>
---
mm/shmem.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+)
diff --git a/mm/shmem.c b/mm/shmem.c
index 38a77ee13db1..493d3c84e13b 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -39,6 +39,7 @@
#include <linux/frontswap.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
+#include <linux/fsinfo.h>
#include <asm/tlbflush.h> /* for arch/microblaze update_mmu_cache() */
@@ -1403,6 +1404,20 @@ static void shmem_show_mpol(struct seq_file *seq, struct mempolicy *mpol)
seq_printf(seq, ",mpol=%s", buffer);
}
+#ifdef CONFIG_FSINFO
+static void shmem_fsinfo_mpol(struct fsinfo_kparams *params, struct mempolicy *mpol)
+{
+ char buffer[64];
+
+ if (!mpol || mpol->mode == MPOL_DEFAULT)
+ return; /* show nothing */
+
+ mpol_to_str(buffer, sizeof(buffer), mpol);
+
+ fsinfo_note_paramf(params, "mpol", "%s", buffer);
+}
+#endif
+
static struct mempolicy *shmem_get_sbmpol(struct shmem_sb_info *sbinfo)
{
struct mempolicy *mpol = NULL;
@@ -1418,6 +1433,11 @@ static struct mempolicy *shmem_get_sbmpol(struct shmem_sb_info *sbinfo)
static inline void shmem_show_mpol(struct seq_file *seq, struct mempolicy *mpol)
{
}
+#ifdef CONFIG_FSINFO
+static void shmem_fsinfo_mpol(struct fsinfo_kparams *params, struct mempolicy *mpol)
+{
+}
+#endif
static inline struct mempolicy *shmem_get_sbmpol(struct shmem_sb_info *sbinfo)
{
return NULL;
@@ -3478,7 +3498,9 @@ static int shmem_parse_param(struct fs_context *fc, struct fs_parameter *param)
struct shmem_fs_context *ctx = fc->fs_private;
struct fs_parse_result result;
unsigned long long size;
+#ifdef CONFIG_NUMA
struct mempolicy *mpol;
+#endif
char *rest;
int opt;
@@ -3623,6 +3645,52 @@ static int shmem_show_options(struct seq_file *seq, struct dentry *root)
return 0;
}
+#ifdef CONFIG_FSINFO
+static int shmem_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct shmem_sb_info *sbinfo = SHMEM_SB(path->dentry->d_sb);
+ struct fsinfo_capabilities *caps;
+
+ switch (params->request) {
+ case FSINFO_ATTR_CAPABILITIES:
+ caps = params->buffer;
+ fsinfo_set_unix_caps(caps);
+ fsinfo_set_cap(caps, FSINFO_CAP_IS_MEMORY_FS);
+ fsinfo_set_cap(caps, FSINFO_CAP_NOT_PERSISTENT);
+#ifdef CONFIG_TMPFS_XATTR
+ fsinfo_set_cap(caps, FSINFO_CAP_XATTRS);
+#endif
+ return sizeof(*caps);
+
+ case FSINFO_ATTR_PARAMETERS:
+ if (sbinfo->max_blocks != shmem_default_max_blocks())
+ fsinfo_note_paramf(params, "size", "%luk",
+ sbinfo->max_blocks << (PAGE_SHIFT - 10));
+ if (sbinfo->max_inodes != shmem_default_max_inodes())
+ fsinfo_note_paramf(params, "nr_inodes",
+ "%lu", sbinfo->max_inodes);
+ if (sbinfo->mode != (0777 | S_ISVTX))
+ fsinfo_note_paramf(params, "mode",
+ "%03ho", sbinfo->mode);
+ if (!uid_eq(sbinfo->uid, GLOBAL_ROOT_UID))
+ fsinfo_note_paramf(params, "uid", "%u",
+ from_kuid_munged(&init_user_ns, sbinfo->uid));
+ if (!gid_eq(sbinfo->gid, GLOBAL_ROOT_GID))
+ fsinfo_note_paramf(params, "gid", "%u",
+ from_kgid_munged(&init_user_ns, sbinfo->gid));
+#ifdef CONFIG_TRANSPARENT_HUGE_PAGECACHE
+ /* Rightly or wrongly, show huge mount option unmasked by shmem_huge */
+ if (sbinfo->huge)
+ fsinfo_note_paramf(params, "huge", "%s",
+ shmem_format_huge(sbinfo->huge));
+#endif
+ shmem_fsinfo_mpol(params, sbinfo->mpol);
+ return params->usage;
+ default:
+ return generic_fsinfo(path, params);
+ }
+}
+#endif /* CONFIG_FSINFO */
#endif /* CONFIG_TMPFS */
static void shmem_put_super(struct super_block *sb)
@@ -3854,6 +3922,9 @@ static const struct super_operations shmem_ops = {
#ifdef CONFIG_TMPFS
.statfs = shmem_statfs,
.show_options = shmem_show_options,
+#ifdef CONFIG_FSINFO
+ .fsinfo = shmem_fsinfo,
+#endif
#endif
.evict_inode = shmem_evict_inode,
.drop_inode = generic_delete_inode,
^ permalink raw reply related
* [PATCH 17/25] fsinfo: autofs - add sb operation fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:13 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
From: Ian Kent <raven@themaw.net>
The new fsinfo() system call adds a new super block operation
->fsinfo() which is used by file systems to provide file
system specific information for fsinfo() requests.
The fsinfo() request FSINFO_ATTR_PARAMETERS provides the same
function as sb operation ->show_options() so it needs to be
implemented by any file system that provides ->show_options()
as a minimum.
Also add a simple FSINFO_ATTR_CAPABILITIES implementation.
Signed-off-by: Ian Kent <raven@themaw.net>
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/autofs/inode.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/fs/autofs/inode.c b/fs/autofs/inode.c
index 58457ec0ab27..bd9126d8c541 100644
--- a/fs/autofs/inode.c
+++ b/fs/autofs/inode.c
@@ -11,6 +11,7 @@
#include <linux/pagemap.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
+#include <linux/fsinfo.h>
#include "autofs_i.h"
@@ -101,6 +102,65 @@ static int autofs_show_options(struct seq_file *m, struct dentry *root)
return 0;
}
+#ifdef CONFIG_FSINFO
+/*
+ * Get filesystem information.
+ */
+static int autofs_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct autofs_sb_info *sbi = autofs_sbi(path->dentry->d_sb);
+ struct inode *inode = d_inode(path->dentry->d_sb->s_root);
+ struct fsinfo_capabilities *caps;
+
+ switch (params->request) {
+ case FSINFO_ATTR_CAPABILITIES:
+ caps = params->buffer;
+ fsinfo_set_cap(caps, FSINFO_CAP_IS_AUTOMOUNTER_FS);
+ fsinfo_set_cap(caps, FSINFO_CAP_AUTOMOUNTS);
+ fsinfo_set_cap(caps, FSINFO_CAP_NOT_PERSISTENT);
+ return sizeof(*caps);
+
+ case FSINFO_ATTR_PARAMETERS:
+ fsinfo_note_paramf(params, "fd", "%d", sbi->pipefd);
+ if (!uid_eq(inode->i_uid, GLOBAL_ROOT_UID))
+ fsinfo_note_paramf(params, "uid", "%u",
+ from_kuid_munged(&init_user_ns, inode->i_uid));
+ if (!gid_eq(inode->i_gid, GLOBAL_ROOT_GID))
+ fsinfo_note_paramf(params, "gid", "%u",
+ from_kgid_munged(&init_user_ns, inode->i_gid));
+ fsinfo_note_paramf(params, "pgrp", "%d",
+ pid_vnr(sbi->oz_pgrp));
+ fsinfo_note_paramf(params, "timeout", "%lu",
+ sbi->exp_timeout/HZ);
+ fsinfo_note_paramf(params, "minproto", "%d",
+ sbi->min_proto);
+ fsinfo_note_paramf(params, "maxproto", "%d",
+ sbi->max_proto);
+ if (autofs_type_offset(sbi->type))
+ fsinfo_note_param(params, "offset", NULL);
+ else if (autofs_type_direct(sbi->type))
+ fsinfo_note_param(params, "direct", NULL);
+ else
+ fsinfo_note_param(params, "indirect", NULL);
+ if (sbi->flags & AUTOFS_SBI_STRICTEXPIRE)
+ fsinfo_note_param(params, "strictexpire", NULL);
+ if (sbi->flags & AUTOFS_SBI_IGNORE)
+ fsinfo_note_param(params, "ignore", NULL);
+#ifdef CONFIG_CHECKPOINT_RESTORE
+ if (sbi->pipe)
+ fsinfo_note_paramf(params, "pipe_ino",
+ "%ld", file_inode(sbi->pipe)->i_ino);
+ else
+ fsinfo_note_param(params, "pipe_ino", "-1");
+#endif
+ return params->usage;
+
+ default:
+ return generic_fsinfo(path, params);
+ }
+}
+#endif /* CONFIG_FSINFO */
+
static void autofs_evict_inode(struct inode *inode)
{
clear_inode(inode);
@@ -111,6 +171,9 @@ static const struct super_operations autofs_sops = {
.statfs = simple_statfs,
.show_options = autofs_show_options,
.evict_inode = autofs_evict_inode,
+#ifdef CONFIG_FSINFO
+ .fsinfo = autofs_fsinfo,
+#endif
};
struct autofs_fs_context {
^ permalink raw reply related
* [PATCH 16/25] nfs: Support fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:13 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Allow fsinfo() to retrieve information about a superblock, including the
values configured by the parameters passed at superblock creation.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/nfs/fs_context.c | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++
fs/nfs/internal.h | 6 ++
fs/nfs/nfs4super.c | 3 +
fs/nfs/super.c | 77 ++++++++++++++++++++++++
4 files changed, 248 insertions(+), 1 deletion(-)
diff --git a/fs/nfs/fs_context.c b/fs/nfs/fs_context.c
index d05271b91e38..f550b0e54833 100644
--- a/fs/nfs/fs_context.c
+++ b/fs/nfs/fs_context.c
@@ -17,6 +17,8 @@
#include <linux/fs.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
+#include <linux/fsinfo.h>
+#include <linux/mount.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_mount.h>
#include <linux/nfs4_mount.h>
@@ -1407,3 +1409,164 @@ MODULE_ALIAS_FS("nfs4");
MODULE_ALIAS("nfs4");
EXPORT_SYMBOL_GPL(nfs4_fs_type);
#endif /* CONFIG_NFS_V4 */
+
+#ifdef CONFIG_FSINFO
+/*
+ * Allow the filesystem parameters to be queried.
+ */
+int nfs_fsinfo_parameters(struct fsinfo_kparams *params, struct path *path,
+ const struct nfs_server *server)
+{
+ const struct nfs_client *client = server->nfs_client;
+ const struct sockaddr *sap = (const struct sockaddr *)&server->mountd_address;
+ unsigned int version = client->rpc_ops->version;
+ unsigned int sf = server->flags;
+ const char *b;
+ char *e;
+ int i;
+
+ static const struct proc_nfs_info {
+ int flag;
+ const char *str;
+ const char *nostr;
+ } nfs_info[] = {
+ { NFS_MOUNT_SOFT, "soft", "hard" },
+ { NFS_MOUNT_POSIX, "posix", "" },
+ { NFS_MOUNT_NOCTO, "nocto", "" },
+ { NFS_MOUNT_NOAC, "noac", "" },
+ { NFS_MOUNT_NONLM, "nolock", "" },
+ { NFS_MOUNT_NOACL, "noacl", "" },
+ { NFS_MOUNT_NORDIRPLUS, "nordirplus", "" },
+ { NFS_MOUNT_UNSHARED, "nosharecache", "" },
+ { NFS_MOUNT_NORESVPORT, "noresvport", "" },
+ };
+
+ rcu_read_lock();
+
+ b = params->scratch_buffer;
+ b = nfs_path(&e, path->mnt->mnt_root, params->scratch_buffer, params->buf_size, 0);
+ if (b < e)
+ fsinfo_note_param(params, "source", b);
+
+ if (version == 4)
+ fsinfo_note_paramf(params, "vers", "4.%u", client->cl_minorversion);
+ else
+ fsinfo_note_paramf(params, "vers", "%u", version);
+
+ fsinfo_note_paramf(params, "rsize", "%u", server->rsize);
+ fsinfo_note_paramf(params, "wsize", "%u", server->wsize);
+ if (server->bsize)
+ fsinfo_note_paramf(params, "bsize", "%u", server->bsize);
+ fsinfo_note_paramf(params, "namlen", "%u", server->namelen);
+
+ if (server->acregmin != NFS_DEF_ACREGMIN*HZ)
+ fsinfo_note_paramf(params, "acregmin", "%u", server->acregmin/HZ);
+ if (server->acregmax != NFS_DEF_ACREGMAX*HZ)
+ fsinfo_note_paramf(params, "acregmin", "%u", server->acregmax/HZ);
+ if (server->acdirmin != NFS_DEF_ACDIRMIN*HZ)
+ fsinfo_note_paramf(params, "acdirmin", "%u", server->acdirmin/HZ);
+ if (server->acdirmax != NFS_DEF_ACDIRMAX*HZ)
+ fsinfo_note_paramf(params, "acdirmin", "%u", server->acdirmax/HZ);
+
+ for (i = 0; i < ARRAY_SIZE(nfs_info); i++) {
+ if (sf & nfs_info[i].flag)
+ b = nfs_info[i].str;
+ else
+ b = nfs_info[i].nostr;
+ if (b[0])
+ fsinfo_note_param(params, b, NULL);
+ }
+
+ fsinfo_note_param(params, "proto",
+ rpc_peeraddr2str(server->client, RPC_DISPLAY_NETID));
+ if (version != 4 || server->port != NFS_PORT)
+ fsinfo_note_paramf(params, "port", "%u", server->port);
+
+ fsinfo_note_paramf(params, "timeo", "%lu",
+ 10U * server->client->cl_timeout->to_initval / HZ);
+ fsinfo_note_paramf(params, "retrans", "%u",
+ server->client->cl_timeout->to_retries);
+ fsinfo_note_param(params, "sec",
+ nfs_pseudoflavour_to_name(server->client->cl_auth->au_flavor));
+
+ if (server->options & NFS_OPTION_FSCACHE)
+ fsinfo_note_param(params, "fsc", NULL);
+ if (server->options & NFS_OPTION_MIGRATION)
+ fsinfo_note_param(params, "migration", NULL);
+
+ if (server->flags & NFS_MOUNT_LOOKUP_CACHE_NONEG) {
+ if (server->flags & NFS_MOUNT_LOOKUP_CACHE_NONE)
+ fsinfo_note_param(params, "lookupcache", "none");
+ else
+ fsinfo_note_param(params, "lookupcache", "pos");
+ }
+
+ switch (server->flags & (NFS_MOUNT_LOCAL_FLOCK | NFS_MOUNT_LOCAL_FCNTL)) {
+ case 0: b = "none"; break;
+ case NFS_MOUNT_LOCAL_FLOCK: b = "flock"; break;
+ case NFS_MOUNT_LOCAL_FCNTL: b = "posix"; break;
+ default: b = "all"; break;
+ }
+ fsinfo_note_param(params, "local_lock", b);
+
+ if (version == 4)
+ fsinfo_note_param(params, "clientaddr", client->cl_ipaddr);
+
+ if (version != 4 && !(server->flags & NFS_MOUNT_LEGACY_INTERFACE)) {
+ switch (sap->sa_family) {
+ case AF_INET: {
+ struct sockaddr_in *sin = (struct sockaddr_in *)sap;
+ fsinfo_note_paramf(params, "mountaddr", "%pI4",
+ &sin->sin_addr.s_addr);
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap;
+ fsinfo_note_paramf(params, "mountaddr", "%pI6c",
+ &sin6->sin6_addr);
+ break;
+ }
+ }
+
+ if (server->mountd_port &&
+ server->mountd_port != (unsigned short)NFS_UNSPEC_PORT)
+ fsinfo_note_paramf(params, "mountport", "%u", server->mountd_port);
+
+ switch (sap->sa_family) {
+ case AF_INET:
+ switch (server->mountd_protocol) {
+ case IPPROTO_UDP:
+ b = RPCBIND_NETID_UDP;
+ break;
+ case IPPROTO_TCP:
+ b = RPCBIND_NETID_TCP;
+ break;
+ }
+ break;
+ case AF_INET6:
+ switch (server->mountd_protocol) {
+ case IPPROTO_UDP:
+ b = RPCBIND_NETID_UDP6;
+ break;
+ case IPPROTO_TCP:
+ b = RPCBIND_NETID_TCP6;
+ break;
+ }
+ break;
+ }
+
+ if (b)
+ fsinfo_note_param(params, "mountproto", b);
+
+ if (server->mountd_version)
+ fsinfo_note_paramf(params, "mountvers", "%u",
+ server->mountd_version);
+ }
+
+ fsinfo_note_param(params, "addr",
+ rpc_peeraddr2str(server->nfs_client->cl_rpcclient, RPC_DISPLAY_ADDR));
+
+ rcu_read_unlock();
+ return params->usage;
+}
+#endif /* CONFIG_FSINFO */
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index da088f5611f0..c218e715881d 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -244,6 +244,10 @@ extern const struct svc_version nfs4_callback_version4;
/* fs_context.c */
extern struct file_system_type nfs_fs_type;
+#ifdef CONFIG_FSINFO
+extern int nfs_fsinfo_parameters(struct fsinfo_kparams *params, struct path *path,
+ const struct nfs_server *server);
+#endif
/* pagelist.c */
extern int __init nfs_init_nfspagecache(void);
@@ -408,6 +412,7 @@ bool nfs_auth_info_match(const struct nfs_auth_info *, rpc_authflavor_t);
int nfs_try_get_tree(struct fs_context *);
int nfs_get_tree_common(struct fs_context *);
void nfs_kill_super(struct super_block *);
+const char *nfs_pseudoflavour_to_name(rpc_authflavor_t);
extern struct rpc_stat nfs_rpcstat;
@@ -455,6 +460,7 @@ extern void nfs_pageio_reset_read_mds(struct nfs_pageio_descriptor *pgio);
/* super.c */
void nfs_umount_begin(struct super_block *);
int nfs_statfs(struct dentry *, struct kstatfs *);
+int nfs_fsinfo(struct path *, struct fsinfo_kparams *);
int nfs_show_options(struct seq_file *, struct dentry *);
int nfs_show_devname(struct seq_file *, struct dentry *);
int nfs_show_path(struct seq_file *, struct dentry *);
diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c
index 0240429ec596..22d8f2842ac1 100644
--- a/fs/nfs/nfs4super.c
+++ b/fs/nfs/nfs4super.c
@@ -31,6 +31,9 @@ static const struct super_operations nfs4_sops = {
.show_devname = nfs_show_devname,
.show_path = nfs_show_path,
.show_stats = nfs_show_stats,
+#ifdef CONFIG_FSINFO
+ .fsinfo = nfs_fsinfo,
+#endif
};
struct nfs_subversion nfs_v4 = {
diff --git a/fs/nfs/super.c b/fs/nfs/super.c
index c455ebeeadc9..dde6c59d0210 100644
--- a/fs/nfs/super.c
+++ b/fs/nfs/super.c
@@ -54,6 +54,7 @@
#include <linux/parser.h>
#include <linux/nsproxy.h>
#include <linux/rcupdate.h>
+#include <linux/fsinfo.h>
#include <linux/uaccess.h>
@@ -81,6 +82,9 @@ const struct super_operations nfs_sops = {
.show_devname = nfs_show_devname,
.show_path = nfs_show_path,
.show_stats = nfs_show_stats,
+#ifdef CONFIG_FSINFO
+ .fsinfo = nfs_fsinfo,
+#endif
};
EXPORT_SYMBOL_GPL(nfs_sops);
@@ -241,10 +245,81 @@ int nfs_statfs(struct dentry *dentry, struct kstatfs *buf)
}
EXPORT_SYMBOL_GPL(nfs_statfs);
+#ifdef CONFIG_FSINFO
+/*
+ * Get filesystem information.
+ */
+int nfs_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct fsinfo_server_address *addr;
+ struct fsinfo_capabilities *caps;
+ struct nfs_server *server = NFS_SB(path->dentry->d_sb);
+ struct nfs_client *client = server->nfs_client;
+ struct rpc_clnt *clnt;
+ struct rpc_xprt *xprt;
+ const char *str;
+ unsigned int version = client->rpc_ops->version;
+
+ switch (params->request) {
+ case FSINFO_ATTR_CAPABILITIES:
+ caps = params->buffer;
+ fsinfo_set_cap(caps, FSINFO_CAP_IS_NETWORK_FS);
+ fsinfo_set_cap(caps, FSINFO_CAP_AUTOMOUNTS);
+ fsinfo_set_cap(caps, FSINFO_CAP_ADV_LOCKS);
+ fsinfo_set_cap(caps, FSINFO_CAP_UIDS);
+ fsinfo_set_cap(caps, FSINFO_CAP_GIDS);
+ fsinfo_set_cap(caps, FSINFO_CAP_O_SYNC);
+ fsinfo_set_cap(caps, FSINFO_CAP_O_DIRECT);
+ fsinfo_set_cap(caps, FSINFO_CAP_SYMLINKS);
+ fsinfo_set_cap(caps, FSINFO_CAP_HARD_LINKS);
+ fsinfo_set_cap(caps, FSINFO_CAP_DEVICE_FILES);
+ fsinfo_set_cap(caps, FSINFO_CAP_UNIX_SPECIALS);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_ATIME);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_CTIME);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_MTIME);
+ if (version == 4) {
+ fsinfo_set_cap(caps, FSINFO_CAP_LEASES);
+ fsinfo_set_cap(caps, FSINFO_CAP_IVER_ALL_CHANGE);
+ }
+ return sizeof(*caps);
+
+ case FSINFO_ATTR_SERVER_NAME:
+ if (params->Nth || params->Mth)
+ return -ENODATA;
+ str = client->cl_hostname;
+ goto string;
+
+ case FSINFO_ATTR_SERVER_ADDRESS:
+ if (params->Nth || params->Mth)
+ return -ENODATA;
+ addr = params->buffer;
+ clnt = client->cl_rpcclient;
+ rcu_read_lock();
+ xprt = rcu_dereference(clnt->cl_xprt);
+ memcpy(&addr->address, &xprt->addr, xprt->addrlen);
+ rcu_read_unlock();
+ return sizeof(*addr);
+
+ case FSINFO_ATTR_PARAMETERS:
+ return nfs_fsinfo_parameters(params, path, server);
+
+ default:
+ return generic_fsinfo(path, params);
+ }
+
+string:
+ if (!str)
+ return 0;
+ strcpy(params->buffer, str);
+ return strlen(params->buffer);
+}
+EXPORT_SYMBOL_GPL(nfs_fsinfo);
+#endif /* CONFIG_FSINFO */
+
/*
* Map the security flavour number to a name
*/
-static const char *nfs_pseudoflavour_to_name(rpc_authflavor_t flavour)
+const char *nfs_pseudoflavour_to_name(rpc_authflavor_t flavour)
{
static const struct {
rpc_authflavor_t flavour;
^ permalink raw reply related
* [PATCH 15/25] afs: Support fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:13 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Add fsinfo support to the AFS filesystem.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/afs/internal.h | 1
fs/afs/super.c | 155 ++++++++++++++++++++++++++++++++++++++++++-
fs/fsinfo.c | 3 +
include/uapi/linux/fsinfo.h | 12 +++
samples/vfs/test-fsinfo.c | 33 +++++++++
5 files changed, 202 insertions(+), 2 deletions(-)
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 2073c1a3ab4b..da40ea036c6a 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -254,6 +254,7 @@ struct afs_super_info {
struct afs_volume *volume; /* volume record */
enum afs_flock_mode flock_mode:8; /* File locking emulation mode */
bool dyn_root; /* True if dynamic root */
+ bool autocell; /* True if autocell */
};
static inline struct afs_super_info *AFS_FS_S(struct super_block *sb)
diff --git a/fs/afs/super.c b/fs/afs/super.c
index f18911e8d770..2b1f37aa1b37 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -26,6 +26,7 @@
#include <linux/sched.h>
#include <linux/nsproxy.h>
#include <linux/magic.h>
+#include <linux/fsinfo.h>
#include <net/net_namespace.h>
#include "internal.h"
@@ -35,6 +36,9 @@ static struct inode *afs_alloc_inode(struct super_block *sb);
static void afs_destroy_inode(struct inode *inode);
static void afs_free_inode(struct inode *inode);
static int afs_statfs(struct dentry *dentry, struct kstatfs *buf);
+#ifdef CONFIG_FSINFO
+static int afs_fsinfo(struct path *path, struct fsinfo_kparams *params);
+#endif
static int afs_show_devname(struct seq_file *m, struct dentry *root);
static int afs_show_options(struct seq_file *m, struct dentry *root);
static int afs_init_fs_context(struct fs_context *fc);
@@ -54,6 +58,9 @@ int afs_net_id;
static const struct super_operations afs_super_ops = {
.statfs = afs_statfs,
+#ifdef CONFIG_FSINFO
+ .fsinfo = afs_fsinfo,
+#endif
.alloc_inode = afs_alloc_inode,
.drop_inode = afs_drop_inode,
.destroy_inode = afs_destroy_inode,
@@ -199,7 +206,7 @@ static int afs_show_options(struct seq_file *m, struct dentry *root)
if (as->dyn_root)
seq_puts(m, ",dyn");
- if (test_bit(AFS_VNODE_AUTOCELL, &AFS_FS_I(d_inode(root))->flags))
+ if (as->autocell)
seq_puts(m, ",autocell");
switch (as->flock_mode) {
case afs_flock_mode_unset: break;
@@ -463,7 +470,7 @@ static int afs_fill_super(struct super_block *sb, struct afs_fs_context *ctx)
if (IS_ERR(inode))
return PTR_ERR(inode);
- if (ctx->autocell || as->dyn_root)
+ if (as->autocell || as->dyn_root)
set_bit(AFS_VNODE_AUTOCELL, &AFS_FS_I(inode)->flags);
ret = -ENOMEM;
@@ -503,6 +510,8 @@ static struct afs_super_info *afs_alloc_sbi(struct fs_context *fc)
as->cell = afs_get_cell(ctx->cell);
as->volume = __afs_get_volume(ctx->volume);
}
+ if (ctx->autocell)
+ as->autocell = true;
}
return as;
}
@@ -765,3 +774,145 @@ static int afs_statfs(struct dentry *dentry, struct kstatfs *buf)
return ret;
}
+
+#ifdef CONFIG_FSINFO
+/*
+ * Get filesystem information.
+ */
+static int afs_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct fsinfo_timestamp_info *tsinfo;
+ struct fsinfo_server_address *addr;
+ struct fsinfo_capabilities *caps;
+ struct fsinfo_supports *sup;
+ struct dentry *dentry = path->dentry;
+ struct afs_server_list *slist;
+ struct afs_super_info *as = AFS_FS_S(dentry->d_sb);
+ struct afs_addr_list *alist;
+ struct afs_server *server;
+ struct afs_volume *volume = as->volume;
+ struct afs_cell *cell = as->cell;
+ struct afs_net *net = afs_d2net(dentry);
+ bool dyn_root = as->dyn_root;
+ int ret;
+
+ switch (params->request) {
+ case FSINFO_ATTR_TIMESTAMP_INFO:
+ tsinfo = params->buffer;
+ tsinfo->minimum_timestamp = 0;
+ tsinfo->maximum_timestamp = UINT_MAX;
+ tsinfo->mtime_gran_mantissa = 1;
+ tsinfo->mtime_gran_exponent = 0;
+ return sizeof(*tsinfo);
+
+ case FSINFO_ATTR_SUPPORTS:
+ sup = params->buffer;
+ sup->stx_mask = (STATX_TYPE | STATX_MODE |
+ STATX_NLINK |
+ STATX_UID | STATX_GID |
+ STATX_MTIME | STATX_INO |
+ STATX_SIZE);
+ sup->stx_attributes = STATX_ATTR_AUTOMOUNT;
+ return sizeof(*sup);
+
+ case FSINFO_ATTR_CAPABILITIES:
+ caps = params->buffer;
+ if (dyn_root) {
+ fsinfo_set_cap(caps, FSINFO_CAP_IS_AUTOMOUNTER_FS);
+ fsinfo_set_cap(caps, FSINFO_CAP_AUTOMOUNTS);
+ } else {
+ fsinfo_set_cap(caps, FSINFO_CAP_IS_NETWORK_FS);
+ fsinfo_set_cap(caps, FSINFO_CAP_AUTOMOUNTS);
+ fsinfo_set_cap(caps, FSINFO_CAP_ADV_LOCKS);
+ fsinfo_set_cap(caps, FSINFO_CAP_UIDS);
+ fsinfo_set_cap(caps, FSINFO_CAP_GIDS);
+ fsinfo_set_cap(caps, FSINFO_CAP_VOLUME_ID);
+ fsinfo_set_cap(caps, FSINFO_CAP_VOLUME_NAME);
+ fsinfo_set_cap(caps, FSINFO_CAP_IVER_MONO_INCR);
+ fsinfo_set_cap(caps, FSINFO_CAP_SYMLINKS);
+ fsinfo_set_cap(caps, FSINFO_CAP_HARD_LINKS_1DIR);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_MTIME);
+ }
+ return sizeof(*caps);
+
+ case FSINFO_ATTR_VOLUME_NAME:
+ if (dyn_root)
+ return -EOPNOTSUPP;
+ memcpy(params->buffer, volume->name, volume->name_len);
+ return volume->name_len;
+
+ case FSINFO_ATTR_CELL_NAME:
+ if (dyn_root)
+ return -EOPNOTSUPP;
+ memcpy(params->buffer, cell->name, cell->name_len);
+ return cell->name_len;
+
+ case FSINFO_ATTR_SERVER_NAME:
+ if (dyn_root)
+ return -EOPNOTSUPP;
+ read_lock(&volume->servers_lock);
+ slist = afs_get_serverlist(volume->servers);
+ read_unlock(&volume->servers_lock);
+
+ if (params->Nth < slist->nr_servers) {
+ server = slist->servers[params->Nth].server;
+ ret = sprintf(params->buffer, "%pU", &server->uuid);
+ } else {
+ ret = -ENODATA;
+ }
+
+ afs_put_serverlist(net, slist);
+ return ret;
+
+ case FSINFO_ATTR_SERVER_ADDRESS:
+ addr = params->buffer;
+ if (dyn_root)
+ return -EOPNOTSUPP;
+ read_lock(&volume->servers_lock);
+ slist = afs_get_serverlist(volume->servers);
+ read_unlock(&volume->servers_lock);
+
+ ret = -ENODATA;
+ if (params->Nth >= slist->nr_servers)
+ goto put_slist;
+ server = slist->servers[params->Nth].server;
+
+ read_lock(&server->fs_lock);
+ alist = afs_get_addrlist(rcu_access_pointer(server->addresses));
+ read_unlock(&server->fs_lock);
+ if (!alist)
+ goto put_slist;
+
+ if (params->Mth >= alist->nr_addrs)
+ goto put_alist;
+
+ memcpy(addr, &alist->addrs[params->Mth],
+ sizeof(struct sockaddr_rxrpc));
+ ret = sizeof(*addr);
+
+ put_alist:
+ afs_put_addrlist(alist);
+ put_slist:
+ afs_put_serverlist(net, slist);
+ return ret;
+
+ case FSINFO_ATTR_PARAMETERS:
+ if (!dyn_root)
+ fsinfo_note_paramf(params, "source", "%c%s:%s%s",
+ volume->type == AFSVL_RWVOL ? '%' : '#',
+ cell->name,
+ volume->name,
+ volume->type == AFSVL_RWVOL ? "" :
+ volume->type == AFSVL_ROVOL ? ".readonly" :
+ ".backup");
+ if (as->autocell)
+ fsinfo_note_param(params, "autocell", NULL);
+ if (dyn_root)
+ fsinfo_note_param(params, "dyn", NULL);
+ return params->usage;
+
+ default:
+ return generic_fsinfo(path, params);
+ }
+}
+#endif /* CONFIG_FSINFO */
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index b9f8712907dc..3ec64d3cba08 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -603,6 +603,9 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING (MOUNT_DEVNAME, mount_devname),
FSINFO_STRUCT_ARRAY (MOUNT_CHILDREN, mount_child),
FSINFO_STRING_N (MOUNT_SUBMOUNT, mount_submount),
+ FSINFO_STRING_N (SERVER_NAME, server_name),
+ FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
+ FSINFO_STRING (CELL_NAME, cell_name),
};
/**
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index 7f7a75e9758a..7247088332c2 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -36,6 +36,9 @@ enum fsinfo_attribute {
FSINFO_ATTR_MOUNT_DEVNAME = 18, /* Mount object device name (string) */
FSINFO_ATTR_MOUNT_CHILDREN = 19, /* Submount list (array) */
FSINFO_ATTR_MOUNT_SUBMOUNT = 20, /* Relative path of Nth submount (string) */
+ FSINFO_ATTR_SERVER_NAME = 21, /* Name of the Nth server (string) */
+ FSINFO_ATTR_SERVER_ADDRESS = 22, /* Mth address of the Nth server */
+ FSINFO_ATTR_CELL_NAME = 23, /* Cell name (string) */
FSINFO_ATTR__NR
};
@@ -296,4 +299,13 @@ struct fsinfo_mount_child {
__u32 notify_counter; /* Number of notifications generated on mount. */
};
+/*
+ * Information struct for fsinfo(fsinfo_attr_server_addresses).
+ *
+ * Find the Mth address of the Nth server for a network mount.
+ */
+struct fsinfo_server_address {
+ struct __kernel_sockaddr_storage address;
+};
+
#endif /* _UAPI_LINUX_FSINFO_H */
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index a838adcdca9e..af29da74559e 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -87,6 +87,9 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING (MOUNT_DEVNAME, mount_devname),
FSINFO_STRUCT_ARRAY (MOUNT_CHILDREN, mount_child),
FSINFO_STRING_N (MOUNT_SUBMOUNT, mount_submount),
+ FSINFO_STRING_N (SERVER_NAME, server_name),
+ FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
+ FSINFO_STRING (CELL_NAME, cell_name),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -112,6 +115,9 @@ static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
FSINFO_NAME (MOUNT_DEVNAME, mount_devname),
FSINFO_NAME (MOUNT_CHILDREN, mount_children),
FSINFO_NAME (MOUNT_SUBMOUNT, mount_submount),
+ FSINFO_NAME (SERVER_NAME, server_name),
+ FSINFO_NAME (SERVER_ADDRESS, server_address),
+ FSINFO_NAME (CELL_NAME, cell_name),
};
union reply {
@@ -126,6 +132,7 @@ union reply {
struct fsinfo_volume_uuid uuid;
struct fsinfo_mount_info mount_info;
struct fsinfo_mount_child mount_children[1];
+ struct fsinfo_server_address srv_addr;
};
static void dump_hex(unsigned int *data, int from, int to)
@@ -322,6 +329,31 @@ static void dump_attr_VOLUME_UUID(union reply *r, int size)
f->uuid[14], f->uuid[15]);
}
+static void dump_attr_SERVER_ADDRESS(union reply *r, int size)
+{
+ struct fsinfo_server_address *f = &r->srv_addr;
+ struct sockaddr_in6 *sin6;
+ struct sockaddr_in *sin;
+ char buf[1024];
+
+ switch (f->address.ss_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)&f->address;
+ if (!inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)))
+ break;
+ printf("IPv4: %s\n", buf);
+ return;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)&f->address;
+ if (!inet_ntop(AF_INET6, &sin6->sin6_addr, buf, sizeof(buf)))
+ break;
+ printf("IPv6: %s\n", buf);
+ return;
+ }
+
+ printf("family=%u\n", f->address.ss_family);
+}
+
static void dump_attr_MOUNT_INFO(union reply *r, int size)
{
struct fsinfo_mount_info *f = &r->mount_info;
@@ -362,6 +394,7 @@ static const dumper_t fsinfo_attr_dumper[FSINFO_ATTR__NR] = {
FSINFO_DUMPER(VOLUME_UUID),
FSINFO_DUMPER(MOUNT_INFO),
FSINFO_DUMPER(MOUNT_CHILDREN),
+ FSINFO_DUMPER(SERVER_ADDRESS),
};
static void dump_fsinfo(enum fsinfo_attribute attr,
^ permalink raw reply related
* [PATCH 14/25] fsinfo: Support Smack superblock parameter retrieval [ver #13]
From: David Howells @ 2019-05-28 15:13 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Add support to Smack for retrieval of the superblock parameters.
Signed-off-by: David Howells <dhowells@redhat.com>
---
security/smack/smack_lsm.c | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c
index 0de725f88bed..0a1e88d50ad3 100644
--- a/security/smack/smack_lsm.c
+++ b/security/smack/smack_lsm.c
@@ -45,6 +45,7 @@
#include <linux/parser.h>
#include <linux/fs_context.h>
#include <linux/fs_parser.h>
+#include <linux/fsinfo.h>
#include "smack.h"
#define TRANS_TRUE "TRUE"
@@ -893,6 +894,45 @@ static int smack_sb_statfs(struct dentry *dentry)
return rc;
}
+#ifdef CONFIG_FSINFO
+/*
+ * Retrieve the Smack filesystem information, including mount parameters.
+ */
+static int smack_sb_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct superblock_smack *sp = path->dentry->d_sb->s_security;
+
+ switch (params->request) {
+ case FSINFO_ATTR_LSM_PARAMETERS: {
+ struct dentry *root = path->dentry->d_sb->s_root;
+ struct inode *inode = d_backing_inode(root);
+ struct inode_smack *isp = inode->i_security;
+
+ if (sp->smk_flags & SMK_SB_INITIALIZED)
+ return 0;
+ if (sp->smk_floor)
+ fsinfo_note_param(params, "fsfloor", sp->smk_floor->smk_known);
+ if (sp->smk_hat)
+ fsinfo_note_param(params, "fshat", sp->smk_hat->smk_known);
+ if (sp->smk_default)
+ fsinfo_note_param(params, "fsdefault", sp->smk_default->smk_known);
+
+ if (sp->smk_root) {
+ if (isp && isp->smk_flags & SMK_INODE_TRANSMUTE)
+ fsinfo_note_param(params, "fstransmute", sp->smk_root->smk_known);
+ else
+ fsinfo_note_param(params, "fsroot", sp->smk_root->smk_known);
+ }
+ return params->usage;
+ }
+
+ default:
+ return -ENODATA;
+ }
+ return 0;
+}
+#endif
+
/*
* BPRM hooks
*/
@@ -4606,6 +4646,9 @@ static struct security_hook_list smack_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(sb_free_mnt_opts, smack_free_mnt_opts),
LSM_HOOK_INIT(sb_eat_lsm_opts, smack_sb_eat_lsm_opts),
LSM_HOOK_INIT(sb_statfs, smack_sb_statfs),
+#ifdef CONFIG_FSINFO
+ LSM_HOOK_INIT(sb_fsinfo, smack_sb_fsinfo),
+#endif
LSM_HOOK_INIT(sb_set_mnt_opts, smack_set_mnt_opts),
LSM_HOOK_INIT(bprm_set_creds, smack_bprm_set_creds),
^ permalink raw reply related
* [PATCH 13/25] fsinfo: Support SELinux superblock parameter retrieval [ver #13]
From: David Howells @ 2019-05-28 15:12 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Add support to SELinux for retrieval of the superblock parameters by
fsinfo(FSINFO_ATTR_LSM_PARAMETERS).
Signed-off-by: David Howells <dhowells@redhat.com>
---
security/selinux/hooks.c | 41 +++++++++++++++++++++++++++++
security/selinux/include/security.h | 2 +
security/selinux/ss/services.c | 49 +++++++++++++++++++++++++++++++++++
3 files changed, 92 insertions(+)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index c61787b15f27..9b5dbdcde9e6 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -91,6 +91,7 @@
#include <linux/bpf.h>
#include <linux/kernfs.h>
#include <linux/stringhash.h> /* for hashlen_string() */
+#include <linux/fsinfo.h>
#include <uapi/linux/mount.h>
#include "avc.h"
@@ -2735,6 +2736,43 @@ static int selinux_sb_statfs(struct dentry *dentry)
return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad);
}
+#ifdef CONFIG_FSINFO
+/*
+ * Retrieve the SELinux filesystem information, including mount parameters.
+ */
+static int selinux_sb_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct superblock_security_struct *sbsec = path->dentry->d_sb->s_security;
+
+ switch (params->request) {
+ case FSINFO_ATTR_LSM_PARAMETERS:
+ if (!(sbsec->flags & SE_SBINITIALIZED) ||
+ !selinux_state.initialized)
+ return params->usage;
+
+ if (sbsec->flags & FSCONTEXT_MNT)
+ fsinfo_note_sid(params, FSCONTEXT_STR, sbsec->sid);
+ if (sbsec->flags & CONTEXT_MNT)
+ fsinfo_note_sid(params, CONTEXT_STR, sbsec->mntpoint_sid);
+ if (sbsec->flags & DEFCONTEXT_MNT)
+ fsinfo_note_sid(params, DEFCONTEXT_STR, sbsec->def_sid);
+ if (sbsec->flags & ROOTCONTEXT_MNT) {
+ struct dentry *root = sbsec->sb->s_root;
+ struct inode_security_struct *isec = backing_inode_security(root);
+ fsinfo_note_sid(params, ROOTCONTEXT_STR, isec->sid);
+ }
+ if (sbsec->flags & SBLABEL_MNT)
+ fsinfo_note_param(params, SECLABEL_STR, NULL);
+
+ return params->usage;
+
+ default:
+ return -ENODATA;
+ }
+ return 0;
+}
+#endif
+
static int selinux_mount(const char *dev_name,
const struct path *path,
const char *type,
@@ -6761,6 +6799,9 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options),
LSM_HOOK_INIT(sb_statfs, selinux_sb_statfs),
+#ifdef CONFIG_FSINFO
+ LSM_HOOK_INIT(sb_fsinfo, selinux_sb_fsinfo),
+#endif
LSM_HOOK_INIT(sb_mount, selinux_mount),
LSM_HOOK_INIT(sb_umount, selinux_umount),
LSM_HOOK_INIT(sb_set_mnt_opts, selinux_set_mnt_opts),
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index 111121281c47..e9617bfcc6ee 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -67,6 +67,7 @@
#define SECLABEL_STR "seclabel"
struct netlbl_lsm_secattr;
+struct fsinfo_kparams;
extern int selinux_enabled;
@@ -258,6 +259,7 @@ int security_sid_to_context_force(struct selinux_state *state,
int security_sid_to_context_inval(struct selinux_state *state,
u32 sid, char **scontext, u32 *scontext_len);
+void fsinfo_note_sid(struct fsinfo_kparams *params, const char *key, u32 sid);
int security_context_to_sid(struct selinux_state *state,
const char *scontext, u32 scontext_len,
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index cc043bc8fd4c..1111b02a999b 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -50,6 +50,7 @@
#include <linux/audit.h>
#include <linux/mutex.h>
#include <linux/vmalloc.h>
+#include <linux/fsinfo.h>
#include <net/netlabel.h>
#include "flask.h"
@@ -1374,6 +1375,54 @@ int security_sid_to_context_inval(struct selinux_state *state, u32 sid,
scontext_len, 1, 1);
}
+#ifdef CONFIG_FSINFO
+void fsinfo_note_sid(struct fsinfo_kparams *params, const char *key, u32 sid)
+{
+ struct selinux_state *state = &selinux_state;
+ struct policydb *policydb;
+ struct context *context;
+ const char *val = "<<<INVALID>>>";
+ char *p;
+ int n;
+
+ if (!state->initialized) {
+ if (sid <= SECINITSID_NUM) {
+ val = initial_sid_to_string[sid];
+ goto out;
+ }
+
+ pr_err("SELinux: %s: called before initial "
+ "load_policy on unknown SID %d\n", __func__, sid);
+ goto out;
+ }
+
+ read_lock(&state->ss->policy_rwlock);
+
+ policydb = &state->ss->policydb;
+ context = sidtab_search(state->ss->sidtab, sid);
+ if (!context) {
+ pr_err("SELinux: %s: unrecognized SID %d\n", __func__, sid);
+ } else {
+ /* Copy the user name, role name and type name into the scratch
+ * buffer and then tack on the MLS.
+ */
+ val = p = params->scratch_buffer;
+ n = sprintf(p, "%s:%s:%s",
+ sym_name(policydb, SYM_USERS, context->user - 1),
+ sym_name(policydb, SYM_ROLES, context->role - 1),
+ sym_name(policydb, SYM_TYPES, context->type - 1));
+
+ p += n;
+ mls_sid_to_context(policydb, context, &p);
+ *p = 0;
+ }
+
+ read_unlock(&state->ss->policy_rwlock);
+out:
+ fsinfo_note_param(params, key, val);
+}
+#endif
+
/*
* Caveat: Mutates scontext.
*/
^ permalink raw reply related
* [PATCH 12/25] kernfs, cgroup: Add fsinfo support [ver #13]
From: David Howells @ 2019-05-28 15:12 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Add support for fsinfo() to kernfs and cgroup.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/kernfs/mount.c | 20 ++++++++++++++++++++
include/linux/kernfs.h | 4 ++++
kernel/cgroup/cgroup-v1.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
kernel/cgroup/cgroup.c | 19 +++++++++++++++++++
4 files changed, 87 insertions(+)
diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c
index 9a4646eecb71..f40d467d274b 100644
--- a/fs/kernfs/mount.c
+++ b/fs/kernfs/mount.c
@@ -17,6 +17,7 @@
#include <linux/namei.h>
#include <linux/seq_file.h>
#include <linux/exportfs.h>
+#include <linux/fsinfo.h>
#include "kernfs-internal.h"
@@ -45,6 +46,22 @@ static int kernfs_sop_show_path(struct seq_file *sf, struct dentry *dentry)
return 0;
}
+#ifdef CONFIG_FSINFO
+static int kernfs_sop_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct kernfs_root *root = kernfs_root(kernfs_dentry_node(path->dentry));
+ struct kernfs_syscall_ops *scops = root->syscall_ops;
+ int ret;
+
+ if (scops && scops->fsinfo) {
+ ret = scops->fsinfo(root, params);
+ if (ret != -EAGAIN)
+ return ret;
+ }
+ return generic_fsinfo(path, params);
+}
+#endif
+
const struct super_operations kernfs_sops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
@@ -52,6 +69,9 @@ const struct super_operations kernfs_sops = {
.show_options = kernfs_sop_show_options,
.show_path = kernfs_sop_show_path,
+#ifdef CONFIG_FSINFO
+ .fsinfo = kernfs_sop_fsinfo,
+#endif
};
/*
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
index 2bf477f86eb1..d01ec4dc2db1 100644
--- a/include/linux/kernfs.h
+++ b/include/linux/kernfs.h
@@ -27,6 +27,7 @@ struct super_block;
struct file_system_type;
struct poll_table_struct;
struct fs_context;
+struct fsinfo_kparams;
struct kernfs_fs_context;
struct kernfs_open_node;
@@ -171,6 +172,9 @@ struct kernfs_node {
*/
struct kernfs_syscall_ops {
int (*show_options)(struct seq_file *sf, struct kernfs_root *root);
+#ifdef CONFIG_FSINFO
+ int (*fsinfo)(struct kernfs_root *root, struct fsinfo_kparams *params);
+#endif
int (*mkdir)(struct kernfs_node *parent, const char *name,
umode_t mode);
diff --git a/kernel/cgroup/cgroup-v1.c b/kernel/cgroup/cgroup-v1.c
index 68ca5de7ec27..c8a85dfcac87 100644
--- a/kernel/cgroup/cgroup-v1.c
+++ b/kernel/cgroup/cgroup-v1.c
@@ -14,6 +14,7 @@
#include <linux/pid_namespace.h>
#include <linux/cgroupstats.h>
#include <linux/fs_parser.h>
+#include <linux/fsinfo.h>
#include <trace/events/cgroup.h>
@@ -921,6 +922,46 @@ const struct fs_parameter_description cgroup1_fs_parameters = {
.specs = cgroup1_param_specs,
};
+#ifdef CONFIG_FSINFO
+static int cgroup1_fsinfo(struct kernfs_root *kf_root, struct fsinfo_kparams *params)
+{
+ struct cgroup_root *root = cgroup_root_from_kf(kf_root);
+ struct cgroup_subsys *ss;
+ int ssid;
+
+ switch (params->request) {
+ case FSINFO_ATTR_PARAMETERS:
+ if (root->name[0])
+ fsinfo_note_param(params, "name", root->name);
+
+ if (test_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->cgrp.flags))
+ fsinfo_note_param(params, "clone_children", NULL);
+ if (root->flags & CGRP_ROOT_CPUSET_V2_MODE)
+ fsinfo_note_param(params, "noprefix", NULL);
+ if (root->flags & CGRP_ROOT_NOPREFIX)
+ fsinfo_note_param(params, "noprefix", NULL);
+ if (root->flags & CGRP_ROOT_XATTR)
+ fsinfo_note_param(params, "xattr", NULL);
+
+ spin_lock(&release_agent_path_lock);
+ if (root->release_agent_path[0])
+ fsinfo_note_param(params, "release_agent",
+ root->release_agent_path);
+ spin_unlock(&release_agent_path_lock);
+
+
+ for_each_subsys(ss, ssid) {
+ if (root->subsys_mask & (1 << ssid))
+ fsinfo_note_param(params, ss->legacy_name, NULL);
+ }
+ return params->usage;
+
+ default:
+ return -EAGAIN; /* Tell kernfs to call generic_fsinfo() */
+ }
+}
+#endif /* CONFIG_FSINFO */
+
int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct cgroup_fs_context *ctx = cgroup_fc2context(fc);
@@ -1114,6 +1155,9 @@ int cgroup1_reconfigure(struct fs_context *fc)
struct kernfs_syscall_ops cgroup1_kf_syscall_ops = {
.rename = cgroup1_rename,
.show_options = cgroup1_show_options,
+#ifdef CONFIG_FSINFO
+ .fsinfo = cgroup1_fsinfo,
+#endif
.mkdir = cgroup_mkdir,
.rmdir = cgroup_rmdir,
.show_path = cgroup_show_path,
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 4a0eb465d17e..7e32570905e9 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -55,6 +55,7 @@
#include <linux/nsproxy.h>
#include <linux/file.h>
#include <linux/fs_parser.h>
+#include <linux/fsinfo.h>
#include <linux/sched/cputime.h>
#include <linux/psi.h>
#include <net/sock.h>
@@ -1858,6 +1859,21 @@ static int cgroup_show_options(struct seq_file *seq, struct kernfs_root *kf_root
return 0;
}
+#ifdef CONFIG_FSINFO
+static int cgroup_fsinfo(struct kernfs_root *kf_root, struct fsinfo_kparams *params)
+{
+ switch (params->request) {
+ case FSINFO_ATTR_PARAMETERS:
+ if (cgrp_dfl_root.flags & CGRP_ROOT_NS_DELEGATE)
+ fsinfo_note_param(params, "nsdelegate", NULL);
+ return params->usage;
+
+ default:
+ return -EAGAIN; /* Tell kernfs to call generic_fsinfo() */
+ }
+}
+#endif /* CONFIG_FSINFO */
+
static int cgroup_reconfigure(struct fs_context *fc)
{
struct cgroup_fs_context *ctx = cgroup_fc2context(fc);
@@ -5550,6 +5566,9 @@ int cgroup_rmdir(struct kernfs_node *kn)
static struct kernfs_syscall_ops cgroup_kf_syscall_ops = {
.show_options = cgroup_show_options,
+#ifdef CONFIG_FSINFO
+ .fsinfo = cgroup_fsinfo,
+#endif
.mkdir = cgroup_mkdir,
.rmdir = cgroup_rmdir,
.show_path = cgroup_show_path,
^ permalink raw reply related
* [PATCH 11/25] hugetlbfs: Add support for fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:12 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Add support for fsinfo().
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/hugetlbfs/inode.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 1dcc57189382..e6ecebd9ebc2 100644
--- a/fs/hugetlbfs/inode.c
+++ b/fs/hugetlbfs/inode.c
@@ -28,6 +28,7 @@
#include <linux/hugetlb.h>
#include <linux/pagevec.h>
#include <linux/fs_parser.h>
+#include <linux/fsinfo.h>
#include <linux/mman.h>
#include <linux/slab.h>
#include <linux/dnotify.h>
@@ -958,6 +959,58 @@ static int hugetlbfs_show_options(struct seq_file *m, struct dentry *root)
return 0;
}
+#ifdef CONFIG_FSINFO
+static int hugetlbfs_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct dentry *dentry = path->dentry;
+ struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(dentry->d_sb);
+ struct hugepage_subpool *spool = sbinfo->spool;
+ unsigned long hpage_size = huge_page_size(sbinfo->hstate);
+ unsigned hpage_shift = huge_page_shift(sbinfo->hstate);
+ char mod;
+
+ switch (params->request) {
+ case FSINFO_ATTR_PARAMETERS:
+ if (!uid_eq(sbinfo->uid, GLOBAL_ROOT_UID))
+ fsinfo_note_paramf(params, "uid", "%u",
+ from_kuid_munged(&init_user_ns,
+ sbinfo->uid));
+
+ if (!gid_eq(sbinfo->gid, GLOBAL_ROOT_GID))
+ fsinfo_note_paramf(params, "gid", "%u",
+ from_kgid_munged(&init_user_ns,
+ sbinfo->gid));
+
+ if (spool && spool->max_hpages != -1)
+ fsinfo_note_paramf(params, "size", "%llu",
+ (unsigned long long)spool->max_hpages << hpage_shift);
+
+ if (spool && spool->min_hpages != -1)
+ fsinfo_note_paramf(params, "min_size", "%llu",
+ (unsigned long long)spool->min_hpages << hpage_shift);
+
+ hpage_size /= 1024;
+ mod = 'K';
+ if (hpage_size >= 1024) {
+ hpage_size /= 1024;
+ mod = 'M';
+ }
+ fsinfo_note_paramf(params, "pagesize", "%lu%c", hpage_size, mod);
+
+ if (sbinfo->mode != 0755)
+ fsinfo_note_paramf(params, "mode", "%o", sbinfo->mode);
+
+ if (sbinfo->max_inodes != -1)
+ fsinfo_note_paramf(params, "nr_inodes", "%lu",
+ sbinfo->max_inodes);
+ return params->usage;
+
+ default:
+ return generic_fsinfo(path, params);
+ }
+}
+#endif /* CONFIG_FSINFO */
+
static int hugetlbfs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
struct hugetlbfs_sb_info *sbinfo = HUGETLBFS_SB(dentry->d_sb);
@@ -1116,6 +1169,9 @@ static const struct super_operations hugetlbfs_ops = {
.statfs = hugetlbfs_statfs,
.put_super = hugetlbfs_put_super,
.show_options = hugetlbfs_show_options,
+#ifdef CONFIG_FSINFO
+ .fsinfo = hugetlbfs_fsinfo,
+#endif
};
/*
^ permalink raw reply related
* [PATCH 10/25] vfs: fsinfo sample: Mount listing program [ver #13]
From: David Howells @ 2019-05-28 15:12 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Implement a program to demonstrate mount listing using the new fsinfo()
syscall, for example:
# ./test-mntinfo
ROOT 5d c ext4 8:12
\_ sys 13 8 sysfs 0:13
| \_ kernel/security 16 0 securityfs 0:7
| \_ fs/cgroup 1a 10 tmpfs 0:17
| | \_ unified 1b 0 cgroup2 0:18
| | \_ systemd 1c 0 cgroup 0:19
| | \_ freezer 20 0 cgroup 0:1d
| | \_ cpu,cpuacct 21 0 cgroup 0:1e
| | \_ memory 22 0 cgroup 0:1f
| | \_ cpuset 23 0 cgroup 0:20
| | \_ hugetlb 24 0 cgroup 0:21
| | \_ net_cls,net_prio 25 0 cgroup 0:22
| | \_ blkio 26 0 cgroup 0:23
| | \_ perf_event 27 0 cgroup 0:24
| | \_ devices 28 0 cgroup 0:25
| | \_ rdma 29 0 cgroup 0:26
| \_ fs/pstore 1d 0 pstore 0:1a
| \_ firmware/efi/efivars 1e 0 efivarfs 0:1b
| \_ fs/bpf 1f 0 bpf 0:1c
| \_ kernel/config 5a 0 configfs 0:10
| \_ fs/selinux 2a 0 selinuxfs 0:12
| \_ kernel/debug 2e 0 debugfs 0:8
\_ dev 15 4 devtmpfs 0:6
| \_ shm 17 0 tmpfs 0:14
| \_ pts 18 0 devpts 0:15
| \_ hugepages 2b 0 hugetlbfs 0:27
| \_ mqueue 2c 0 mqueue 0:11
\_ run 19 1 tmpfs 0:16
| \_ user/0 1b4 0 tmpfs 0:2d
\_ proc 14 1 proc 0:4
| \_ sys/fs/binfmt_misc 2d 0 autofs 0:28
\_ tmp 2f 7d0 tmpfs 0:29
\_ var/cache/fscache 71 0 tmpfs 0:2a
\_ boot 74 0 ext4 8:15
\_ home 74 0 ext4 8:15
\_ var/lib/nfs/rpc_pipefs bf 0 rpc_pipefs 0:2b
\_ mnt 15b 5 tmpfs 0:2c
| \_ foo 164 0 tmpfs 0:2e
| \_ foo1 16d 0 tmpfs 0:2f
| \_ foo2 176 0 tmpfs 0:30
| \_ foo3 17f 0 tmpfs 0:31
| \_ foo4 188 1 tmpfs 0:32
| \_ "" 191 0 tmpfs 0:33
\_ afs 19a 2 afs 0:34
\_ procyon.org.uk 1a3 0 afs 0:35
\_ grand.central.org 1ac 0 afs 0:36
Signed-off-by: David Howells <dhowells@redhat.com>
---
samples/vfs/Makefile | 3 +
samples/vfs/test-mntinfo.c | 239 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 242 insertions(+)
create mode 100644 samples/vfs/test-mntinfo.c
diff --git a/samples/vfs/Makefile b/samples/vfs/Makefile
index 3c542d3b9479..d377b1f7de79 100644
--- a/samples/vfs/Makefile
+++ b/samples/vfs/Makefile
@@ -3,6 +3,7 @@ hostprogs-y := \
test-fsinfo \
test-fs-query \
test-fsmount \
+ test-mntinfo \
test-statx
# Tell kbuild to always build the programs
@@ -10,6 +11,8 @@ always := $(hostprogs-y)
HOSTCFLAGS_test-fsinfo.o += -I$(objtree)/usr/include
HOSTLDLIBS_test-fsinfo += -lm
+HOSTCFLAGS_test-mntinfo.o += -I$(objtree)/usr/include
+HOSTLDLIBS_test-mntinfo += -lm
HOSTCFLAGS_test-fs-query.o += -I$(objtree)/usr/include
HOSTCFLAGS_test-fsmount.o += -I$(objtree)/usr/include
diff --git a/samples/vfs/test-mntinfo.c b/samples/vfs/test-mntinfo.c
new file mode 100644
index 000000000000..00fbefae98fa
--- /dev/null
+++ b/samples/vfs/test-mntinfo.c
@@ -0,0 +1,239 @@
+/* Test the fsinfo() system call
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#define _ATFILE_SOURCE
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h>
+#include <sys/syscall.h>
+#include <linux/fsinfo.h>
+#include <linux/socket.h>
+#include <linux/fcntl.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+
+#ifndef __NR_fsinfo
+#define __NR_fsinfo -1
+#endif
+
+static __attribute__((unused))
+ssize_t fsinfo(int dfd, const char *filename, struct fsinfo_params *params,
+ void *buffer, size_t buf_size)
+{
+ return syscall(__NR_fsinfo, dfd, filename, params, buffer, buf_size);
+}
+
+static char tree_buf[4096];
+static char bar_buf[4096];
+
+/*
+ * Get an fsinfo attribute in a statically allocated buffer.
+ */
+static void get_attr(unsigned int mnt_id, enum fsinfo_attribute attr,
+ void *buf, size_t buf_size)
+{
+ struct fsinfo_params params = {
+ .at_flags = AT_FSINFO_MOUNTID_PATH,
+ .request = attr,
+ };
+ char file[32];
+ long ret;
+
+ sprintf(file, "%u", mnt_id);
+
+ memset(buf, 0xbd, buf_size);
+
+ ret = fsinfo(AT_FDCWD, file, ¶ms, buf, buf_size);
+ if (ret == -1) {
+ fprintf(stderr, "mount-%s: %m\n", file);
+ exit(1);
+ }
+}
+
+/*
+ * Get an fsinfo attribute in a dynamically allocated buffer.
+ */
+static void *get_attr_alloc(unsigned int mnt_id, enum fsinfo_attribute attr,
+ unsigned int Nth, size_t *_size)
+{
+ struct fsinfo_params params = {
+ .at_flags = AT_FSINFO_MOUNTID_PATH,
+ .request = attr,
+ .Nth = Nth,
+ };
+ size_t buf_size = 4096;
+ char file[32];
+ void *r;
+ long ret;
+
+ sprintf(file, "%u", mnt_id);
+
+ for (;;) {
+ r = malloc(buf_size);
+ if (!r) {
+ perror("malloc");
+ exit(1);
+ }
+ memset(r, 0xbd, buf_size);
+
+ ret = fsinfo(AT_FDCWD, file, ¶ms, r, buf_size);
+ if (ret == -1) {
+ fprintf(stderr, "mount-%s: %m\n", file);
+ exit(1);
+ }
+
+ if (ret <= buf_size) {
+ *_size = ret;
+ break;
+ }
+ buf_size = (ret + 4096 - 1) & ~(4096 - 1);
+ }
+
+ return r;
+}
+
+/*
+ * Display a mount and then recurse through its children.
+ */
+static void display_mount(unsigned int mnt_id, unsigned int depth, char *path)
+{
+ struct fsinfo_mount_child *children;
+ struct fsinfo_mount_info info;
+ struct fsinfo_ids ids;
+ unsigned int d;
+ size_t ch_size, p_size;
+ int i, n, s;
+
+ get_attr(mnt_id, FSINFO_ATTR_MOUNT_INFO, &info, sizeof(info));
+ get_attr(mnt_id, FSINFO_ATTR_IDS, &ids, sizeof(ids));
+ if (depth > 0)
+ printf("%s", tree_buf);
+
+ s = strlen(path);
+ printf("%s", !s ? "\"\"" : path);
+ if (!s)
+ s += 2;
+ s += depth;
+ if (s < 40)
+ s = 40 - s;
+ else
+ s = 1;
+ printf("%*.*s", s, s, "");
+
+ printf("%8x %8x %s %x:%x",
+ info.mnt_id, info.notify_counter,
+ ids.f_fs_name, ids.f_dev_major, ids.f_dev_minor);
+ putchar('\n');
+
+ children = get_attr_alloc(mnt_id, FSINFO_ATTR_MOUNT_CHILDREN, 0, &ch_size);
+ n = ch_size / sizeof(children[0]) - 1;
+
+ bar_buf[depth + 1] = '|';
+ if (depth > 0) {
+ tree_buf[depth - 4 + 1] = bar_buf[depth - 4 + 1];
+ tree_buf[depth - 4 + 2] = ' ';
+ }
+
+ tree_buf[depth + 0] = ' ';
+ tree_buf[depth + 1] = '\\';
+ tree_buf[depth + 2] = '_';
+ tree_buf[depth + 3] = ' ';
+ tree_buf[depth + 4] = 0;
+ d = depth + 4;
+
+ for (i = 0; i < n; i++) {
+ if (i == n - 1)
+ bar_buf[depth + 1] = ' ';
+ path = get_attr_alloc(mnt_id, FSINFO_ATTR_MOUNT_SUBMOUNT, i, &p_size);
+ display_mount(children[i].mnt_id, d, path + 1);
+ free(path);
+ }
+
+ free(children);
+ if (depth > 0) {
+ tree_buf[depth - 4 + 1] = '\\';
+ tree_buf[depth - 4 + 2] = '_';
+ }
+ tree_buf[depth] = 0;
+}
+
+/*
+ * Find the ID of whatever is at the nominated path.
+ */
+static unsigned int lookup_mnt_by_path(const char *path)
+{
+ struct fsinfo_mount_info mnt;
+ struct fsinfo_params params = {
+ .request = FSINFO_ATTR_MOUNT_INFO,
+ };
+
+ if (fsinfo(AT_FDCWD, path, ¶ms, &mnt, sizeof(mnt)) == -1) {
+ perror(path);
+ exit(1);
+ }
+
+ return mnt.mnt_id;
+}
+
+/*
+ *
+ */
+int main(int argc, char **argv)
+{
+ unsigned int mnt_id;
+ char *path;
+ bool use_mnt_id = false;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "M"))) {
+ switch (opt) {
+ case 'M':
+ use_mnt_id = true;
+ continue;
+ }
+ break;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ switch (argc) {
+ case 0:
+ mnt_id = lookup_mnt_by_path("/");
+ path = "ROOT";
+ break;
+ case 1:
+ path = argv[0];
+ if (use_mnt_id) {
+ mnt_id = strtoul(argv[0], NULL, 0);
+ break;
+ }
+
+ mnt_id = lookup_mnt_by_path(argv[0]);
+ break;
+ default:
+ printf("Format: test-mntinfo\n");
+ printf("Format: test-mntinfo <path>\n");
+ printf("Format: test-mntinfo -M <mnt_id>\n");
+ exit(2);
+ }
+
+ display_mount(mnt_id, 0, path);
+ return 0;
+}
^ permalink raw reply related
* [PATCH 09/25] vfs: Allow mount information to be queried by fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:12 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Allow mount information, including information about the topology tree to
be queried with the fsinfo() system call. Usage of AT_FSINFO_MOUNTID_PATH
allows overlapping mounts to be queried.
To this end, four fsinfo() attributes are provided:
(1) FSINFO_ATTR_MOUNT_INFO.
This is a structure providing information about a mount, including:
- Mounted superblock ID.
- Mount ID (as AT_FSINFO_MOUNTID_PATH).
- Parent mount ID.
- Mount attributes (eg. R/O, NOEXEC).
- Number of change notifications generated.
Note that the parent mount ID is overridden to the ID of the queried
mount if the parent lies outside of the chroot or dfd tree.
(2) FSINFO_ATTR_MOUNT_DEVNAME.
This a string providing the device name associated with the mount.
Note that the device name may be a path that lies outside of the root.
(3) FSINFO_ATTR_MOUNT_CHILDREN.
This produces an array of structures, one for each child and capped
with one for the argument mount (checked after listing all the
children). Each element contains the mount ID and the notification
counter of the respective mount object.
(4) FSINFO_ATTR_MOUNT_SUBMOUNT.
This is a 1D array of strings, indexed with struct fsinfo_params::Nth.
Each string is the relative pathname of the corresponding child
returned by FSINFO_ATTR_MOUNT_CHILD.
Note that paths in the mount at the base of the tree (whether that be
dfd or chroot) are relative to the base of the tree, not the root
directory of that mount.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/d_path.c | 2
fs/fsinfo.c | 9 ++
fs/internal.h | 9 ++
fs/namespace.c | 175 +++++++++++++++++++++++++++++++++++++++++++
include/uapi/linux/fsinfo.h | 28 +++++++
samples/vfs/test-fsinfo.c | 47 +++++++++++-
6 files changed, 266 insertions(+), 4 deletions(-)
diff --git a/fs/d_path.c b/fs/d_path.c
index a7d0a96b35ce..89d77c264c5f 100644
--- a/fs/d_path.c
+++ b/fs/d_path.c
@@ -227,7 +227,7 @@ static int prepend_unreachable(char **buffer, int *buflen)
return prepend(buffer, buflen, "(unreachable)", 13);
}
-static void get_fs_root_rcu(struct fs_struct *fs, struct path *root)
+void get_fs_root_rcu(struct fs_struct *fs, struct path *root)
{
unsigned seq;
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 84fdcd35bb5e..b9f8712907dc 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -312,6 +312,7 @@ int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
#define _gen(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(path, params->buffer)
#define _genf(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(f, params)
+#define _genp(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(path, params)
switch (params->request) {
case _gen(STATFS, statfs);
@@ -326,6 +327,10 @@ int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
case _genf(PARAM_DESCRIPTION, param_description);
case _genf(PARAM_SPECIFICATION, param_specification);
case _genf(PARAM_ENUM, param_enum);
+ case _genp(MOUNT_INFO, mount_info);
+ case _genp(MOUNT_DEVNAME, mount_devname);
+ case _genp(MOUNT_CHILDREN, mount_children);
+ case _genp(MOUNT_SUBMOUNT, mount_submount);
default:
return -EOPNOTSUPP;
}
@@ -594,6 +599,10 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
FSINFO_OPAQUE (PARAMETERS, -),
FSINFO_OPAQUE (LSM_PARAMETERS, -),
+ FSINFO_STRUCT (MOUNT_INFO, mount_info),
+ FSINFO_STRING (MOUNT_DEVNAME, mount_devname),
+ FSINFO_STRUCT_ARRAY (MOUNT_CHILDREN, mount_child),
+ FSINFO_STRING_N (MOUNT_SUBMOUNT, mount_submount),
};
/**
diff --git a/fs/internal.h b/fs/internal.h
index 074b1c65e3bd..bb3d8efa7f49 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -53,6 +53,11 @@ void __generic_write_end(struct inode *inode, loff_t pos, unsigned copied,
*/
extern void __init chrdev_init(void);
+/*
+ * d_path.c
+ */
+extern void get_fs_root_rcu(struct fs_struct *fs, struct path *root);
+
/*
* fs_context.c
*/
@@ -98,6 +103,10 @@ extern void __mnt_drop_write_file(struct file *);
extern void dissolve_on_fput(struct vfsmount *);
extern int lookup_mount_object(struct path *, int, struct path *);
+extern int fsinfo_generic_mount_info(struct path *, struct fsinfo_kparams *);
+extern int fsinfo_generic_mount_devname(struct path *, struct fsinfo_kparams *);
+extern int fsinfo_generic_mount_children(struct path *, struct fsinfo_kparams *);
+extern int fsinfo_generic_mount_submount(struct path *, struct fsinfo_kparams *);
/*
* fs_struct.c
diff --git a/fs/namespace.c b/fs/namespace.c
index 1450faab96b9..ae03066b2d9b 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -29,6 +29,7 @@
#include <linux/sched/task.h>
#include <uapi/linux/mount.h>
#include <linux/fs_context.h>
+#include <linux/fsinfo.h>
#include "pnode.h"
#include "internal.h"
@@ -4112,3 +4113,177 @@ int lookup_mount_object(struct path *root, int mnt_id, struct path *_mntpt)
unlock_mount_hash();
goto out_unlock;
}
+
+#ifdef CONFIG_FSINFO
+int fsinfo_generic_mount_info(struct path *path, struct fsinfo_kparams *params)
+{
+ struct fsinfo_mount_info *p = params->buffer;
+ struct super_block *sb;
+ struct mount *m;
+ struct path root;
+ unsigned int flags;
+
+ if (!path->mnt)
+ return -ENODATA;
+
+ m = real_mount(path->mnt);
+ sb = m->mnt.mnt_sb;
+
+ p->f_sb_id = sb->s_unique_id;
+ p->mnt_id = m->mnt_id;
+ p->parent_id = m->mnt_parent->mnt_id;
+ p->notify_counter = atomic_read(&m->mnt_notify_counter);
+
+ get_fs_root(current->fs, &root);
+ if (path->mnt == root.mnt) {
+ p->parent_id = p->mnt_id;
+ } else {
+ rcu_read_lock();
+ if (!are_paths_connected(&root, path))
+ p->parent_id = p->mnt_id;
+ rcu_read_unlock();
+ }
+ if (IS_MNT_SHARED(m))
+ p->group_id = m->mnt_group_id;
+ if (IS_MNT_SLAVE(m)) {
+ int master = m->mnt_master->mnt_group_id;
+ int dom = get_dominating_id(m, &root);
+ p->master_id = master;
+ if (dom && dom != master)
+ p->from_id = dom;
+ }
+ path_put(&root);
+
+ flags = READ_ONCE(m->mnt.mnt_flags);
+ if (flags & MNT_READONLY)
+ p->attr |= MOUNT_ATTR_RDONLY;
+ if (flags & MNT_NOSUID)
+ p->attr |= MOUNT_ATTR_NOSUID;
+ if (flags & MNT_NODEV)
+ p->attr |= MOUNT_ATTR_NODEV;
+ if (flags & MNT_NOEXEC)
+ p->attr |= MOUNT_ATTR_NOEXEC;
+ if (flags & MNT_NODIRATIME)
+ p->attr |= MOUNT_ATTR_NODIRATIME;
+
+ if (flags & MNT_NOATIME)
+ p->attr |= MOUNT_ATTR_NOATIME;
+ else if (flags & MNT_RELATIME)
+ p->attr |= MOUNT_ATTR_RELATIME;
+ else
+ p->attr |= MOUNT_ATTR_STRICTATIME;
+ return sizeof(*p);
+}
+
+int fsinfo_generic_mount_devname(struct path *path, struct fsinfo_kparams *params)
+{
+ struct mount *m;
+ size_t len;
+
+ if (!path->mnt)
+ return -ENODATA;
+
+ m = real_mount(path->mnt);
+ len = strlen(m->mnt_devname);
+ memcpy(params->buffer, m->mnt_devname, len);
+ return len;
+}
+
+/*
+ * Store a mount record into the fsinfo buffer.
+ */
+static void store_mount_fsinfo(struct fsinfo_kparams *params,
+ struct fsinfo_mount_child *child)
+{
+ unsigned int usage = params->usage;
+ unsigned int total = sizeof(*child);
+
+ if (params->usage >= INT_MAX)
+ return;
+ params->usage = usage + total;
+ if (params->buffer && params->usage <= params->buf_size)
+ memcpy(params->buffer + usage, child, total);
+}
+
+/*
+ * Return information about the submounts relative to path.
+ */
+int fsinfo_generic_mount_children(struct path *path, struct fsinfo_kparams *params)
+{
+ struct fsinfo_mount_child record;
+ struct mount *m, *child;
+
+ if (!path->mnt)
+ return -ENODATA;
+
+ rcu_read_lock();
+
+ m = real_mount(path->mnt);
+ list_for_each_entry_rcu(child, &m->mnt_mounts, mnt_child) {
+ if (child->mnt_parent != m)
+ continue;
+ record.mnt_id = child->mnt_id;
+ record.notify_counter = atomic_read(&child->mnt_notify_counter);
+ store_mount_fsinfo(params, &record);
+ }
+
+ record.mnt_id = m->mnt_id;
+ record.notify_counter = atomic_read(&m->mnt_notify_counter);
+ store_mount_fsinfo(params, &record);
+
+ rcu_read_unlock();
+ return params->usage;
+}
+
+/*
+ * Return the path of the Nth submount relative to path. This is derived from
+ * d_path(), but the root determination is more complicated.
+ */
+int fsinfo_generic_mount_submount(struct path *path, struct fsinfo_kparams *params)
+{
+ struct mountpoint *mp;
+ struct mount *m, *child;
+ struct path mountpoint, root;
+ unsigned int n = params->Nth;
+ size_t len;
+ void *p;
+
+ if (!path->mnt)
+ return -ENODATA;
+
+ rcu_read_lock();
+
+ m = real_mount(path->mnt);
+ list_for_each_entry_rcu(child, &m->mnt_mounts, mnt_child) {
+ mp = READ_ONCE(child->mnt_mp);
+ if (child->mnt_parent != m || !mp)
+ continue;
+ if (n-- == 0)
+ goto found;
+ }
+ rcu_read_unlock();
+ return -ENODATA;
+
+found:
+ mountpoint.mnt = path->mnt;
+ mountpoint.dentry = READ_ONCE(mp->m_dentry);
+
+ get_fs_root_rcu(current->fs, &root);
+ if (root.mnt != path->mnt) {
+ root.mnt = path->mnt;
+ root.dentry = path->mnt->mnt_root;
+ }
+
+ p = __d_path(&mountpoint, &root, params->buffer, params->buf_size);
+ rcu_read_unlock();
+
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+ if (!p)
+ return -EPERM;
+
+ len = (params->buffer + params->buf_size) - p;
+ memmove(params->buffer, p, len);
+ return len;
+}
+#endif /* CONFIG_FSINFO */
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index dae2e8dd757e..7f7a75e9758a 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -32,6 +32,10 @@ enum fsinfo_attribute {
FSINFO_ATTR_PARAM_ENUM = 14, /* Nth enum-to-val */
FSINFO_ATTR_PARAMETERS = 15, /* Mount parameters (large string) */
FSINFO_ATTR_LSM_PARAMETERS = 16, /* LSM Mount parameters (large string) */
+ FSINFO_ATTR_MOUNT_INFO = 17, /* Mount object information */
+ FSINFO_ATTR_MOUNT_DEVNAME = 18, /* Mount object device name (string) */
+ FSINFO_ATTR_MOUNT_CHILDREN = 19, /* Submount list (array) */
+ FSINFO_ATTR_MOUNT_SUBMOUNT = 20, /* Relative path of Nth submount (string) */
FSINFO_ATTR__NR
};
@@ -268,4 +272,28 @@ struct fsinfo_param_enum {
char name[252]; /* Name of the enum value */
};
+/*
+ * Information struct for fsinfo(FSINFO_ATTR_MOUNT_INFO).
+ */
+struct fsinfo_mount_info {
+ __u64 f_sb_id; /* Superblock ID */
+ __u32 mnt_id; /* Mount identifier (use with AT_FSINFO_MOUNTID_PATH) */
+ __u32 parent_id; /* Parent mount identifier */
+ __u32 group_id; /* Mount group ID */
+ __u32 master_id; /* Slave master group ID */
+ __u32 from_id; /* Slave propogated from ID */
+ __u32 attr; /* MOUNT_ATTR_* flags */
+ __u32 notify_counter; /* Number of notifications generated. */
+ __u32 __reserved[1];
+};
+
+/*
+ * Information struct element for fsinfo(FSINFO_ATTR_MOUNT_CHILDREN).
+ * - An extra element is placed on the end representing the parent mount.
+ */
+struct fsinfo_mount_child {
+ __u32 mnt_id; /* Mount identifier (use with AT_FSINFO_MOUNTID_PATH) */
+ __u32 notify_counter; /* Number of notifications generated on mount. */
+};
+
#endif /* _UAPI_LINUX_FSINFO_H */
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index 90926024e1c5..a838adcdca9e 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -21,10 +21,10 @@
#include <errno.h>
#include <time.h>
#include <math.h>
-#include <fcntl.h>
#include <sys/syscall.h>
#include <linux/fsinfo.h>
#include <linux/socket.h>
+#include <linux/fcntl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
@@ -83,6 +83,10 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
FSINFO_OVERLARGE (PARAMETERS, -),
FSINFO_OVERLARGE (LSM_PARAMETERS, -),
+ FSINFO_STRUCT (MOUNT_INFO, mount_info),
+ FSINFO_STRING (MOUNT_DEVNAME, mount_devname),
+ FSINFO_STRUCT_ARRAY (MOUNT_CHILDREN, mount_child),
+ FSINFO_STRING_N (MOUNT_SUBMOUNT, mount_submount),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -104,6 +108,10 @@ static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
FSINFO_NAME (PARAM_ENUM, param_enum),
FSINFO_NAME (PARAMETERS, parameters),
FSINFO_NAME (LSM_PARAMETERS, lsm_parameters),
+ FSINFO_NAME (MOUNT_INFO, mount_info),
+ FSINFO_NAME (MOUNT_DEVNAME, mount_devname),
+ FSINFO_NAME (MOUNT_CHILDREN, mount_children),
+ FSINFO_NAME (MOUNT_SUBMOUNT, mount_submount),
};
union reply {
@@ -116,6 +124,8 @@ union reply {
struct fsinfo_capabilities caps;
struct fsinfo_timestamp_info timestamps;
struct fsinfo_volume_uuid uuid;
+ struct fsinfo_mount_info mount_info;
+ struct fsinfo_mount_child mount_children[1];
};
static void dump_hex(unsigned int *data, int from, int to)
@@ -312,6 +322,29 @@ static void dump_attr_VOLUME_UUID(union reply *r, int size)
f->uuid[14], f->uuid[15]);
}
+static void dump_attr_MOUNT_INFO(union reply *r, int size)
+{
+ struct fsinfo_mount_info *f = &r->mount_info;
+
+ printf("\n");
+ printf("\tsb_id : %llx\n", (unsigned long long)f->f_sb_id);
+ printf("\tmnt_id : %x\n", f->mnt_id);
+ printf("\tparent : %x\n", f->parent_id);
+ printf("\tgroup : %x\n", f->group_id);
+ printf("\tattr : %x\n", f->attr);
+ printf("\tnotifs : %x\n", f->notify_counter);
+}
+
+static void dump_attr_MOUNT_CHILDREN(union reply *r, int size)
+{
+ struct fsinfo_mount_child *f = r->mount_children;
+ int i = 0;
+
+ printf("\n");
+ for (; size >= sizeof(*f); size -= sizeof(*f), f++)
+ printf("\t[%u] %8x %8x\n", i++, f->mnt_id, f->notify_counter);
+}
+
/*
*
*/
@@ -327,6 +360,8 @@ static const dumper_t fsinfo_attr_dumper[FSINFO_ATTR__NR] = {
FSINFO_DUMPER(CAPABILITIES),
FSINFO_DUMPER(TIMESTAMP_INFO),
FSINFO_DUMPER(VOLUME_UUID),
+ FSINFO_DUMPER(MOUNT_INFO),
+ FSINFO_DUMPER(MOUNT_CHILDREN),
};
static void dump_fsinfo(enum fsinfo_attribute attr,
@@ -529,16 +564,21 @@ int main(int argc, char **argv)
unsigned int attr;
int raw = 0, opt, Nth, Mth;
- while ((opt = getopt(argc, argv, "adlr"))) {
+ while ((opt = getopt(argc, argv, "Madlr"))) {
switch (opt) {
+ case 'M':
+ params.at_flags = AT_FSINFO_MOUNTID_PATH;
+ continue;
case 'a':
params.at_flags |= AT_NO_AUTOMOUNT;
+ params.at_flags &= ~AT_FSINFO_MOUNTID_PATH;
continue;
case 'd':
debug = true;
continue;
case 'l':
params.at_flags &= ~AT_SYMLINK_NOFOLLOW;
+ params.at_flags &= ~AT_FSINFO_MOUNTID_PATH;
continue;
case 'r':
raw = 1;
@@ -551,7 +591,8 @@ int main(int argc, char **argv)
argv += optind;
if (argc != 1) {
- printf("Format: test-fsinfo [-alr] <file>\n");
+ printf("Format: test-fsinfo [-adlr] <file>\n");
+ printf("Format: test-fsinfo [-dr] -M <mnt_id>\n");
exit(2);
}
^ permalink raw reply related
* [PATCH 08/25] vfs: Add mount notification count [ver #13]
From: David Howells @ 2019-05-28 15:12 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Add a notification count on mount objects so that the user can easily check
to see if a mount has changed its attributes or its children.
Future patches will:
(1) Provide this value through fsinfo() attributes.
(2) Hook into the notify_mount() function to provide a notification
interface for userspace to monitor.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/mount.h | 22 ++++++++++++++++++++++
fs/namespace.c | 13 +++++++++++++
2 files changed, 35 insertions(+)
diff --git a/fs/mount.h b/fs/mount.h
index 6250de544760..47795802f78e 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -70,6 +70,7 @@ struct mount {
struct hlist_head mnt_pins;
struct fs_pin mnt_umount;
struct dentry *mnt_ex_mountpoint;
+ atomic_t mnt_notify_counter; /* Number of notifications generated */
} __randomize_layout;
#define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
@@ -151,3 +152,24 @@ static inline bool is_anon_ns(struct mnt_namespace *ns)
{
return ns->seq == 0;
}
+
+/*
+ * Type of mount topology change notification.
+ */
+enum mount_notification_subtype {
+ NOTIFY_MOUNT_NEW_MOUNT = 0, /* New mount added */
+ NOTIFY_MOUNT_UNMOUNT = 1, /* Mount removed manually */
+ NOTIFY_MOUNT_EXPIRY = 2, /* Automount expired */
+ NOTIFY_MOUNT_READONLY = 3, /* Mount R/O state changed */
+ NOTIFY_MOUNT_SETATTR = 4, /* Mount attributes changed */
+ NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */
+ NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */
+};
+
+static inline void notify_mount(struct mount *changed,
+ struct mount *aux,
+ enum mount_notification_subtype subtype,
+ u32 info_flags)
+{
+ atomic_inc(&changed->mnt_notify_counter);
+}
diff --git a/fs/namespace.c b/fs/namespace.c
index a49a7d9ed482..1450faab96b9 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -513,6 +513,8 @@ static int mnt_make_readonly(struct mount *mnt)
smp_wmb();
mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD;
unlock_mount_hash();
+ if (ret == 0)
+ notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY, 0x10000);
return ret;
}
@@ -521,6 +523,7 @@ static int __mnt_unmake_readonly(struct mount *mnt)
lock_mount_hash();
mnt->mnt.mnt_flags &= ~MNT_READONLY;
unlock_mount_hash();
+ notify_mount(mnt, NULL, NOTIFY_MOUNT_READONLY, 0);
return 0;
}
@@ -833,6 +836,7 @@ static void umount_mnt(struct mount *mnt)
{
/* old mountpoint will be dropped when we can do that */
mnt->mnt_ex_mountpoint = mnt->mnt_mountpoint;
+ notify_mount(mnt->mnt_parent, mnt, NOTIFY_MOUNT_UNMOUNT, 0);
unhash_mnt(mnt);
}
@@ -1472,6 +1476,7 @@ static void umount_tree(struct mount *mnt, enum umount_tree_flags how)
p = list_first_entry(&tmp_list, struct mount, mnt_list);
list_del_init(&p->mnt_expire);
list_del_init(&p->mnt_list);
+
ns = p->mnt_ns;
if (ns) {
ns->mounts--;
@@ -2095,7 +2100,10 @@ static int attach_recursive_mnt(struct mount *source_mnt,
lock_mount_hash();
}
if (parent_path) {
+ notify_mount(source_mnt->mnt_parent, source_mnt,
+ NOTIFY_MOUNT_MOVE_FROM, 0);
detach_mnt(source_mnt, parent_path);
+ notify_mount(dest_mnt, source_mnt, NOTIFY_MOUNT_MOVE_TO, 0);
attach_mnt(source_mnt, dest_mnt, dest_mp);
touch_mnt_namespace(source_mnt->mnt_ns);
} else {
@@ -2104,6 +2112,9 @@ static int attach_recursive_mnt(struct mount *source_mnt,
list_del_init(&source_mnt->mnt_ns->list);
}
mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
+ notify_mount(dest_mnt, source_mnt, NOTIFY_MOUNT_NEW_MOUNT,
+ source_mnt->mnt.mnt_sb->s_flags & SB_SUBMOUNT ?
+ 0x10000 : 0);
commit_tree(source_mnt);
}
@@ -2480,6 +2491,7 @@ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags)
mnt->mnt.mnt_flags = mnt_flags;
touch_mnt_namespace(mnt->mnt_ns);
unlock_mount_hash();
+ notify_mount(mnt, NULL, NOTIFY_MOUNT_SETATTR, 0);
}
/*
@@ -2880,6 +2892,7 @@ void mark_mounts_for_expiry(struct list_head *mounts)
if (!xchg(&mnt->mnt_expiry_mark, 1) ||
propagate_mount_busy(mnt, 1))
continue;
+ notify_mount(mnt, NULL, NOTIFY_MOUNT_EXPIRY, 0);
list_move(&mnt->mnt_expire, &graveyard);
}
while (!list_empty(&graveyard)) {
^ permalink raw reply related
* [PATCH 07/25] vfs: Allow fsinfo() to look up a mount object by ID [ver #13]
From: David Howells @ 2019-05-28 15:12 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Allow the fsinfo() syscall to look up a mount object by ID rather than by
pathname. This is necessary as there can be multiple mounts stacked up at
the same pathname and there's no way to look through them otherwise.
This is done by passing AT_FSINFO_MOUNTID_PATH to fsinfo() in the
parameters and then passing the mount ID as a string to fsinfo() in place
of the filename:
struct fsinfo_params params = {
.at_flags = AT_FSINFO_MOUNTID_PATH,
.request = FSINFO_ATTR_IDS,
};
ret = fsinfo(AT_FDCWD, "21", ¶ms, buffer, sizeof(buffer));
The caller is only permitted to query a mount object if the root directory
of that mount connects directly to the current chroot if dfd == AT_FDCWD[*]
or the directory specified by dfd otherwise. Note that this is not
available to the pathwalk of any other syscall.
[*] This needs to be something other than AT_FDCWD, perhaps AT_FDROOT.
[!] This probably needs an LSM hook.
[!] This might want to check the permissions on all the intervening dirs -
but it would have to do that under RCU conditions.
[!] This might want to check a CAP_* flag.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 55 ++++++++++++++++++++-
fs/internal.h | 2 +
fs/namespace.c | 117 +++++++++++++++++++++++++++++++++++++++++++-
include/uapi/linux/fcntl.h | 2 +
4 files changed, 172 insertions(+), 4 deletions(-)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 6afeabfe9451..84fdcd35bb5e 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -483,6 +483,57 @@ static int vfs_fsinfo_fd(unsigned int fd, struct fsinfo_kparams *params)
return ret;
}
+/*
+ * Look up the root of a mount object. This allows access to mount objects
+ * (and their attached superblocks) that can't be retrieved by path because
+ * they're entirely covered.
+ *
+ * We only permit access to a mount that has a direct path between either the
+ * dentry pointed to by dfd or to our chroot (if dfd is AT_FDCWD).
+ */
+static int vfs_fsinfo_mount(int dfd, const char __user *filename,
+ struct fsinfo_kparams *params)
+{
+ struct path path;
+ struct fd f = {};
+ char *name;
+ long mnt_id;
+ int ret;
+
+ if ((params->at_flags & ~AT_FSINFO_MOUNTID_PATH) ||
+ !filename)
+ return -EINVAL;
+
+ name = strndup_user(filename, 32);
+ if (IS_ERR(name))
+ return PTR_ERR(name);
+ ret = kstrtoul(name, 0, &mnt_id);
+ if (ret < 0)
+ goto out_name;
+ if (mnt_id > INT_MAX)
+ goto out_name;
+
+ if (dfd != AT_FDCWD) {
+ ret = -EBADF;
+ f = fdget_raw(dfd);
+ if (!f.file)
+ goto out_name;
+ }
+
+ ret = lookup_mount_object(f.file ? &f.file->f_path : NULL,
+ mnt_id, &path);
+ if (ret < 0)
+ goto out_fd;
+
+ ret = vfs_fsinfo(&path, params);
+ path_put(&path);
+out_fd:
+ fdput(f);
+out_name:
+ kfree(name);
+ return ret;
+}
+
/*
* Return buffer information by requestable attribute.
*
@@ -648,7 +699,9 @@ SYSCALL_DEFINE5(fsinfo,
if (!params.buffer)
goto error_scratch;
- if (filename)
+ if (params.at_flags & AT_FSINFO_MOUNTID_PATH)
+ ret = vfs_fsinfo_mount(dfd, filename, ¶ms);
+ else if (filename)
ret = vfs_fsinfo_path(dfd, filename, ¶ms);
else
ret = vfs_fsinfo_fd(dfd, ¶ms);
diff --git a/fs/internal.h b/fs/internal.h
index b089a489da1f..074b1c65e3bd 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -97,6 +97,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 lookup_mount_object(struct path *, int, struct path *);
+
/*
* fs_struct.c
*/
diff --git a/fs/namespace.c b/fs/namespace.c
index 1141641dff96..a49a7d9ed482 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -62,7 +62,7 @@ static int __init set_mphash_entries(char *str)
__setup("mphash_entries=", set_mphash_entries);
static u64 event;
-static DEFINE_IDA(mnt_id_ida);
+static DEFINE_IDR(mnt_id_ida);
static DEFINE_IDA(mnt_group_ida);
static struct hlist_head *mount_hashtable __read_mostly;
@@ -101,17 +101,27 @@ static inline struct hlist_head *mp_hash(struct dentry *dentry)
static int mnt_alloc_id(struct mount *mnt)
{
- int res = ida_alloc(&mnt_id_ida, GFP_KERNEL);
+ int res;
+ /* Allocate an ID, but don't set the pointer back to the mount until
+ * later, as once we do that, we have to follow RCU protocols to get
+ * rid of the mount struct.
+ */
+ res = idr_alloc(&mnt_id_ida, NULL, 0, INT_MAX, GFP_KERNEL);
if (res < 0)
return res;
mnt->mnt_id = res;
return 0;
}
+static void mnt_publish_id(struct mount *mnt)
+{
+ idr_replace(&mnt_id_ida, mnt, mnt->mnt_id);
+}
+
static void mnt_free_id(struct mount *mnt)
{
- ida_free(&mnt_id_ida, mnt->mnt_id);
+ idr_remove(&mnt_id_ida, mnt->mnt_id);
}
/*
@@ -974,6 +984,7 @@ struct vfsmount *vfs_create_mount(struct fs_context *fc)
lock_mount_hash();
list_add_tail(&mnt->mnt_instance, &mnt->mnt.mnt_sb->s_mounts);
unlock_mount_hash();
+ mnt_publish_id(mnt);
return &mnt->mnt;
}
EXPORT_SYMBOL(vfs_create_mount);
@@ -1067,6 +1078,7 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
lock_mount_hash();
list_add_tail(&mnt->mnt_instance, &sb->s_mounts);
unlock_mount_hash();
+ mnt_publish_id(mnt);
if ((flag & CL_SLAVE) ||
((flag & CL_SHARED_TO_SLAVE) && IS_MNT_SHARED(old))) {
@@ -3988,3 +4000,102 @@ const struct proc_ns_operations mntns_operations = {
.install = mntns_install,
.owner = mntns_owner,
};
+
+/*
+ * See if one path point connects directly to another by ancestral relationship
+ * across mountpoints. Must call with the RCU read lock held.
+ */
+static bool are_paths_connected(struct path *ancestor, struct path *to_check)
+{
+ struct mount *mnt, *parent;
+ struct path cursor;
+ unsigned seq;
+ bool connected;
+
+ seq = 0;
+restart:
+ cursor = *to_check;
+
+ read_seqbegin_or_lock(&rename_lock, &seq);
+ while (cursor.mnt != ancestor->mnt) {
+ mnt = real_mount(cursor.mnt);
+ parent = READ_ONCE(mnt->mnt_parent);
+ if (mnt == parent)
+ goto failed;
+ cursor.dentry = READ_ONCE(mnt->mnt_mountpoint);
+ cursor.mnt = &parent->mnt;
+ }
+
+ while (cursor.dentry != ancestor->dentry) {
+ if (cursor.dentry == cursor.mnt->mnt_root ||
+ IS_ROOT(cursor.dentry))
+ goto failed;
+ cursor.dentry = READ_ONCE(cursor.dentry->d_parent);
+ }
+
+ connected = true;
+out:
+ done_seqretry(&rename_lock, seq);
+ return connected;
+
+failed:
+ if (need_seqretry(&rename_lock, seq)) {
+ seq = 1;
+ goto restart;
+ }
+ connected = false;
+ goto out;
+}
+
+/**
+ * lookup_mount_object - Look up a vfsmount object by ID
+ * @root: The mount root must connect backwards to this point (or chroot if NULL).
+ * @id: The ID of the mountpoint.
+ * @_mntpt: Where to return the resulting mountpoint path.
+ *
+ * Look up the root of the mount with the corresponding ID. This is only
+ * permitted if that mount connects directly to the specified root/chroot.
+ */
+int lookup_mount_object(struct path *root, int mnt_id, struct path *_mntpt)
+{
+ struct mount *mnt;
+ struct path stop, mntpt = {};
+ int ret = -EPERM;
+
+ if (!root)
+ get_fs_root(current->fs, &stop);
+ else
+ stop = *root;
+
+ rcu_read_lock();
+ lock_mount_hash();
+ mnt = idr_find(&mnt_id_ida, mnt_id);
+ if (!mnt)
+ goto out_unlock_mh;
+ if (mnt->mnt.mnt_flags & (MNT_SYNC_UMOUNT | MNT_UMOUNT | MNT_DOOMED))
+ goto out_unlock_mh;
+ if (mnt_get_count(mnt) == 0)
+ goto out_unlock_mh;
+ mnt_add_count(mnt, 1);
+ mntpt.mnt = &mnt->mnt;
+ mntpt.dentry = dget(mnt->mnt.mnt_root);
+ unlock_mount_hash();
+
+ if (are_paths_connected(&stop, &mntpt)) {
+ *_mntpt = mntpt;
+ mntpt.mnt = NULL;
+ mntpt.dentry = NULL;
+ ret = 0;
+ }
+
+out_unlock:
+ rcu_read_unlock();
+ if (!root)
+ path_put(&stop);
+ path_put(&mntpt);
+ return ret;
+
+out_unlock_mh:
+ unlock_mount_hash();
+ goto out_unlock;
+}
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 1d338357df8a..d5828846bea6 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -91,6 +91,8 @@
#define AT_STATX_FORCE_SYNC 0x2000 /* - Force the attributes to be sync'd with the server */
#define AT_STATX_DONT_SYNC 0x4000 /* - Don't sync attributes with the server */
+#define AT_FSINFO_MOUNTID_PATH 0x2000 /* The path is a mount object ID, not an actual path */
+
#define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */
^ permalink raw reply related
* [PATCH 06/25] vfs: Introduce a non-repeating system-unique superblock ID [ver #13]
From: David Howells @ 2019-05-28 15:11 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Introduce a non-repeating system-unique superblock ID that can be used to
tag superblock notification messages. The ID is time-based to make it
harder to use it as a covert communications channel.
Make it so that this ID can be fetched by the fsinfo() system call.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 1 +
fs/super.c | 24 ++++++++++++++++++++++++
include/linux/fs.h | 3 +++
samples/vfs/test-fsinfo.c | 1 +
4 files changed, 29 insertions(+)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 256a87b62eed..6afeabfe9451 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -92,6 +92,7 @@ static int fsinfo_generic_ids(struct path *path, struct fsinfo_ids *p)
p->f_fstype = sb->s_magic;
p->f_dev_major = MAJOR(sb->s_dev);
p->f_dev_minor = MINOR(sb->s_dev);
+ p->f_sb_id = sb->s_unique_id;
memcpy(&p->f_fsid, &buf.f_fsid, sizeof(p->f_fsid));
strlcpy(p->f_fs_name, path->dentry->d_sb->s_type->name,
diff --git a/fs/super.c b/fs/super.c
index 27b3e41394c8..61819e8e5469 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -43,6 +43,8 @@ static int thaw_super_locked(struct super_block *sb);
static LIST_HEAD(super_blocks);
static DEFINE_SPINLOCK(sb_lock);
+static u64 sb_last_identifier;
+static u64 sb_identifier_offset;
static char *sb_writers_name[SB_FREEZE_LEVELS] = {
"sb_writers",
@@ -187,6 +189,27 @@ static void destroy_unused_super(struct super_block *s)
destroy_super_work(&s->destroy_work);
}
+/*
+ * Generate a unique identifier for a superblock.
+ */
+static void generate_super_id(struct super_block *s)
+{
+ u64 id = ktime_to_ns(ktime_get());
+
+ spin_lock(&sb_lock);
+
+ id += sb_identifier_offset;
+ if (id <= sb_last_identifier) {
+ id = sb_last_identifier + 1;
+ sb_identifier_offset = sb_last_identifier - id;
+ }
+
+ sb_last_identifier = id;
+ spin_unlock(&sb_lock);
+
+ s->s_unique_id = id;
+}
+
/**
* alloc_super - create new superblock
* @type: filesystem type superblock should belong to
@@ -270,6 +293,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
goto fail;
if (list_lru_init_memcg(&s->s_inode_lru, &s->s_shrink))
goto fail;
+ generate_super_id(s);
return s;
fail:
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 71ce3b054c42..f1c74596cd77 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1527,6 +1527,9 @@ struct super_block {
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
+
+ /* Superblock event notifications */
+ u64 s_unique_id;
} __randomize_layout;
/* Helper functions so that in most cases filesystems will
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index e98384e8fb46..90926024e1c5 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -179,6 +179,7 @@ static void dump_attr_IDS(union reply *r, int size)
printf("\tdev : %02x:%02x\n", f->f_dev_major, f->f_dev_minor);
printf("\tfs : type=%x name=%s\n", f->f_fstype, f->f_fs_name);
printf("\tfsid : %llx\n", (unsigned long long)f->f_fsid);
+ printf("\tsbid : %llx\n", (unsigned long long)f->f_sb_id);
}
static void dump_attr_LIMITS(union reply *r, int size)
^ permalink raw reply related
* [PATCH 05/25] fsinfo: Implement retrieval of LSM parameters with fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:11 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Implement LSM parameter value retrieval with fsinfo() - akin to parsing
/proc/mounts. This allows all the LSM parameters to be retrieved in one go
with:
struct fsinfo_params params = {
.request = FSINFO_ATTR_LSM_PARAMETER,
};
The format is a blob containing pairs of length-prefixed strings to avoid
the need to escape commas and suchlike in the values. This is the same as
for FSINFO_ATTR_PARAMETER.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 21 +++++++++++++++------
include/linux/lsm_hooks.h | 13 +++++++++++++
include/linux/security.h | 11 +++++++++++
include/uapi/linux/fsinfo.h | 1 +
samples/vfs/test-fsinfo.c | 6 +++++-
security/security.c | 12 ++++++++++++
6 files changed, 57 insertions(+), 7 deletions(-)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 2da321b34bdf..256a87b62eed 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -341,7 +341,8 @@ static int vfs_fsinfo(struct path *path, struct fsinfo_kparams *params)
int (*fsinfo)(struct path *, struct fsinfo_kparams *);
int ret;
- if (params->request == FSINFO_ATTR_FSINFO) {
+ switch (params->request) {
+ case FSINFO_ATTR_FSINFO: {
struct fsinfo_fsinfo *info = params->buffer;
info->max_attr = FSINFO_ATTR__NR;
@@ -349,11 +350,18 @@ static int vfs_fsinfo(struct path *path, struct fsinfo_kparams *params)
return sizeof(*info);
}
- fsinfo = dentry->d_sb->s_op->fsinfo;
- if (!fsinfo) {
- if (!dentry->d_sb->s_op->statfs)
- return -EOPNOTSUPP;
- fsinfo = generic_fsinfo;
+ case FSINFO_ATTR_LSM_PARAMETERS:
+ fsinfo = security_sb_fsinfo;
+ break;
+
+ default:
+ fsinfo = dentry->d_sb->s_op->fsinfo;
+ if (!fsinfo) {
+ if (!dentry->d_sb->s_op->statfs)
+ return -EOPNOTSUPP;
+ fsinfo = generic_fsinfo;
+ }
+ break;
}
ret = security_sb_statfs(dentry);
@@ -533,6 +541,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRUCT_N (PARAM_SPECIFICATION, param_specification),
FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
FSINFO_OPAQUE (PARAMETERS, -),
+ FSINFO_OPAQUE (LSM_PARAMETERS, -),
};
/**
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 47f58cfb6a19..2474c3f785ca 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -108,6 +108,13 @@
* mountpoint.
* @dentry is a handle on the superblock for the filesystem.
* Return 0 if permission is granted.
+ * @sb_fsinfo:
+ * Query LSM information for a filesystem.
+ * @path is a handle on the superblock for the filesystem.
+ * @params is the fsinfo parameter and buffer block.
+ * - Currently, params->request can only be FSINFO_ATTR_LSM_PARAMETERS.
+ * Return the length of the data in the buffer (and can return -ENODATA to
+ * indicate no value under certain circumstances).
* @sb_mount:
* Check permission before an object specified by @dev_name is mounted on
* the mount point named by @nd. For an ordinary mount, @dev_name
@@ -1492,6 +1499,9 @@ union security_list_options {
int (*sb_kern_mount)(struct super_block *sb);
int (*sb_show_options)(struct seq_file *m, struct super_block *sb);
int (*sb_statfs)(struct dentry *dentry);
+#ifdef CONFIG_FSINFO
+ int (*sb_fsinfo)(struct path *path, struct fsinfo_kparams *params);
+#endif
int (*sb_mount)(const char *dev_name, const struct path *path,
const char *type, unsigned long flags, void *data);
int (*sb_umount)(struct vfsmount *mnt, int flags);
@@ -1838,6 +1848,9 @@ struct security_hook_heads {
struct hlist_head sb_kern_mount;
struct hlist_head sb_show_options;
struct hlist_head sb_statfs;
+#ifdef CONFIG_FSINFO
+ struct hlist_head sb_fsinfo;
+#endif
struct hlist_head sb_mount;
struct hlist_head sb_umount;
struct hlist_head sb_pivotroot;
diff --git a/include/linux/security.h b/include/linux/security.h
index 659071c2e57c..23c8b602c0ab 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -57,6 +57,7 @@ struct mm_struct;
struct fs_context;
struct fs_parameter;
enum fs_value_type;
+struct fsinfo_kparams;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -237,6 +238,9 @@ int security_sb_remount(struct super_block *sb, void *mnt_opts);
int security_sb_kern_mount(struct super_block *sb);
int security_sb_show_options(struct seq_file *m, struct super_block *sb);
int security_sb_statfs(struct dentry *dentry);
+#ifdef CONFIG_FSINFO
+int security_sb_fsinfo(struct path *path, struct fsinfo_kparams *params);
+#endif
int security_sb_mount(const char *dev_name, const struct path *path,
const char *type, unsigned long flags, void *data);
int security_sb_umount(struct vfsmount *mnt, int flags);
@@ -575,6 +579,13 @@ static inline int security_sb_statfs(struct dentry *dentry)
return 0;
}
+#ifdef CONFIG_FSINFO
+static inline int security_sb_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ return 0;
+}
+#endif
+
static inline int security_sb_mount(const char *dev_name, const struct path *path,
const char *type, unsigned long flags,
void *data)
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index 0f134847e88b..dae2e8dd757e 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -31,6 +31,7 @@ enum fsinfo_attribute {
FSINFO_ATTR_PARAM_SPECIFICATION = 13, /* Nth parameter specification */
FSINFO_ATTR_PARAM_ENUM = 14, /* Nth enum-to-val */
FSINFO_ATTR_PARAMETERS = 15, /* Mount parameters (large string) */
+ FSINFO_ATTR_LSM_PARAMETERS = 16, /* LSM Mount parameters (large string) */
FSINFO_ATTR__NR
};
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index 2960fa2b9843..e98384e8fb46 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -82,6 +82,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRUCT_N (PARAM_SPECIFICATION, param_specification),
FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
FSINFO_OVERLARGE (PARAMETERS, -),
+ FSINFO_OVERLARGE (LSM_PARAMETERS, -),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -102,6 +103,7 @@ static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
FSINFO_NAME (PARAM_SPECIFICATION, param_specification),
FSINFO_NAME (PARAM_ENUM, param_enum),
FSINFO_NAME (PARAMETERS, parameters),
+ FSINFO_NAME (LSM_PARAMETERS, lsm_parameters),
};
union reply {
@@ -452,6 +454,7 @@ static int try_one(const char *file, struct fsinfo_params *params, bool raw)
switch (params->request) {
case FSINFO_ATTR_PARAMETERS:
+ case FSINFO_ATTR_LSM_PARAMETERS:
if (ret == 0)
return 0;
}
@@ -498,7 +501,8 @@ static int try_one(const char *file, struct fsinfo_params *params, bool raw)
return 0;
case __FSINFO_OVER:
- if (params->request == FSINFO_ATTR_PARAMETERS)
+ if (params->request == FSINFO_ATTR_PARAMETERS ||
+ params->request == FSINFO_ATTR_LSM_PARAMETERS)
dump_params(about, r, ret);
return 0;
diff --git a/security/security.c b/security/security.c
index 613a5c00e602..3af886e8fced 100644
--- a/security/security.c
+++ b/security/security.c
@@ -25,6 +25,7 @@
#include <linux/ima.h>
#include <linux/evm.h>
#include <linux/fsnotify.h>
+#include <linux/fsinfo.h>
#include <linux/mman.h>
#include <linux/mount.h>
#include <linux/personality.h>
@@ -821,6 +822,17 @@ int security_sb_statfs(struct dentry *dentry)
return call_int_hook(sb_statfs, 0, dentry);
}
+#ifdef CONFIG_FSINFO
+int security_sb_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ int ret = -ENODATA;
+
+ if (params->request == FSINFO_ATTR_LSM_PARAMETERS)
+ ret = 0; /* This is cumulative amongst all LSMs */
+ return call_int_hook(sb_fsinfo, ret, path, params);
+}
+#endif
+
int security_sb_mount(const char *dev_name, const struct path *path,
const char *type, unsigned long flags, void *data)
{
^ permalink raw reply related
* [PATCH 04/25] vfs: Implement parameter value retrieval with fsinfo() [ver #13]
From: David Howells @ 2019-05-28 15:11 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Implement parameter value retrieval with fsinfo() - akin to parsing
/proc/mounts.
This allows all the parameters to be retrieved in one go with:
struct fsinfo_params params = {
.request = FSINFO_ATTR_PARAMETER,
};
Each parameter comes as a pair of blobs with a length tacked on the front
rather than using separators, since any printable character that could be
used as a separator can be found in some value somewhere (including comma).
In fact, cifs allows the separator to be set using the "sep=" option in
parameter parsing.
The length on the front of each blob is 1-3 bytes long. Each byte has a
flag in bit 7 that's set if there are more bytes and clear on the last
byte; bits 0-6 should be shifted and OR'd into the length count. The bytes
are most-significant first.
For example, 0x83 0xf5 0x06 is the length (0x03<<14 | 0x75<<7 | 0x06).
As mentioned, each parameter comes as a pair of blobs in key, value order.
The value has length zero if not present. So, for example:
\x08compress\x04zlib
from btrfs would be equivalent to "compress=zlib" and:
\x02ro\x00\x06noexec\x00
would be equivalent to "ro,noexec".
The test-fsinfo sample program is modified to dump the parameters.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 114 +++++++++++++++++++++++++++++++++++++++++++
include/linux/fsinfo.h | 3 +
include/uapi/linux/fsinfo.h | 1
samples/vfs/test-fsinfo.c | 38 ++++++++++++++
4 files changed, 156 insertions(+)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index c7f9c894c737..2da321b34bdf 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -283,6 +283,25 @@ static int fsinfo_generic_param_enum(struct file_system_type *f,
return sizeof(*p);
}
+static void fsinfo_insert_sb_flag_parameters(struct path *path,
+ struct fsinfo_kparams *params)
+{
+ int s_flags = READ_ONCE(path->dentry->d_sb->s_flags);
+
+ if (s_flags & SB_DIRSYNC)
+ fsinfo_note_param(params, "dirsync", NULL);
+ if (s_flags & SB_LAZYTIME)
+ fsinfo_note_param(params, "lazytime", NULL);
+ if (s_flags & SB_MANDLOCK)
+ fsinfo_note_param(params, "mand", NULL);
+ if (s_flags & SB_POSIXACL)
+ fsinfo_note_param(params, "posixacl", NULL);
+ if (s_flags & SB_RDONLY)
+ fsinfo_note_param(params, "ro", NULL);
+ if (s_flags & SB_SYNCHRONOUS)
+ fsinfo_note_param(params, "sync", NULL);
+}
+
/*
* Implement some queries generically from stuff in the superblock.
*/
@@ -345,8 +364,17 @@ static int vfs_fsinfo(struct path *path, struct fsinfo_kparams *params)
return fsinfo(path, params);
while (!signal_pending(current)) {
+ if (params->request == FSINFO_ATTR_PARAMETERS) {
+ if (down_read_killable(&dentry->d_sb->s_umount) < 0)
+ return -ERESTARTSYS;
+ fsinfo_insert_sb_flag_parameters(path, params);
+ }
+
params->usage = 0;
ret = fsinfo(path, params);
+ if (params->request == FSINFO_ATTR_PARAMETERS)
+ up_read(&dentry->d_sb->s_umount);
+
if (ret <= (int)params->buf_size)
return ret; /* Error or it fitted */
kvfree(params->buffer);
@@ -504,6 +532,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRUCT (PARAM_DESCRIPTION, param_description),
FSINFO_STRUCT_N (PARAM_SPECIFICATION, param_specification),
FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
+ FSINFO_OPAQUE (PARAMETERS, -),
};
/**
@@ -644,3 +673,88 @@ SYSCALL_DEFINE5(fsinfo,
error:
return ret;
}
+
+/*
+ * Store a parameter into the user's parameter buffer. The key is prefixed by
+ * a single byte length (1-127) and the value by one (0-0x7f) or two bytes
+ * (0x80-0x3fff) or three bytes (0x4000-0x1fffff).
+ *
+ * Note that we must always make the size determination, even if the buffer is
+ * already full, so that we can tell the caller how much buffer we actually
+ * need.
+ */
+static void __fsinfo_note_param(struct fsinfo_kparams *params, const char *key,
+ const char *val, unsigned int vlen)
+{
+ char *p;
+ unsigned int usage;
+ int klen, total, vmeta;
+ u8 x;
+
+ klen = strlen(key);
+ BUG_ON(klen < 1 || klen > 127);
+ BUG_ON(vlen > (1 << 21) - 1);
+ BUG_ON(vlen > 0 && !val);
+
+ vmeta = (vlen <= 127) ? 1 : (vlen <= 127 * 127) ? 2 : 3;
+
+ total = 1 + klen + vmeta + vlen;
+
+ usage = params->usage;
+ params->usage = usage + total;
+ if (!params->buffer || params->usage > params->buf_size)
+ return;
+
+ p = params->buffer + usage;
+ *p++ = klen;
+ p = memcpy(p, key, klen);
+ p += klen;
+
+ /* The more significant groups of 7 bits in the size are included in
+ * most->least order with 0x80 OR'd in. The least significant 7 bits
+ * are last with the top bit clear.
+ */
+ x = vlen >> 14;
+ if (x & 0x7f)
+ *p++ = 0x80 | x;
+
+ x = vlen >> 7;
+ if (x & 0x7f)
+ *p++ = 0x80 | x;
+
+ *p++ = vlen & 0x7f;
+ memcpy(p, val, vlen);
+}
+
+/**
+ * fsinfo_note_param - Store a parameter for FSINFO_ATTR_PARAMETERS
+ * @params: The parameter buffer
+ * @key: The parameter's key
+ * @val: The parameter's value (or NULL)
+ */
+void fsinfo_note_param(struct fsinfo_kparams *params, const char *key,
+ const char *val)
+{
+ __fsinfo_note_param(params, key, val, val ? strlen(val) : 0);
+}
+EXPORT_SYMBOL(fsinfo_note_param);
+
+/**
+ * fsinfo_note_paramf - Store a formatted parameter for FSINFO_ATTR_PARAMETERS
+ * @params: The parameter buffer
+ * @key: The parameter's key
+ * @val_fmt: Format string for the parameter's value
+ */
+void fsinfo_note_paramf(struct fsinfo_kparams *params, const char *key,
+ const char *val_fmt, ...)
+{
+ va_list va;
+ int n;
+
+ va_start(va, val_fmt);
+ n = vsnprintf(params->scratch_buffer, 4096, val_fmt, va);
+ va_end(va);
+
+ __fsinfo_note_param(params, key, params->scratch_buffer, n);
+}
+EXPORT_SYMBOL(fsinfo_note_paramf);
diff --git a/include/linux/fsinfo.h b/include/linux/fsinfo.h
index e17e4f0bae18..3383027a6e9d 100644
--- a/include/linux/fsinfo.h
+++ b/include/linux/fsinfo.h
@@ -29,6 +29,9 @@ struct fsinfo_kparams {
};
extern int generic_fsinfo(struct path *, struct fsinfo_kparams *);
+extern void fsinfo_note_param(struct fsinfo_kparams *, const char *, const char *);
+extern void fsinfo_note_paramf(struct fsinfo_kparams *, const char *, const char *, ...)
+ __printf(3, 4);
static inline void fsinfo_set_cap(struct fsinfo_capabilities *c,
enum fsinfo_capability cap)
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index 248c3c4a1e32..0f134847e88b 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -30,6 +30,7 @@ enum fsinfo_attribute {
FSINFO_ATTR_PARAM_DESCRIPTION = 12, /* General fs parameter description */
FSINFO_ATTR_PARAM_SPECIFICATION = 13, /* Nth parameter specification */
FSINFO_ATTR_PARAM_ENUM = 14, /* Nth enum-to-val */
+ FSINFO_ATTR_PARAMETERS = 15, /* Mount parameters (large string) */
FSINFO_ATTR__NR
};
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index 71c7b68e76b3..2960fa2b9843 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -81,6 +81,7 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRUCT (PARAM_DESCRIPTION, param_description),
FSINFO_STRUCT_N (PARAM_SPECIFICATION, param_specification),
FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
+ FSINFO_OVERLARGE (PARAMETERS, -),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -100,6 +101,7 @@ static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
FSINFO_NAME (PARAM_DESCRIPTION, param_description),
FSINFO_NAME (PARAM_SPECIFICATION, param_specification),
FSINFO_NAME (PARAM_ENUM, param_enum),
+ FSINFO_NAME (PARAMETERS, parameters),
};
union reply {
@@ -345,6 +347,34 @@ static void dump_fsinfo(enum fsinfo_attribute attr,
dumper(r, size);
}
+static void dump_params(struct fsinfo_attr_info about, union reply *r, int size)
+{
+ int len;
+ char *p = r->buffer, *e = p + size;
+ bool is_key = true;
+
+ while (p < e) {
+ len = 0;
+ while (p[0] & 0x80) {
+ len <<= 7;
+ len |= *p++ & 0x7f;
+ }
+
+ len <<= 7;
+ len |= *p++;
+ if (len > e - p)
+ break;
+ if (is_key || len)
+ printf("%s%*.*s", is_key ? "[PARM] " : "= ", len, len, p);
+ if (is_key)
+ putchar(' ');
+ else
+ putchar('\n');
+ p += len;
+ is_key = !is_key;
+ }
+}
+
/*
* Try one subinstance of an attribute.
*/
@@ -420,6 +450,12 @@ static int try_one(const char *file, struct fsinfo_params *params, bool raw)
return 0;
}
+ switch (params->request) {
+ case FSINFO_ATTR_PARAMETERS:
+ if (ret == 0)
+ return 0;
+ }
+
switch (about.flags & (__FSINFO_N | __FSINFO_NM)) {
case 0:
printf("\e[33m%s\e[m: ",
@@ -462,6 +498,8 @@ static int try_one(const char *file, struct fsinfo_params *params, bool raw)
return 0;
case __FSINFO_OVER:
+ if (params->request == FSINFO_ATTR_PARAMETERS)
+ dump_params(about, r, ret);
return 0;
case __FSINFO_STRUCT_ARRAY:
^ permalink raw reply related
* [PATCH 03/25] vfs: Allow fsinfo() to be used to query an fs parameter description [ver #13]
From: David Howells @ 2019-05-28 15:11 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Provide fsinfo() attributes that can be used to query a filesystem
parameter description. To do this, fsinfo() can be called on an
fs_context that doesn't yet have a superblock created and attached.
It can be obtained by doing, for example:
fd = fsopen("ext4", 0);
struct fsinfo_param_name name;
struct fsinfo_params params = {
.request = fsinfo_attr_param_name,
.Nth = 3,
};
fsinfo(fd, NULL, ¶ms, &name, sizeof(name));
to query the 4th parameter name in the name to parameter ID mapping table.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 94 +++++++++++++++++++++++++++++
include/uapi/linux/fsinfo.h | 58 ++++++++++++++++++
samples/vfs/Makefile | 2 +
samples/vfs/test-fs-query.c | 139 +++++++++++++++++++++++++++++++++++++++++++
samples/vfs/test-fsinfo.c | 14 ++++
5 files changed, 307 insertions(+)
create mode 100644 samples/vfs/test-fs-query.c
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 14db881dd02d..c7f9c894c737 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -9,6 +9,7 @@
#include <linux/uaccess.h>
#include <linux/fsinfo.h>
#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
#include <uapi/linux/mount.h>
#include "internal.h"
@@ -210,12 +211,87 @@ static int fsinfo_generic_name_encoding(struct path *path, char *buf)
return sizeof(encoding) - 1;
}
+static int fsinfo_generic_param_description(struct file_system_type *f,
+ struct fsinfo_kparams *params)
+{
+ const struct fs_parameter_description *desc = f->parameters;
+ const struct fs_parameter_spec *s;
+ const struct fs_parameter_enum *e;
+ struct fsinfo_param_description *p = params->buffer;
+
+ if (desc && desc->specs) {
+ for (s = desc->specs; s->name; s++) {}
+ p->nr_params = s - desc->specs;
+ if (desc->enums) {
+ for (e = desc->enums; e->name[0]; e++) {}
+ p->nr_enum_names = e - desc->enums;
+ }
+ }
+
+ return sizeof(*p);
+}
+
+static int fsinfo_generic_param_specification(struct file_system_type *f,
+ struct fsinfo_kparams *params)
+{
+ const struct fs_parameter_description *desc = f->parameters;
+ const struct fs_parameter_spec *s;
+ struct fsinfo_param_specification *p = params->buffer;
+ unsigned int nth = params->Nth;
+
+ if (!desc || !desc->specs)
+ return -ENODATA;
+
+ for (s = desc->specs; s->name; s++) {
+ if (nth == 0)
+ goto found;
+ nth--;
+ }
+
+ return -ENODATA;
+
+found:
+ p->type = s->type;
+ p->flags = s->flags;
+ p->opt = s->opt;
+ strlcpy(p->name, s->name, sizeof(p->name));
+ return sizeof(*p);
+}
+
+static int fsinfo_generic_param_enum(struct file_system_type *f,
+ struct fsinfo_kparams *params)
+{
+ const struct fs_parameter_description *desc = f->parameters;
+ const struct fs_parameter_enum *e;
+ struct fsinfo_param_enum *p = params->buffer;
+ unsigned int nth = params->Nth;
+
+ if (!desc || !desc->enums)
+ return -ENODATA;
+
+ for (e = desc->enums; e->name; e++) {
+ if (nth == 0)
+ goto found;
+ nth--;
+ }
+
+ return -ENODATA;
+
+found:
+ p->opt = e->opt;
+ strlcpy(p->name, e->name, sizeof(p->name));
+ return sizeof(*p);
+}
+
/*
* Implement some queries generically from stuff in the superblock.
*/
int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
{
+ struct file_system_type *f = path->dentry->d_sb->s_type;
+
#define _gen(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(path, params->buffer)
+#define _genf(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(f, params)
switch (params->request) {
case _gen(STATFS, statfs);
@@ -227,6 +303,9 @@ int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
case _gen(VOLUME_UUID, volume_uuid);
case _gen(VOLUME_ID, volume_id);
case _gen(NAME_ENCODING, name_encoding);
+ case _genf(PARAM_DESCRIPTION, param_description);
+ case _genf(PARAM_SPECIFICATION, param_specification);
+ case _genf(PARAM_ENUM, param_enum);
default:
return -EOPNOTSUPP;
}
@@ -319,11 +398,23 @@ static int vfs_fsinfo_path(int dfd, const char __user *filename,
static int vfs_fsinfo_fscontext(struct fs_context *fc,
struct fsinfo_kparams *params)
{
+ struct file_system_type *f = fc->fs_type;
int ret;
if (fc->ops == &legacy_fs_context_ops)
return -EOPNOTSUPP;
+ /* Filesystem parameter query is static information and doesn't need a
+ * lock to read it.
+ */
+ switch (params->request) {
+ case _genf(PARAM_DESCRIPTION, param_description);
+ case _genf(PARAM_SPECIFICATION, param_specification);
+ case _genf(PARAM_ENUM, param_enum);
+ default:
+ break;
+ }
+
ret = mutex_lock_interruptible(&fc->uapi_mutex);
if (ret < 0)
return ret;
@@ -410,6 +501,9 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING (VOLUME_NAME, volume_name),
FSINFO_STRING (NAME_ENCODING, name_encoding),
FSINFO_STRING (NAME_CODEPAGE, name_codepage),
+ FSINFO_STRUCT (PARAM_DESCRIPTION, param_description),
+ FSINFO_STRUCT_N (PARAM_SPECIFICATION, param_specification),
+ FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
};
/**
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index 0af09b7e734c..248c3c4a1e32 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -27,6 +27,9 @@ enum fsinfo_attribute {
FSINFO_ATTR_VOLUME_NAME = 9, /* Volume name (string) */
FSINFO_ATTR_NAME_ENCODING = 10, /* Filename encoding (string) */
FSINFO_ATTR_NAME_CODEPAGE = 11, /* Filename codepage (string) */
+ FSINFO_ATTR_PARAM_DESCRIPTION = 12, /* General fs parameter description */
+ FSINFO_ATTR_PARAM_SPECIFICATION = 13, /* Nth parameter specification */
+ FSINFO_ATTR_PARAM_ENUM = 14, /* Nth enum-to-val */
FSINFO_ATTR__NR
};
@@ -208,4 +211,59 @@ struct fsinfo_fsinfo {
__u32 max_cap; /* Number of supported capabilities (fsinfo_cap__nr) */
};
+/*
+ * Information struct for fsinfo(fsinfo_attr_param_description).
+ *
+ * Query the parameter set for a filesystem.
+ */
+struct fsinfo_param_description {
+ __u32 nr_params; /* Number of individual parameters */
+ __u32 nr_enum_names; /* Number of enum names */
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_param_specification).
+ *
+ * Query the specification of the Nth filesystem parameter.
+ */
+struct fsinfo_param_specification {
+ __u32 type; /* enum fsinfo_param_specification_type */
+ __u32 flags; /* Qualifiers */
+ __u32 opt; /* Corresponding params have same ID here */
+ char name[240];
+};
+
+enum fsinfo_param_specification_type {
+ FSINFO_PARAM_SPEC_NOT_DEFINED = 0,
+ FSINFO_PARAM_SPEC_IS_FLAG = 1,
+ FSINFO_PARAM_SPEC_IS_BOOL = 2,
+ FSINFO_PARAM_SPEC_IS_U32 = 3,
+ FSINFO_PARAM_SPEC_IS_U32_OCTAL = 4,
+ FSINFO_PARAM_SPEC_IS_U32_HEX = 5,
+ FSINFO_PARAM_SPEC_IS_S32 = 6,
+ FSINFO_PARAM_SPEC_IS_U64 = 7,
+ FSINFO_PARAM_SPEC_IS_ENUM = 8,
+ FSINFO_PARAM_SPEC_IS_STRING = 9,
+ FSINFO_PARAM_SPEC_IS_BLOB = 10,
+ FSINFO_PARAM_SPEC_IS_BLOCKDEV = 11,
+ FSINFO_PARAM_SPEC_IS_PATH = 12,
+ FSINFO_PARAM_SPEC_IS_FD = 13,
+ NR__FSINFO_PARAM_SPEC
+};
+
+#define FSINFO_PARAM_SPEC_VALUE_IS_OPTIONAL 0X00000001
+#define FSINFO_PARAM_SPEC_PREFIX_NO_IS_NEG 0X00000002
+#define FSINFO_PARAM_SPEC_EMPTY_STRING_IS_NEG 0X00000004
+#define FSINFO_PARAM_SPEC_DEPRECATED 0X00000008
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_param_enum).
+ *
+ * Query the Nth filesystem enum parameter value name.
+ */
+struct fsinfo_param_enum {
+ __u32 opt; /* ->opt of the relevant parameter specification */
+ char name[252]; /* Name of the enum value */
+};
+
#endif /* _UAPI_LINUX_FSINFO_H */
diff --git a/samples/vfs/Makefile b/samples/vfs/Makefile
index d3cc8e9a4fd8..3c542d3b9479 100644
--- a/samples/vfs/Makefile
+++ b/samples/vfs/Makefile
@@ -1,6 +1,7 @@
# List of programs to build
hostprogs-y := \
test-fsinfo \
+ test-fs-query \
test-fsmount \
test-statx
@@ -10,5 +11,6 @@ always := $(hostprogs-y)
HOSTCFLAGS_test-fsinfo.o += -I$(objtree)/usr/include
HOSTLDLIBS_test-fsinfo += -lm
+HOSTCFLAGS_test-fs-query.o += -I$(objtree)/usr/include
HOSTCFLAGS_test-fsmount.o += -I$(objtree)/usr/include
HOSTCFLAGS_test-statx.o += -I$(objtree)/usr/include
diff --git a/samples/vfs/test-fs-query.c b/samples/vfs/test-fs-query.c
new file mode 100644
index 000000000000..4ef978c5e90a
--- /dev/null
+++ b/samples/vfs/test-fs-query.c
@@ -0,0 +1,139 @@
+/* Test using the fsinfo() system call to query mount parameters.
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#define _ATFILE_SOURCE
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h>
+#include <fcntl.h>
+#include <sys/syscall.h>
+#include <linux/fsinfo.h>
+#include <linux/socket.h>
+#include <sys/stat.h>
+
+#ifndef __NR_fsopen
+#define __NR_fsopen -1
+#endif
+#ifndef __NR_fsinfo
+#define __NR_fsinfo -1
+#endif
+
+static int fsopen(const char *fs_name, unsigned int flags)
+{
+ return syscall(__NR_fsopen, fs_name, flags);
+}
+
+static ssize_t fsinfo(int dfd, const char *filename, struct fsinfo_params *params,
+ void *buffer, size_t buf_size)
+{
+ return syscall(__NR_fsinfo, dfd, filename, params, buffer, buf_size);
+}
+
+static const char *param_types[NR__FSINFO_PARAM_SPEC] = {
+ [FSINFO_PARAM_SPEC_NOT_DEFINED] = "?undef",
+ [FSINFO_PARAM_SPEC_IS_FLAG] = "flag",
+ [FSINFO_PARAM_SPEC_IS_BOOL] = "bool",
+ [FSINFO_PARAM_SPEC_IS_U32] = "u32",
+ [FSINFO_PARAM_SPEC_IS_U32_OCTAL] = "octal",
+ [FSINFO_PARAM_SPEC_IS_U32_HEX] = "hex",
+ [FSINFO_PARAM_SPEC_IS_S32] = "s32",
+ [FSINFO_PARAM_SPEC_IS_U64] = "u64",
+ [FSINFO_PARAM_SPEC_IS_ENUM] = "enum",
+ [FSINFO_PARAM_SPEC_IS_STRING] = "string",
+ [FSINFO_PARAM_SPEC_IS_BLOB] = "binary",
+ [FSINFO_PARAM_SPEC_IS_BLOCKDEV] = "blockdev",
+ [FSINFO_PARAM_SPEC_IS_PATH] = "path",
+ [FSINFO_PARAM_SPEC_IS_FD] = "fd",
+};
+
+/*
+ *
+ */
+int main(int argc, char **argv)
+{
+ struct fsinfo_param_description desc;
+ struct fsinfo_param_specification spec;
+ struct fsinfo_param_enum enum_name;
+
+ struct fsinfo_params params = {
+ .at_flags = AT_SYMLINK_NOFOLLOW,
+ };
+ int fd;
+
+ if (argc != 2) {
+ printf("Format: test-fs-query <fs_name>\n");
+ exit(2);
+ }
+
+ fd = fsopen(argv[1], 0);
+ if (fd == -1) {
+ perror(argv[1]);
+ exit(1);
+ }
+
+ params.request = FSINFO_ATTR_PARAM_DESCRIPTION;
+ if (fsinfo(fd, NULL, ¶ms, &desc, sizeof(desc)) == -1) {
+ perror("fsinfo/desc");
+ exit(1);
+ }
+
+ printf("Filesystem %s has %u parameters\n", argv[1], desc.nr_params);
+
+ params.request = FSINFO_ATTR_PARAM_SPECIFICATION;
+ for (params.Nth = 0; params.Nth < desc.nr_params; params.Nth++) {
+ char type[32];
+
+ if (fsinfo(fd, NULL, ¶ms, &spec, sizeof(spec)) == -1) {
+ if (errno == ENODATA)
+ break;
+ perror("fsinfo/spec");
+ exit(1);
+ }
+
+ if (spec.type < NR__FSINFO_PARAM_SPEC)
+ strcpy(type, param_types[spec.type]);
+ else
+ sprintf(type, "?%u", spec.type);
+
+ printf("- PARAM[%3u] %-20s %3u %s%s%s%s%s\n",
+ params.Nth,
+ spec.name,
+ spec.opt,
+ type,
+ spec.flags & FSINFO_PARAM_SPEC_VALUE_IS_OPTIONAL ? ",opt" : "",
+ spec.flags & FSINFO_PARAM_SPEC_PREFIX_NO_IS_NEG ? ",neg-no" : "",
+ spec.flags & FSINFO_PARAM_SPEC_EMPTY_STRING_IS_NEG ? ",neg-empty" : "",
+ spec.flags & FSINFO_PARAM_SPEC_DEPRECATED ? ",dep" : "");
+ }
+
+ printf("Filesystem has %u enumeration values\n", desc.nr_enum_names);
+
+ params.request = FSINFO_ATTR_PARAM_ENUM;
+ for (params.Nth = 0; params.Nth < desc.nr_enum_names; params.Nth++) {
+ if (fsinfo(fd, NULL, ¶ms, &enum_name, sizeof(enum_name)) == -1) {
+ if (errno == ENODATA)
+ break;
+ perror("fsinfo/enum");
+ exit(1);
+ }
+ printf("- ENUM[%3u] %3u=%s\n",
+ params.Nth, enum_name.opt, enum_name.name);
+ }
+ return 0;
+}
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
index ae62c132bdc2..71c7b68e76b3 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -78,6 +78,9 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING (VOLUME_NAME, volume_name),
FSINFO_STRING (NAME_ENCODING, name_encoding),
FSINFO_STRING (NAME_CODEPAGE, name_codepage),
+ FSINFO_STRUCT (PARAM_DESCRIPTION, param_description),
+ FSINFO_STRUCT_N (PARAM_SPECIFICATION, param_specification),
+ FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -94,6 +97,9 @@ static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
FSINFO_NAME (VOLUME_NAME, volume_name),
FSINFO_NAME (NAME_ENCODING, name_encoding),
FSINFO_NAME (NAME_CODEPAGE, name_codepage),
+ FSINFO_NAME (PARAM_DESCRIPTION, param_description),
+ FSINFO_NAME (PARAM_SPECIFICATION, param_specification),
+ FSINFO_NAME (PARAM_ENUM, param_enum),
};
union reply {
@@ -507,6 +513,14 @@ int main(int argc, char **argv)
}
for (attr = 0; attr <= FSINFO_ATTR__NR; attr++) {
+ switch (attr) {
+ case FSINFO_ATTR_PARAM_DESCRIPTION:
+ case FSINFO_ATTR_PARAM_SPECIFICATION:
+ case FSINFO_ATTR_PARAM_ENUM:
+ /* See test-fs-query.c instead */
+ continue;
+ }
+
Nth = 0;
do {
Mth = 0;
^ permalink raw reply related
* [PATCH 02/25] vfs: Allow fsinfo() to query what's in an fs_context [ver #13]
From: David Howells @ 2019-05-28 15:11 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Allow fsinfo() to be used to query the filesystem attached to an fs_context
once a superblock has been created or if it comes from fspick().
This is done with something like:
fd = fsopen("ext4", 0);
...
fsconfig(fd, fsconfig_cmd_create, ...);
fsinfo(fd, NULL, ...);
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 30 +++++++++++++++++++++++++++++-
fs/statfs.c | 2 +-
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index f9a63410e9a2..14db881dd02d 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -8,6 +8,7 @@
#include <linux/security.h>
#include <linux/uaccess.h>
#include <linux/fsinfo.h>
+#include <linux/fs_context.h>
#include <uapi/linux/mount.h>
#include "internal.h"
@@ -315,13 +316,40 @@ static int vfs_fsinfo_path(int dfd, const char __user *filename,
return ret;
}
+static int vfs_fsinfo_fscontext(struct fs_context *fc,
+ struct fsinfo_kparams *params)
+{
+ int ret;
+
+ if (fc->ops == &legacy_fs_context_ops)
+ return -EOPNOTSUPP;
+
+ ret = mutex_lock_interruptible(&fc->uapi_mutex);
+ if (ret < 0)
+ return ret;
+
+ ret = -EIO;
+ if (fc->root) {
+ struct path path = { .dentry = fc->root };
+
+ ret = vfs_fsinfo(&path, params);
+ }
+
+ mutex_unlock(&fc->uapi_mutex);
+ return ret;
+}
+
static int vfs_fsinfo_fd(unsigned int fd, struct fsinfo_kparams *params)
{
struct fd f = fdget_raw(fd);
int ret = -EBADF;
if (f.file) {
- ret = vfs_fsinfo(&f.file->f_path, params);
+ if (f.file->f_op == &fscontext_fops)
+ ret = vfs_fsinfo_fscontext(f.file->private_data,
+ params);
+ else
+ ret = vfs_fsinfo(&f.file->f_path, params);
fdput(f);
}
return ret;
diff --git a/fs/statfs.c b/fs/statfs.c
index eea7af6f2f22..b9b63d9f4f24 100644
--- a/fs/statfs.c
+++ b/fs/statfs.c
@@ -86,7 +86,7 @@ int vfs_statfs(const struct path *path, struct kstatfs *buf)
int error;
error = statfs_by_dentry(path->dentry, buf);
- if (!error)
+ if (!error && path->mnt)
buf->f_flags = calculate_f_flags(path->mnt);
return error;
}
^ permalink raw reply related
* [PATCH 01/25] vfs: syscall: Add fsinfo() to query filesystem information [ver #13]
From: David Howells @ 2019-05-28 15:11 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905626142.1662.18430571708534506785.stgit@warthog.procyon.org.uk>
Add a system call to allow filesystem information to be queried. A request
value can be given to indicate the desired attribute. Support is provided
for enumerating multi-value attributes.
===============
NEW SYSTEM CALL
===============
The new system call looks like:
int ret = fsinfo(int dfd,
const char *filename,
const struct fsinfo_params *params,
void *buffer,
size_t buf_size);
The params parameter optionally points to a block of parameters:
struct fsinfo_params {
__u32 at_flags;
__u32 request;
__u32 Nth;
__u32 Mth;
__u32 __reserved[6];
};
If params is NULL, it is assumed params->request should be
fsinfo_attr_statfs, params->Nth should be 0, params->Mth should be 0 and
params->at_flags should be 0.
If params is given, all of params->__reserved[] must be 0.
dfd, filename and params->at_flags indicate the file to query. There is no
equivalent of lstat() as that can be emulated with fsinfo() by setting
AT_SYMLINK_NOFOLLOW in params->at_flags. There is also no equivalent of
fstat() as that can be emulated by passing a NULL filename to fsinfo() with
the fd of interest in dfd. AT_NO_AUTOMOUNT can also be used to an allow
automount point to be queried without triggering it.
params->request indicates the attribute/attributes to be queried. This can
be one of:
FSINFO_ATTR_STATFS - statfs-style info
FSINFO_ATTR_FSINFO - Information about fsinfo()
FSINFO_ATTR_IDS - Filesystem IDs
FSINFO_ATTR_LIMITS - Filesystem limits
FSINFO_ATTR_SUPPORTS - What's supported in statx(), IOC flags
FSINFO_ATTR_CAPABILITIES - Filesystem capabilities
FSINFO_ATTR_TIMESTAMP_INFO - Inode timestamp info
FSINFO_ATTR_VOLUME_ID - Volume ID (string)
FSINFO_ATTR_VOLUME_UUID - Volume UUID
FSINFO_ATTR_VOLUME_NAME - Volume name (string)
FSINFO_ATTR_NAME_ENCODING - Filename encoding (string)
FSINFO_ATTR_NAME_CODEPAGE - Filename codepage (string)
Some attributes (such as the servers backing a network filesystem) can have
multiple values. These can be enumerated by setting params->Nth and
params->Mth to 0, 1, ... until ENODATA is returned.
buffer and buf_size point to the reply buffer. The buffer is filled up to
the specified size, even if this means truncating the reply. The full size
of the reply is returned. In future versions, this will allow extra fields
to be tacked on to the end of the reply, but anyone not expecting them will
only get the subset they're expecting. If either buffer of buf_size are 0,
no copy will take place and the data size will be returned.
At the moment, this will only work on x86_64 and i386 as it requires the
system call to be wired up.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: linux-api@vger.kernel.org
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 7
fs/Makefile | 1
fs/fsinfo.c | 524 +++++++++++++++++++++++++++++++
include/linux/fs.h | 5
include/linux/fsinfo.h | 66 ++++
include/linux/syscalls.h | 4
include/uapi/linux/fsinfo.h | 211 ++++++++++++
kernel/sys_ni.c | 3
samples/vfs/Makefile | 4
samples/vfs/test-fsinfo.c | 544 ++++++++++++++++++++++++++++++++
12 files changed, 1371 insertions(+)
create mode 100644 fs/fsinfo.c
create mode 100644 include/linux/fsinfo.h
create mode 100644 include/uapi/linux/fsinfo.h
create mode 100644 samples/vfs/test-fsinfo.c
diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
index ad968b7bac72..03decae51513 100644
--- a/arch/x86/entry/syscalls/syscall_32.tbl
+++ b/arch/x86/entry/syscalls/syscall_32.tbl
@@ -438,3 +438,4 @@
431 i386 fsconfig sys_fsconfig __ia32_sys_fsconfig
432 i386 fsmount sys_fsmount __ia32_sys_fsmount
433 i386 fspick sys_fspick __ia32_sys_fspick
+434 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index b4e6f9e6204a..ea63df9a1020 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -355,6 +355,7 @@
431 common fsconfig __x64_sys_fsconfig
432 common fsmount __x64_sys_fsmount
433 common fspick __x64_sys_fspick
+434 common fsinfo __x64_sys_fsinfo
#
# x32-specific system call numbers start at 512 to avoid cache impact
diff --git a/fs/Kconfig b/fs/Kconfig
index cbbffc8b9ef5..9e7d2f2c0111 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -15,6 +15,13 @@ config VALIDATE_FS_PARSER
Enable this to perform validation of the parameter description for a
filesystem when it is registered.
+config FSINFO
+ bool "Enable the fsinfo() system call"
+ help
+ Enable the file system information querying system call to allow
+ comprehensive information to be retrieved about a filesystem,
+ superblock or mount object.
+
if BLOCK
config FS_IOMAP
diff --git a/fs/Makefile b/fs/Makefile
index c9aea23aba56..26eaeae4b9a1 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_SYSCTL) += drop_caches.o
obj-$(CONFIG_FHANDLE) += fhandle.o
obj-$(CONFIG_FS_IOMAP) += iomap.o
+obj-$(CONFIG_FSINFO) += fsinfo.o
obj-y += quota/
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
new file mode 100644
index 000000000000..f9a63410e9a2
--- /dev/null
+++ b/fs/fsinfo.c
@@ -0,0 +1,524 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/syscalls.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/statfs.h>
+#include <linux/security.h>
+#include <linux/uaccess.h>
+#include <linux/fsinfo.h>
+#include <uapi/linux/mount.h>
+#include "internal.h"
+
+static u32 calc_mount_attrs(u32 mnt_flags)
+{
+ u32 attrs = 0;
+
+ if (mnt_flags & MNT_READONLY)
+ attrs |= MOUNT_ATTR_RDONLY;
+ if (mnt_flags & MNT_NOSUID)
+ attrs |= MOUNT_ATTR_NOSUID;
+ if (mnt_flags & MNT_NODEV)
+ attrs |= MOUNT_ATTR_NODEV;
+ if (mnt_flags & MNT_NOEXEC)
+ attrs |= MOUNT_ATTR_NOEXEC;
+ if (mnt_flags & MNT_NODIRATIME)
+ attrs |= MOUNT_ATTR_NODIRATIME;
+
+ if (mnt_flags & MNT_NOATIME)
+ attrs |= MOUNT_ATTR_NOATIME;
+ else if (mnt_flags & MNT_RELATIME)
+ attrs |= MOUNT_ATTR_RELATIME;
+ else
+ attrs |= MOUNT_ATTR_STRICTATIME;
+ return attrs;
+}
+
+static u32 calc_sb_flags(u32 s_flags)
+{
+ u32 flags = 0;
+
+ if (s_flags & SB_RDONLY) flags |= MS_RDONLY;
+ if (s_flags & SB_SYNCHRONOUS) flags |= MS_SYNCHRONOUS;
+ if (s_flags & SB_MANDLOCK) flags |= MS_MANDLOCK;
+ if (s_flags & SB_DIRSYNC) flags |= MS_DIRSYNC;
+ if (s_flags & SB_SILENT) flags |= MS_SILENT;
+ if (s_flags & SB_POSIXACL) flags |= MS_POSIXACL;
+ if (s_flags & SB_LAZYTIME) flags |= MS_LAZYTIME;
+ if (s_flags & SB_I_VERSION) flags |= MS_I_VERSION;
+ return flags;
+}
+
+/*
+ * Get basic filesystem stats from statfs.
+ */
+static int fsinfo_generic_statfs(struct path *path, struct fsinfo_statfs *p)
+{
+ struct kstatfs buf;
+ int ret;
+
+ ret = vfs_statfs(path, &buf);
+ if (ret < 0)
+ return ret;
+
+ p->f_blocks = buf.f_blocks;
+ p->f_bfree = buf.f_bfree;
+ p->f_bavail = buf.f_bavail;
+ p->f_files = buf.f_files;
+ p->f_ffree = buf.f_ffree;
+ p->f_favail = buf.f_ffree;
+ p->f_bsize = buf.f_bsize;
+ p->f_frsize = buf.f_frsize;
+
+ p->mnt_attrs = calc_mount_attrs(path->mnt->mnt_flags);
+ p->sb_flags = calc_sb_flags(path->mnt->mnt_sb->s_flags);
+ return sizeof(*p);
+}
+
+static int fsinfo_generic_ids(struct path *path, struct fsinfo_ids *p)
+{
+ struct super_block *sb;
+ struct kstatfs buf;
+ int ret;
+
+ ret = vfs_statfs(path, &buf);
+ if (ret < 0 && ret != -ENOSYS)
+ return ret;
+
+ sb = path->dentry->d_sb;
+ p->f_fstype = sb->s_magic;
+ p->f_dev_major = MAJOR(sb->s_dev);
+ p->f_dev_minor = MINOR(sb->s_dev);
+
+ memcpy(&p->f_fsid, &buf.f_fsid, sizeof(p->f_fsid));
+ strlcpy(p->f_fs_name, path->dentry->d_sb->s_type->name,
+ sizeof(p->f_fs_name));
+ return sizeof(*p);
+}
+
+static int fsinfo_generic_limits(struct path *path, struct fsinfo_limits *lim)
+{
+ struct super_block *sb = path->dentry->d_sb;
+
+ lim->max_file_size = sb->s_maxbytes;
+ lim->max_hard_links = sb->s_max_links;
+ lim->max_uid = UINT_MAX;
+ lim->max_gid = UINT_MAX;
+ lim->max_projid = UINT_MAX;
+ lim->max_filename_len = NAME_MAX;
+ lim->max_symlink_len = PAGE_SIZE;
+ lim->max_xattr_name_len = XATTR_NAME_MAX;
+ lim->max_xattr_body_len = XATTR_SIZE_MAX;
+ lim->max_dev_major = 0xffffff;
+ lim->max_dev_minor = 0xff;
+ return sizeof(*lim);
+}
+
+static int fsinfo_generic_supports(struct path *path, struct fsinfo_supports *c)
+{
+ struct super_block *sb = path->dentry->d_sb;
+
+ c->stx_mask = STATX_BASIC_STATS;
+ if (sb->s_d_op && sb->s_d_op->d_automount)
+ c->stx_attributes |= STATX_ATTR_AUTOMOUNT;
+ return sizeof(*c);
+}
+
+static int fsinfo_generic_capabilities(struct path *path,
+ struct fsinfo_capabilities *c)
+{
+ struct super_block *sb = path->dentry->d_sb;
+
+ if (sb->s_mtd)
+ fsinfo_set_cap(c, FSINFO_CAP_IS_FLASH_FS);
+ else if (sb->s_bdev)
+ fsinfo_set_cap(c, FSINFO_CAP_IS_BLOCK_FS);
+
+ if (sb->s_quota_types & QTYPE_MASK_USR)
+ fsinfo_set_cap(c, FSINFO_CAP_USER_QUOTAS);
+ if (sb->s_quota_types & QTYPE_MASK_GRP)
+ fsinfo_set_cap(c, FSINFO_CAP_GROUP_QUOTAS);
+ if (sb->s_quota_types & QTYPE_MASK_PRJ)
+ fsinfo_set_cap(c, FSINFO_CAP_PROJECT_QUOTAS);
+ if (sb->s_d_op && sb->s_d_op->d_automount)
+ fsinfo_set_cap(c, FSINFO_CAP_AUTOMOUNTS);
+ if (sb->s_id[0])
+ fsinfo_set_cap(c, FSINFO_CAP_VOLUME_ID);
+
+ fsinfo_set_cap(c, FSINFO_CAP_HAS_ATIME);
+ fsinfo_set_cap(c, FSINFO_CAP_HAS_CTIME);
+ fsinfo_set_cap(c, FSINFO_CAP_HAS_MTIME);
+ return sizeof(*c);
+}
+
+static int fsinfo_generic_timestamp_info(struct path *path,
+ struct fsinfo_timestamp_info *ts)
+{
+ struct super_block *sb = path->dentry->d_sb;
+
+ /* If unset, assume 1s granularity */
+ u16 mantissa = 1;
+ s8 exponent = 0;
+
+ ts->minimum_timestamp = S64_MIN;
+ ts->maximum_timestamp = S64_MAX;
+ if (sb->s_time_gran < 1000000000) {
+ if (sb->s_time_gran < 1000)
+ exponent = -9;
+ else if (sb->s_time_gran < 1000000)
+ exponent = -6;
+ else
+ exponent = -3;
+ }
+#define set_gran(x) \
+ do { \
+ ts->x##_mantissa = mantissa; \
+ ts->x##_exponent = exponent; \
+ } while (0)
+ set_gran(atime_gran);
+ set_gran(btime_gran);
+ set_gran(ctime_gran);
+ set_gran(mtime_gran);
+ return sizeof(*ts);
+}
+
+static int fsinfo_generic_volume_uuid(struct path *path,
+ struct fsinfo_volume_uuid *vu)
+{
+ struct super_block *sb = path->dentry->d_sb;
+
+ memcpy(vu, &sb->s_uuid, sizeof(*vu));
+ return sizeof(*vu);
+}
+
+static int fsinfo_generic_volume_id(struct path *path, char *buf)
+{
+ struct super_block *sb = path->dentry->d_sb;
+ size_t len = strlen(sb->s_id);
+
+ memcpy(buf, sb->s_id, len + 1);
+ return len;
+}
+
+static int fsinfo_generic_name_encoding(struct path *path, char *buf)
+{
+ static const char encoding[] = "utf8";
+
+ memcpy(buf, encoding, sizeof(encoding) - 1);
+ return sizeof(encoding) - 1;
+}
+
+/*
+ * Implement some queries generically from stuff in the superblock.
+ */
+int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+#define _gen(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(path, params->buffer)
+
+ switch (params->request) {
+ case _gen(STATFS, statfs);
+ case _gen(IDS, ids);
+ case _gen(LIMITS, limits);
+ case _gen(SUPPORTS, supports);
+ case _gen(CAPABILITIES, capabilities);
+ case _gen(TIMESTAMP_INFO, timestamp_info);
+ case _gen(VOLUME_UUID, volume_uuid);
+ case _gen(VOLUME_ID, volume_id);
+ case _gen(NAME_ENCODING, name_encoding);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+EXPORT_SYMBOL(generic_fsinfo);
+
+/*
+ * Retrieve the filesystem info. We make some stuff up if the operation is not
+ * supported.
+ */
+static int vfs_fsinfo(struct path *path, struct fsinfo_kparams *params)
+{
+ struct dentry *dentry = path->dentry;
+ int (*fsinfo)(struct path *, struct fsinfo_kparams *);
+ int ret;
+
+ if (params->request == FSINFO_ATTR_FSINFO) {
+ struct fsinfo_fsinfo *info = params->buffer;
+
+ info->max_attr = FSINFO_ATTR__NR;
+ info->max_cap = FSINFO_CAP__NR;
+ return sizeof(*info);
+ }
+
+ fsinfo = dentry->d_sb->s_op->fsinfo;
+ if (!fsinfo) {
+ if (!dentry->d_sb->s_op->statfs)
+ return -EOPNOTSUPP;
+ fsinfo = generic_fsinfo;
+ }
+
+ ret = security_sb_statfs(dentry);
+ if (ret)
+ return ret;
+
+ if (!params->overlarge)
+ return fsinfo(path, params);
+
+ while (!signal_pending(current)) {
+ params->usage = 0;
+ ret = fsinfo(path, params);
+ if (ret <= (int)params->buf_size)
+ return ret; /* Error or it fitted */
+ kvfree(params->buffer);
+ params->buffer = NULL;
+ params->buf_size = roundup(ret, PAGE_SIZE);
+ if (params->buf_size > INT_MAX)
+ return -ETOOSMALL;
+ params->buffer = kvmalloc(params->buf_size, GFP_KERNEL);
+ if (!params->buffer)
+ return -ENOMEM;
+ }
+
+ return -ERESTARTSYS;
+}
+
+static int vfs_fsinfo_path(int dfd, const char __user *filename,
+ struct fsinfo_kparams *params)
+{
+ struct path path;
+ unsigned lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+ int ret = -EINVAL;
+
+ if ((params->at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
+ AT_EMPTY_PATH)) != 0)
+ return -EINVAL;
+
+ if (params->at_flags & AT_SYMLINK_NOFOLLOW)
+ lookup_flags &= ~LOOKUP_FOLLOW;
+ if (params->at_flags & AT_NO_AUTOMOUNT)
+ lookup_flags &= ~LOOKUP_AUTOMOUNT;
+ if (params->at_flags & AT_EMPTY_PATH)
+ lookup_flags |= LOOKUP_EMPTY;
+
+retry:
+ ret = user_path_at(dfd, filename, lookup_flags, &path);
+ if (ret)
+ goto out;
+
+ ret = vfs_fsinfo(&path, params);
+ path_put(&path);
+ if (retry_estale(ret, lookup_flags)) {
+ lookup_flags |= LOOKUP_REVAL;
+ goto retry;
+ }
+out:
+ return ret;
+}
+
+static int vfs_fsinfo_fd(unsigned int fd, struct fsinfo_kparams *params)
+{
+ struct fd f = fdget_raw(fd);
+ int ret = -EBADF;
+
+ if (f.file) {
+ ret = vfs_fsinfo(&f.file->f_path, params);
+ fdput(f);
+ }
+ return ret;
+}
+
+/*
+ * Return buffer information by requestable attribute.
+ *
+ * STRUCT indicates a fixed-size structure with only one instance.
+ * STRUCT_N indicates a 1D array of STRUCT, indexed by Nth
+ * STRUCT_NM indicates a 2D-array of STRUCT, indexed by Nth, Mth
+ * STRING indicates a string with only one instance.
+ * STRING_N indicates a 1D array of STRING, indexed by Nth
+ * STRING_NM indicates a 2D-array of STRING, indexed by Nth, Mth
+ * OPAQUE indicates a blob that can be larger than 4K.
+ * STRUCT_ARRAY indicates an array of structs that can be larger than 4K
+ *
+ * If an entry is marked STRUCT, STRUCT_N or STRUCT_NM then if no buffer is
+ * supplied to sys_fsinfo(), sys_fsinfo() will handle returning the buffer size
+ * without calling vfs_fsinfo() and the filesystem.
+ *
+ * No struct may have more than 4K bytes.
+ */
+struct fsinfo_attr_info {
+ u8 type;
+ u8 flags;
+ u16 size;
+};
+
+#define __FSINFO_STRUCT 0
+#define __FSINFO_STRING 1
+#define __FSINFO_OPAQUE 2
+#define __FSINFO_STRUCT_ARRAY 3
+#define __FSINFO_0 0
+#define __FSINFO_N 0x0001
+#define __FSINFO_NM 0x0002
+
+#define _Z(T, F, S) { .type = __FSINFO_##T, .flags = __FSINFO_##F, .size = S }
+#define FSINFO_STRING(X,Y) [FSINFO_ATTR_##X] = _Z(STRING, 0, 0)
+#define FSINFO_STRUCT(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT, 0, sizeof(struct fsinfo_##Y))
+#define FSINFO_STRING_N(X,Y) [FSINFO_ATTR_##X] = _Z(STRING, N, 0)
+#define FSINFO_STRUCT_N(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT, N, sizeof(struct fsinfo_##Y))
+#define FSINFO_STRING_NM(X,Y) [FSINFO_ATTR_##X] = _Z(STRING, NM, 0)
+#define FSINFO_STRUCT_NM(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT, NM, sizeof(struct fsinfo_##Y))
+#define FSINFO_OPAQUE(X,Y) [FSINFO_ATTR_##X] = _Z(OPAQUE, 0, 0)
+#define FSINFO_STRUCT_ARRAY(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT_ARRAY, 0, sizeof(struct fsinfo_##Y))
+
+static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
+ FSINFO_STRUCT (STATFS, statfs),
+ FSINFO_STRUCT (FSINFO, fsinfo),
+ FSINFO_STRUCT (IDS, ids),
+ FSINFO_STRUCT (LIMITS, limits),
+ FSINFO_STRUCT (CAPABILITIES, capabilities),
+ FSINFO_STRUCT (SUPPORTS, supports),
+ FSINFO_STRUCT (TIMESTAMP_INFO, timestamp_info),
+ FSINFO_STRING (VOLUME_ID, volume_id),
+ FSINFO_STRUCT (VOLUME_UUID, volume_uuid),
+ FSINFO_STRING (VOLUME_NAME, volume_name),
+ FSINFO_STRING (NAME_ENCODING, name_encoding),
+ FSINFO_STRING (NAME_CODEPAGE, name_codepage),
+};
+
+/**
+ * sys_fsinfo - System call to get filesystem information
+ * @dfd: Base directory to pathwalk from or fd referring to filesystem.
+ * @filename: Filesystem to query or NULL.
+ * @_params: Parameters to define request (or NULL for enhanced statfs).
+ * @user_buffer: Result buffer.
+ * @user_buf_size: Size of result buffer.
+ *
+ * Get information on a filesystem. The filesystem attribute to be queried is
+ * indicated by @_params->request, and some of the attributes can have multiple
+ * values, indexed by @_params->Nth and @_params->Mth. If @_params is NULL,
+ * then the 0th fsinfo_attr_statfs attribute is queried. If an attribute does
+ * not exist, EOPNOTSUPP is returned; if the Nth,Mth value does not exist,
+ * ENODATA is returned.
+ *
+ * On success, the size of the attribute's value is returned. If
+ * @user_buf_size is 0 or @user_buffer is NULL, only the size is returned. If
+ * the size of the value is larger than @user_buf_size, it will be truncated by
+ * the copy. If the size of the value is smaller than @user_buf_size then the
+ * excess buffer space will be cleared. The full size of the value will be
+ * returned, irrespective of how much data is actually placed in the buffer.
+ */
+SYSCALL_DEFINE5(fsinfo,
+ int, dfd, const char __user *, filename,
+ struct fsinfo_params __user *, _params,
+ void __user *, user_buffer, size_t, user_buf_size)
+{
+ struct fsinfo_attr_info info;
+ struct fsinfo_params user_params;
+ struct fsinfo_kparams params;
+ unsigned int result_size;
+ int ret;
+
+ memset(¶ms, 0, sizeof(params));
+
+ if (_params) {
+ if (copy_from_user(&user_params, _params, sizeof(user_params)))
+ return -EFAULT;
+ if (user_params.__reserved[0] ||
+ user_params.__reserved[1] ||
+ user_params.__reserved[2])
+ return -EINVAL;
+ if (user_params.request >= FSINFO_ATTR__NR)
+ return -EOPNOTSUPP;
+ params.at_flags = user_params.at_flags;
+ params.request = user_params.request;
+ params.Nth = user_params.Nth;
+ params.Mth = user_params.Mth;
+ } else {
+ params.request = FSINFO_ATTR_STATFS;
+ }
+
+ if (!user_buffer || !user_buf_size) {
+ user_buf_size = 0;
+ user_buffer = NULL;
+ }
+
+ /* Allocate an appropriately-sized buffer. We will truncate the
+ * contents when we write the contents back to userspace.
+ */
+ info = fsinfo_buffer_info[params.request];
+ if (params.Nth != 0 && !(info.flags & (__FSINFO_N | __FSINFO_NM)))
+ return -ENODATA;
+ if (params.Mth != 0 && !(info.flags & __FSINFO_NM))
+ return -ENODATA;
+
+ switch (info.type) {
+ case __FSINFO_STRUCT:
+ params.buf_size = info.size;
+ if (user_buf_size == 0)
+ return info.size; /* We know how big the buffer should be */
+ break;
+
+ case __FSINFO_STRING:
+ params.buf_size = 4096;
+ break;
+
+ case __FSINFO_OPAQUE:
+ case __FSINFO_STRUCT_ARRAY:
+ /* Opaque blob or array of struct elements. We also create a
+ * buffer that can be used for scratch space.
+ */
+ ret = -ENOMEM;
+ params.scratch_buffer = kmalloc(4096, GFP_KERNEL);
+ if (!params.scratch_buffer)
+ goto error;
+ params.overlarge = true;
+ params.buf_size = 4096;
+ break;
+
+ default:
+ return -ENOBUFS;
+ }
+
+ /* We always allocate a buffer for a string, even if buf_size == 0 and
+ * we're not going to return any data. This means that the filesystem
+ * code needn't care about whether the buffer actually exists or not.
+ */
+ ret = -ENOMEM;
+ params.buffer = kvzalloc(params.buf_size, GFP_KERNEL);
+ if (!params.buffer)
+ goto error_scratch;
+
+ if (filename)
+ ret = vfs_fsinfo_path(dfd, filename, ¶ms);
+ else
+ ret = vfs_fsinfo_fd(dfd, ¶ms);
+ if (ret < 0)
+ goto error_buffer;
+
+ result_size = ret;
+ if (result_size > user_buf_size)
+ result_size = user_buf_size;
+
+ if (result_size > 0 &&
+ copy_to_user(user_buffer, params.buffer, result_size)) {
+ ret = -EFAULT;
+ goto error_buffer;
+ }
+
+ /* Clear any part of the buffer that we won't fill if we're putting a
+ * struct in there. Strings, opaque objects and arrays are expected to
+ * be variable length.
+ */
+ if (info.type == __FSINFO_STRUCT &&
+ user_buf_size > result_size &&
+ clear_user(user_buffer + result_size, user_buf_size - result_size) != 0) {
+ ret = -EFAULT;
+ goto error_buffer;
+ }
+
+error_buffer:
+ kvfree(params.buffer);
+error_scratch:
+ kfree(params.scratch_buffer);
+error:
+ return ret;
+}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 6a38b7124143..71ce3b054c42 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -66,6 +66,8 @@ struct fscrypt_info;
struct fscrypt_operations;
struct fs_context;
struct fs_parameter_description;
+struct fsinfo_kparams;
+enum fsinfo_attribute;
extern void __init inode_init(void);
extern void __init inode_init_early(void);
@@ -1922,6 +1924,9 @@ struct super_operations {
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
+#ifdef CONFIG_FSINFO
+ int (*fsinfo) (struct path *, struct fsinfo_kparams *);
+#endif
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
diff --git a/include/linux/fsinfo.h b/include/linux/fsinfo.h
new file mode 100644
index 000000000000..e17e4f0bae18
--- /dev/null
+++ b/include/linux/fsinfo.h
@@ -0,0 +1,66 @@
+/* Filesystem information query
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_FSINFO_H
+#define _LINUX_FSINFO_H
+
+#ifdef CONFIG_FSINFO
+
+#include <uapi/linux/fsinfo.h>
+
+struct fsinfo_kparams {
+ __u32 at_flags; /* AT_SYMLINK_NOFOLLOW and similar */
+ enum fsinfo_attribute request; /* What is being asking for */
+ __u32 Nth; /* Instance of it (some may have multiple) */
+ __u32 Mth; /* Subinstance */
+ bool overlarge; /* T if the buffer may be resized */
+ unsigned int usage; /* Amount of buffer used (overlarge=T) */
+ unsigned int buf_size; /* Size of ->buffer[] */
+ void *buffer; /* Where to place the reply */
+ char *scratch_buffer; /* 4K scratch buffer (overlarge=T) */
+};
+
+extern int generic_fsinfo(struct path *, struct fsinfo_kparams *);
+
+static inline void fsinfo_set_cap(struct fsinfo_capabilities *c,
+ enum fsinfo_capability cap)
+{
+ c->capabilities[cap / 8] |= 1 << (cap % 8);
+}
+
+static inline void fsinfo_clear_cap(struct fsinfo_capabilities *c,
+ enum fsinfo_capability cap)
+{
+ c->capabilities[cap / 8] &= ~(1 << (cap % 8));
+}
+
+/**
+ * fsinfo_set_unix_caps - Set standard UNIX capabilities.
+ * @c: The capabilities mask to alter
+ */
+static inline void fsinfo_set_unix_caps(struct fsinfo_capabilities *caps)
+{
+ fsinfo_set_cap(caps, FSINFO_CAP_UIDS);
+ fsinfo_set_cap(caps, FSINFO_CAP_GIDS);
+ fsinfo_set_cap(caps, FSINFO_CAP_DIRECTORIES);
+ fsinfo_set_cap(caps, FSINFO_CAP_SYMLINKS);
+ fsinfo_set_cap(caps, FSINFO_CAP_HARD_LINKS);
+ fsinfo_set_cap(caps, FSINFO_CAP_DEVICE_FILES);
+ fsinfo_set_cap(caps, FSINFO_CAP_UNIX_SPECIALS);
+ fsinfo_set_cap(caps, FSINFO_CAP_SPARSE);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_ATIME);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_CTIME);
+ fsinfo_set_cap(caps, FSINFO_CAP_HAS_MTIME);
+}
+
+#endif /* CONFIG_FSINFO */
+
+#endif /* _LINUX_FSINFO_H */
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index e2870fe1be5b..217d25b62b4f 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -50,6 +50,7 @@ struct stat64;
struct statfs;
struct statfs64;
struct statx;
+struct fsinfo_params;
struct __sysctl_args;
struct sysinfo;
struct timespec;
@@ -997,6 +998,9 @@ asmlinkage long sys_fspick(int dfd, const char __user *path, unsigned int flags)
asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
siginfo_t __user *info,
unsigned int flags);
+asmlinkage long sys_fsinfo(int dfd, const char __user *path,
+ struct fsinfo_params __user *params,
+ void __user *buffer, size_t buf_size);
/*
* Architecture-specific system calls
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
new file mode 100644
index 000000000000..0af09b7e734c
--- /dev/null
+++ b/include/uapi/linux/fsinfo.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* fsinfo() definitions.
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+#ifndef _UAPI_LINUX_FSINFO_H
+#define _UAPI_LINUX_FSINFO_H
+
+#include <linux/types.h>
+#include <linux/socket.h>
+
+/*
+ * The filesystem attributes that can be requested. Note that some attributes
+ * may have multiple instances which can be switched in the parameter block.
+ */
+enum fsinfo_attribute {
+ FSINFO_ATTR_STATFS = 0, /* statfs()-style state */
+ FSINFO_ATTR_FSINFO = 1, /* Information about fsinfo() */
+ FSINFO_ATTR_IDS = 2, /* Filesystem IDs */
+ FSINFO_ATTR_LIMITS = 3, /* Filesystem limits */
+ FSINFO_ATTR_SUPPORTS = 4, /* What's supported in statx, iocflags, ... */
+ FSINFO_ATTR_CAPABILITIES = 5, /* Filesystem capabilities (bits) */
+ FSINFO_ATTR_TIMESTAMP_INFO = 6, /* Inode timestamp info */
+ FSINFO_ATTR_VOLUME_ID = 7, /* Volume ID (string) */
+ FSINFO_ATTR_VOLUME_UUID = 8, /* Volume UUID (LE uuid) */
+ FSINFO_ATTR_VOLUME_NAME = 9, /* Volume name (string) */
+ FSINFO_ATTR_NAME_ENCODING = 10, /* Filename encoding (string) */
+ FSINFO_ATTR_NAME_CODEPAGE = 11, /* Filename codepage (string) */
+ FSINFO_ATTR__NR
+};
+
+/*
+ * Optional fsinfo() parameter structure.
+ *
+ * If this is not given, it is assumed that fsinfo_attr_statfs instance 0,0 is
+ * desired.
+ */
+struct fsinfo_params {
+ __u32 at_flags; /* AT_SYMLINK_NOFOLLOW and similar flags */
+ __u32 request; /* What is being asking for (enum fsinfo_attribute) */
+ __u32 Nth; /* Instance of it (some may have multiple) */
+ __u32 Mth; /* Subinstance of Nth instance */
+ __u64 __reserved[3]; /* Reserved params; all must be 0 */
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_statfs).
+ * - This gives extended filesystem information.
+ */
+struct fsinfo_statfs {
+ __u64 f_type; /* Filesystem type from linux/magic.h [uncond] */
+ __u64 f_bsize; /* Optimal block size */
+ __u64 f_blocks; /* Total number of blocks in fs */
+ __u64 f_bfree; /* Total number of free blocks */
+ __u64 f_bavail; /* Number of free blocks available to ordinary user */
+ __u64 f_files; /* Total number of file nodes in fs */
+ __u64 f_ffree; /* Number of free file nodes */
+ __u64 f_favail; /* Number of file nodes available to ordinary user */
+ __u64 f_fsid; /* Filesystem identifier */
+ __u64 f_namelen; /* Maximum filename length */
+ __u64 f_frsize; /* Fragment size */
+ __u64 sb_flags; /* Superblock flags (MS_*) */
+ __u64 mnt_attrs; /* Mount attributes (MOUNT_ATTR_*) */
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_ids).
+ *
+ * List of basic identifiers as is normally found in statfs().
+ */
+struct fsinfo_ids {
+ char f_fs_name[15 + 1];
+ __u64 f_fsid; /* Short 64-bit Filesystem ID (as statfs) */
+ __u64 f_sb_id; /* Internal superblock ID for sbnotify()/mntnotify() */
+ __u32 f_fstype; /* Filesystem type from linux/magic.h [uncond] */
+ __u32 f_dev_major; /* As st_dev_* from struct statx [uncond] */
+ __u32 f_dev_minor;
+ __u32 __reserved[1];
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_limits).
+ *
+ * List of supported filesystem limits.
+ */
+struct fsinfo_limits {
+ __u64 max_file_size; /* Maximum file size */
+ __u64 max_uid; /* Maximum UID supported */
+ __u64 max_gid; /* Maximum GID supported */
+ __u64 max_projid; /* Maximum project ID supported */
+ __u32 max_dev_major; /* Maximum device major representable */
+ __u32 max_dev_minor; /* Maximum device minor representable */
+ __u32 max_hard_links; /* Maximum number of hard links on a file */
+ __u32 max_xattr_body_len; /* Maximum xattr content length */
+ __u32 max_xattr_name_len; /* Maximum xattr name length */
+ __u32 max_filename_len; /* Maximum filename length */
+ __u32 max_symlink_len; /* Maximum symlink content length */
+ __u32 __reserved[1];
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_supports).
+ *
+ * What's supported in various masks, such as statx() attribute and mask bits
+ * and IOC flags.
+ */
+struct fsinfo_supports {
+ __u64 stx_attributes; /* What statx::stx_attributes are supported */
+ __u32 stx_mask; /* What statx::stx_mask bits are supported */
+ __u32 ioc_flags; /* What FS_IOC_* flags are supported */
+ __u32 win_file_attrs; /* What DOS/Windows FILE_* attributes are supported */
+ __u32 __reserved[1];
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_capabilities).
+ *
+ * Bitmask indicating filesystem capabilities where renderable as single bits.
+ */
+enum fsinfo_capability {
+ FSINFO_CAP_IS_KERNEL_FS = 0, /* fs is kernel-special filesystem */
+ FSINFO_CAP_IS_BLOCK_FS = 1, /* fs is block-based filesystem */
+ FSINFO_CAP_IS_FLASH_FS = 2, /* fs is flash filesystem */
+ FSINFO_CAP_IS_NETWORK_FS = 3, /* fs is network filesystem */
+ FSINFO_CAP_IS_AUTOMOUNTER_FS = 4, /* fs is automounter special filesystem */
+ FSINFO_CAP_IS_MEMORY_FS = 5, /* fs is memory-based filesystem */
+ FSINFO_CAP_AUTOMOUNTS = 6, /* fs supports automounts */
+ FSINFO_CAP_ADV_LOCKS = 7, /* fs supports advisory file locking */
+ FSINFO_CAP_MAND_LOCKS = 8, /* fs supports mandatory file locking */
+ FSINFO_CAP_LEASES = 9, /* fs supports file leases */
+ FSINFO_CAP_UIDS = 10, /* fs supports numeric uids */
+ FSINFO_CAP_GIDS = 11, /* fs supports numeric gids */
+ FSINFO_CAP_PROJIDS = 12, /* fs supports numeric project ids */
+ FSINFO_CAP_STRING_USER_IDS = 13, /* fs supports string user identifiers */
+ FSINFO_CAP_GUID_USER_IDS = 14, /* fs supports GUID user identifiers */
+ FSINFO_CAP_WINDOWS_ATTRS = 15, /* fs has windows attributes */
+ FSINFO_CAP_USER_QUOTAS = 16, /* fs has per-user quotas */
+ FSINFO_CAP_GROUP_QUOTAS = 17, /* fs has per-group quotas */
+ FSINFO_CAP_PROJECT_QUOTAS = 18, /* fs has per-project quotas */
+ FSINFO_CAP_XATTRS = 19, /* fs has xattrs */
+ FSINFO_CAP_JOURNAL = 20, /* fs has a journal */
+ FSINFO_CAP_DATA_IS_JOURNALLED = 21, /* fs is using data journalling */
+ FSINFO_CAP_O_SYNC = 22, /* fs supports O_SYNC */
+ FSINFO_CAP_O_DIRECT = 23, /* fs supports O_DIRECT */
+ FSINFO_CAP_VOLUME_ID = 24, /* fs has a volume ID */
+ FSINFO_CAP_VOLUME_UUID = 25, /* fs has a volume UUID */
+ FSINFO_CAP_VOLUME_NAME = 26, /* fs has a volume name */
+ FSINFO_CAP_VOLUME_FSID = 27, /* fs has a volume FSID */
+ FSINFO_CAP_IVER_ALL_CHANGE = 28, /* i_version represents data + meta changes */
+ FSINFO_CAP_IVER_DATA_CHANGE = 29, /* i_version represents data changes only */
+ FSINFO_CAP_IVER_MONO_INCR = 30, /* i_version incremented monotonically */
+ FSINFO_CAP_DIRECTORIES = 31, /* fs supports (sub)directories */
+ FSINFO_CAP_SYMLINKS = 32, /* fs supports symlinks */
+ FSINFO_CAP_HARD_LINKS = 33, /* fs supports hard links */
+ FSINFO_CAP_HARD_LINKS_1DIR = 34, /* fs supports hard links in same dir only */
+ FSINFO_CAP_DEVICE_FILES = 35, /* fs supports bdev, cdev */
+ FSINFO_CAP_UNIX_SPECIALS = 36, /* fs supports pipe, fifo, socket */
+ FSINFO_CAP_RESOURCE_FORKS = 37, /* fs supports resource forks/streams */
+ FSINFO_CAP_NAME_CASE_INDEP = 38, /* Filename case independence is mandatory */
+ FSINFO_CAP_NAME_NON_UTF8 = 39, /* fs has non-utf8 names */
+ FSINFO_CAP_NAME_HAS_CODEPAGE = 40, /* fs has a filename codepage */
+ FSINFO_CAP_SPARSE = 41, /* fs supports sparse files */
+ FSINFO_CAP_NOT_PERSISTENT = 42, /* fs is not persistent */
+ FSINFO_CAP_NO_UNIX_MODE = 43, /* fs does not support unix mode bits */
+ FSINFO_CAP_HAS_ATIME = 44, /* fs supports access time */
+ FSINFO_CAP_HAS_BTIME = 45, /* fs supports birth/creation time */
+ FSINFO_CAP_HAS_CTIME = 46, /* fs supports change time */
+ FSINFO_CAP_HAS_MTIME = 47, /* fs supports modification time */
+ FSINFO_CAP__NR
+};
+
+struct fsinfo_capabilities {
+ __u8 capabilities[(FSINFO_CAP__NR + 7) / 8];
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_timestamp_info).
+ */
+struct fsinfo_timestamp_info {
+ __s64 minimum_timestamp; /* Minimum timestamp value in seconds */
+ __s64 maximum_timestamp; /* Maximum timestamp value in seconds */
+ __u16 atime_gran_mantissa; /* Granularity(secs) = mant * 10^exp */
+ __u16 btime_gran_mantissa;
+ __u16 ctime_gran_mantissa;
+ __u16 mtime_gran_mantissa;
+ __s8 atime_gran_exponent;
+ __s8 btime_gran_exponent;
+ __s8 ctime_gran_exponent;
+ __s8 mtime_gran_exponent;
+ __u32 __reserved[1];
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_volume_uuid).
+ */
+struct fsinfo_volume_uuid {
+ __u8 uuid[16];
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_fsinfo).
+ *
+ * This gives information about fsinfo() itself.
+ */
+struct fsinfo_fsinfo {
+ __u32 max_attr; /* Number of supported attributes (fsinfo_attr__nr) */
+ __u32 max_cap; /* Number of supported capabilities (fsinfo_cap__nr) */
+};
+
+#endif /* _UAPI_LINUX_FSINFO_H */
diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
index 4d9ae5ea6caf..d1d9d76cae1e 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -71,6 +71,9 @@ COND_SYSCALL_COMPAT(epoll_pwait);
/* fs/fcntl.c */
+/* fs/fsinfo.c */
+COND_SYSCALL(fsinfo);
+
/* fs/inotify_user.c */
COND_SYSCALL(inotify_init1);
COND_SYSCALL(inotify_add_watch);
diff --git a/samples/vfs/Makefile b/samples/vfs/Makefile
index a3e4ffd4c773..d3cc8e9a4fd8 100644
--- a/samples/vfs/Makefile
+++ b/samples/vfs/Makefile
@@ -1,10 +1,14 @@
# List of programs to build
hostprogs-y := \
+ test-fsinfo \
test-fsmount \
test-statx
# Tell kbuild to always build the programs
always := $(hostprogs-y)
+HOSTCFLAGS_test-fsinfo.o += -I$(objtree)/usr/include
+HOSTLDLIBS_test-fsinfo += -lm
+
HOSTCFLAGS_test-fsmount.o += -I$(objtree)/usr/include
HOSTCFLAGS_test-statx.o += -I$(objtree)/usr/include
diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
new file mode 100644
index 000000000000..ae62c132bdc2
--- /dev/null
+++ b/samples/vfs/test-fsinfo.c
@@ -0,0 +1,544 @@
+/* Test the fsinfo() system call
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#define _ATFILE_SOURCE
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h>
+#include <fcntl.h>
+#include <sys/syscall.h>
+#include <linux/fsinfo.h>
+#include <linux/socket.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+
+#ifndef __NR_fsinfo
+#define __NR_fsinfo -1
+#endif
+
+static bool debug = 0;
+
+static __attribute__((unused))
+ssize_t fsinfo(int dfd, const char *filename, struct fsinfo_params *params,
+ void *buffer, size_t buf_size)
+{
+ return syscall(__NR_fsinfo, dfd, filename, params, buffer, buf_size);
+}
+
+struct fsinfo_attr_info {
+ unsigned char type;
+ unsigned char flags;
+ unsigned short size;
+};
+
+#define __FSINFO_STRUCT 0
+#define __FSINFO_STRING 1
+#define __FSINFO_OVER 2
+#define __FSINFO_STRUCT_ARRAY 3
+#define __FSINFO_0 0
+#define __FSINFO_N 0x0001
+#define __FSINFO_NM 0x0002
+
+#define _Z(T, F, S) { .type = __FSINFO_##T, .flags = __FSINFO_##F, .size = S }
+#define FSINFO_STRING(X,Y) [FSINFO_ATTR_##X] = _Z(STRING, 0, 0)
+#define FSINFO_STRUCT(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT, 0, sizeof(struct fsinfo_##Y))
+#define FSINFO_STRING_N(X,Y) [FSINFO_ATTR_##X] = _Z(STRING, N, 0)
+#define FSINFO_STRUCT_N(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT, N, sizeof(struct fsinfo_##Y))
+#define FSINFO_STRING_NM(X,Y) [FSINFO_ATTR_##X] = _Z(STRING, NM, 0)
+#define FSINFO_STRUCT_NM(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT, NM, sizeof(struct fsinfo_##Y))
+#define FSINFO_OVERLARGE(X,Y) [FSINFO_ATTR_##X] = _Z(OVER, 0, 0)
+#define FSINFO_STRUCT_ARRAY(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT_ARRAY, 0, sizeof(struct fsinfo_##Y))
+
+static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
+ FSINFO_STRUCT (STATFS, statfs),
+ FSINFO_STRUCT (FSINFO, fsinfo),
+ FSINFO_STRUCT (IDS, ids),
+ FSINFO_STRUCT (LIMITS, limits),
+ FSINFO_STRUCT (CAPABILITIES, capabilities),
+ FSINFO_STRUCT (SUPPORTS, supports),
+ FSINFO_STRUCT (TIMESTAMP_INFO, timestamp_info),
+ FSINFO_STRING (VOLUME_ID, volume_id),
+ FSINFO_STRUCT (VOLUME_UUID, volume_uuid),
+ FSINFO_STRING (VOLUME_NAME, volume_name),
+ FSINFO_STRING (NAME_ENCODING, name_encoding),
+ FSINFO_STRING (NAME_CODEPAGE, name_codepage),
+};
+
+#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
+static const char *fsinfo_attr_names[FSINFO_ATTR__NR] = {
+ FSINFO_NAME (STATFS, statfs),
+ FSINFO_NAME (FSINFO, fsinfo),
+ FSINFO_NAME (IDS, ids),
+ FSINFO_NAME (LIMITS, limits),
+ FSINFO_NAME (CAPABILITIES, capabilities),
+ FSINFO_NAME (SUPPORTS, supports),
+ FSINFO_NAME (TIMESTAMP_INFO, timestamp_info),
+ FSINFO_NAME (VOLUME_ID, volume_id),
+ FSINFO_NAME (VOLUME_UUID, volume_uuid),
+ FSINFO_NAME (VOLUME_NAME, volume_name),
+ FSINFO_NAME (NAME_ENCODING, name_encoding),
+ FSINFO_NAME (NAME_CODEPAGE, name_codepage),
+};
+
+union reply {
+ char buffer[4096];
+ struct fsinfo_statfs statfs;
+ struct fsinfo_fsinfo fsinfo;
+ struct fsinfo_ids ids;
+ struct fsinfo_limits limits;
+ struct fsinfo_supports supports;
+ struct fsinfo_capabilities caps;
+ struct fsinfo_timestamp_info timestamps;
+ struct fsinfo_volume_uuid uuid;
+};
+
+static void dump_hex(unsigned int *data, int from, int to)
+{
+ unsigned offset, print_offset = 1, col = 0;
+
+ from /= 4;
+ to = (to + 3) / 4;
+
+ for (offset = from; offset < to; offset++) {
+ if (print_offset) {
+ printf("%04x: ", offset * 8);
+ print_offset = 0;
+ }
+ printf("%08x", data[offset]);
+ col++;
+ if ((col & 3) == 0) {
+ printf("\n");
+ print_offset = 1;
+ } else {
+ printf(" ");
+ }
+ }
+
+ if (!print_offset)
+ printf("\n");
+}
+
+static void dump_attr_STATFS(union reply *r, int size)
+{
+ struct fsinfo_statfs *f = &r->statfs;
+
+ printf("\n");
+ printf("\tblocks: n=%llu fr=%llu av=%llu\n",
+ (unsigned long long)f->f_blocks,
+ (unsigned long long)f->f_bfree,
+ (unsigned long long)f->f_bavail);
+
+ printf("\tfiles : n=%llu fr=%llu av=%llu\n",
+ (unsigned long long)f->f_files,
+ (unsigned long long)f->f_ffree,
+ (unsigned long long)f->f_favail);
+ printf("\tbsize : %llu\n", f->f_bsize);
+ printf("\tfrsize: %llu\n", f->f_frsize);
+ printf("\tmntfl : %llx\n", (unsigned long long)f->mnt_attrs);
+ printf("\tsbfl : %llx\n", (unsigned long long)f->sb_flags);
+}
+
+static void dump_attr_FSINFO(union reply *r, int size)
+{
+ struct fsinfo_fsinfo *f = &r->fsinfo;
+
+ printf("max_attr=%u max_cap=%u\n", f->max_attr, f->max_cap);
+}
+
+static void dump_attr_IDS(union reply *r, int size)
+{
+ struct fsinfo_ids *f = &r->ids;
+
+ printf("\n");
+ printf("\tdev : %02x:%02x\n", f->f_dev_major, f->f_dev_minor);
+ printf("\tfs : type=%x name=%s\n", f->f_fstype, f->f_fs_name);
+ printf("\tfsid : %llx\n", (unsigned long long)f->f_fsid);
+}
+
+static void dump_attr_LIMITS(union reply *r, int size)
+{
+ struct fsinfo_limits *f = &r->limits;
+
+ printf("\n");
+ printf("\tmax file size: %llx\n",
+ (unsigned long long)f->max_file_size);
+ printf("\tmax ids : u=%llx g=%llx p=%llx\n",
+ (unsigned long long)f->max_uid,
+ (unsigned long long)f->max_gid,
+ (unsigned long long)f->max_projid);
+ printf("\tmax dev : maj=%x min=%x\n",
+ f->max_dev_major, f->max_dev_minor);
+ printf("\tmax links : %x\n", f->max_hard_links);
+ printf("\tmax xattr : n=%x b=%x\n",
+ f->max_xattr_name_len, f->max_xattr_body_len);
+ printf("\tmax len : file=%x sym=%x\n",
+ f->max_filename_len, f->max_symlink_len);
+}
+
+static void dump_attr_SUPPORTS(union reply *r, int size)
+{
+ struct fsinfo_supports *f = &r->supports;
+
+ printf("\n");
+ printf("\tstx_attr=%llx\n", (unsigned long long)f->stx_attributes);
+ printf("\tstx_mask=%x\n", f->stx_mask);
+ printf("\tioc_flags=%x\n", f->ioc_flags);
+ printf("\twin_fattrs=%x\n", f->win_file_attrs);
+}
+
+#define FSINFO_CAP_NAME(C) [FSINFO_CAP_##C] = #C
+static const char *fsinfo_cap_names[FSINFO_CAP__NR] = {
+ FSINFO_CAP_NAME(IS_KERNEL_FS),
+ FSINFO_CAP_NAME(IS_BLOCK_FS),
+ FSINFO_CAP_NAME(IS_FLASH_FS),
+ FSINFO_CAP_NAME(IS_NETWORK_FS),
+ FSINFO_CAP_NAME(IS_AUTOMOUNTER_FS),
+ FSINFO_CAP_NAME(IS_MEMORY_FS),
+ FSINFO_CAP_NAME(AUTOMOUNTS),
+ FSINFO_CAP_NAME(ADV_LOCKS),
+ FSINFO_CAP_NAME(MAND_LOCKS),
+ FSINFO_CAP_NAME(LEASES),
+ FSINFO_CAP_NAME(UIDS),
+ FSINFO_CAP_NAME(GIDS),
+ FSINFO_CAP_NAME(PROJIDS),
+ FSINFO_CAP_NAME(STRING_USER_IDS),
+ FSINFO_CAP_NAME(GUID_USER_IDS),
+ FSINFO_CAP_NAME(WINDOWS_ATTRS),
+ FSINFO_CAP_NAME(USER_QUOTAS),
+ FSINFO_CAP_NAME(GROUP_QUOTAS),
+ FSINFO_CAP_NAME(PROJECT_QUOTAS),
+ FSINFO_CAP_NAME(XATTRS),
+ FSINFO_CAP_NAME(JOURNAL),
+ FSINFO_CAP_NAME(DATA_IS_JOURNALLED),
+ FSINFO_CAP_NAME(O_SYNC),
+ FSINFO_CAP_NAME(O_DIRECT),
+ FSINFO_CAP_NAME(VOLUME_ID),
+ FSINFO_CAP_NAME(VOLUME_UUID),
+ FSINFO_CAP_NAME(VOLUME_NAME),
+ FSINFO_CAP_NAME(VOLUME_FSID),
+ FSINFO_CAP_NAME(IVER_ALL_CHANGE),
+ FSINFO_CAP_NAME(IVER_DATA_CHANGE),
+ FSINFO_CAP_NAME(IVER_MONO_INCR),
+ FSINFO_CAP_NAME(DIRECTORIES),
+ FSINFO_CAP_NAME(SYMLINKS),
+ FSINFO_CAP_NAME(HARD_LINKS),
+ FSINFO_CAP_NAME(HARD_LINKS_1DIR),
+ FSINFO_CAP_NAME(DEVICE_FILES),
+ FSINFO_CAP_NAME(UNIX_SPECIALS),
+ FSINFO_CAP_NAME(RESOURCE_FORKS),
+ FSINFO_CAP_NAME(NAME_CASE_INDEP),
+ FSINFO_CAP_NAME(NAME_NON_UTF8),
+ FSINFO_CAP_NAME(NAME_HAS_CODEPAGE),
+ FSINFO_CAP_NAME(SPARSE),
+ FSINFO_CAP_NAME(NOT_PERSISTENT),
+ FSINFO_CAP_NAME(NO_UNIX_MODE),
+ FSINFO_CAP_NAME(HAS_ATIME),
+ FSINFO_CAP_NAME(HAS_BTIME),
+ FSINFO_CAP_NAME(HAS_CTIME),
+ FSINFO_CAP_NAME(HAS_MTIME),
+};
+
+static void dump_attr_CAPABILITIES(union reply *r, int size)
+{
+ struct fsinfo_capabilities *f = &r->caps;
+ int i;
+
+ for (i = 0; i < sizeof(f->capabilities); i++)
+ printf("%02x", f->capabilities[i]);
+ printf("\n");
+ for (i = 0; i < FSINFO_CAP__NR; i++)
+ if (f->capabilities[i / 8] & (1 << (i % 8)))
+ printf("\t- %s\n", fsinfo_cap_names[i]);
+}
+
+static void dump_attr_TIMESTAMP_INFO(union reply *r, int size)
+{
+ struct fsinfo_timestamp_info *f = &r->timestamps;
+
+ printf("range=%llx-%llx\n",
+ (unsigned long long)f->minimum_timestamp,
+ (unsigned long long)f->maximum_timestamp);
+
+#define print_time(G) \
+ printf("\t"#G"time : gran=%gs\n", \
+ (f->G##time_gran_mantissa * \
+ pow(10., f->G##time_gran_exponent)))
+ print_time(a);
+ print_time(b);
+ print_time(c);
+ print_time(m);
+}
+
+static void dump_attr_VOLUME_UUID(union reply *r, int size)
+{
+ struct fsinfo_volume_uuid *f = &r->uuid;
+
+ printf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x"
+ "-%02x%02x%02x%02x%02x%02x\n",
+ f->uuid[ 0], f->uuid[ 1],
+ f->uuid[ 2], f->uuid[ 3],
+ f->uuid[ 4], f->uuid[ 5],
+ f->uuid[ 6], f->uuid[ 7],
+ f->uuid[ 8], f->uuid[ 9],
+ f->uuid[10], f->uuid[11],
+ f->uuid[12], f->uuid[13],
+ f->uuid[14], f->uuid[15]);
+}
+
+/*
+ *
+ */
+typedef void (*dumper_t)(union reply *r, int size);
+
+#define FSINFO_DUMPER(N) [FSINFO_ATTR_##N] = dump_attr_##N
+static const dumper_t fsinfo_attr_dumper[FSINFO_ATTR__NR] = {
+ FSINFO_DUMPER(STATFS),
+ FSINFO_DUMPER(FSINFO),
+ FSINFO_DUMPER(IDS),
+ FSINFO_DUMPER(LIMITS),
+ FSINFO_DUMPER(SUPPORTS),
+ FSINFO_DUMPER(CAPABILITIES),
+ FSINFO_DUMPER(TIMESTAMP_INFO),
+ FSINFO_DUMPER(VOLUME_UUID),
+};
+
+static void dump_fsinfo(enum fsinfo_attribute attr,
+ struct fsinfo_attr_info about,
+ union reply *r, int size)
+{
+ dumper_t dumper = fsinfo_attr_dumper[attr];
+ unsigned int len;
+
+ if (!dumper) {
+ printf("<no dumper>\n");
+ return;
+ }
+
+ len = about.size;
+ if (about.type == __FSINFO_STRUCT && size < len) {
+ printf("<short data %u/%u>\n", size, len);
+ return;
+ }
+
+ dumper(r, size);
+}
+
+/*
+ * Try one subinstance of an attribute.
+ */
+static int try_one(const char *file, struct fsinfo_params *params, bool raw)
+{
+ struct fsinfo_attr_info about;
+ union reply *r;
+ size_t buf_size = 4096;
+ char *p;
+ int ret;
+
+ for (;;) {
+ r = malloc(buf_size);
+ if (!r) {
+ perror("malloc");
+ exit(1);
+ }
+ memset(r->buffer, 0xbd, buf_size);
+
+ errno = 0;
+ ret = fsinfo(AT_FDCWD, file, params, r->buffer, buf_size);
+ if (params->request >= FSINFO_ATTR__NR) {
+ if (ret == -1 && errno == EOPNOTSUPP)
+ exit(0);
+ fprintf(stderr, "Unexpected error for too-large command %u: %m\n",
+ params->request);
+ exit(1);
+ }
+ if (ret == -1)
+ break;
+
+ if (ret <= buf_size)
+ break;
+ buf_size = (ret + 4096 - 1) & ~(4096 - 1);
+ }
+
+ if (debug)
+ printf("fsinfo(%s,%s,%u,%u) = %d: %m\n",
+ file, fsinfo_attr_names[params->request],
+ params->Nth, params->Mth, ret);
+
+ about = fsinfo_buffer_info[params->request];
+ if (ret == -1) {
+ if (errno == ENODATA) {
+ if (!(about.flags & (__FSINFO_N | __FSINFO_NM)) &&
+ params->Nth == 0 && params->Mth == 0) {
+ fprintf(stderr,
+ "Unexpected ENODATA (%u[%u][%u])\n",
+ params->request, params->Nth, params->Mth);
+ exit(1);
+ }
+ return (params->Mth == 0) ? 2 : 1;
+ }
+ if (errno == EOPNOTSUPP) {
+ if (params->Nth > 0 || params->Mth > 0) {
+ fprintf(stderr,
+ "Should return -ENODATA (%u[%u][%u])\n",
+ params->request, params->Nth, params->Mth);
+ exit(1);
+ }
+ //printf("\e[33m%s\e[m: <not supported>\n",
+ // fsinfo_attr_names[attr]);
+ return 2;
+ }
+ perror(file);
+ exit(1);
+ }
+
+ if (raw) {
+ if (ret > 4096)
+ ret = 4096;
+ dump_hex((unsigned int *)r->buffer, 0, ret);
+ return 0;
+ }
+
+ switch (about.flags & (__FSINFO_N | __FSINFO_NM)) {
+ case 0:
+ printf("\e[33m%s\e[m: ",
+ fsinfo_attr_names[params->request]);
+ break;
+ case __FSINFO_N:
+ printf("\e[33m%s[%u]\e[m: ",
+ fsinfo_attr_names[params->request],
+ params->Nth);
+ break;
+ case __FSINFO_NM:
+ printf("\e[33m%s[%u][%u]\e[m: ",
+ fsinfo_attr_names[params->request],
+ params->Nth, params->Mth);
+ break;
+ }
+
+ switch (about.type) {
+ case __FSINFO_STRUCT:
+ dump_fsinfo(params->request, about, r, ret);
+ return 0;
+
+ case __FSINFO_STRING:
+ if (ret >= 4096) {
+ ret = 4096;
+ r->buffer[4092] = '.';
+ r->buffer[4093] = '.';
+ r->buffer[4094] = '.';
+ r->buffer[4095] = 0;
+ } else {
+ r->buffer[ret] = 0;
+ }
+ for (p = r->buffer; *p; p++) {
+ if (!isprint(*p)) {
+ printf("<non-printable>\n");
+ continue;
+ }
+ }
+ printf("%s\n", r->buffer);
+ return 0;
+
+ case __FSINFO_OVER:
+ return 0;
+
+ case __FSINFO_STRUCT_ARRAY:
+ dump_fsinfo(params->request, about, r, ret);
+ return 0;
+
+ default:
+ fprintf(stderr, "Fishy about %u %u,%u,%u\n",
+ params->request, about.type, about.flags, about.size);
+ exit(1);
+ }
+}
+
+/*
+ *
+ */
+int main(int argc, char **argv)
+{
+ struct fsinfo_params params = {
+ .at_flags = AT_SYMLINK_NOFOLLOW,
+ };
+ unsigned int attr;
+ int raw = 0, opt, Nth, Mth;
+
+ while ((opt = getopt(argc, argv, "adlr"))) {
+ switch (opt) {
+ case 'a':
+ params.at_flags |= AT_NO_AUTOMOUNT;
+ continue;
+ case 'd':
+ debug = true;
+ continue;
+ case 'l':
+ params.at_flags &= ~AT_SYMLINK_NOFOLLOW;
+ continue;
+ case 'r':
+ raw = 1;
+ continue;
+ }
+ break;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1) {
+ printf("Format: test-fsinfo [-alr] <file>\n");
+ exit(2);
+ }
+
+ for (attr = 0; attr <= FSINFO_ATTR__NR; attr++) {
+ Nth = 0;
+ do {
+ Mth = 0;
+ do {
+ params.request = attr;
+ params.Nth = Nth;
+ params.Mth = Mth;
+
+ switch (try_one(argv[0], ¶ms, raw)) {
+ case 0:
+ continue;
+ case 1:
+ goto done_M;
+ case 2:
+ goto done_N;
+ }
+ } while (++Mth < 100);
+
+ done_M:
+ if (Mth >= 100) {
+ fprintf(stderr, "Fishy: Mth == %u\n", Mth);
+ break;
+ }
+
+ } while (++Nth < 100);
+
+ done_N:
+ if (Nth >= 100) {
+ fprintf(stderr, "Fishy: Nth == %u\n", Nth);
+ break;
+ }
+ }
+
+ return 0;
+}
^ permalink raw reply related
* [PATCH 00/25] VFS: Introduce filesystem information query syscall [ver #13]
From: David Howells @ 2019-05-28 15:11 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
Hi Al,
Here are a set of patches that adds a syscall, fsinfo(), that allows
attributes of a filesystem/superblock to be queried. Attribute values are
of four basic types:
(1) Version dependent-length structure (size defined by type).
(2) Variable-length string (up to PAGE_SIZE).
(3) Array of fixed-length structures (up to INT_MAX size).
(4) Opaque blob (up to INT_MAX size).
Attributes can have multiple values in up to two dimensions and all the
values of a particular attribute must have the same type.
Note that the attribute values *are* allowed to vary between dentries
within a single superblock, depending on the specific dentry that you're
looking at.
I've tried to make the interface as light as possible, so integer/enum
attribute selector rather than string and the core does all the allocation
and extensibility support work rather than leaving that to the filesystems.
That means that for the first two attribute types, sb->s_op->fsinfo() may
assume that the provided buffer is always present and always big enough.
Further, this removes the possibility of the filesystem gaining access to the
userspace buffer.
fsinfo() allows a variety of information to be retrieved about a filesystem
and the mount topology:
(1) General superblock attributes:
- The amount of space/free space in a filesystem (as statfs()).
- Filesystem identifiers (UUID, volume label, device numbers, ...)
- The limits on a filesystem's capabilities
- Information on supported statx fields and attributes and IOC flags.
- A variety single-bit flags indicating supported capabilities.
- Timestamp resolution and range.
- Sources (as per mount(2), but fsconfig() allows multiple sources).
- In-filesystem filename format information.
- Filesystem parameters ("mount -o xxx"-type things).
- LSM parameters (again "mount -o xxx"-type things).
(2) Filesystem-specific superblock attributes:
- Server names and addresses.
- Cell name.
(3) Filesystem configuration metadata attributes:
- Filesystem parameter type descriptions.
- Name -> parameter mappings.
- Simple enumeration name -> value mappings.
(4) Mount topology:
- General information about a mount object.
- Mount device name(s).
- Children of a mount object and their relative paths.
(5) Information about what the fsinfo() syscall itself supports, including
the number of attibutes supported and the number of capability bits
supported.
The system is extensible:
(1) New attributes can be added. There is no requirement that a
filesystem implement every attribute. Note that the core VFS keeps a
table of types and sizes so it can handle future extensibility rather
than delegating this to the filesystems.
(2) Version length-dependent structure attributes can be made larger and
have additional information tacked on the end, provided it keeps the
layout of the existing fields. If an older process asks for a shorter
structure, it will only be given the bits it asks for. If a newer
process asks for a longer structure on an older kernel, the extra
space will be set to 0. In all cases, the size of the data actually
available is returned.
In essence, the size of a structure is that structure's version: a
smaller size is an earlier version and a later version includes
everything that the earlier version did.
(3) New single-bit capability flags can be added. This is a structure-typed
attribute and, as such, (2) applies. Any bits you wanted but the kernel
doesn't support are automatically set to 0.
If a filesystem-specific attribute is added, it should just take up the next
number in the enumeration. Currently, I do not intend that the number space
should be subdivided between interested parties.
fsinfo() may be called like the following, for example:
struct fsinfo_params params = {
.at_flags = AT_SYMLINK_NOFOLLOW,
.request = FSINFO_ATTR_SERVER_ADDRESS;
.Nth = 2;
.Mth = 1;
};
struct fsinfo_server_address address;
len = fsinfo(AT_FDCWD, "/afs/grand.central.org/doc", ¶ms,
&address, sizeof(address));
The above example would query a network filesystem, such as AFS or NFS, and
ask what the 2nd address (Mth) of the 3rd server (Nth) that the superblock is
using is. Whereas:
struct fsinfo_params params = {
.at_flags = AT_SYMLINK_NOFOLLOW,
.request = FSINFO_ATTR_CELL_NAME;
};
char cell_name[256];
len = fsinfo(AT_FDCWD, "/afs/grand.central.org/doc", ¶ms,
&cell_name, sizeof(cell_name));
would retrieve the name of an AFS cell as a string.
fsinfo() can also be used to query a context from fsopen() or fspick():
fd = fsopen("ext4", 0);
struct fsinfo_params params = {
.request = FSINFO_ATTR_PARAM_DESCRIPTION;
};
struct fsinfo_param_description desc;
fsinfo(fd, NULL, ¶ms, &desc, sizeof(desc));
even if that context doesn't currently have a superblock attached (though if
there's no superblock attached, only filesystem-specific things like parameter
descriptions can be accessed).
The patches can be found here also:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git
on branch:
fsinfo
===================
SIGNIFICANT CHANGES
===================
ver #13:
(*) Provided a "fixed-struct array" type so that the list of children of a
mount and all their change counters can be read atomically.
(*) Additional filesystem examples.
(*) Documented the API.
ver #12:
(*) Rename ->get_fsinfo() to ->fsinfo().
(*) Pass the path through to to ->fsinfo() as it's needed for NFS to
retrocalculate the source name.
(*) Indicated which is the source parameter in the param-description
attribute.
(*) Dropped the realm attribute.
David
---
David Howells (17):
vfs: syscall: Add fsinfo() to query filesystem information
vfs: Allow fsinfo() to query what's in an fs_context
vfs: Allow fsinfo() to be used to query an fs parameter description
vfs: Implement parameter value retrieval with fsinfo()
fsinfo: Implement retrieval of LSM parameters with fsinfo()
vfs: Introduce a non-repeating system-unique superblock ID
vfs: Allow fsinfo() to look up a mount object by ID
vfs: Add mount notification count
vfs: Allow mount information to be queried by fsinfo()
vfs: fsinfo sample: Mount listing program
hugetlbfs: Add support for fsinfo()
kernfs, cgroup: Add fsinfo support
fsinfo: Support SELinux superblock parameter retrieval
fsinfo: Support Smack superblock parameter retrieval
afs: Support fsinfo()
nfs: Support fsinfo()
fsinfo: Add API documentation
Ian Kent (8):
fsinfo: autofs - add sb operation fsinfo()
fsinfo: shmem - add tmpfs sb operation fsinfo()
fsinfo: proc - add sb operation fsinfo()
fsinfo: devpts - add sb operation fsinfo()
fsinfo: pstore - add sb operation fsinfo()
fsinfo: debugfs - add sb operation fsinfo()
fsinfo: bpf - add sb operation fsinfo()
fsinfo: ufs - add sb operation fsinfo()
Documentation/filesystems/fsinfo.rst | 571 ++++++++++++++++++++++
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 7
fs/Makefile | 1
fs/afs/internal.h | 1
fs/afs/super.c | 155 ++++++
fs/autofs/inode.c | 63 ++
fs/d_path.c | 2
fs/debugfs/inode.c | 37 +
fs/devpts/inode.c | 42 ++
fs/fsinfo.c | 835 ++++++++++++++++++++++++++++++++
fs/hugetlbfs/inode.c | 56 ++
fs/internal.h | 11
fs/kernfs/mount.c | 20 +
fs/mount.h | 22 +
fs/namespace.c | 305 ++++++++++++
fs/nfs/fs_context.c | 163 ++++++
fs/nfs/internal.h | 6
fs/nfs/nfs4super.c | 3
fs/nfs/super.c | 77 +++
fs/proc/inode.c | 36 +
fs/pstore/inode.c | 31 +
fs/statfs.c | 2
fs/super.c | 24 +
fs/ufs/super.c | 57 ++
include/linux/fs.h | 8
include/linux/fsinfo.h | 69 +++
include/linux/kernfs.h | 4
include/linux/lsm_hooks.h | 13
include/linux/security.h | 11
include/linux/syscalls.h | 4
include/uapi/linux/fcntl.h | 2
include/uapi/linux/fsinfo.h | 311 ++++++++++++
kernel/bpf/inode.c | 24 +
kernel/cgroup/cgroup-v1.c | 44 ++
kernel/cgroup/cgroup.c | 19 +
kernel/sys_ni.c | 3
mm/shmem.c | 71 +++
samples/vfs/Makefile | 9
samples/vfs/test-fs-query.c | 139 +++++
samples/vfs/test-fsinfo.c | 675 ++++++++++++++++++++++++++
samples/vfs/test-mntinfo.c | 239 +++++++++
security/security.c | 12
security/selinux/hooks.c | 41 ++
security/selinux/include/security.h | 2
security/selinux/ss/services.c | 49 ++
security/smack/smack_lsm.c | 43 ++
48 files changed, 4313 insertions(+), 8 deletions(-)
create mode 100644 Documentation/filesystems/fsinfo.rst
create mode 100644 fs/fsinfo.c
create mode 100644 include/linux/fsinfo.h
create mode 100644 include/uapi/linux/fsinfo.h
create mode 100644 samples/vfs/test-fs-query.c
create mode 100644 samples/vfs/test-fsinfo.c
create mode 100644 samples/vfs/test-mntinfo.c
^ permalink raw reply
* [PATCH 2/7] keys: Add a notification facility [ver #13]
From: David Howells @ 2019-05-28 15:10 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905621951.1304.5956310120238620025.stgit@warthog.procyon.org.uk>
Add a key/keyring change notification facility whereby notifications about
changes in key and keyring content and attributes can be received.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_KEY_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, fd, 0x01);
After that, records will be placed into the queue when events occur in
which keys are changed in some way. Records are of the following format:
struct key_notification {
struct watch_notification watch;
__u32 key_id;
__u32 aux;
} *n;
Where:
n->watch.type will be WATCH_TYPE_KEY_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_KEY_REVOKED.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the second argument to
keyctl_watch_key(), shifted.
n->key will be the ID of the affected key.
n->aux will hold subtype-dependent information, such as the key
being linked into the keyring specified by n->key in the case of
NOTIFY_KEY_LINKED.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/security/keys/core.rst | 58 ++++++++++++++++++++++
include/linux/key.h | 4 ++
include/uapi/linux/keyctl.h | 1
include/uapi/linux/watch_queue.h | 25 ++++++++++
security/keys/Kconfig | 10 ++++
security/keys/compat.c | 2 +
security/keys/gc.c | 5 ++
security/keys/internal.h | 30 +++++++++++-
security/keys/key.c | 37 +++++++++-----
security/keys/keyctl.c | 88 +++++++++++++++++++++++++++++++++-
security/keys/keyring.c | 17 +++++--
security/keys/request_key.c | 4 +-
12 files changed, 257 insertions(+), 24 deletions(-)
diff --git a/Documentation/security/keys/core.rst b/Documentation/security/keys/core.rst
index 9521c4207f01..05ef58c753f3 100644
--- a/Documentation/security/keys/core.rst
+++ b/Documentation/security/keys/core.rst
@@ -808,6 +808,7 @@ The keyctl syscall functions are:
A process must have search permission on the key for this function to be
successful.
+
* Compute a Diffie-Hellman shared secret or public key::
long keyctl(KEYCTL_DH_COMPUTE, struct keyctl_dh_params *params,
@@ -1001,6 +1002,63 @@ The keyctl syscall functions are:
written into the output buffer. Verification returns 0 on success.
+ * Watch a key or keyring for changes::
+
+ long keyctl(KEYCTL_WATCH_KEY, key_serial_t key, int queue_fd,
+ const struct watch_notification_filter *filter);
+
+ This will set or remove a watch for changes on the specified key or
+ keyring.
+
+ "key" is the ID of the key to be watched.
+
+ "queue_fd" is a file descriptor referring to an open "/dev/watch_queue"
+ which manages the buffer into which notifications will be delivered.
+
+ "filter" is either NULL to remove a watch or a filter specification to
+ indicate what events are required from the key.
+
+ See Documentation/watch_queue.rst for more information.
+
+ Note that only one watch may be emplaced for any particular { key,
+ queue_fd } combination.
+
+ Notification records look like::
+
+ struct key_notification {
+ struct watch_notification watch;
+ __u32 key_id;
+ __u32 aux;
+ };
+
+ In this, watch::type will be "WATCH_TYPE_KEY_NOTIFY" and subtype will be
+ one of::
+
+ NOTIFY_KEY_INSTANTIATED
+ NOTIFY_KEY_UPDATED
+ NOTIFY_KEY_LINKED
+ NOTIFY_KEY_UNLINKED
+ NOTIFY_KEY_CLEARED
+ NOTIFY_KEY_REVOKED
+ NOTIFY_KEY_INVALIDATED
+ NOTIFY_KEY_SETATTR
+
+ Where these indicate a key being instantiated/rejected, updated, a link
+ being made in a keyring, a link being removed from a keyring, a keyring
+ being cleared, a key being revoked, a key being invalidated or a key
+ having one of its attributes changed (user, group, perm, timeout,
+ restriction).
+
+ If a watched key is deleted, a basic watch_notification will be issued
+ with "type" set to WATCH_TYPE_META and "subtype" set to
+ watch_meta_removal_notification. The watchpoint ID will be set in the
+ "info" field.
+
+ This needs to be configured by enabling:
+
+ "Provide key/keyring change notifications" (KEY_NOTIFICATIONS)
+
+
Kernel Services
===============
diff --git a/include/linux/key.h b/include/linux/key.h
index 7099985e35a9..f1c43852c0c6 100644
--- a/include/linux/key.h
+++ b/include/linux/key.h
@@ -159,6 +159,9 @@ struct key {
struct list_head graveyard_link;
struct rb_node serial_node;
};
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct watch_list *watchers; /* Entities watching this key for changes */
+#endif
struct rw_semaphore sem; /* change vs change sem */
struct key_user *user; /* owner of this key */
void *security; /* security data for this key */
@@ -193,6 +196,7 @@ struct key {
#define KEY_FLAG_ROOT_CAN_INVAL 7 /* set if key can be invalidated by root without permission */
#define KEY_FLAG_KEEP 8 /* set if key should not be removed */
#define KEY_FLAG_UID_KEYRING 9 /* set if key is a user or user session keyring */
+#define KEY_FLAG_SET_WATCH_PROXY 10 /* Set if watch_proxy should be set on added keys */
/* the key type and key description string
* - the desc is used to match a key against search criteria
diff --git a/include/uapi/linux/keyctl.h b/include/uapi/linux/keyctl.h
index f45ee0f69c0c..e9e7da849619 100644
--- a/include/uapi/linux/keyctl.h
+++ b/include/uapi/linux/keyctl.h
@@ -67,6 +67,7 @@
#define KEYCTL_PKEY_SIGN 27 /* Create a public key signature */
#define KEYCTL_PKEY_VERIFY 28 /* Verify a public key signature */
#define KEYCTL_RESTRICT_KEYRING 29 /* Restrict keys allowed to link to a keyring */
+#define KEYCTL_WATCH_KEY 30 /* Watch a key or ring of keys for changes */
/* keyctl structures */
struct keyctl_dh_params {
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
index 01746982c2ba..e3bb35a480ae 100644
--- a/include/uapi/linux/watch_queue.h
+++ b/include/uapi/linux/watch_queue.h
@@ -79,4 +79,29 @@ struct watch_notification_filter {
struct watch_notification_type_filter filters[];
};
+/*
+ * Type of key/keyring change notification.
+ */
+enum key_notification_subtype {
+ NOTIFY_KEY_INSTANTIATED = 0, /* Key was instantiated (aux is error code) */
+ NOTIFY_KEY_UPDATED = 1, /* Key was updated */
+ NOTIFY_KEY_LINKED = 2, /* Key (aux) was added to watched keyring */
+ NOTIFY_KEY_UNLINKED = 3, /* Key (aux) was removed from watched keyring */
+ NOTIFY_KEY_CLEARED = 4, /* Keyring was cleared */
+ NOTIFY_KEY_REVOKED = 5, /* Key was revoked */
+ NOTIFY_KEY_INVALIDATED = 6, /* Key was invalidated */
+ NOTIFY_KEY_SETATTR = 7, /* Key's attributes got changed */
+};
+
+/*
+ * Key/keyring notification record.
+ * - watch.type = WATCH_TYPE_KEY_NOTIFY
+ * - watch.subtype = enum key_notification_type
+ */
+struct key_notification {
+ struct watch_notification watch;
+ __u32 key_id; /* The key/keyring affected */
+ __u32 aux; /* Per-type auxiliary data */
+};
+
#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/security/keys/Kconfig b/security/keys/Kconfig
index 6462e6654ccf..fbe064fa0a17 100644
--- a/security/keys/Kconfig
+++ b/security/keys/Kconfig
@@ -101,3 +101,13 @@ config KEY_DH_OPERATIONS
in the kernel.
If you are unsure as to whether this is required, answer N.
+
+config KEY_NOTIFICATIONS
+ bool "Provide key/keyring change notifications"
+ depends on KEYS
+ select WATCH_QUEUE
+ help
+ This option provides support for getting change notifications on keys
+ and keyrings on which the caller has View permission. This makes use
+ of the /dev/watch_queue misc device to handle the notification
+ buffer and provides KEYCTL_WATCH_KEY to enable/disable watches.
diff --git a/security/keys/compat.c b/security/keys/compat.c
index 9482df601dc3..021d8e1c9233 100644
--- a/security/keys/compat.c
+++ b/security/keys/compat.c
@@ -158,6 +158,8 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option,
case KEYCTL_PKEY_VERIFY:
return keyctl_pkey_verify(compat_ptr(arg2), compat_ptr(arg3),
compat_ptr(arg4), compat_ptr(arg5));
+ case KEYCTL_WATCH_KEY:
+ return keyctl_watch_key(arg2, arg3, arg4);
default:
return -EOPNOTSUPP;
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 634e96b380e8..b685b9a85a9e 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -135,6 +135,11 @@ static noinline void key_gc_unused_keys(struct list_head *keys)
kdebug("- %u", key->serial);
key_check(key);
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ remove_watch_list(key->watchers);
+ key->watchers = NULL;
+#endif
+
/* Throw away the key data if the key is instantiated */
if (state == KEY_IS_POSITIVE && key->type->destroy)
key->type->destroy(key);
diff --git a/security/keys/internal.h b/security/keys/internal.h
index 8f533c81aa8d..a7ac0f823ade 100644
--- a/security/keys/internal.h
+++ b/security/keys/internal.h
@@ -19,6 +19,7 @@
#include <linux/task_work.h>
#include <linux/keyctl.h>
#include <linux/refcount.h>
+#include <linux/watch_queue.h>
#include <linux/compat.h>
struct iovec;
@@ -97,7 +98,8 @@ extern int __key_link_begin(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit **_edit);
extern int __key_link_check_live_key(struct key *keyring, struct key *key);
-extern void __key_link(struct key *key, struct assoc_array_edit **_edit);
+extern void __key_link(struct key *keyring, struct key *key,
+ struct assoc_array_edit **_edit);
extern void __key_link_end(struct key *keyring,
const struct keyring_index_key *index_key,
struct assoc_array_edit *edit);
@@ -178,6 +180,23 @@ extern int key_task_permission(const key_ref_t key_ref,
const struct cred *cred,
key_perm_t perm);
+static inline void notify_key(struct key *key,
+ enum key_notification_subtype subtype, u32 aux)
+{
+#ifdef CONFIG_KEY_NOTIFICATIONS
+ struct key_notification n = {
+ .watch.type = WATCH_TYPE_KEY_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = sizeof(n),
+ .key_id = key_serial(key),
+ .aux = aux,
+ };
+
+ post_watch_notification(key->watchers, &n.watch, current_cred(),
+ n.key_id);
+#endif
+}
+
/*
* Check to see whether permission is granted to use a key in the desired way.
*/
@@ -324,6 +343,15 @@ static inline long keyctl_pkey_e_d_s(int op,
}
#endif
+#ifdef CONFIG_KEY_NOTIFICATIONS
+extern long keyctl_watch_key(key_serial_t, int, int);
+#else
+static inline long keyctl_watch_key(key_serial_t key_id, int watch_fd, int watch_id)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
/*
* Debugging key validation
*/
diff --git a/security/keys/key.c b/security/keys/key.c
index 696f1c092c50..9d9f94992470 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -412,6 +412,7 @@ static void mark_key_instantiated(struct key *key, int reject_error)
*/
smp_store_release(&key->state,
(reject_error < 0) ? reject_error : KEY_IS_POSITIVE);
+ notify_key(key, NOTIFY_KEY_INSTANTIATED, reject_error);
}
/*
@@ -454,7 +455,7 @@ static int __key_instantiate_and_link(struct key *key,
if (test_bit(KEY_FLAG_KEEP, &keyring->flags))
set_bit(KEY_FLAG_KEEP, &key->flags);
- __key_link(key, _edit);
+ __key_link(keyring, key, _edit);
}
/* disable the authorisation key */
@@ -603,7 +604,7 @@ int key_reject_and_link(struct key *key,
/* and link it into the destination keyring */
if (keyring && link_ret == 0)
- __key_link(key, &edit);
+ __key_link(keyring, key, &edit);
/* disable the authorisation key */
if (authkey)
@@ -756,9 +757,11 @@ static inline key_ref_t __key_update(key_ref_t key_ref,
down_write(&key->sem);
ret = key->type->update(key, prep);
- if (ret == 0)
+ if (ret == 0) {
/* Updating a negative key positively instantiates it */
mark_key_instantiated(key, 0);
+ notify_key(key, NOTIFY_KEY_UPDATED, 0);
+ }
up_write(&key->sem);
@@ -999,9 +1002,11 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen)
down_write(&key->sem);
ret = key->type->update(key, &prep);
- if (ret == 0)
+ if (ret == 0) {
/* Updating a negative key positively instantiates it */
mark_key_instantiated(key, 0);
+ notify_key(key, NOTIFY_KEY_UPDATED, 0);
+ }
up_write(&key->sem);
@@ -1033,15 +1038,17 @@ void key_revoke(struct key *key)
* instantiated
*/
down_write_nested(&key->sem, 1);
- if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags) &&
- key->type->revoke)
- key->type->revoke(key);
-
- /* set the death time to no more than the expiry time */
- time = ktime_get_real_seconds();
- if (key->revoked_at == 0 || key->revoked_at > time) {
- key->revoked_at = time;
- key_schedule_gc(key->revoked_at + key_gc_delay);
+ if (!test_and_set_bit(KEY_FLAG_REVOKED, &key->flags)) {
+ notify_key(key, NOTIFY_KEY_REVOKED, 0);
+ if (key->type->revoke)
+ key->type->revoke(key);
+
+ /* set the death time to no more than the expiry time */
+ time = ktime_get_real_seconds();
+ if (key->revoked_at == 0 || key->revoked_at > time) {
+ key->revoked_at = time;
+ key_schedule_gc(key->revoked_at + key_gc_delay);
+ }
}
up_write(&key->sem);
@@ -1063,8 +1070,10 @@ void key_invalidate(struct key *key)
if (!test_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
down_write_nested(&key->sem, 1);
- if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags))
+ if (!test_and_set_bit(KEY_FLAG_INVALIDATED, &key->flags)) {
+ notify_key(key, NOTIFY_KEY_INVALIDATED, 0);
key_schedule_gc_links();
+ }
up_write(&key->sem);
}
}
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 3e4053a217c3..cc2e6feafbc7 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -914,6 +914,7 @@ long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group)
if (group != (gid_t) -1)
key->gid = gid;
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
error_put:
@@ -964,6 +965,7 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm)
/* if we're not the sysadmin, we can only change a key that we own */
if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) {
key->perm = perm;
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
ret = 0;
}
@@ -1355,10 +1357,12 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
okay:
key = key_ref_to_ptr(key_ref);
ret = 0;
- if (test_bit(KEY_FLAG_KEEP, &key->flags))
+ if (test_bit(KEY_FLAG_KEEP, &key->flags)) {
ret = -EPERM;
- else
+ } else {
key_set_timeout(key, timeout);
+ notify_key(key, NOTIFY_KEY_SETATTR, 0);
+ }
key_put(key);
error:
@@ -1631,6 +1635,83 @@ long keyctl_restrict_keyring(key_serial_t id, const char __user *_type,
return ret;
}
+#ifdef CONFIG_KEY_NOTIFICATIONS
+/*
+ * Watch for changes to a key.
+ *
+ * The caller must have View permission to watch a key or keyring.
+ */
+long keyctl_watch_key(key_serial_t id, int watch_queue_fd, int watch_id)
+{
+ struct watch_queue *wqueue;
+ struct watch_list *wlist = NULL;
+ struct watch *watch;
+ struct key *key;
+ key_ref_t key_ref;
+ long ret = -ENOMEM;
+
+ if (watch_id < -1 || watch_id > 0xff)
+ return -EINVAL;
+
+ key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE, KEY_NEED_VIEW);
+ if (IS_ERR(key_ref))
+ return PTR_ERR(key_ref);
+ key = key_ref_to_ptr(key_ref);
+
+ wqueue = get_watch_queue(watch_queue_fd);
+ if (IS_ERR(wqueue)) {
+ ret = PTR_ERR(wqueue);
+ goto err_key;
+ }
+
+ if (watch_id >= 0) {
+ if (!key->watchers) {
+ wlist = kzalloc(sizeof(*wlist), GFP_KERNEL);
+ if (!wlist)
+ goto err_wqueue;
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+ }
+
+ watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+ if (!watch)
+ goto err_wlist;
+
+ init_watch(watch, wqueue);
+ watch->id = key->serial;
+ watch->info_id = (u32)watch_id << 24;
+
+ down_write(&key->sem);
+ if (!key->watchers) {
+ key->watchers = wlist;
+ wlist = NULL;
+ }
+
+ ret = add_watch_to_object(watch, key->watchers);
+ up_write(&key->sem);
+
+ if (ret < 0)
+ kfree(watch);
+ } else if (key->watchers) {
+ down_write(&key->sem);
+ ret = remove_watch_from_object(key->watchers,
+ wqueue, key_serial(key),
+ false);
+ up_write(&key->sem);
+ } else {
+ ret = -EBADSLT;
+ }
+
+err_wlist:
+ kfree(wlist);
+err_wqueue:
+ put_watch_queue(wqueue);
+err_key:
+ key_put(key);
+ return ret;
+}
+#endif /* CONFIG_KEY_NOTIFICATIONS */
+
/*
* The key control system call
*/
@@ -1771,6 +1852,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3,
(const void __user *)arg4,
(const void __user *)arg5);
+ case KEYCTL_WATCH_KEY:
+ return keyctl_watch_key((key_serial_t)arg2, (int)arg3, (int)arg4);
+
default:
return -EOPNOTSUPP;
}
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index e14f09e3a4b0..f0f9ab3c5587 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -1018,12 +1018,14 @@ int keyring_restrict(key_ref_t keyring_ref, const char *type,
down_write(&keyring->sem);
down_write(&keyring_serialise_restrict_sem);
- if (keyring->restrict_link)
+ if (keyring->restrict_link) {
ret = -EEXIST;
- else if (keyring_detect_restriction_cycle(keyring, restrict_link))
+ } else if (keyring_detect_restriction_cycle(keyring, restrict_link)) {
ret = -EDEADLK;
- else
+ } else {
keyring->restrict_link = restrict_link;
+ notify_key(keyring, NOTIFY_KEY_SETATTR, 0);
+ }
up_write(&keyring_serialise_restrict_sem);
up_write(&keyring->sem);
@@ -1286,12 +1288,14 @@ int __key_link_check_live_key(struct key *keyring, struct key *key)
* holds at most one link to any given key of a particular type+description
* combination.
*/
-void __key_link(struct key *key, struct assoc_array_edit **_edit)
+void __key_link(struct key *keyring, struct key *key,
+ struct assoc_array_edit **_edit)
{
__key_get(key);
assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key));
assoc_array_apply_edit(*_edit);
*_edit = NULL;
+ notify_key(keyring, NOTIFY_KEY_LINKED, key_serial(key));
}
/*
@@ -1369,7 +1373,7 @@ int key_link(struct key *keyring, struct key *key)
if (ret == 0)
ret = __key_link_check_live_key(keyring, key);
if (ret == 0)
- __key_link(key, &edit);
+ __key_link(keyring, key, &edit);
__key_link_end(keyring, &key->index_key, edit);
}
@@ -1398,6 +1402,7 @@ EXPORT_SYMBOL(key_link);
int key_unlink(struct key *keyring, struct key *key)
{
struct assoc_array_edit *edit;
+ key_serial_t target = key_serial(key);
int ret;
key_check(keyring);
@@ -1419,6 +1424,7 @@ int key_unlink(struct key *keyring, struct key *key)
goto error;
assoc_array_apply_edit(edit);
+ notify_key(keyring, NOTIFY_KEY_UNLINKED, target);
key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES);
ret = 0;
@@ -1452,6 +1458,7 @@ int keyring_clear(struct key *keyring)
} else {
if (edit)
assoc_array_apply_edit(edit);
+ notify_key(keyring, NOTIFY_KEY_CLEARED, 0);
key_payload_reserve(keyring, 0);
ret = 0;
}
diff --git a/security/keys/request_key.c b/security/keys/request_key.c
index 75d87f9e0f49..5f474d0e8620 100644
--- a/security/keys/request_key.c
+++ b/security/keys/request_key.c
@@ -387,7 +387,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
goto key_already_present;
if (dest_keyring)
- __key_link(key, &edit);
+ __key_link(dest_keyring, key, &edit);
mutex_unlock(&key_construction_mutex);
if (dest_keyring)
@@ -406,7 +406,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx,
if (dest_keyring) {
ret = __key_link_check_live_key(dest_keyring, key);
if (ret == 0)
- __key_link(key, &edit);
+ __key_link(dest_keyring, key, &edit);
__key_link_end(dest_keyring, &ctx->index_key, edit);
if (ret < 0)
goto link_check_failed;
^ permalink raw reply related
* [PATCH 1/7] General notification queue with user mmap()'able ring buffer [ver #13]
From: David Howells @ 2019-05-28 15:10 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
In-Reply-To: <155905621951.1304.5956310120238620025.stgit@warthog.procyon.org.uk>
Implement a misc device that implements a general notification queue as a
ring buffer that can be mmap()'d from userspace.
The way this is done is:
(1) An application opens the device and indicates the size of the ring
buffer that it wants to reserve in pages (this can only be set once):
fd = open("/dev/watch_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_NR_PAGES, nr_of_pages);
(2) The application should then map the pages that the device has
reserved. Each instance of the device created by open() allocates
separate pages so that maps of different fds don't interfere with one
another. Multiple mmap() calls on the same fd, however, will all work
together.
page_size = sysconf(_SC_PAGESIZE);
mapping_size = nr_of_pages * page_size;
char *buf = mmap(NULL, mapping_size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
The ring is divided into 8-byte slots. Entries written into the ring are
variable size and can use between 1 and 63 slots. A special entry is
maintained in the first two slots of the ring that contains the head and
tail pointers. This is skipped when the ring wraps round. Note that
multislot entries, therefore, aren't allowed to be broken over the end of
the ring, but instead "skip" entries are inserted to pad out the buffer.
Each entry has a 1-slot header that describes it:
struct watch_notification {
__u32 type:24;
__u32 subtype:8;
__u32 info;
};
The type indicates the source (eg. mount tree changes, superblock events,
keyring changes, block layer events) and the subtype indicates the event
type (eg. mount, unmount; EIO, EDQUOT; link, unlink). The info field
indicates a number of things, including the entry length, an ID assigned to
a watchpoint contributing to this buffer, type-specific flags and meta
flags, such as an overrun indicator.
Supplementary data, such as the key ID that generated an event, are
attached in additional slots.
Signed-off-by: David Howells <dhowells@redhat.com>
---
Documentation/watch_queue.rst | 311 +++++++++++++
drivers/misc/Kconfig | 13 +
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 877 ++++++++++++++++++++++++++++++++++++++
include/linux/lsm_hooks.h | 15 +
include/linux/security.h | 14 +
include/linux/watch_queue.h | 86 ++++
include/uapi/linux/watch_queue.h | 82 ++++
mm/interval_tree.c | 2
mm/memory.c | 1
security/security.c | 9
11 files changed, 1411 insertions(+)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 drivers/misc/watch_queue.c
create mode 100644 include/linux/watch_queue.h
create mode 100644 include/uapi/linux/watch_queue.h
diff --git a/Documentation/watch_queue.rst b/Documentation/watch_queue.rst
new file mode 100644
index 000000000000..01fe937092d6
--- /dev/null
+++ b/Documentation/watch_queue.rst
@@ -0,0 +1,311 @@
+============================
+Mappable notifications queue
+============================
+
+This is a misc device that acts as a mapped ring buffer by which userspace can
+receive notifications from the kernel. This is can be used in conjunction
+with::
+
+ * Key/keyring notifications
+
+ * Mount topology change notifications
+
+ * Superblock event notifications
+
+
+The notifications buffers can be enabled by:
+
+ "Device Drivers"/"Misc devices"/"Mappable notification queue"
+ (CONFIG_WATCH_QUEUE)
+
+This document has the following sections:
+
+.. contents:: :local:
+
+
+Overview
+========
+
+This facility appears as a misc device file that is opened and then mapped and
+polled. Each time it is opened, it creates a new buffer specific to the
+returned file descriptor. Then, when the opening process sets watches, it
+indicates that particular buffer it wants notifications from that watch to be
+written into. Note that there are no read() and write() methods (except for
+debugging). The user is expected to access the ring directly and to use poll
+to wait for new data.
+
+If a watch is in place, notifications are only written into the buffer if the
+filter criteria are passed and if there's sufficient space available in the
+ring. If neither of those is so, a notification will be discarded. In the
+latter case, an overrun indicator will also be set.
+
+Note that when producing a notification, the kernel does not wait for the
+consumers to collect it, but rather just continues on. This means that
+notifications can be generated whilst spinlocks are held and also protects the
+kernel from being held up indefinitely by a userspace malfunction.
+
+As far as the ring goes, the head index belongs to the kernel and the tail
+index belongs to userspace. The kernel will refuse to write anything if the
+tail index becomes invalid. Userspace *must* use appropriate memory barriers
+between reading or updating the tail index and reading the ring.
+
+
+Record Structure
+================
+
+Notification records in the ring may occupy a variable number of slots within
+the buffer, beginning with a 1-slot header::
+
+ struct watch_notification {
+ __u16 type;
+ __u16 subtype;
+ __u32 info;
+ };
+
+"type" indicates the source of the notification record and "subtype" indicates
+the type of record from that source (see the Watch Sources section below). The
+type may also be "WATCH_TYPE_META". This is a special record type generated
+internally by the watch queue driver itself. There are two subtypes, one of
+which indicates records that should be just skipped (padding or metadata):
+
+ * WATCH_META_SKIP_NOTIFICATION
+ * WATCH_META_REMOVAL_NOTIFICATION
+
+The former indicates a record that should just be skipped and the latter
+indicates that an object on which a watchpoint was installed was removed or
+destroyed.
+
+"info" indicates a bunch of things, including:
+
+ * The length of the record (mask with WATCH_INFO_LENGTH). This indicates the
+ size of the record, which may be between 1 and 63 slots. Note that this is
+ placed appropriately within the info value so that no shifting is required
+ to convert number of occupied slots to byte length.
+
+ * The watchpoint ID (mask with WATCH_INFO_ID). This indicates that caller's
+ ID of the watchpoint, which may be between 0 and 255. Multiple watchpoints
+ may share a queue, and this provides a means to distinguish them.
+
+ * A buffer overrun flag (WATCH_INFO_OVERRUN flag). If this is set in a
+ notification record, some of the preceding records were discarded.
+
+ * An ENOMEM-loss flag (WATCH_INFO_ENOMEM flag). This is set to indicate that
+ an event was lost to ENOMEM.
+
+ * A recursive-change flag (WATCH_INFO_RECURSIVE flag). This is set to
+ indicate that the change that happened was recursive - for instance
+ changing the attributes on an entire mount subtree.
+
+ * An exact-match flag (WATCH_INFO_IN_SUBTREE flag). This is set if the event
+ didn't happen exactly at the watchpoint, but rather somewhere in the
+ subtree thereunder.
+
+ * Some type-specific flags (WATCH_INFO_TYPE_FLAGS). These are set by the
+ notification producer to indicate some meaning to the kernel.
+
+Everything in info apart from the length can be used for filtering.
+
+
+Ring Structure
+==============
+
+The ring is divided into 8-byte slots. The caller uses an ioctl() to set the
+size of the ring after opening and this must be a power-of-2 multiple of the
+system page size (so that the mask can be used with AND).
+
+The head and tail indices are stored in the first two slots in the ring, which
+are marked out as a skippable entry::
+
+ struct watch_queue_buffer {
+ union {
+ struct {
+ struct watch_notification watch;
+ volatile __u32 head;
+ volatile __u32 tail;
+ __u32 mask;
+ } meta;
+ struct watch_notification slots[0];
+ };
+ };
+
+In "meta.watch", type will be set to WATCH_TYPE_META and subtype to
+WATCH_META_SKIP_NOTIFICATION so that anyone processing the buffer will just
+skip this record. Also, because this record is here, records cannot wrap round
+the end of the buffer, so a skippable padding element will be inserted at the
+end of the buffer if needed. Thus the contents of a notification record in the
+buffer are always contiguous.
+
+"meta.mask" is an AND'able mask to turn the index counters into slots array
+indices.
+
+The buffer is empty if "meta.head" == "meta.tail".
+
+[!] NOTE that the ring indices "meta.head" and "meta.tail" are indices into
+"slots[]" not byte offsets into the buffer.
+
+[!] NOTE that userspace must never change the head pointer. This belongs to
+the kernel and will be updated by that. The kernel will never change the tail
+pointer.
+
+[!] NOTE that userspace must never AND-off the tail pointer before updating it,
+but should just keep adding to it and letting it wrap naturally. The value
+*should* be masked off when used as an index into slots[].
+
+[!] NOTE that if the distance between head and tail becomes too great, the
+kernel will assume the buffer is full and write no more until the issue is
+resolved.
+
+
+Watch Sources
+=============
+
+Any particular buffer can be fed from multiple sources. Sources include:
+
+ * WATCH_TYPE_MOUNT_NOTIFY
+
+ Notifications of this type indicate mount tree topology changes and mount
+ attribute changes. A watchpoint can be set on a particular file or
+ directory and notifications from the path subtree rooted at that point will
+ be intercepted.
+
+ * WATCH_TYPE_SB_NOTIFY
+
+ Notifications of this type indicate superblock events, such as quota limits
+ being hit, I/O errors being produced or network server loss/reconnection.
+ Watchpoints of this type are set directly on superblocks.
+
+ * WATCH_TYPE_KEY_NOTIFY
+
+ Notifications of this type indicate changes to keys and keyrings, including
+ the changes of keyring contents or the attributes of keys.
+
+ See Documentation/security/keys/core.rst for more information.
+
+ * WATCH_TYPE_BLOCK_NOTIFY
+
+ Notifications of this type indicate block layer events, such as I/O errors
+ or temporary link loss. Watchpoints of this type are set on a global
+ queue.
+
+
+Configuring Watchpoints
+=======================
+
+When a watchpoint is set up, the caller assigns an ID and can set filtering
+parameters. The following structure is filled out and passed to the
+watchpoint creation system call::
+
+ struct watch_notification_filter {
+ __u64 subtype_filter[4];
+ __u32 info_filter;
+ __u32 info_mask;
+ __u32 info_id;
+ __u32 __reserved;
+ };
+
+"subtype_filter" is a bitmask indicating the subtypes that are of interest. In
+this version of the structure, only the first 256 subtypes are supported. Bit
+0 of subtype_filter[0] corresponds to subtype 0, bit 1 to subtype 1, and so on.
+
+"info_filter" and "info_mask" act as a filter on the info field of the
+notification record. The notification is only written into the buffer if::
+
+ (watch.info & info_mask) == info_filter
+
+This can be used, for example, to ignore events that are not exactly on the
+watched point in a mount tree by specifying WATCH_INFO_IN_SUBTREE must be 0.
+
+"info_id" is OR'd into watch.info. This indicates the watchpoint ID in the top
+8 bits. All bits outside of WATCH_INFO_ID must be 0.
+
+"__reserved" must be 0.
+
+If the pointer to this structure is NULL, this indicates to the system call
+that the watchpoint should be removed.
+
+
+Polling
+=======
+
+The file descriptor that holds the buffer may be used with poll() and similar.
+POLLIN and POLLRDNORM are set if the buffer indices differ. POLLERR is set if
+the buffer indices are further apart than the size of the buffer. Wake-up
+events are only generated if the buffer is transitioned from an empty state.
+
+
+Example
+=======
+
+A buffer is created with something like the following::
+
+ fd = open("/dev/watch_queue", O_RDWR);
+
+ #define BUF_SIZE 4
+ ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
+
+ page_size = sysconf(_SC_PAGESIZE);
+ buf = mmap(NULL, BUF_SIZE * page_size,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+It can then be set to receive mount topology change notifications, keyring
+change notifications and superblock notifications::
+
+ memset(&filter, 0, sizeof(filter));
+ filter.subtype_filter[0] = ~0ULL;
+ filter.info_mask = WATCH_INFO_IN_SUBTREE;
+ filter.info_filter = 0;
+ filter.info_id = 0x01000000;
+
+ keyctl(KEYCTL_WATCH_KEY, KEY_SPEC_SESSION_KEYRING, fd, &filter);
+
+ mount_notify(AT_FDCWD, "/", 0, fd, &filter);
+
+ sb_notify(AT_FDCWD, "/", 0, fd, &filter);
+
+The notifications can then be consumed by something like the following::
+
+ extern void saw_mount_change(struct watch_notification *n);
+ extern void saw_key_change(struct watch_notification *n);
+
+ static int consumer(int fd, struct watch_queue_buffer *buf)
+ {
+ struct watch_notification *n;
+ struct pollfd p[1];
+ unsigned int head, tail, mask = buf->meta.mask;
+
+ for (;;) {
+ p[0].fd = fd;
+ p[0].events = POLLIN | POLLERR;
+ p[0].revents = 0;
+
+ if (poll(p, 1, -1) == -1 || p[0].revents & POLLERR)
+ goto went_wrong;
+
+ while (head = _atomic_load_acquire(buf->meta.head),
+ tail = buf->meta.tail,
+ tail != head
+ ) {
+ n = &buf->slots[tail & mask];
+ if ((n->info & WATCH_INFO_LENGTH) == 0)
+ goto went_wrong;
+
+ switch (n->type) {
+ case WATCH_TYPE_MOUNT_NOTIFY:
+ saw_mount_change(n);
+ break;
+ case WATCH_TYPE_KEY_NOTIFY:
+ saw_key_change(n);
+ break;
+ }
+
+ tail += (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+ _atomic_store_release(buf->meta.tail, tail);
+ }
+ }
+
+ went_wrong:
+ return 0;
+ }
+
+Note the memory barriers when loading the head pointer and storing the tail
+pointer!
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 6a0365b2332c..19668c0ebe03 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -4,6 +4,19 @@
menu "Misc devices"
+config WATCH_QUEUE
+ bool "Mappable notification queue"
+ default n
+ depends on MMU
+ help
+ This is a general notification queue for the kernel to pass events to
+ userspace through a mmap()'able ring buffer. It can be used in
+ conjunction with watches for mount topology change notifications,
+ superblock change notifications and key/keyring change notifications.
+
+ Note that in theory this should work fine with NOMMU, but I'm not
+ sure how to make that work.
+
config SENSORS_LIS3LV02D
tristate
depends on INPUT
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b9affcdaa3d6..bf16acd9f8cc 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -3,6 +3,7 @@
# Makefile for misc devices that really don't fit anywhere else.
#
+obj-$(CONFIG_WATCH_QUEUE) += watch_queue.o
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_IBMVMC) += ibmvmc.o
obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o
diff --git a/drivers/misc/watch_queue.c b/drivers/misc/watch_queue.c
new file mode 100644
index 000000000000..39a09ea15d97
--- /dev/null
+++ b/drivers/misc/watch_queue.c
@@ -0,0 +1,877 @@
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#define pr_fmt(fmt) "watchq: " fmt
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/printk.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/poll.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/file.h>
+#include <linux/security.h>
+#include <linux/cred.h>
+#include <linux/watch_queue.h>
+
+#define DEBUG_WITH_WRITE /* Allow use of write() to record notifications */
+
+MODULE_DESCRIPTION("Watch queue");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+struct watch_type_filter {
+ enum watch_notification_type type;
+ __u32 subtype_filter[1]; /* Bitmask of subtypes to filter on */
+ __u32 info_filter; /* Filter on watch_notification::info */
+ __u32 info_mask; /* Mask of relevant bits in info_filter */
+};
+
+struct watch_filter {
+ union {
+ struct rcu_head rcu;
+ unsigned long type_filter[2]; /* Bitmask of accepted types */
+ };
+ u32 nr_filters; /* Number of filters */
+ struct watch_type_filter filters[];
+};
+
+struct watch_queue {
+ struct rcu_head rcu;
+ struct address_space mapping;
+ const struct cred *cred; /* Creds of the owner of the queue */
+ struct watch_filter __rcu *filter;
+ wait_queue_head_t waiters;
+ struct hlist_head watches; /* Contributory watches */
+ refcount_t usage;
+ spinlock_t lock;
+ bool defunct; /* T when queues closed */
+ u8 nr_pages; /* Size of pages[] */
+ u8 flag_next; /* Flag to apply to next item */
+#ifdef DEBUG_WITH_WRITE
+ u8 debug;
+#endif
+ u32 size;
+ struct watch_queue_buffer *buffer; /* Pointer to first record */
+
+ /* The mappable pages. The zeroth page holds the ring pointers. */
+ struct page **pages;
+};
+
+/**
+ * post_one_notification - Post an event notification to one queue
+ * @wqueue: The watch queue to add the event to.
+ * @n: The notification record to post.
+ * @cred: The credentials to use in security checks.
+ *
+ * Post a notification of an event into an mmap'd queue and let the user know.
+ * Returns true if successful and false on failure (eg. buffer overrun or
+ * userspace mucked up the ring indices).
+ *
+ *
+ * The size of the notification should be set in n->flags & WATCH_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+static bool post_one_notification(struct watch_queue *wqueue,
+ struct watch_notification *n,
+ const struct cred *cred)
+{
+ struct watch_queue_buffer *buf = wqueue->buffer;
+ unsigned int metalen = sizeof(buf->meta) / sizeof(buf->slots[0]);
+ unsigned int size = wqueue->size, mask = size - 1;
+ unsigned int len;
+ unsigned int ring_tail, tail, head, used, segment, h;
+
+ if (!buf)
+ return false;
+
+ len = (n->info & WATCH_INFO_LENGTH) >> WATCH_LENGTH_SHIFT;
+ if (len == 0)
+ return false;
+
+ spin_lock_bh(&wqueue->lock); /* Protect head pointer */
+
+ if (wqueue->defunct ||
+ security_post_notification(wqueue->cred, cred, n) < 0)
+ goto out;
+
+ ring_tail = READ_ONCE(buf->meta.tail);
+ head = READ_ONCE(buf->meta.head);
+ used = head - ring_tail;
+
+ /* Check to see if userspace mucked up the pointers */
+ if (used >= size)
+ goto overrun;
+ tail = ring_tail & mask;
+ if (tail > 0 && tail < metalen)
+ goto overrun;
+
+ h = head & mask;
+ if (h >= tail) {
+ /* Head is at or after tail in the buffer. There may then be
+ * two segments: one to the end of buffer and one at the
+ * beginning of the buffer between the metadata block and the
+ * tail pointer.
+ */
+ segment = size - h;
+ if (len > segment) {
+ /* Not enough space in the post-head segment; we need
+ * to wrap. When wrapping, we will have to skip the
+ * metadata at the beginning of the buffer.
+ */
+ if (len > tail - metalen)
+ goto overrun;
+
+ /* Fill the space at the end of the page */
+ buf->slots[h].type = WATCH_TYPE_META;
+ buf->slots[h].subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->slots[h].info = segment << WATCH_LENGTH_SHIFT;
+ head += segment;
+ h = 0;
+ if (h >= tail)
+ goto overrun;
+ }
+ }
+
+ if (h == 0) {
+ /* Reset and skip the header metadata */
+ buf->meta.watch.type = WATCH_TYPE_META;
+ buf->meta.watch.subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->meta.watch.info = metalen << WATCH_LENGTH_SHIFT;
+ head += metalen;
+ h = metalen;
+ if (h >= tail)
+ goto overrun;
+ }
+
+ if (h < tail) {
+ /* Head is before tail in the buffer. There may be one segment
+ * between the two, but we may need to skip the metadata block.
+ */
+ segment = tail - h;
+ if (len > segment)
+ goto overrun;
+ }
+
+ n->info |= wqueue->flag_next;
+ wqueue->flag_next = 0;
+ memcpy(buf->slots + h, n, len * sizeof(buf->slots[0]));
+ head += len;
+
+ smp_store_release(&buf->meta.head, head);
+ spin_unlock_bh(&wqueue->lock);
+ if (used == 0)
+ wake_up(&wqueue->waiters);
+ return true;
+
+overrun:
+ wqueue->flag_next = WATCH_INFO_OVERRUN;
+out:
+ spin_unlock_bh(&wqueue->lock);
+ return false;
+}
+
+/*
+ * Apply filter rules to a notification.
+ */
+static bool filter_watch_notification(const struct watch_filter *wf,
+ const struct watch_notification *n)
+{
+ const struct watch_type_filter *wt;
+ int i;
+
+ if (!test_bit(n->type, wf->type_filter))
+ return false;
+
+ for (i = 0; i < wf->nr_filters; i++) {
+ wt = &wf->filters[i];
+ if (n->type == wt->type &&
+ ((1U << n->subtype) & wt->subtype_filter[0]) &&
+ (n->info & wt->info_mask) == wt->info_filter)
+ return true;
+ }
+
+ return false; /* If there is a filter, the default is to reject. */
+}
+
+/**
+ * __post_watch_notification - Post an event notification
+ * @wlist: The watch list to post the event to.
+ * @n: The notification record to post.
+ * @cred: The creds of the process that triggered the notification.
+ * @id: The ID to match on the watch.
+ *
+ * Post a notification of an event into a set of watch queues and let the users
+ * know.
+ *
+ * If @n is NULL then WATCH_INFO_LENGTH will be set on the next event posted.
+ *
+ * The size of the notification should be set in n->info & WATCH_INFO_LENGTH and
+ * should be in units of sizeof(*n).
+ */
+void __post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id)
+{
+ const struct watch_filter *wf;
+ struct watch_queue *wqueue;
+ struct watch *watch;
+
+ rcu_read_lock();
+
+ hlist_for_each_entry_rcu(watch, &wlist->watchers, list_node) {
+ if (watch->id != id)
+ continue;
+ n->info &= ~(WATCH_INFO_ID | WATCH_INFO_OVERRUN);
+ n->info |= watch->info_id;
+
+ wqueue = rcu_dereference(watch->queue);
+ wf = rcu_dereference(wqueue->filter);
+ if (wf && !filter_watch_notification(wf, n))
+ continue;
+
+ post_one_notification(wqueue, n, cred);
+ }
+
+ rcu_read_unlock();
+}
+EXPORT_SYMBOL(__post_watch_notification);
+
+/*
+ * Allow the queue to be polled.
+ */
+static __poll_t watch_queue_poll(struct file *file, poll_table *wait)
+{
+ struct watch_queue *wqueue = file->private_data;
+ struct watch_queue_buffer *buf = wqueue->buffer;
+ unsigned int head, tail;
+ __poll_t mask = 0;
+
+ poll_wait(file, &wqueue->waiters, wait);
+
+ head = READ_ONCE(buf->meta.head);
+ tail = READ_ONCE(buf->meta.tail);
+ if (head != tail)
+ mask |= EPOLLIN | EPOLLRDNORM;
+ if (head - tail > wqueue->size)
+ mask |= EPOLLERR;
+ return mask;
+}
+
+static int watch_queue_set_page_dirty(struct page *page)
+{
+ SetPageDirty(page);
+ return 0;
+}
+
+static const struct address_space_operations watch_queue_aops = {
+ .set_page_dirty = watch_queue_set_page_dirty,
+};
+
+static vm_fault_t watch_queue_fault(struct vm_fault *vmf)
+{
+ struct watch_queue *wqueue = vmf->vma->vm_file->private_data;
+ struct page *page;
+
+ page = wqueue->pages[vmf->pgoff];
+ get_page(page);
+ if (!lock_page_or_retry(page, vmf->vma->vm_mm, vmf->flags)) {
+ put_page(page);
+ return VM_FAULT_RETRY;
+ }
+ vmf->page = page;
+ return VM_FAULT_LOCKED;
+}
+
+static void watch_queue_map_pages(struct vm_fault *vmf,
+ pgoff_t start_pgoff, pgoff_t end_pgoff)
+{
+ struct watch_queue *wqueue = vmf->vma->vm_file->private_data;
+ struct page *page;
+
+ rcu_read_lock();
+
+ do {
+ page = wqueue->pages[start_pgoff];
+ if (trylock_page(page)) {
+ vm_fault_t ret;
+ get_page(page);
+ ret = alloc_set_pte(vmf, NULL, page);
+ if (ret != 0)
+ put_page(page);
+
+ unlock_page(page);
+ }
+ } while (++start_pgoff < end_pgoff);
+
+ rcu_read_unlock();
+}
+
+static const struct vm_operations_struct watch_queue_vm_ops = {
+ .fault = watch_queue_fault,
+ .map_pages = watch_queue_map_pages,
+};
+
+/*
+ * Map the buffer.
+ */
+static int watch_queue_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct watch_queue *wqueue = file->private_data;
+
+ if (vma->vm_pgoff != 0 ||
+ vma->vm_end - vma->vm_start > wqueue->nr_pages * PAGE_SIZE ||
+ !(pgprot_val(vma->vm_page_prot) & pgprot_val(PAGE_SHARED)))
+ return -EINVAL;
+
+ vma->vm_ops = &watch_queue_vm_ops;
+
+ vma_interval_tree_insert(vma, &wqueue->mapping.i_mmap);
+ return 0;
+}
+
+/*
+ * Allocate the required number of pages.
+ */
+static long watch_queue_set_size(struct watch_queue *wqueue, unsigned long nr_pages)
+{
+ struct watch_queue_buffer *buf;
+ u32 len;
+ int i;
+
+ if (nr_pages == 0 ||
+ nr_pages > 16 || /* TODO: choose a better hard limit */
+ !is_power_of_2(nr_pages))
+ return -EINVAL;
+
+ wqueue->pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL);
+ if (!wqueue->pages)
+ goto err;
+
+ for (i = 0; i < nr_pages; i++) {
+ wqueue->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!wqueue->pages[i])
+ goto err_some_pages;
+ wqueue->pages[i]->mapping = &wqueue->mapping;
+ SetPageUptodate(wqueue->pages[i]);
+ }
+
+ buf = vmap(wqueue->pages, nr_pages, VM_MAP, PAGE_SHARED);
+ if (!buf)
+ goto err_some_pages;
+
+ wqueue->buffer = buf;
+ wqueue->nr_pages = nr_pages;
+ wqueue->size = ((nr_pages * PAGE_SIZE) / sizeof(struct watch_notification));
+
+ /* The first four slots in the buffer contain metadata about the ring,
+ * including the head and tail indices and mask.
+ */
+ len = sizeof(buf->meta) / sizeof(buf->slots[0]);
+ buf->meta.watch.info = len << WATCH_LENGTH_SHIFT;
+ buf->meta.watch.type = WATCH_TYPE_META;
+ buf->meta.watch.subtype = WATCH_META_SKIP_NOTIFICATION;
+ buf->meta.tail = len;
+ buf->meta.mask = wqueue->size - 1;
+ smp_store_release(&buf->meta.head, len);
+ return 0;
+
+err_some_pages:
+ for (i--; i >= 0; i--) {
+ ClearPageUptodate(wqueue->pages[i]);
+ wqueue->pages[i]->mapping = NULL;
+ put_page(wqueue->pages[i]);
+ }
+
+ kfree(wqueue->pages);
+ wqueue->pages = NULL;
+err:
+ return -ENOMEM;
+}
+
+/*
+ * Set the filter on a watch queue.
+ */
+static long watch_queue_set_filter(struct inode *inode,
+ struct watch_queue *wqueue,
+ struct watch_notification_filter __user *_filter)
+{
+ struct watch_notification_type_filter *tf;
+ struct watch_notification_filter filter;
+ struct watch_type_filter *q;
+ struct watch_filter *wfilter;
+ int ret, nr_filter = 0, i;
+
+ if (!_filter) {
+ /* Remove the old filter */
+ wfilter = NULL;
+ goto set;
+ }
+
+ /* Grab the user's filter specification */
+ if (copy_from_user(&filter, _filter, sizeof(filter)) != 0)
+ return -EFAULT;
+ if (filter.nr_filters == 0 ||
+ filter.nr_filters > 16 ||
+ filter.__reserved != 0)
+ return -EINVAL;
+
+ tf = memdup_user(_filter->filters, filter.nr_filters * sizeof(*tf));
+ if (IS_ERR(tf))
+ return PTR_ERR(tf);
+
+ ret = -EINVAL;
+ for (i = 0; i < filter.nr_filters; i++) {
+ if ((tf[i].info_filter & ~tf[i].info_mask) ||
+ tf[i].info_mask & WATCH_INFO_LENGTH)
+ goto err_filter;
+ /* Ignore any unknown types */
+ if (tf[i].type >= sizeof(wfilter->type_filter) * 8)
+ continue;
+ nr_filter++;
+ }
+
+ /* Now we need to build the internal filter from only the relevant
+ * user-specified filters.
+ */
+ ret = -ENOMEM;
+ wfilter = kzalloc(struct_size(wfilter, filters, nr_filter), GFP_KERNEL);
+ if (!wfilter)
+ goto err_filter;
+ wfilter->nr_filters = nr_filter;
+
+ q = wfilter->filters;
+ for (i = 0; i < filter.nr_filters; i++) {
+ if (tf[i].type >= sizeof(wfilter->type_filter) * BITS_PER_LONG)
+ continue;
+
+ q->type = tf[i].type;
+ q->info_filter = tf[i].info_filter;
+ q->info_mask = tf[i].info_mask;
+ q->subtype_filter[0] = tf[i].subtype_filter[0];
+ __set_bit(q->type, wfilter->type_filter);
+ q++;
+ }
+
+ kfree(tf);
+set:
+ rcu_swap_protected(wqueue->filter, wfilter,
+ lockdep_is_held(&inode->i_rwsem));
+ if (wfilter)
+ kfree_rcu(wfilter, rcu);
+ return 0;
+
+err_filter:
+ kfree(tf);
+ return ret;
+}
+
+/*
+ * Set parameters.
+ */
+static long watch_queue_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct watch_queue *wqueue = file->private_data;
+ struct inode *inode = file_inode(file);
+ long ret;
+
+ switch (cmd) {
+ case IOC_WATCH_QUEUE_SET_SIZE:
+ if (wqueue->buffer)
+ return -EBUSY;
+ inode_lock(inode);
+ ret = watch_queue_set_size(wqueue, arg);
+ inode_unlock(inode);
+ return ret;
+
+ case IOC_WATCH_QUEUE_SET_FILTER:
+ inode_lock(inode);
+ ret = watch_queue_set_filter(
+ inode, wqueue,
+ (struct watch_notification_filter __user *)arg);
+ inode_unlock(inode);
+ return ret;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+/*
+ * Open the file.
+ */
+static int watch_queue_open(struct inode *inode, struct file *file)
+{
+ struct watch_queue *wqueue;
+
+ wqueue = kzalloc(sizeof(*wqueue), GFP_KERNEL);
+ if (!wqueue)
+ return -ENOMEM;
+
+ wqueue->mapping.a_ops = &watch_queue_aops;
+ wqueue->mapping.i_mmap = RB_ROOT_CACHED;
+ init_rwsem(&wqueue->mapping.i_mmap_rwsem);
+ spin_lock_init(&wqueue->mapping.private_lock);
+
+ refcount_set(&wqueue->usage, 1);
+ spin_lock_init(&wqueue->lock);
+ init_waitqueue_head(&wqueue->waiters);
+ wqueue->cred = get_cred(file->f_cred);
+
+ file->private_data = wqueue;
+ return 0;
+}
+
+/**
+ * put_watch_queue - Dispose of a ref on a watchqueue.
+ * @wqueue: The watch queue to unref.
+ */
+void put_watch_queue(struct watch_queue *wqueue)
+{
+ if (refcount_dec_and_test(&wqueue->usage))
+ kfree_rcu(wqueue, rcu);
+}
+EXPORT_SYMBOL(put_watch_queue);
+
+static void free_watch(struct rcu_head *rcu)
+{
+ struct watch *watch = container_of(rcu, struct watch, rcu);
+
+ put_watch_queue(rcu_access_pointer(watch->queue));
+}
+
+/*
+ * Discard a watch.
+ */
+static void put_watch(struct watch *watch)
+{
+ if (refcount_dec_and_test(&watch->usage))
+ call_rcu(&watch->rcu, free_watch);
+}
+
+/**
+ * init_watch_queue - Initialise a watch
+ * @watch: The watch to initialise.
+ * @wqueue: The queue to assign.
+ *
+ * Initialise a watch and set the watch queue.
+ */
+void init_watch(struct watch *watch, struct watch_queue *wqueue)
+{
+ refcount_set(&watch->usage, 1);
+ INIT_HLIST_NODE(&watch->list_node);
+ INIT_HLIST_NODE(&watch->queue_node);
+ rcu_assign_pointer(watch->queue, wqueue);
+}
+
+/**
+ * add_watch_to_object - Add a watch on an object to a watch list
+ * @watch: The watch to add
+ * @wlist: The watch list to add to
+ *
+ * @watch->queue must have been set to point to the queue to post notifications
+ * to and the watch list of the object to be watched.
+ *
+ * The caller must pin the queue and the list both and must hold the list
+ * locked against racing watch additions/removals.
+ */
+int add_watch_to_object(struct watch *watch, struct watch_list *wlist)
+{
+ struct watch_queue *wqueue = rcu_access_pointer(watch->queue);
+ struct watch *w;
+
+ hlist_for_each_entry(w, &wlist->watchers, list_node) {
+ if (watch->id == w->id)
+ return -EBUSY;
+ }
+
+ rcu_assign_pointer(watch->watch_list, wlist);
+
+ spin_lock_bh(&wqueue->lock);
+ refcount_inc(&wqueue->usage);
+ hlist_add_head(&watch->queue_node, &wqueue->watches);
+ spin_unlock_bh(&wqueue->lock);
+
+ hlist_add_head(&watch->list_node, &wlist->watchers);
+ return 0;
+}
+EXPORT_SYMBOL(add_watch_to_object);
+
+/**
+ * remove_watch_from_object - Remove a watch or all watches from an object.
+ * @wlist: The watch list to remove from
+ * @wq: The watch queue of interest (ignored if @all is true)
+ * @id: The ID of the watch to remove (ignored if @all is true)
+ * @all: True to remove all objects
+ *
+ * Remove a specific watch or all watches from an object. A notification is
+ * sent to the watcher to tell them that this happened.
+ */
+int remove_watch_from_object(struct watch_list *wlist, struct watch_queue *wq,
+ u64 id, bool all)
+{
+ struct watch_notification n;
+ struct watch_queue *wqueue;
+ struct watch *watch;
+ int ret = -EBADSLT;
+
+ rcu_read_lock();
+
+again:
+ spin_lock(&wlist->lock);
+ hlist_for_each_entry(watch, &wlist->watchers, list_node) {
+ if (all ||
+ (watch->id == id && rcu_access_pointer(watch->queue) == wq))
+ goto found;
+ }
+ spin_unlock(&wlist->lock);
+ goto out;
+
+found:
+ ret = 0;
+ hlist_del_init_rcu(&watch->list_node);
+ rcu_assign_pointer(watch->watch_list, NULL);
+ spin_unlock(&wlist->lock);
+
+ n.type = WATCH_TYPE_META;
+ n.subtype = WATCH_META_REMOVAL_NOTIFICATION;
+ n.info = watch->info_id | sizeof(n);
+
+ wqueue = rcu_dereference(watch->queue);
+ post_one_notification(wqueue, &n, wq ? wq->cred : NULL);
+
+ /* We don't need the watch list lock for the next bit as RCU is
+ * protecting everything from being deallocated.
+ */
+ if (wqueue) {
+ spin_lock_bh(&wqueue->lock);
+
+ if (!hlist_unhashed(&watch->queue_node)) {
+ hlist_del_init_rcu(&watch->queue_node);
+ put_watch(watch);
+ }
+
+ spin_unlock_bh(&wqueue->lock);
+ }
+
+ if (wlist->release_watch) {
+ rcu_read_unlock();
+ wlist->release_watch(wlist, watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+
+ if (all && !hlist_empty(&wlist->watchers))
+ goto again;
+out:
+ rcu_read_unlock();
+ return ret;
+}
+EXPORT_SYMBOL(remove_watch_from_object);
+
+/*
+ * Remove all the watches that are contributory to a queue. This will
+ * potentially race with removal of the watches by the destruction of the
+ * objects being watched or the distribution of notifications.
+ */
+static void watch_queue_clear(struct watch_queue *wqueue)
+{
+ struct watch_list *wlist;
+ struct watch *watch;
+ bool release;
+
+ rcu_read_lock();
+ spin_lock_bh(&wqueue->lock);
+
+ /* Prevent new additions and prevent notifications from happening */
+ wqueue->defunct = true;
+
+ while (!hlist_empty(&wqueue->watches)) {
+ watch = hlist_entry(wqueue->watches.first, struct watch, queue_node);
+ hlist_del_init_rcu(&watch->queue_node);
+ spin_unlock_bh(&wqueue->lock);
+
+ /* We can't do the next bit under the queue lock as we need to
+ * get the list lock - which would cause a deadlock if someone
+ * was removing from the opposite direction at the same time or
+ * posting a notification.
+ */
+ wlist = rcu_dereference(watch->watch_list);
+ if (wlist) {
+ spin_lock(&wlist->lock);
+
+ release = !hlist_unhashed(&watch->list_node);
+ if (release) {
+ hlist_del_init_rcu(&watch->list_node);
+ rcu_assign_pointer(watch->watch_list, NULL);
+ }
+
+ spin_unlock(&wlist->lock);
+
+ if (release) {
+ if (wlist->release_watch) {
+ rcu_read_unlock();
+ /* This might need to call dput(), so
+ * we have to drop all the locks.
+ */
+ wlist->release_watch(wlist, watch);
+ rcu_read_lock();
+ }
+ put_watch(watch);
+ }
+ }
+
+ put_watch(watch);
+ spin_lock_bh(&wqueue->lock);
+ }
+
+ spin_unlock_bh(&wqueue->lock);
+ rcu_read_unlock();
+}
+
+/*
+ * Release the file.
+ */
+static int watch_queue_release(struct inode *inode, struct file *file)
+{
+ struct watch_filter *wfilter;
+ struct watch_queue *wqueue = file->private_data;
+ int i, pgref;
+
+ watch_queue_clear(wqueue);
+
+ if (wqueue->pages && wqueue->pages[0])
+ WARN_ON(page_ref_count(wqueue->pages[0]) != 1);
+
+ if (wqueue->buffer)
+ vfree(wqueue->buffer);
+ for (i = 0; i < wqueue->nr_pages; i++) {
+ ClearPageUptodate(wqueue->pages[i]);
+ wqueue->pages[i]->mapping = NULL;
+ pgref = page_ref_count(wqueue->pages[i]);
+ WARN(pgref != 1,
+ "FREE PAGE[%d] refcount %d\n", i, page_ref_count(wqueue->pages[i]));
+ __free_page(wqueue->pages[i]);
+ }
+
+ wfilter = rcu_access_pointer(wqueue->filter);
+ if (wfilter)
+ kfree_rcu(wfilter, rcu);
+ kfree(wqueue->pages);
+ put_cred(wqueue->cred);
+ put_watch_queue(wqueue);
+ return 0;
+}
+
+#ifdef DEBUG_WITH_WRITE
+static ssize_t watch_queue_write(struct file *file,
+ const char __user *_buf, size_t len, loff_t *pos)
+{
+ struct watch_notification *n;
+ struct watch_queue *wqueue = file->private_data;
+ ssize_t ret;
+
+ if (!wqueue->buffer)
+ return -ENOBUFS;
+
+ if (len & ~WATCH_INFO_LENGTH || len == 0 || !_buf)
+ return -EINVAL;
+
+ n = memdup_user(_buf, len);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+
+ ret = -EINVAL;
+ if ((n->info & WATCH_INFO_LENGTH) != len)
+ goto error;
+ n->info &= (WATCH_INFO_LENGTH | WATCH_INFO_TYPE_FLAGS | WATCH_INFO_ID);
+
+ if (post_one_notification(wqueue, n, file->f_cred))
+ wqueue->debug = 0;
+ else
+ wqueue->debug++;
+ ret = len;
+ if (wqueue->debug > 20)
+ ret = -EIO;
+
+error:
+ kfree(n);
+ return ret;
+}
+#endif
+
+static const struct file_operations watch_queue_fops = {
+ .owner = THIS_MODULE,
+ .open = watch_queue_open,
+ .release = watch_queue_release,
+ .unlocked_ioctl = watch_queue_ioctl,
+ .poll = watch_queue_poll,
+ .mmap = watch_queue_mmap,
+#ifdef DEBUG_WITH_WRITE
+ .write = watch_queue_write,
+#endif
+ .llseek = no_llseek,
+};
+
+/**
+ * get_watch_queue - Get a watch queue from its file descriptor.
+ * @fd: The fd to query.
+ */
+struct watch_queue *get_watch_queue(int fd)
+{
+ struct watch_queue *wqueue = ERR_PTR(-EBADF);
+ struct fd f;
+
+ f = fdget(fd);
+ if (f.file) {
+ wqueue = ERR_PTR(-EINVAL);
+ if (f.file->f_op == &watch_queue_fops) {
+ wqueue = f.file->private_data;
+ refcount_inc(&wqueue->usage);
+ }
+ fdput(f);
+ }
+
+ return wqueue;
+}
+EXPORT_SYMBOL(get_watch_queue);
+
+static struct miscdevice watch_queue_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "watch_queue",
+ .fops = &watch_queue_fops,
+ .mode = 0666,
+};
+
+static int __init watch_queue_init(void)
+{
+ int ret;
+
+ ret = misc_register(&watch_queue_dev);
+ if (ret < 0)
+ pr_err("Failed to register %d\n", ret);
+ return ret;
+}
+fs_initcall(watch_queue_init);
+
+static void __exit watch_queue_exit(void)
+{
+ misc_deregister(&watch_queue_dev);
+}
+module_exit(watch_queue_exit);
diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 2474c3f785ca..2f72ea80d4fe 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1420,6 +1420,13 @@
* @ctx is a pointer in which to place the allocated security context.
* @ctxlen points to the place to put the length of @ctx.
*
+ * @post_notification:
+ * Check to see if a watch notification can be posted to a particular
+ * queue.
+ * @q_cred: The credentials of the target watch queue.
+ * @cred: The event-triggerer's credentials
+ * @n: The notification being posted
+ *
* Security hooks for using the eBPF maps and programs functionalities through
* eBPF syscalls.
*
@@ -1698,6 +1705,11 @@ union security_list_options {
int (*inode_notifysecctx)(struct inode *inode, void *ctx, u32 ctxlen);
int (*inode_setsecctx)(struct dentry *dentry, void *ctx, u32 ctxlen);
int (*inode_getsecctx)(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_WATCH_QUEUE
+ int (*post_notification)(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
+#endif
#ifdef CONFIG_SECURITY_NETWORK
int (*unix_stream_connect)(struct sock *sock, struct sock *other,
@@ -1977,6 +1989,9 @@ struct security_hook_heads {
struct hlist_head inode_notifysecctx;
struct hlist_head inode_setsecctx;
struct hlist_head inode_getsecctx;
+#ifdef CONFIG_WATCH_QUEUE
+ struct hlist_head post_notification;
+#endif
#ifdef CONFIG_SECURITY_NETWORK
struct hlist_head unix_stream_connect;
struct hlist_head unix_may_send;
diff --git a/include/linux/security.h b/include/linux/security.h
index 23c8b602c0ab..1df8d55de8da 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -58,6 +58,7 @@ struct fs_context;
struct fs_parameter;
enum fs_value_type;
struct fsinfo_kparams;
+struct watch_notification;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@@ -396,6 +397,11 @@ void security_inode_invalidate_secctx(struct inode *inode);
int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n);
+#endif
#else /* CONFIG_SECURITY */
static inline int call_lsm_notifier(enum lsm_event event, void *data)
@@ -1215,6 +1221,14 @@ static inline int security_inode_getsecctx(struct inode *inode, void **ctx, u32
{
return -EOPNOTSUPP;
}
+#ifdef CONFIG_WATCH_QUEUE
+static inline int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return 0;
+}
+#endif
#endif /* CONFIG_SECURITY */
#ifdef CONFIG_SECURITY_NETWORK
diff --git a/include/linux/watch_queue.h b/include/linux/watch_queue.h
new file mode 100644
index 000000000000..f200b68c799e
--- /dev/null
+++ b/include/linux/watch_queue.h
@@ -0,0 +1,86 @@
+/* User-mappable watch queue
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ *
+ * See Documentation/watch_queue.rst
+ */
+
+#ifndef _LINUX_WATCH_QUEUE_H
+#define _LINUX_WATCH_QUEUE_H
+
+#include <uapi/linux/watch_queue.h>
+
+#ifdef CONFIG_WATCH_QUEUE
+
+struct watch_queue;
+
+/*
+ * Representation of a watch on an object.
+ */
+struct watch {
+ union {
+ struct rcu_head rcu;
+ u32 info_id; /* ID to be OR'd in to info field */
+ };
+ struct watch_queue __rcu *queue; /* Queue to post events to */
+ struct hlist_node queue_node; /* Link in queue->watches */
+ struct watch_list __rcu *watch_list;
+ struct hlist_node list_node; /* Link in watch_list->watchers */
+ void *private; /* Private data for the watched object */
+ u64 id; /* Internal identifier */
+ refcount_t usage;
+};
+
+/*
+ * List of watches on an object.
+ */
+struct watch_list {
+ struct rcu_head rcu;
+ struct hlist_head watchers;
+ void (*release_watch)(struct watch_list *, struct watch *);
+ spinlock_t lock;
+};
+
+extern void __post_watch_notification(struct watch_list *,
+ struct watch_notification *,
+ const struct cred *,
+ u64);
+extern struct watch_queue *get_watch_queue(int);
+extern void put_watch_queue(struct watch_queue *);
+extern void put_watch_list(struct watch_list *);
+extern void init_watch(struct watch *, struct watch_queue *);
+extern int add_watch_to_object(struct watch *, struct watch_list *);
+extern int remove_watch_from_object(struct watch_list *, struct watch_queue *, u64, bool);
+
+static inline void init_watch_list(struct watch_list *wlist)
+{
+ INIT_HLIST_HEAD(&wlist->watchers);
+ spin_lock_init(&wlist->lock);
+}
+
+static inline void post_watch_notification(struct watch_list *wlist,
+ struct watch_notification *n,
+ const struct cred *cred,
+ u64 id)
+{
+ if (unlikely(wlist))
+ __post_watch_notification(wlist, n, cred, id);
+}
+
+static inline void remove_watch_list(struct watch_list *wlist)
+{
+ if (wlist) {
+ remove_watch_from_object(wlist, NULL, 0, true);
+ kfree_rcu(wlist, rcu);
+ }
+}
+
+#endif
+
+#endif /* _LINUX_WATCH_QUEUE_H */
diff --git a/include/uapi/linux/watch_queue.h b/include/uapi/linux/watch_queue.h
new file mode 100644
index 000000000000..01746982c2ba
--- /dev/null
+++ b/include/uapi/linux/watch_queue.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_WATCH_QUEUE_H
+#define _UAPI_LINUX_WATCH_QUEUE_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define IOC_WATCH_QUEUE_SET_SIZE _IO('s', 0x01) /* Set the size in pages */
+#define IOC_WATCH_QUEUE_SET_FILTER _IO('s', 0x02) /* Set the filter */
+
+enum watch_notification_type {
+ WATCH_TYPE_META = 0, /* Special record */
+ WATCH_TYPE_MOUNT_NOTIFY = 1, /* Mount notification record */
+ WATCH_TYPE_SB_NOTIFY = 2, /* Superblock notification */
+ WATCH_TYPE_KEY_NOTIFY = 3, /* Key/keyring change notification */
+ WATCH_TYPE_BLOCK_NOTIFY = 4, /* Block layer notifications */
+#define WATCH_TYPE___NR 5
+};
+
+enum watch_meta_notification_subtype {
+ WATCH_META_SKIP_NOTIFICATION = 0, /* Just skip this record */
+ WATCH_META_REMOVAL_NOTIFICATION = 1, /* Watched object was removed */
+};
+
+/*
+ * Notification record
+ */
+struct watch_notification {
+ __u32 type:24; /* enum watch_notification_type */
+ __u32 subtype:8; /* Type-specific subtype (filterable) */
+ __u32 info;
+#define WATCH_INFO_OVERRUN 0x00000001 /* Event(s) lost due to overrun */
+#define WATCH_INFO_ENOMEM 0x00000002 /* Event(s) lost due to ENOMEM */
+#define WATCH_INFO_RECURSIVE 0x00000004 /* Change was recursive */
+#define WATCH_INFO_LENGTH 0x000001f8 /* Length of record / sizeof(watch_notification) */
+#define WATCH_INFO_IN_SUBTREE 0x00000200 /* Change was not at watched root */
+#define WATCH_INFO_TYPE_FLAGS 0x00ff0000 /* Type-specific flags */
+#define WATCH_INFO_FLAG_0 0x00010000
+#define WATCH_INFO_FLAG_1 0x00020000
+#define WATCH_INFO_FLAG_2 0x00040000
+#define WATCH_INFO_FLAG_3 0x00080000
+#define WATCH_INFO_FLAG_4 0x00100000
+#define WATCH_INFO_FLAG_5 0x00200000
+#define WATCH_INFO_FLAG_6 0x00400000
+#define WATCH_INFO_FLAG_7 0x00800000
+#define WATCH_INFO_ID 0xff000000 /* ID of watchpoint */
+};
+
+#define WATCH_LENGTH_SHIFT 3
+
+struct watch_queue_buffer {
+ union {
+ /* The first few entries are special, containing the
+ * ring management variables.
+ */
+ struct {
+ struct watch_notification watch; /* WATCH_TYPE_SKIP */
+ volatile __u32 head; /* Ring head index */
+ volatile __u32 tail; /* Ring tail index */
+ __u32 mask; /* Ring index mask */
+ } meta;
+ struct watch_notification slots[0];
+ };
+};
+
+/*
+ * Notification filtering rules (IOC_WATCH_QUEUE_SET_FILTER).
+ */
+struct watch_notification_type_filter {
+ __u32 type; /* Type to apply filter to */
+ __u32 info_filter; /* Filter on watch_notification::info */
+ __u32 info_mask; /* Mask of relevant bits in info_filter */
+ __u32 subtype_filter[8]; /* Bitmask of subtypes to filter on */
+};
+
+struct watch_notification_filter {
+ __u32 nr_filters; /* Number of filters */
+ __u32 __reserved; /* Must be 0 */
+ struct watch_notification_type_filter filters[];
+};
+
+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
diff --git a/mm/interval_tree.c b/mm/interval_tree.c
index 27ddfd29112a..9a53ddf4bd62 100644
--- a/mm/interval_tree.c
+++ b/mm/interval_tree.c
@@ -25,6 +25,8 @@ INTERVAL_TREE_DEFINE(struct vm_area_struct, shared.rb,
unsigned long, shared.rb_subtree_last,
vma_start_pgoff, vma_last_pgoff,, vma_interval_tree)
+EXPORT_SYMBOL_GPL(vma_interval_tree_insert);
+
/* Insert node immediately after prev in the interval tree */
void vma_interval_tree_insert_after(struct vm_area_struct *node,
struct vm_area_struct *prev,
diff --git a/mm/memory.c b/mm/memory.c
index 96f1d473c89a..9f2fa2138287 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -3360,6 +3360,7 @@ vm_fault_t alloc_set_pte(struct vm_fault *vmf, struct mem_cgroup *memcg,
return 0;
}
+EXPORT_SYMBOL_GPL(alloc_set_pte);
/**
diff --git a/security/security.c b/security/security.c
index 3af886e8fced..af758dc71e24 100644
--- a/security/security.c
+++ b/security/security.c
@@ -1929,6 +1929,15 @@ int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen)
}
EXPORT_SYMBOL(security_inode_getsecctx);
+#ifdef CONFIG_WATCH_QUEUE
+int security_post_notification(const struct cred *q_cred,
+ const struct cred *cred,
+ struct watch_notification *n)
+{
+ return call_int_hook(post_notification, 0, q_cred, cred, n);
+}
+#endif
+
#ifdef CONFIG_SECURITY_NETWORK
int security_unix_stream_connect(struct sock *sock, struct sock *other, struct sock *newsk)
^ permalink raw reply related
* [PATCH 0/7] VFS: Introduce filesystem information query syscall [ver #13]
From: David Howells @ 2019-05-28 15:10 UTC (permalink / raw)
To: viro; +Cc: dhowells, raven, linux-api, linux-fsdevel, linux-kernel, mszeredi
Hi Al,
Here are a set of patches that adds a syscall, fsinfo(), that allows
attributes of a filesystem/superblock to be queried. Attribute values are
of four basic types:
(1) Version dependent-length structure (size defined by type).
(2) Variable-length string (up to PAGE_SIZE).
(3) Array of fixed-length structures (up to INT_MAX size).
(4) Opaque blob (up to INT_MAX size).
Attributes can have multiple values in up to two dimensions and all the
values of a particular attribute must have the same type.
Note that the attribute values *are* allowed to vary between dentries
within a single superblock, depending on the specific dentry that you're
looking at.
I've tried to make the interface as light as possible, so integer/enum
attribute selector rather than string and the core does all the allocation
and extensibility support work rather than leaving that to the filesystems.
That means that for the first two attribute types, sb->s_op->fsinfo() may
assume that the provided buffer is always present and always big enough.
Further, this removes the possibility of the filesystem gaining access to the
userspace buffer.
fsinfo() allows a variety of information to be retrieved about a filesystem
and the mount topology:
(1) General superblock attributes:
- The amount of space/free space in a filesystem (as statfs()).
- Filesystem identifiers (UUID, volume label, device numbers, ...)
- The limits on a filesystem's capabilities
- Information on supported statx fields and attributes and IOC flags.
- A variety single-bit flags indicating supported capabilities.
- Timestamp resolution and range.
- Sources (as per mount(2), but fsconfig() allows multiple sources).
- In-filesystem filename format information.
- Filesystem parameters ("mount -o xxx"-type things).
- LSM parameters (again "mount -o xxx"-type things).
(2) Filesystem-specific superblock attributes:
- Server names and addresses.
- Cell name.
(3) Filesystem configuration metadata attributes:
- Filesystem parameter type descriptions.
- Name -> parameter mappings.
- Simple enumeration name -> value mappings.
(4) Mount topology:
- General information about a mount object.
- Mount device name(s).
- Children of a mount object and their relative paths.
(5) Information about what the fsinfo() syscall itself supports, including
the number of attibutes supported and the number of capability bits
supported.
The system is extensible:
(1) New attributes can be added. There is no requirement that a
filesystem implement every attribute. Note that the core VFS keeps a
table of types and sizes so it can handle future extensibility rather
than delegating this to the filesystems.
(2) Version length-dependent structure attributes can be made larger and
have additional information tacked on the end, provided it keeps the
layout of the existing fields. If an older process asks for a shorter
structure, it will only be given the bits it asks for. If a newer
process asks for a longer structure on an older kernel, the extra
space will be set to 0. In all cases, the size of the data actually
available is returned.
In essence, the size of a structure is that structure's version: a
smaller size is an earlier version and a later version includes
everything that the earlier version did.
(3) New single-bit capability flags can be added. This is a structure-typed
attribute and, as such, (2) applies. Any bits you wanted but the kernel
doesn't support are automatically set to 0.
If a filesystem-specific attribute is added, it should just take up the next
number in the enumeration. Currently, I do not intend that the number space
should be subdivided between interested parties.
fsinfo() may be called like the following, for example:
struct fsinfo_params params = {
.at_flags = AT_SYMLINK_NOFOLLOW,
.request = FSINFO_ATTR_SERVER_ADDRESS;
.Nth = 2;
.Mth = 1;
};
struct fsinfo_server_address address;
len = fsinfo(AT_FDCWD, "/afs/grand.central.org/doc", ¶ms,
&address, sizeof(address));
The above example would query a network filesystem, such as AFS or NFS, and
ask what the 2nd address (Mth) of the 3rd server (Nth) that the superblock is
using is. Whereas:
struct fsinfo_params params = {
.at_flags = AT_SYMLINK_NOFOLLOW,
.request = FSINFO_ATTR_CELL_NAME;
};
char cell_name[256];
len = fsinfo(AT_FDCWD, "/afs/grand.central.org/doc", ¶ms,
&cell_name, sizeof(cell_name));
would retrieve the name of an AFS cell as a string.
fsinfo() can also be used to query a context from fsopen() or fspick():
fd = fsopen("ext4", 0);
struct fsinfo_params params = {
.request = FSINFO_ATTR_PARAM_DESCRIPTION;
};
struct fsinfo_param_description desc;
fsinfo(fd, NULL, ¶ms, &desc, sizeof(desc));
even if that context doesn't currently have a superblock attached (though if
there's no superblock attached, only filesystem-specific things like parameter
descriptions can be accessed).
The patches can be found here also:
https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-fs.git
on branch:
fsinfo
===================
SIGNIFICANT CHANGES
===================
ver #13:
(*) Provided a "fixed-struct array" type so that the list of children of a
mount and all their change counters can be read atomically.
(*) Additional filesystem examples.
(*) Documented the API.
ver #12:
(*) Rename ->get_fsinfo() to ->fsinfo().
(*) Pass the path through to to ->fsinfo() as it's needed for NFS to
retrocalculate the source name.
(*) Indicated which is the source parameter in the param-description
attribute.
(*) Dropped the realm attribute.
David
---
David Howells (7):
General notification queue with user mmap()'able ring buffer
keys: Add a notification facility
vfs: Add a mount-notification facility
vfs: Add superblock notifications
fsinfo: Export superblock notification counter
block: Add block layer notifications
Add sample notification program
Documentation/security/keys/core.rst | 58 ++
Documentation/watch_queue.rst | 311 +++++++++++
arch/x86/entry/syscalls/syscall_32.tbl | 3
arch/x86/entry/syscalls/syscall_64.tbl | 3
block/Kconfig | 9
block/Makefile | 1
block/blk-core.c | 28 +
block/blk-notify.c | 83 +++
drivers/misc/Kconfig | 13
drivers/misc/Makefile | 1
drivers/misc/watch_queue.c | 877 ++++++++++++++++++++++++++++++++
fs/Kconfig | 21 +
fs/Makefile | 1
fs/fsinfo.c | 12
fs/mount.h | 33 +
fs/mount_notify.c | 178 ++++++
fs/namespace.c | 9
fs/super.c | 116 ++++
include/linux/blkdev.h | 10
include/linux/dcache.h | 1
include/linux/fs.h | 76 +++
include/linux/key.h | 4
include/linux/lsm_hooks.h | 15 +
include/linux/security.h | 14 +
include/linux/syscalls.h | 5
include/linux/watch_queue.h | 86 +++
include/uapi/linux/fsinfo.h | 10
include/uapi/linux/keyctl.h | 1
include/uapi/linux/watch_queue.h | 185 +++++++
kernel/sys_ni.c | 6
mm/interval_tree.c | 2
mm/memory.c | 1
samples/Kconfig | 6
samples/Makefile | 1
samples/vfs/test-fsinfo.c | 13
samples/watch_queue/Makefile | 9
samples/watch_queue/watch_test.c | 284 ++++++++++
security/keys/Kconfig | 10
security/keys/compat.c | 2
security/keys/gc.c | 5
security/keys/internal.h | 30 +
security/keys/key.c | 37 +
security/keys/keyctl.c | 88 +++
security/keys/keyring.c | 17 -
security/keys/request_key.c | 4
security/security.c | 9
46 files changed, 2650 insertions(+), 38 deletions(-)
create mode 100644 Documentation/watch_queue.rst
create mode 100644 block/blk-notify.c
create mode 100644 drivers/misc/watch_queue.c
create mode 100644 fs/mount_notify.c
create mode 100644 include/linux/watch_queue.h
create mode 100644 include/uapi/linux/watch_queue.h
create mode 100644 samples/watch_queue/Makefile
create mode 100644 samples/watch_queue/watch_test.c
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox