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 4096, no NUL).
(3) Array of fixed-length structures (up to INT_MAX size).
(4) Opaque blob (up to INT_MAX size).
Attributes can have multiple values either as a sequence of values or a
sequence-of-sequences of values and all the values of a particular
attribute must be of the same type.
Note that the values of an attribute *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) Information about what the fsinfo() syscall itself supports, including
the number of attibutes supported and the number of capability bits
supported.
(5) Future patches will include information about the mount topology.
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_AFS_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-core
===================
SIGNIFICANT CHANGES
===================
ver #15:
(*) Rebase the patchset to v5.2-rc1 to remove the dependencies on my
mount-api-viro branch. Al has remunged the patches, but has not
exposed what he might send upstream.
(*) Split out various of the individual filesystem implementation patches
that depend on mount API changes having been made.
(*) Split out the specify-by-mount-ID patch and the mount-topology query
patches into the fsinfo-mount branch.
ver #14:
(*) Increase to 128-bit the fields for number of blocks and files in the
filesystem and also the max file size and max inode number fields.
(*) Increase to 64-bit the fields for max hard links and max xattr body
length.
(*) Provide struct fsinfo_timestamp_one to represent the characteristics
of a single timestamp and move the range into it. FAT, for example,
has different ranges for different timestamps. Each timestamp is then
represented by one of these structs.
(*) Don't expose MS_* flags (such as MS_RDONLY) through this interface as
they ought to be considered deprecated; instead anyone who wants them
should parse FSINFO_ATTR_PARAMETERS for the string equivalents.
(*) Add a flag, AT_FSINFO_FROM_FSOPEN, to indicate that the fd being
accessed is from fsopen()/fspick() and that fsinfo() should look
inside and access the filesystem referred to by the fs_context.
(*) If the filesystem implements FSINFO_ATTR_PARAMETERS for itself, don't
automatically include flags for the SB_* bits that are normally
rendered by, say, /proc/mounts (such as SB_RDONLY). Rather, a helper
is provided that the filesystem must call with an appropriately
wangled s_flags.
(*) Drop the NFS fsinfo patch for now as NFS fs_context support is
unlikely to get upstream in the upcoming merge window.
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 (10):
vfs: syscall: Add fsinfo() to query filesystem information
fsinfo: Add syscalls to other arches
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()
afs: Support fsinfo()
fsinfo: Add API documentation
hugetlbfs: Add support for fsinfo()
kernfs, cgroup: Add fsinfo support
Ian Kent (1):
fsinfo: proc - add sb operation fsinfo()
Documentation/filesystems/fsinfo.rst | 561 +++++++++++++++++++
arch/alpha/kernel/syscalls/syscall.tbl | 1
arch/arm/tools/syscall.tbl | 1
arch/arm64/include/asm/unistd.h | 2
arch/arm64/include/asm/unistd32.h | 2
arch/ia64/kernel/syscalls/syscall.tbl | 1
arch/m68k/kernel/syscalls/syscall.tbl | 1
arch/microblaze/kernel/syscalls/syscall.tbl | 1
arch/mips/kernel/syscalls/syscall_n32.tbl | 1
arch/mips/kernel/syscalls/syscall_n64.tbl | 1
arch/mips/kernel/syscalls/syscall_o32.tbl | 1
arch/parisc/kernel/syscalls/syscall.tbl | 1
arch/powerpc/kernel/syscalls/syscall.tbl | 1
arch/s390/kernel/syscalls/syscall.tbl | 1
arch/sh/kernel/syscalls/syscall.tbl | 1
arch/sparc/kernel/syscalls/syscall.tbl | 1
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
arch/xtensa/kernel/syscalls/syscall.tbl | 1
fs/Kconfig | 7
fs/Makefile | 1
fs/afs/internal.h | 1
fs/afs/super.c | 180 ++++++
fs/fsinfo.c | 818 +++++++++++++++++++++++++++
fs/hugetlbfs/inode.c | 57 ++
fs/kernfs/mount.c | 20 +
fs/proc/inode.c | 37 +
fs/statfs.c | 2
include/linux/fs.h | 5
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/asm-generic/unistd.h | 4
include/uapi/linux/fcntl.h | 2
include/uapi/linux/fsinfo.h | 291 ++++++++++
kernel/cgroup/cgroup-v1.c | 44 +
kernel/cgroup/cgroup.c | 19 +
kernel/sys_ni.c | 1
samples/vfs/Makefile | 6
samples/vfs/test-fs-query.c | 138 +++++
samples/vfs/test-fsinfo.c | 640 +++++++++++++++++++++
security/security.c | 12
44 files changed, 2961 insertions(+), 6 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
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;
__u64 __reserved[3];
};
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 <[email protected]>
cc: [email protected]
---
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 | 545 ++++++++++++++++++++++++++++++++
include/linux/fs.h | 5
include/linux/fsinfo.h | 65 ++++
include/linux/syscalls.h | 4
include/uapi/asm-generic/unistd.h | 4
include/uapi/linux/fsinfo.h | 219 +++++++++++++
kernel/sys_ni.c | 1
samples/vfs/Makefile | 4
samples/vfs/test-fsinfo.c | 551 ++++++++++++++++++++++++++++++++
13 files changed, 1407 insertions(+), 1 deletion(-)
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..09e743b16235
--- /dev/null
+++ b/fs/fsinfo.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Filesystem information query.
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+#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;
+}
+
+/*
+ * 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.hi = 0;
+ p->f_blocks.lo = buf.f_blocks;
+ p->f_bfree.hi = 0;
+ p->f_bfree.lo = buf.f_bfree;
+ p->f_bavail.hi = 0;
+ p->f_bavail.lo = buf.f_bavail;
+ p->f_files.hi = 0;
+ p->f_files.lo = buf.f_files;
+ p->f_ffree.hi = 0;
+ p->f_ffree.lo = buf.f_ffree;
+ p->f_favail.hi = 0;
+ p->f_favail.lo = 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);
+ 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.hi = 0;
+ lim->max_file_size.lo = 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 const struct fsinfo_timestamp_info fsinfo_default_timestamp_info = {
+ .atime = {
+ .minimum = S64_MIN,
+ .maximum = S64_MAX,
+ .gran_mantissa = 1,
+ .gran_exponent = 0,
+ },
+ .mtime = {
+ .minimum = S64_MIN,
+ .maximum = S64_MAX,
+ .gran_mantissa = 1,
+ .gran_exponent = 0,
+ },
+ .ctime = {
+ .minimum = S64_MIN,
+ .maximum = S64_MAX,
+ .gran_mantissa = 1,
+ .gran_exponent = 0,
+ },
+ .btime = {
+ .minimum = S64_MIN,
+ .maximum = S64_MAX,
+ .gran_mantissa = 1,
+ .gran_exponent = 0,
+ },
+};
+
+static int fsinfo_generic_timestamp_info(struct path *path,
+ struct fsinfo_timestamp_info *ts)
+{
+ struct super_block *sb = path->dentry->d_sb;
+ s8 exponent;
+
+ *ts = fsinfo_default_timestamp_info;
+
+
+ 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;
+
+ ts->atime.gran_exponent = exponent;
+ ts->mtime.gran_exponent = exponent;
+ ts->ctime.gran_exponent = exponent;
+ ts->btime.gran_exponent = exponent;
+ }
+
+ 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 (IS_ERR_VALUE((long)ret))
+ return ret; /* Error */
+ if ((unsigned int)ret <= params->buf_size)
+ return ret; /* 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 *pathname,
+ 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, pathname, 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 - a fixed-size structure with only one instance.
+ * STRUCT_N - a sequence of STRUCTs, indexed by Nth
+ * STRUCT_NM - a sequence of sequences of STRUCTs, indexed by Nth, Mth
+ * STRING - a string with only one instance.
+ * STRING_N - a sequence of STRING, indexed by Nth
+ * STRING_NM - a sequence of sequences of STRING, indexed by Nth, Mth
+ * OPAQUE - a blob that can be larger than 4K.
+ * STRUCT_ARRAY - 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) [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) [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) [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) [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),
+ FSINFO_STRUCT (VOLUME_UUID, volume_uuid),
+ FSINFO_STRING (VOLUME_NAME),
+ FSINFO_STRING (NAME_ENCODING),
+ FSINFO_STRING (NAME_CODEPAGE),
+};
+
+/**
+ * sys_fsinfo - System call to get filesystem information
+ * @dfd: Base directory to pathwalk from or fd referring to filesystem.
+ * @pathname: 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 *, pathname,
+ 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 kparams;
+ unsigned int result_size;
+ int ret;
+
+ memset(&kparams, 0, sizeof(kparams));
+
+ 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;
+ kparams.at_flags = user_params.at_flags;
+ kparams.request = user_params.request;
+ kparams.Nth = user_params.Nth;
+ kparams.Mth = user_params.Mth;
+ } else {
+ kparams.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[kparams.request];
+ if (kparams.Nth != 0 && !(info.flags & (__FSINFO_N | __FSINFO_NM)))
+ return -ENODATA;
+ if (kparams.Mth != 0 && !(info.flags & __FSINFO_NM))
+ return -ENODATA;
+
+ switch (info.type) {
+ case __FSINFO_STRUCT:
+ kparams.buf_size = info.size;
+ if (user_buf_size == 0)
+ return info.size; /* We know how big the buffer should be */
+ break;
+
+ case __FSINFO_STRING:
+ kparams.buf_size = FSINFO_NORMAL_ATTR_MAX_SIZE;
+ 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;
+ kparams.scratch_buffer = kmalloc(FSINFO_SCRATCH_BUFFER_SIZE,
+ GFP_KERNEL);
+ if (!kparams.scratch_buffer)
+ goto error;
+ kparams.overlarge = true;
+ kparams.buf_size = FSINFO_NORMAL_ATTR_MAX_SIZE;
+ 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;
+ kparams.buffer = kvzalloc(kparams.buf_size, GFP_KERNEL);
+ if (!kparams.buffer)
+ goto error_scratch;
+
+ if (pathname)
+ ret = vfs_fsinfo_path(dfd, pathname, &kparams);
+ else
+ ret = vfs_fsinfo_fd(dfd, &kparams);
+ 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, kparams.buffer, result_size) != 0) {
+ 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(kparams.buffer);
+error_scratch:
+ kfree(kparams.scratch_buffer);
+error:
+ return ret;
+}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f7fdfe93e25d..50f58eac3e1f 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..4c250136d693
--- /dev/null
+++ b/include/linux/fsinfo.h
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Filesystem information query
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#ifndef _LINUX_FSINFO_H
+#define _LINUX_FSINFO_H
+
+#ifdef CONFIG_FSINFO
+
+#include <uapi/linux/fsinfo.h>
+
+#define FSINFO_NORMAL_ATTR_MAX_SIZE 4096
+#define FSINFO_SCRATCH_BUFFER_SIZE 4096
+
+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 (if overlarge=T) */
+ unsigned int buf_size; /* Size of ->buffer[] */
+ void *buffer; /* Where to place the reply */
+ char *scratch_buffer; /* 4K scratch buffer (if 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..958ac427ff37 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 *pathname,
+ struct fsinfo_params __user *params,
+ void __user *buffer, size_t buf_size);
/*
* Architecture-specific system calls
diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
index a87904daf103..50ddf5f25122 100644
--- a/include/uapi/asm-generic/unistd.h
+++ b/include/uapi/asm-generic/unistd.h
@@ -844,9 +844,11 @@ __SYSCALL(__NR_fsconfig, sys_fsconfig)
__SYSCALL(__NR_fsmount, sys_fsmount)
#define __NR_fspick 433
__SYSCALL(__NR_fspick, sys_fspick)
+#define __NR_fsinfo 434
+__SYSCALL(__NR_fsinfo, sys_fsinfo)
#undef __NR_syscalls
-#define __NR_syscalls 434
+#define __NR_syscalls 435
/*
* 32 bit systems traditionally used different
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
new file mode 100644
index 000000000000..cc7e13a9b95f
--- /dev/null
+++ b/include/uapi/linux/fsinfo.h
@@ -0,0 +1,219 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/* fsinfo() definitions.
+ *
+ * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+#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 */
+};
+
+struct fsinfo_u128 {
+#if defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : defined(__BIG_ENDIAN)
+ __u64 hi;
+ __u64 lo;
+#elif defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : defined(__LITTLE_ENDIAN)
+ __u64 lo;
+ __u64 hi;
+#endif
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_statfs).
+ * - This gives extended filesystem information.
+ */
+struct fsinfo_statfs {
+ struct fsinfo_u128 f_blocks; /* Total number of blocks in fs */
+ struct fsinfo_u128 f_bfree; /* Total number of free blocks */
+ struct fsinfo_u128 f_bavail; /* Number of free blocks available to ordinary user */
+ struct fsinfo_u128 f_files; /* Total number of file nodes in fs */
+ struct fsinfo_u128 f_ffree; /* Number of free file nodes */
+ struct fsinfo_u128 f_favail; /* Number of file nodes available to ordinary user */
+ __u64 f_bsize; /* Optimal block size */
+ __u64 f_frsize; /* Fragment size */
+ __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]; /* Filesystem name */
+ __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 {
+ struct fsinfo_u128 max_file_size; /* Maximum file size */
+ struct fsinfo_u128 max_ino; /* Maximum inode number */
+ __u64 max_uid; /* Maximum UID supported */
+ __u64 max_gid; /* Maximum GID supported */
+ __u64 max_projid; /* Maximum project ID supported */
+ __u64 max_hard_links; /* Maximum number of hard links on a file */
+ __u64 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 max_dev_major; /* Maximum device major representable */
+ __u32 max_dev_minor; /* Maximum device minor representable */
+ __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];
+};
+
+struct fsinfo_timestamp_one {
+ __s64 minimum; /* Minimum timestamp value in seconds */
+ __u64 maximum; /* Maximum timestamp value in seconds */
+ __u16 gran_mantissa; /* Granularity(secs) = mant * 10^exp */
+ __s8 gran_exponent;
+ __u8 reserved[5];
+};
+
+/*
+ * Information struct for fsinfo(fsinfo_attr_timestamp_info).
+ */
+struct fsinfo_timestamp_info {
+ struct fsinfo_timestamp_one atime; /* Access time */
+ struct fsinfo_timestamp_one mtime; /* Modification time */
+ struct fsinfo_timestamp_one ctime; /* Change time */
+ struct fsinfo_timestamp_one btime; /* Birth/creation time */
+};
+
+/*
+ * 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..93927072396c 100644
--- a/kernel/sys_ni.c
+++ b/kernel/sys_ni.c
@@ -51,6 +51,7 @@ COND_SYSCALL_COMPAT(io_pgetevents);
COND_SYSCALL(io_uring_setup);
COND_SYSCALL(io_uring_enter);
COND_SYSCALL(io_uring_register);
+COND_SYSCALL(fsinfo);
/* fs/xattr.c */
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..8cce1986df7e
--- /dev/null
+++ b/samples/vfs/test-fsinfo.c
@@ -0,0 +1,551 @@
+/* Test the fsinfo() system call
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * 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.lo,
+ (unsigned long long)f->f_bfree.lo,
+ (unsigned long long)f->f_bavail.lo);
+
+ printf("\tfiles : n=%llu fr=%llu av=%llu\n",
+ (unsigned long long)f->f_files.lo,
+ (unsigned long long)f->f_ffree.lo,
+ (unsigned long long)f->f_favail.lo);
+ printf("\tbsize : %llu\n", f->f_bsize);
+ printf("\tfrsize: %llu\n", f->f_frsize);
+ printf("\tmntfl : %llx\n", (unsigned long long)f->mnt_attrs);
+}
+
+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%016llx\n",
+ (unsigned long long)f->max_file_size.hi,
+ (unsigned long long)f->max_file_size.lo);
+ printf("\tmax ino: %llx%016llx\n",
+ (unsigned long long)f->max_ino.hi,
+ (unsigned long long)f->max_ino.lo);
+ 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 : %llx\n",
+ (unsigned long long)f->max_hard_links);
+ printf("\tmax xattr : n=%x b=%llx\n",
+ f->max_xattr_name_len,
+ (unsigned long long)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 print_time(struct fsinfo_timestamp_one *t, char stamp)
+{
+ printf("\t%ctime : gran=%gs range=%llx-%llx\n",
+ stamp,
+ t->gran_mantissa * pow(10., t->gran_exponent),
+ (long long)t->minimum,
+ (long long)t->maximum);
+}
+
+static void dump_attr_TIMESTAMP_INFO(union reply *r, int size)
+{
+ struct fsinfo_timestamp_info *f = &r->timestamps;
+
+ printf("\n");
+ print_time(&f->atime, 'a');
+ print_time(&f->mtime, 'm');
+ print_time(&f->ctime, 'c');
+ print_time(&f->btime, 'b');
+}
+
+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;
+}
Add the fsinfo syscall to the other arches.
Signed-off-by: David Howells <[email protected]>
---
arch/alpha/kernel/syscalls/syscall.tbl | 1 +
arch/arm/tools/syscall.tbl | 1 +
arch/arm64/include/asm/unistd.h | 2 +-
arch/arm64/include/asm/unistd32.h | 2 +-
arch/ia64/kernel/syscalls/syscall.tbl | 1 +
arch/m68k/kernel/syscalls/syscall.tbl | 1 +
arch/microblaze/kernel/syscalls/syscall.tbl | 1 +
arch/mips/kernel/syscalls/syscall_n32.tbl | 1 +
arch/mips/kernel/syscalls/syscall_n64.tbl | 1 +
arch/mips/kernel/syscalls/syscall_o32.tbl | 1 +
arch/parisc/kernel/syscalls/syscall.tbl | 1 +
arch/powerpc/kernel/syscalls/syscall.tbl | 1 +
arch/s390/kernel/syscalls/syscall.tbl | 1 +
arch/sh/kernel/syscalls/syscall.tbl | 1 +
arch/sparc/kernel/syscalls/syscall.tbl | 1 +
arch/xtensa/kernel/syscalls/syscall.tbl | 1 +
16 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl
index 9e7704e44f6d..624d01c3c8eb 100644
--- a/arch/alpha/kernel/syscalls/syscall.tbl
+++ b/arch/alpha/kernel/syscalls/syscall.tbl
@@ -473,3 +473,4 @@
541 common fsconfig sys_fsconfig
542 common fsmount sys_fsmount
543 common fspick sys_fspick
+544 common fsinfo sys_fsinfo
diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl
index aaf479a9e92d..ad608b49808c 100644
--- a/arch/arm/tools/syscall.tbl
+++ b/arch/arm/tools/syscall.tbl
@@ -447,3 +447,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
diff --git a/arch/arm64/include/asm/unistd.h b/arch/arm64/include/asm/unistd.h
index 70e6882853c0..e8f7d95a1481 100644
--- a/arch/arm64/include/asm/unistd.h
+++ b/arch/arm64/include/asm/unistd.h
@@ -44,7 +44,7 @@
#define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5)
#define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800)
-#define __NR_compat_syscalls 434
+#define __NR_compat_syscalls 435
#endif
#define __ARCH_WANT_SYS_CLONE
diff --git a/arch/arm64/include/asm/unistd32.h b/arch/arm64/include/asm/unistd32.h
index c39e90600bb3..52d0c148b557 100644
--- a/arch/arm64/include/asm/unistd32.h
+++ b/arch/arm64/include/asm/unistd32.h
@@ -884,7 +884,7 @@ __SYSCALL(__NR_fsopen, sys_fsopen)
__SYSCALL(__NR_fsconfig, sys_fsconfig)
#define __NR_fsmount 432
__SYSCALL(__NR_fsmount, sys_fsmount)
-#define __NR_fspick 433
+#define __NR_fspick 434
__SYSCALL(__NR_fspick, sys_fspick)
/*
diff --git a/arch/ia64/kernel/syscalls/syscall.tbl b/arch/ia64/kernel/syscalls/syscall.tbl
index e01df3f2f80d..68314763ad16 100644
--- a/arch/ia64/kernel/syscalls/syscall.tbl
+++ b/arch/ia64/kernel/syscalls/syscall.tbl
@@ -354,3 +354,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl
index 7e3d0734b2f3..ee73a7534b1b 100644
--- a/arch/m68k/kernel/syscalls/syscall.tbl
+++ b/arch/m68k/kernel/syscalls/syscall.tbl
@@ -433,3 +433,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl
index 26339e417695..7bc067f4b713 100644
--- a/arch/microblaze/kernel/syscalls/syscall.tbl
+++ b/arch/microblaze/kernel/syscalls/syscall.tbl
@@ -439,3 +439,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl
index 0e2dd68ade57..29b76bd67cc0 100644
--- a/arch/mips/kernel/syscalls/syscall_n32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n32.tbl
@@ -372,3 +372,4 @@
431 n32 fsconfig sys_fsconfig
432 n32 fsmount sys_fsmount
433 n32 fspick sys_fspick
+434 n32 fsinfo sys_fsinfo
diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl
index 5eebfa0d155c..349fb30bb8b5 100644
--- a/arch/mips/kernel/syscalls/syscall_n64.tbl
+++ b/arch/mips/kernel/syscalls/syscall_n64.tbl
@@ -348,3 +348,4 @@
431 n64 fsconfig sys_fsconfig
432 n64 fsmount sys_fsmount
433 n64 fspick sys_fspick
+434 n64 fsinfo sys_fsinfo
diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl
index 3cc1374e02d0..71057426b503 100644
--- a/arch/mips/kernel/syscalls/syscall_o32.tbl
+++ b/arch/mips/kernel/syscalls/syscall_o32.tbl
@@ -421,3 +421,4 @@
431 o32 fsconfig sys_fsconfig
432 o32 fsmount sys_fsmount
433 o32 fspick sys_fspick
+434 o32 fsinfo sys_fsinfo
diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl
index c9e377d59232..32cff48a1ebd 100644
--- a/arch/parisc/kernel/syscalls/syscall.tbl
+++ b/arch/parisc/kernel/syscalls/syscall.tbl
@@ -430,3 +430,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl
index 103655d84b4b..e5755eb6fb84 100644
--- a/arch/powerpc/kernel/syscalls/syscall.tbl
+++ b/arch/powerpc/kernel/syscalls/syscall.tbl
@@ -515,3 +515,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl
index e822b2964a83..bcd54116e107 100644
--- a/arch/s390/kernel/syscalls/syscall.tbl
+++ b/arch/s390/kernel/syscalls/syscall.tbl
@@ -436,3 +436,4 @@
431 common fsconfig sys_fsconfig sys_fsconfig
432 common fsmount sys_fsmount sys_fsmount
433 common fspick sys_fspick sys_fspick
+434 common fsinfo sys_fsinfo sys_fsinfo
diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl
index 016a727d4357..0320a5c63cbd 100644
--- a/arch/sh/kernel/syscalls/syscall.tbl
+++ b/arch/sh/kernel/syscalls/syscall.tbl
@@ -436,3 +436,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
index e047480b1605..f81b1f9402bd 100644
--- a/arch/sparc/kernel/syscalls/syscall.tbl
+++ b/arch/sparc/kernel/syscalls/syscall.tbl
@@ -479,3 +479,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl
index 5fa0ee1c8e00..729795148850 100644
--- a/arch/xtensa/kernel/syscalls/syscall.tbl
+++ b/arch/xtensa/kernel/syscalls/syscall.tbl
@@ -404,3 +404,4 @@
431 common fsconfig sys_fsconfig
432 common fsmount sys_fsmount
433 common fspick sys_fspick
+434 common fsinfo sys_fsinfo
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().
The caller must specify AT_FSINFO_FROM_FSOPEN in the parameters and must
supply the fd from fsopen() as dfd and must set filename to NULL.
This is done with something like:
fd = fsopen("ext4", 0);
...
struct fsinfo_params params = {
.at_flags = AT_FSINFO_FROM_FSOPEN;
...
};
fsinfo(fd, NULL, ¶ms, ...);
Signed-off-by: David Howells <[email protected]>
---
fs/fsinfo.c | 44 +++++++++++++++++++++++++++++++++++++++++++-
fs/statfs.c | 2 +-
include/uapi/linux/fcntl.h | 2 ++
3 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 09e743b16235..d419cccbf3db 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -13,6 +13,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"
@@ -347,6 +348,42 @@ static int vfs_fsinfo_fd(unsigned int fd, struct fsinfo_kparams *params)
return ret;
}
+/*
+ * Allow access to an fs_context object as created by fsopen() or fspick().
+ */
+static int vfs_fsinfo_fscontext(int fd, struct fsinfo_kparams *params)
+{
+ struct fs_context *fc;
+ struct fd f = fdget(fd);
+ int ret;
+
+ if (!f.file)
+ return -EBADF;
+
+ ret = -EINVAL;
+ if (f.file->f_op != &fscontext_fops)
+ goto out_f;
+ ret = -EOPNOTSUPP;
+ if (fc->ops == &legacy_fs_context_ops)
+ goto out_f;
+
+ ret = mutex_lock_interruptible(&fc->uapi_mutex);
+ if (ret == 0) {
+ ret = -EBADFD;
+ if (fc->root) {
+ struct path path = { .dentry = fc->root };
+
+ ret = vfs_fsinfo(&path, params);
+ }
+
+ mutex_unlock(&fc->uapi_mutex);
+ }
+
+out_f:
+ fdput(f);
+ return ret;
+}
+
/*
* Return buffer information by requestable attribute.
*
@@ -452,6 +489,9 @@ SYSCALL_DEFINE5(fsinfo,
kparams.request = user_params.request;
kparams.Nth = user_params.Nth;
kparams.Mth = user_params.Mth;
+
+ if ((kparams.at_flags & AT_FSINFO_FROM_FSOPEN) && pathname)
+ return -EINVAL;
} else {
kparams.request = FSINFO_ATTR_STATFS;
}
@@ -508,7 +548,9 @@ SYSCALL_DEFINE5(fsinfo,
if (!kparams.buffer)
goto error_scratch;
- if (pathname)
+ if (kparams.at_flags & AT_FSINFO_FROM_FSOPEN)
+ ret = vfs_fsinfo_fscontext(dfd, &kparams);
+ else if (pathname)
ret = vfs_fsinfo_path(dfd, pathname, &kparams);
else
ret = vfs_fsinfo_fd(dfd, &kparams);
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;
}
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 1d338357df8a..6a2402a8fa30 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_FROM_FSOPEN 0x2000 /* Examine the fs_context attached to dfd by fsopen() */
+
#define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */
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 = {
.at_flags = AT_FSINFO_FROM_FSOPEN,
.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 <[email protected]>
---
fs/fsinfo.c | 99 +++++++++++++++++++++++++++++++
include/uapi/linux/fsinfo.h | 58 ++++++++++++++++++
samples/vfs/Makefile | 2 +
samples/vfs/test-fs-query.c | 138 +++++++++++++++++++++++++++++++++++++++++++
samples/vfs/test-fsinfo.c | 14 ++++
5 files changed, 310 insertions(+), 1 deletion(-)
create mode 100644 samples/vfs/test-fs-query.c
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index d419cccbf3db..3218968c5fee 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -14,6 +14,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"
@@ -228,12 +229,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 *fs = 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(fs, params)
switch (params->request) {
case _gen(STATFS, statfs);
@@ -245,6 +321,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;
}
@@ -349,10 +428,11 @@ static int vfs_fsinfo_fd(unsigned int fd, struct fsinfo_kparams *params)
}
/*
- * Allow access to an fs_context object as created by fsopen() or fspick().
+ * Allow an fs_context object as created by fsopen() or fspick() to be queried.
*/
static int vfs_fsinfo_fscontext(int fd, struct fsinfo_kparams *params)
{
+ struct file_system_type *fs;
struct fs_context *fc;
struct fd f = fdget(fd);
int ret;
@@ -363,10 +443,24 @@ static int vfs_fsinfo_fscontext(int fd, struct fsinfo_kparams *params)
ret = -EINVAL;
if (f.file->f_op != &fscontext_fops)
goto out_f;
+ fc = f.file->private_data;
+ fs = fc->fs_type;
+
ret = -EOPNOTSUPP;
if (fc->ops == &legacy_fs_context_ops)
goto out_f;
+ /* Filesystem parameter query is static information and doesn't need a
+ * lock to read it, nor even a dentry or superblock.
+ */
+ 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) {
ret = -EBADFD;
@@ -439,6 +533,9 @@ static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
FSINFO_STRING (VOLUME_NAME),
FSINFO_STRING (NAME_ENCODING),
FSINFO_STRING (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 cc7e13a9b95f..204ab25ec75d 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
};
@@ -216,4 +219,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..7572411ddb7e
--- /dev/null
+++ b/samples/vfs/test-fs-query.c
@@ -0,0 +1,138 @@
+/* Test using the fsinfo() system call to query mount parameters.
+ *
+ * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ *
+ * 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/fcntl.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_FSINFO_FROM_FSOPEN,
+ };
+ 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 8cce1986df7e..3c6ea3a5c157 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 {
@@ -514,6 +520,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;
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 <[email protected]>
---
fs/fsinfo.c | 122 +++++++++++++++++++++++++++++++++++++++++++
include/linux/fsinfo.h | 4 +
include/uapi/linux/fsinfo.h | 1
samples/vfs/test-fsinfo.c | 38 +++++++++++++
4 files changed, 165 insertions(+)
diff --git a/fs/fsinfo.c b/fs/fsinfo.c
index 3218968c5fee..92906a3f4010 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -301,6 +301,32 @@ static int fsinfo_generic_param_enum(struct file_system_type *f,
return sizeof(*p);
}
+void fsinfo_note_sb_params(struct fsinfo_kparams *params, unsigned int 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);
+ else
+ fsinfo_note_param(params, "rw", NULL);
+ if (s_flags & SB_SYNCHRONOUS)
+ fsinfo_note_param(params, "sync", NULL);
+}
+EXPORT_SYMBOL(fsinfo_note_sb_params);
+
+static int fsinfo_generic_parameters(struct path *path,
+ struct fsinfo_kparams *params)
+{
+ fsinfo_note_sb_params(params, READ_ONCE(path->dentry->d_sb->s_flags));
+ return params->usage;
+}
+
/*
* Implement some queries generically from stuff in the superblock.
*/
@@ -309,6 +335,7 @@ int generic_fsinfo(struct path *path, struct fsinfo_kparams *params)
struct file_system_type *fs = path->dentry->d_sb->s_type;
#define _gen(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(path, params->buffer)
+#define _genp(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(path, params)
#define _genf(X, Y) FSINFO_ATTR_##X: return fsinfo_generic_##Y(fs, params)
switch (params->request) {
@@ -324,6 +351,7 @@ 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(PARAMETERS, parameters);
default:
return -EOPNOTSUPP;
}
@@ -363,8 +391,16 @@ 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;
+ }
+
params->usage = 0;
ret = fsinfo(path, params);
+ if (params->request == FSINFO_ATTR_PARAMETERS)
+ up_read(&dentry->d_sb->s_umount);
+
if (IS_ERR_VALUE((long)ret))
return ret; /* Error */
if ((unsigned int)ret <= params->buf_size)
@@ -536,6 +572,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),
};
/**
@@ -682,3 +719,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 4c250136d693..9936ba90f1c9 100644
--- a/include/linux/fsinfo.h
+++ b/include/linux/fsinfo.h
@@ -28,6 +28,10 @@ struct fsinfo_kparams {
};
extern int generic_fsinfo(struct path *, struct fsinfo_kparams *);
+extern void fsinfo_note_sb_params(struct fsinfo_kparams *, unsigned int);
+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 204ab25ec75d..11c4a74638b0 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 3c6ea3a5c157..8cf5b02e333a 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 {
@@ -352,6 +354,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.
*/
@@ -427,6 +457,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: ",
@@ -469,6 +505,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:
Add API documentation for fsinfo.
Signed-off-by: David Howells <[email protected]>
---
Documentation/filesystems/fsinfo.rst | 561 ++++++++++++++++++++++++++++++++++
1 file changed, 561 insertions(+)
create mode 100644 Documentation/filesystems/fsinfo.rst
diff --git a/Documentation/filesystems/fsinfo.rst b/Documentation/filesystems/fsinfo.rst
new file mode 100644
index 000000000000..86c187a46396
--- /dev/null
+++ b/Documentation/filesystems/fsinfo.rst
@@ -0,0 +1,561 @@
+================================
+Filesystem Information Retrieval
+================================
+
+The fsinfo() system call allows the retrieval of filesystem and filesystem
+security information beyond what stat(), statx() and statfs() can query. It
+does not require a file to be opened as does ioctl().
+
+fsinfo() may be called on a path, an open file descriptor, a filesystem-context
+file descriptor as allocated by fsopen() or fspick().
+
+The fsinfo() system call needs to be configured on by enabling:
+
+ "File systems"/"Enable the fsinfo() system call" (CONFIG_FSINFO)
+
+This document has the following sections:
+
+.. contents:: :local:
+
+
+Overview
+========
+
+The fsinfo() system call retrieves one of a number of attributes, specified by
+the "fsinfo_attribute" enumeration::
+
+ FSINFO_ATTR_STATFS - statfs()-style state
+ FSINFO_ATTR_FSINFO - Information about fsinfo() itself
+ FSINFO_ATTR_IDS - Filesystem IDs
+ FSINFO_ATTR_LIMITS - Filesystem limits
+ ...
+
+Each attribute can have a single value, a sequence of values or a
+sequence-of-sequences of values. All of the values of an attribute must be of
+the same type - and this is an inherent property of the attribute. The
+available types are:
+
+ * ``Struct``. This is a structure with a version-dependent length. New
+ versions of the kernel may append more fields, though they are not
+ permitted to remove or replace old ones.
+
+ Older applications, expecting an older version of the field, can ask for a
+ shorter struct and will only get the fields they requested; newer
+ applications running on an older kernel will get the extra fields they
+ requested filled with zeros. Either way, the kernel returns the actual size
+ of the internal struct, regardless of how much data it returned.
+
+ This allows for struct-type fields to be extended in future.
+
+ * ``String``. This is a variable-length string of up to 4096 characters (no
+ NUL character is included). The returned string will be truncated if the
+ output buffer is too small. The total size of the string is returned,
+ regardless of any truncation.
+
+ * ``Array``. This is a variable-length array of fixed-size structures. The
+ element size may not vary over time, so the element format must be designed
+ with care. The maximum length is INT_MAX bytes, though this depends on the
+ kernel being able to allocate an internal buffer large enough.
+
+ * ``Opaque``. This is a variable-length blob of indeterminate structure. It
+ may be up to INT_MAX bytes in size.
+
+
+Filesystem API
+==============
+
+The filesystem is called through a superblock_operations method::
+
+ int (*fsinfo) (struct path *path, struct fsinfo_kparams *params);
+
+where "path" indicates the object to be queried and params indicates the
+parameters and the output buffer description. The function should return the
+total size of the data it would like to produce or an error.
+
+The parameter struct looks like::
+
+ struct fsinfo_kparams {
+ enum fsinfo_attribute request;
+ __u32 Nth;
+ __u32 Mth;
+ unsigned int buf_size;
+ unsigned int usage;
+ void *buffer;
+ char *scratch_buffer;
+ ...
+ };
+
+The fields relevant to the filesystem are as follows:
+
+ * ``request``
+
+ Which attribute is being requested. EOPNOTSUPP should be returned if the
+ attribute is not supported by the filesystem or the LSM.
+
+ * ``Nth`` and ``Mth``
+
+ Which value of an attribute is being requested.
+
+ For a single-value attribute Nth and Mth will both be 0.
+
+ For a "1D" attribute, Nth will indicate which value and Mth will always
+ be 0. Take, for example, FSINFO_ATTR_SERVER_NAME - for a network
+ filesystem, the superblock will be backed by a number of servers. This will
+ return the name of the Nth server. ENODATA will be returned if Nth goes
+ beyond the end of the array.
+
+ For a "2D" attribute, Mth will indicate the index in the Nth set of values.
+ Take, for example, Take, for example, FSINFO_ATTR_SERVER_ADDRESS - each
+ server listed by FSINFO_ATTR_SERVER_NAME may have one or more addresses.
+ This will return the Mth address of the Nth server. ENODATA will be
+ returned if the Nth set doesn't exist or the Mth element of the Nth set
+ doesn't exist.
+
+ * ``buf_size``
+
+ This indicates the current size of the buffer. For the array type and the
+ opaque type this will be increased if the current buffer won't hold the
+ value and the filesystem will be called again.
+
+ * ``usage``
+
+ This indicates how much of the buffer has been used so far for an array or
+ opaque type attribute. This is updated by the fsinfo_note_param*()
+ functions.
+
+ * ``buffer``
+
+ This points to the output buffer. For struct-type and string-type
+ attributes it will always be big enough; for array- and opaque-type, it will
+ be buf_size in size and will be resized if the returned size is larger than
+ this.
+
+ * ``scratch_buffer``
+
+ For array- and opaque-type attributes, this will point to a 4096-byte
+ scratch buffer. Sometimes the value needs to be generated by sprintf(),
+ say, to find out how big is going to be, but that might not be possible in
+ the main buffer without risking an overrun.
+
+To simplify filesystem code, there will always be at least a minimal buffer
+available if the ->fsinfo() method gets called - and the filesystem should
+always write what it can into the buffer. It's possible that the fsinfo()
+system call will then throw the contents away and just return the length.
+
+
+Helper Functions
+================
+
+The API includes a number of helper functions:
+
+ * ``int generic_fsinfo(struct path *path, struct fsinfo_kparams *params);``
+
+ This is the function that does default actions for filling out attribute
+ values from standard data, such as may be found in the file_system_type
+ struct and the super_block struct. It also generates -EOPNOTSUPP for
+ unsupported attributes.
+
+ This should be called by a filesystem if it doesn't want to handle an
+ attribute. The filesystem may also call this function and then adjust the
+ information returned, such as changing the listed capability flags.
+
+ * ``void fsinfo_set_cap(struct fsinfo_capabilities *c,
+ enum fsinfo_capability cap);``
+
+ This function sets a capability flag.
+
+ * ``void fsinfo_clear_cap(struct fsinfo_capabilities *c,
+ enum fsinfo_capability cap);``
+
+ This function clears a capability flag.
+
+ * ``void fsinfo_set_unix_caps(struct fsinfo_capabilities *caps);``
+
+ Set capability flags appropriate to the features of a standard UNIX
+ filesystem, such as having numeric UIDS and GIDS; allowing the creation of
+ directories, symbolic links, hard links, device files, FIFO and socket
+ files; permitting sparse files; and having access, change and modification
+ times.
+
+ * ``void fsinfo_note_sb_params(struct fsinfo_kparams *params,
+ unsigned int s_flags);``
+
+ This function notes the standard parameters corresponding to certain
+ ``SB_*`` flags in ``sb->s_flags`` into the parameter buffer. The filesystem
+ is at liberty to adjust the s_flags mask as it sees fit.
+
+ This is intended for use with FSINFO_ATTR_PARAMETERS.
+
+ * ``void fsinfo_note_param(struct fsinfo_kparams *params, const char *key,
+ const char *val);``
+
+ This function writes a pair of strings with prepended lengths into
+ params->buffer, if there's space, and always updates params->usage. The
+ assumption is that the caller of s->s_op->fsinfo() will resize the buffer if
+ the usage grew too large and call again.
+
+ This is intended for use with FSINFO_ATTR_{,LSM_}PARAMETERS, but is not
+ limited to those. The format allows binary data, though this API function
+ does not support anything with NUL characters in it.
+
+ Note that this function will not sleep, so is safe to take with locks held.
+
+ * ``void fsinfo_note_paramf(struct fsinfo_kparams *params, const char *key,
+ const char *val_fmt, ...);``
+
+ This function is a simple wrapper around fsinfo_note_param(), writing the
+ value using vsnprintf() into params->scratch_buffer and then jumping to
+ fsinfo_note_param().
+
+
+Attribute Summary
+=================
+
+To summarise the attributes that are defined::
+
+ Symbolic name Type
+ ===================================== ===============
+ FSINFO_ATTR_STATFS struct
+ FSINFO_ATTR_FSINFO struct
+ FSINFO_ATTR_IDS struct
+ FSINFO_ATTR_LIMITS struct
+ FSINFO_ATTR_SUPPORTS struct
+ FSINFO_ATTR_CAPABILITIES struct
+ FSINFO_ATTR_TIMESTAMP_INFO struct
+ FSINFO_ATTR_VOLUME_ID string
+ FSINFO_ATTR_VOLUME_UUID struct
+ FSINFO_ATTR_VOLUME_NAME string
+ FSINFO_ATTR_NAME_ENCODING string
+ FSINFO_ATTR_NAME_CODEPAGE string
+ FSINFO_ATTR_PARAM_DESCRIPTION struct
+ FSINFO_ATTR_PARAM_SPECIFICATION N × struct
+ FSINFO_ATTR_PARAM_ENUM N × struct
+ FSINFO_ATTR_PARAMETERS opaque
+ FSINFO_ATTR_LSM_PARAMETERS opaque
+ FSINFO_ATTR_SERVER_NAME N × string
+ FSINFO_ATTR_SERVER_ADDRESS N × M × struct
+ FSINFO_ATTR_AFS_CELL_NAME string
+
+
+Attribute Catalogue
+===================
+
+A number of the attributes convey information about a filesystem superblock:
+
+ * ``FSINFO_ATTR_STATFS``
+
+ This struct-type attribute gives most of the equivalent data to statfs(),
+ but with all the fields as unconditional 64-bit or 128-bit integers. Note
+ that static data like IDs that don't change are retrieved with
+ FSINFO_ATTR_IDS instead.
+
+ Further, superblock flags (such as MS_RDONLY) are not exposed by this
+ attribute; rather the parameters must be listed and the attributes picked
+ out from that.
+
+ * ``FSINFO_ATTR_IDS``
+
+ This struct-type attribute conveys various identifiers used by the target
+ filesystem. This includes the filesystem name, the NFS filesystem ID, the
+ superblock ID used in notifications, the filesystem magic type number and
+ the primary device ID.
+
+ * ``FSINFO_ATTR_LIMITS``
+
+ This struct-type attribute conveys the limits on various aspects of a
+ filesystem, such as maximum file, symlink and xattr sizes, maxiumm filename
+ and xattr name length, maximum number of symlinks, maximum device major and
+ minor numbers and maximum UID, GID and project ID numbers.
+
+ * ``FSINFO_ATTR_SUPPORTS``
+
+ This struct-type attribute conveys information about the support the
+ filesystem has for various UAPI features of a filesystem. This includes
+ information about which bits are supported in various masks employed by the
+ statx system call, what FS_IOC_* flags are supported by ioctls and what
+ DOS/Windows file attribute flags are supported.
+
+ * ``FSINFO_ATTR_CAPABILITIES``
+
+ This is a special attribute, being a set of single-bit capability flags,
+ formatted as struct-type attribute. The meanings of the capability bits
+ are listed below - see the "Capability Bit Catalogue" section. The
+ capability bits are grouped numerically into bytes, such that capilities
+ 0-7 are in byte 0, 8-15 are in byte 1, 16-23 in byte 2 and so on.
+
+ Any capability bit that's not supported by the kernel will be set to false
+ if asked for. The highest supported capability can be obtained from
+ attribute "FSINFO_ATTR_FSINFO".
+
+ * ``FSINFO_ATTR_TIMESTAMP_INFO``
+
+ This struct-type attribute conveys information about the resolution and
+ range of the timestamps available in a filesystem. The resolutions are
+ given as a mantissa and exponent (resolution = mantissa * 10^exponent
+ seconds), where the exponent can be negative to indicate a sub-second
+ resolution (-9 being nanoseconds, for example).
+
+ * ``FSINFO_ATTR_VOLUME_ID``
+
+ This is a string-type attribute that conveys the superblock identifier for
+ the volume. By default it will be filled in from the contents of s_id from
+ the superblock. For a block-based filesystem, for example, this might be
+ the name of the primary block device.
+
+ * ``FSINFO_ATTR_VOLUME_UUID``
+
+ This is a struct-type attribute that conveys the UUID identifier for the
+ volume. By default it will be filled in from the contents of s_uuid from
+ the superblock. If this doesn't exist, it will be an entirely zeros.
+
+ * ``FSINFO_ATTR_VOLUME_NAME``
+
+ This is a string-type attribute that conveys the name of the volume. By
+ default it will return EOPNOTSUPP. For a disk-based filesystem, it might
+ convey the partition label; for a network-based filesystem, it might convey
+ the name of the remote volume.
+
+ * ``FSINFO_ATTR_NAME_ENCODING``
+
+ This is a string-type attribute that returns the type of encoding used for
+ filenames in the medium. By default this will be filled in with "utf8".
+ Not all filesystems can support that, however, so this may indicate a
+ restriction on what characters can be used.
+
+ * ``FSINFO_ATTR_NAME_CODEPAGE``
+
+ This is a string-type attribute that returns the name of the codepage used
+ to transliterate a Linux utf8 filename into whatever the medium supports.
+ By default it returns EOPNOTSUPP.
+
+
+The next attributes give information about the mount parameter parsers and the
+mount parameters values stored in a superblock and its security data. The
+first few of these can be queried on the file descriptor returned by fsopen()
+before any superblock is attached:
+
+ * ``FSINFO_ATTR_PARAM_DESCRIPTION``
+
+ This is a struct-type attribute that returns summary information about what
+ mount options are available on a filesystem, including the number of
+ parameters and the number of enum symbols.
+
+ * ``FSINFO_ATTR_PARAM_SPECIFICATION``
+
+ This is a 1D array of struct-type attributes, indicating the type,
+ qualifiers, name and an option ID for the Nth mount parameter. Parameters
+ that have the same option ID are presumed to be synonyms.
+
+ * ``FSINFO_ATTR_PARAM_ENUM``
+
+ This is a 1D array of struct-type attributes, indicating the Nth value
+ symbol for the set of enumeration-type parameters. All the values are in
+ the same table, so they can be matched to the parameter by option ID, and
+ each option ID may have several entries, each with a different name.
+
+ * ``FSINFO_ATTR_PARAMETERS``
+ * ``FSINFO_ATTR_LSM_PARAMETERS``
+
+ These are a pair of opaque blobs that list all the mount parameter values
+ currently set on a superblock. The first set come from the filesystem and
+ the second is from the LSMs - and, as such, convey security information,
+ such as labelling.
+
+ Inside the filesystem or LSM, the parameter values should be read in one go
+ under lock to avoid races with remount if necessary.
+
+ Each opaque blob is encoded as a series of pairs of elements, where each
+ element begins with a length. The first element of each pair is the key
+ name and the second is the value (which may contain commas, binary data,
+ NUL chars).
+
+ An element length is encoded as a series of bytes in most->least signifcant
+ order. Each byte contributes 7 bits to the length. The MSB in each byte
+ is set if there's another byte of length information following on (ie. all
+ but the last byte in the length have the MSB set).
+
+ A number of helper functions are provided to help record the parameters::
+
+ fsinfo_note_sb_params()
+ fsinfo_note_param()
+ fsinfo_note_paramf()
+
+ Note that the first is not applicable to LSM parameters. It is called
+ automatically if the filesystem doesn't implement the attribute, but must,
+ and should, be called manually otherwise. It should also be called first,
+ before noting any other parameters.
+
+
+Then there are filesystem-specific attributes.
+
+ * ``FSINFO_ATTR_SERVER_NAME``
+
+ This is a string-type attribute that conveys the name of the Nth server
+ backing a network-filesystem superblock.
+
+ * ``FSINFO_ATTR_SERVER_ADDRESS``
+
+ This is a struct-type attribute that conveys the Mth address of the Nth
+ server, as returned by FSINFO_ATTR_SERVER_NAME.
+
+ * ``FSINFO_ATTR_AFS_CELL_NAME``
+
+ This is a string-type attribute that retrieves the AFS cell name of the
+ target object.
+
+
+Lastly, one attribute gives information about fsinfo() itself:
+
+ * ``FSINFO_ATTR_FSINFO``
+
+ This struct-type attribute gives information about the fsinfo() system call
+ itself, including the maximum number of attributes supported and the
+ maximum number of capability bits supported.
+
+
+Capability Bit Catalogue
+========================
+
+The capability bits convey single true/false assertions about a specific
+instance of a filesystem (ie. a specific superblock). They are accessed using
+the "FSINFO_ATTR_CAPABILITY" attribute:
+
+ * ``FSINFO_CAP_IS_KERNEL_FS``
+ * ``FSINFO_CAP_IS_BLOCK_FS``
+ * ``FSINFO_CAP_IS_FLASH_FS``
+ * ``FSINFO_CAP_IS_NETWORK_FS``
+ * ``FSINFO_CAP_IS_AUTOMOUNTER_FS``
+ * ``FSINFO_CAP_IS_MEMORY_FS``
+
+ These indicate what kind of filesystem the target is: kernel API (proc),
+ block-based (ext4), flash/nvm-based (jffs2), remote over the network (NFS),
+ local quasi-filesystem that acts as a tray of mountpoints (autofs), plain
+ in-memory filesystem (shmem).
+
+ * ``FSINFO_CAP_AUTOMOUNTS``
+
+ This indicate if a filesystem may have objects that are automount points.
+
+ * ``FSINFO_CAP_ADV_LOCKS``
+ * ``FSINFO_CAP_MAND_LOCKS``
+ * ``FSINFO_CAP_LEASES``
+
+ These indicate if a filesystem supports advisory locks, mandatory locks or
+ leases.
+
+ * ``FSINFO_CAP_UIDS``
+ * ``FSINFO_CAP_GIDS``
+ * ``FSINFO_CAP_PROJIDS``
+
+ These indicate if a filesystem supports/stores/transports numeric user IDs,
+ group IDs or project IDs. The "FSINFO_ATTR_LIMITS" attribute can be used
+ to find out the upper limits on the IDs values.
+
+ * ``FSINFO_CAP_STRING_USER_IDS``
+
+ This indicates if a filesystem supports/stores/transports string user
+ identifiers.
+
+ * ``FSINFO_CAP_GUID_USER_IDS``
+
+ This indicates if a filesystem supports/stores/transports Windows GUIDs as
+ user identifiers (eg. ntfs).
+
+ * ``FSINFO_CAP_WINDOWS_ATTRS``
+
+ This indicates if a filesystem supports Windows FILE_* attribute bits
+ (eg. cifs, jfs). The "FSINFO_ATTR_SUPPORTS" attribute can be used to find
+ out which windows file attributes are supported by the filesystem.
+
+ * ``FSINFO_CAP_USER_QUOTAS``
+ * ``FSINFO_CAP_GROUP_QUOTAS``
+ * ``FSINFO_CAP_PROJECT_QUOTAS``
+
+ These indicate if a filesystem supports quotas for users, groups or
+ projects.
+
+ * ``FSINFO_CAP_XATTRS``
+
+ These indicate if a filesystem supports extended attributes. The
+ "FSINFO_ATTR_LIMITS" attribute can be used to find out the upper limits on
+ the supported name and body lengths.
+
+ * ``FSINFO_CAP_JOURNAL``
+ * ``FSINFO_CAP_DATA_IS_JOURNALLED``
+
+ These indicate whether the filesystem has a journal and whether data
+ changes are logged to it.
+
+ * ``FSINFO_CAP_O_SYNC``
+ * ``FSINFO_CAP_O_DIRECT``
+
+ These indicate whether the filesystem supports the O_SYNC and O_DIRECT
+ flags.
+
+ * ``FSINFO_CAP_VOLUME_ID``
+ * ``FSINFO_CAP_VOLUME_UUID``
+ * ``FSINFO_CAP_VOLUME_NAME``
+ * ``FSINFO_CAP_VOLUME_FSID``
+
+ These indicate whether ID, UUID, name and FSID identifiers actually exist
+ in the filesystem and thus might be considered persistent.
+
+ * ``FSINFO_CAP_IVER_ALL_CHANGE``
+ * ``FSINFO_CAP_IVER_DATA_CHANGE``
+ * ``FSINFO_CAP_IVER_MONO_INCR``
+
+ These indicate whether i_version in the inode is supported and, if so, what
+ mode it operates in. The first two indicate if it's changed for any data
+ or metadata change, or whether it's only changed for any data changes; the
+ last indicates whether or not it's monotonically increasing for each such
+ change.
+
+ * ``FSINFO_CAP_HARD_LINKS``
+ * ``FSINFO_CAP_HARD_LINKS_1DIR``
+
+ These indicate whether the filesystem can have hard links made in it, and
+ whether they can be made between directory or only within the same
+ directory.
+
+ * ``FSINFO_CAP_DIRECTORIES``
+ * ``FSINFO_CAP_SYMLINKS``
+ * ``FSINFO_CAP_DEVICE_FILES``
+ * ``FSINFO_CAP_UNIX_SPECIALS``
+
+ These indicate whether directories; symbolic links; device files; or pipes
+ and sockets can be made within the filesystem.
+
+ * ``FSINFO_CAP_RESOURCE_FORKS``
+
+ This indicates if the filesystem supports resource forks.
+
+ * ``FSINFO_CAP_NAME_CASE_INDEP``
+ * ``FSINFO_CAP_NAME_NON_UTF8``
+ * ``FSINFO_CAP_NAME_HAS_CODEPAGE``
+
+ These indicate if the filesystem supports case-independent file names,
+ whether the filenames are non-utf8 (see the "FSINFO_ATTR_NAME_ENCODING"
+ attribute) and whether a codepage is in use to transliterate them (see
+ the "FSINFO_ATTR_NAME_CODEPAGE" attribute).
+
+ * ``FSINFO_CAP_SPARSE``
+
+ This indicates if a filesystem supports sparse files.
+
+ * ``FSINFO_CAP_NOT_PERSISTENT``
+
+ This indicates if a filesystem is not persistent.
+
+ * ``FSINFO_CAP_NO_UNIX_MODE``
+
+ This indicates if a filesystem doesn't support UNIX mode bits (though they
+ may be manufactured from other bits, such as Windows file attribute flags).
+
+ * ``FSINFO_CAP_HAS_ATIME``
+ * ``FSINFO_CAP_HAS_BTIME``
+ * ``FSINFO_CAP_HAS_CTIME``
+ * ``FSINFO_CAP_HAS_MTIME``
+
+ These indicate which timestamps a filesystem supports (access, birth,
+ change, modify). The range and resolutions can be queried with the
+ "FSINFO_ATTR_TIMESTAMPS" attribute).
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 <[email protected]>
---
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 92906a3f4010..45bfb10eb8d9 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -368,7 +368,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;
@@ -376,11 +377,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);
@@ -573,6 +581,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 11c4a74638b0..0d57d37311fc 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 8cf5b02e333a..f865bc1af16f 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 {
@@ -459,6 +461,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;
}
@@ -505,7 +508,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)
{
Add fsinfo support to the AFS filesystem.
Signed-off-by: David Howells <[email protected]>
---
fs/afs/internal.h | 1
fs/afs/super.c | 180 +++++++++++++++++++++++++++++++++++++++++++
fs/fsinfo.c | 3 +
include/uapi/linux/fsinfo.h | 12 +++
samples/vfs/test-fsinfo.c | 33 ++++++++
5 files changed, 227 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..d6c9742c0165 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,170 @@ static int afs_statfs(struct dentry *dentry, struct kstatfs *buf)
return ret;
}
+
+#ifdef CONFIG_FSINFO
+static const struct fsinfo_timestamp_info afs_timestamp_info = {
+ .atime = {
+ .minimum = 0,
+ .maximum = UINT_MAX,
+ .gran_mantissa = 1,
+ .gran_exponent = 0,
+ },
+ .mtime = {
+ .minimum = 0,
+ .maximum = UINT_MAX,
+ .gran_mantissa = 1,
+ .gran_exponent = 0,
+ },
+ .ctime = {
+ .minimum = 0,
+ .maximum = UINT_MAX,
+ .gran_mantissa = 1,
+ .gran_exponent = 0,
+ },
+ .btime = {
+ .minimum = 0,
+ .maximum = UINT_MAX,
+ .gran_mantissa = 1,
+ .gran_exponent = 0,
+ },
+};
+
+/*
+ * 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 = afs_timestamp_info;
+ 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_AFS_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:
+ fsinfo_note_sb_params(params, dentry->d_sb->s_flags);
+ 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 45bfb10eb8d9..b3f605c39eb5 100644
--- a/fs/fsinfo.c
+++ b/fs/fsinfo.c
@@ -582,6 +582,9 @@ 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_STRING_N (SERVER_NAME),
+ FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
+ FSINFO_STRING (AFS_CELL_NAME),
};
/**
diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
index 0d57d37311fc..58a50207256f 100644
--- a/include/uapi/linux/fsinfo.h
+++ b/include/uapi/linux/fsinfo.h
@@ -32,6 +32,9 @@ 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_SERVER_NAME = 17, /* Name of the Nth server (string) */
+ FSINFO_ATTR_SERVER_ADDRESS = 18, /* Mth address of the Nth server */
+ FSINFO_ATTR_AFS_CELL_NAME = 19, /* AFS cell name (string) */
FSINFO_ATTR__NR
};
@@ -276,4 +279,13 @@ struct fsinfo_param_enum {
char name[252]; /* Name of the enum value */
};
+/*
+ * 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 f865bc1af16f..6389ae781cbb 100644
--- a/samples/vfs/test-fsinfo.c
+++ b/samples/vfs/test-fsinfo.c
@@ -83,6 +83,9 @@ 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_STRING_N (SERVER_NAME, server_name),
+ FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
+ FSINFO_STRING (AFS_CELL_NAME, -),
};
#define FSINFO_NAME(X,Y) [FSINFO_ATTR_##X] = #Y
@@ -104,6 +107,9 @@ 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 (SERVER_NAME, server_name),
+ FSINFO_NAME (SERVER_ADDRESS, server_address),
+ FSINFO_NAME (AFS_CELL_NAME, afs_cell_name),
};
union reply {
@@ -116,6 +122,7 @@ union reply {
struct fsinfo_capabilities caps;
struct fsinfo_timestamp_info timestamps;
struct fsinfo_volume_uuid uuid;
+ struct fsinfo_server_address srv_addr;
};
static void dump_hex(unsigned int *data, int from, int to)
@@ -318,6 +325,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);
+}
+
/*
*
*/
@@ -333,6 +365,7 @@ static const dumper_t fsinfo_attr_dumper[FSINFO_ATTR__NR] = {
FSINFO_DUMPER(CAPABILITIES),
FSINFO_DUMPER(TIMESTAMP_INFO),
FSINFO_DUMPER(VOLUME_UUID),
+ FSINFO_DUMPER(SERVER_ADDRESS),
};
static void dump_fsinfo(enum fsinfo_attribute attr,
Add support for fsinfo().
Signed-off-by: David Howells <[email protected]>
---
fs/hugetlbfs/inode.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
index 1dcc57189382..62d5d5dec447 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,59 @@ 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:
+ fsinfo_note_sb_params(params, dentry->d_sb->s_flags);
+ 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 +1170,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
};
/*
Add support for fsinfo() to kernfs and cgroup.
Signed-off-by: David Howells <[email protected]>
---
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 217cec4e22c6..2e18e5f9ad70 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);
@@ -5507,6 +5523,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,
From: Ian Kent <[email protected]>
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 <[email protected]>
Signed-off-by: David Howells <[email protected]>
---
fs/proc/inode.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 5f8d215b3fd0..28c287aff6aa 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,39 @@ 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:
+ fsinfo_note_sb_params(params, sb->s_flags);
+ 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 +156,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};
On Fri, Jun 28, 2019 at 04:43:45PM +0100, David Howells wrote:
> 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;
> __u64 __reserved[3];
> };
>
> 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 <[email protected]>
> cc: [email protected]
> ---
>
> 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 | 545 ++++++++++++++++++++++++++++++++
> include/linux/fs.h | 5
> include/linux/fsinfo.h | 65 ++++
> include/linux/syscalls.h | 4
> include/uapi/asm-generic/unistd.h | 4
> include/uapi/linux/fsinfo.h | 219 +++++++++++++
> kernel/sys_ni.c | 1
> samples/vfs/Makefile | 4
> samples/vfs/test-fsinfo.c | 551 ++++++++++++++++++++++++++++++++
> 13 files changed, 1407 insertions(+), 1 deletion(-)
> 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
Hm, any reason why we would hide that syscalls under a config option?
> + 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..09e743b16235
> --- /dev/null
> +++ b/fs/fsinfo.c
> @@ -0,0 +1,545 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Filesystem information query.
> + *
> + * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
> + * Written by David Howells ([email protected])
> + */
> +#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;
> +}
> +
> +/*
> + * 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.hi = 0;
> + p->f_blocks.lo = buf.f_blocks;
> + p->f_bfree.hi = 0;
> + p->f_bfree.lo = buf.f_bfree;
> + p->f_bavail.hi = 0;
> + p->f_bavail.lo = buf.f_bavail;
> + p->f_files.hi = 0;
> + p->f_files.lo = buf.f_files;
> + p->f_ffree.hi = 0;
> + p->f_ffree.lo = buf.f_ffree;
> + p->f_favail.hi = 0;
> + p->f_favail.lo = 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);
> + 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.hi = 0;
> + lim->max_file_size.lo = 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 const struct fsinfo_timestamp_info fsinfo_default_timestamp_info = {
> + .atime = {
> + .minimum = S64_MIN,
> + .maximum = S64_MAX,
> + .gran_mantissa = 1,
> + .gran_exponent = 0,
> + },
> + .mtime = {
> + .minimum = S64_MIN,
> + .maximum = S64_MAX,
> + .gran_mantissa = 1,
> + .gran_exponent = 0,
> + },
> + .ctime = {
> + .minimum = S64_MIN,
> + .maximum = S64_MAX,
> + .gran_mantissa = 1,
> + .gran_exponent = 0,
> + },
> + .btime = {
> + .minimum = S64_MIN,
> + .maximum = S64_MAX,
> + .gran_mantissa = 1,
> + .gran_exponent = 0,
> + },
> +};
> +
> +static int fsinfo_generic_timestamp_info(struct path *path,
> + struct fsinfo_timestamp_info *ts)
> +{
> + struct super_block *sb = path->dentry->d_sb;
> + s8 exponent;
> +
> + *ts = fsinfo_default_timestamp_info;
> +
> +
nit: redundant newline
> + 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;
> +
> + ts->atime.gran_exponent = exponent;
> + ts->mtime.gran_exponent = exponent;
> + ts->ctime.gran_exponent = exponent;
> + ts->btime.gran_exponent = exponent;
> + }
> +
> + 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;
Do we, not have any dumb helpers for scenarios like this?:
#define strlen_literal(x) (sizeof(""x"") - 1)
#define strlen_array(x) (sizeof(x) - 1)
Repeating sizeof(bla) - 1 seems like a good way to forget that -1 later
on :)
> +}
> +
> +/*
> + * 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;
> + }
> +}
[1]:
*grumble* *grumble*
Formal complaint about these code-generating macros again. :)
But fine. :)
> +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 (IS_ERR_VALUE((long)ret))
> + return ret; /* Error */
> + if ((unsigned int)ret <= params->buf_size)
if ((size_t)ret ...? Just for the sake of clarity if for nothing else.
> + return ret; /* It fitted */
Ok, a little confused here, tbh. params->buf_size is size_t and this
function returns an int. Forgot whether you mentioned this before,
buf_size exceed can't exceed INT_MAX?
> + 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 *pathname,
> + 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, pathname, 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 - a fixed-size structure with only one instance.
> + * STRUCT_N - a sequence of STRUCTs, indexed by Nth
> + * STRUCT_NM - a sequence of sequences of STRUCTs, indexed by Nth, Mth
> + * STRING - a string with only one instance.
> + * STRING_N - a sequence of STRING, indexed by Nth
> + * STRING_NM - a sequence of sequences of STRING, indexed by Nth, Mth
> + * OPAQUE - a blob that can be larger than 4K.
> + * STRUCT_ARRAY - 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) [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) [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) [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) [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),
> + FSINFO_STRUCT (VOLUME_UUID, volume_uuid),
> + FSINFO_STRING (VOLUME_NAME),
> + FSINFO_STRING (NAME_ENCODING),
> + FSINFO_STRING (NAME_CODEPAGE),
> +};
See [1]. :)
Is it really wort it to have this code generating stuff in there?
I urge you to think about git grep users. For them this is an absolute
nightmare. :)
It's also annoying because one needs to expand the macro to review the
fsinfo() syscalls below that switches on a lot of the stuff you define
here.
> +
> +/**
> + * sys_fsinfo - System call to get filesystem information
> + * @dfd: Base directory to pathwalk from or fd referring to filesystem.
> + * @pathname: 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 *, pathname,
> + 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 kparams;
> + unsigned int result_size;
Wouldn't it be better if this could be a size_t?
> + int ret;
> +
> + memset(&kparams, 0, sizeof(kparams));
> +
> + 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;
> + kparams.at_flags = user_params.at_flags;
> + kparams.request = user_params.request;
> + kparams.Nth = user_params.Nth;
> + kparams.Mth = user_params.Mth;
> + } else {
> + kparams.request = FSINFO_ATTR_STATFS;
> + }
> +
> + if (!user_buffer || !user_buf_size) {
Maybe we could be a little more strict and require both be set to their
respective zero values, i.e. only support reporting the size if
!user_buffer && user_buf_size = 0 for that to work. If only one of them
is set to their zero value we report EINVAL.
> + 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[kparams.request];
> + if (kparams.Nth != 0 && !(info.flags & (__FSINFO_N | __FSINFO_NM)))
> + return -ENODATA;
> + if (kparams.Mth != 0 && !(info.flags & __FSINFO_NM))
> + return -ENODATA;
> +
> + switch (info.type) {
> + case __FSINFO_STRUCT:
> + kparams.buf_size = info.size;
> + if (user_buf_size == 0)
> + return info.size; /* We know how big the buffer should be */
> + break;
> +
> + case __FSINFO_STRING:
> + kparams.buf_size = FSINFO_NORMAL_ATTR_MAX_SIZE;
> + 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;
> + kparams.scratch_buffer = kmalloc(FSINFO_SCRATCH_BUFFER_SIZE,
> + GFP_KERNEL);
> + if (!kparams.scratch_buffer)
> + goto error;
> + kparams.overlarge = true;
> + kparams.buf_size = FSINFO_NORMAL_ATTR_MAX_SIZE;
> + 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;
> + kparams.buffer = kvzalloc(kparams.buf_size, GFP_KERNEL);
> + if (!kparams.buffer)
> + goto error_scratch;
> +
> + if (pathname)
> + ret = vfs_fsinfo_path(dfd, pathname, &kparams);
> + else
> + ret = vfs_fsinfo_fd(dfd, &kparams);
> + 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, kparams.buffer, result_size) != 0) {
> + 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(kparams.buffer);
> +error_scratch:
> + kfree(kparams.scratch_buffer);
> +error:
> + return ret;
> +}
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index f7fdfe93e25d..50f58eac3e1f 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..4c250136d693
> --- /dev/null
> +++ b/include/linux/fsinfo.h
> @@ -0,0 +1,65 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Filesystem information query
> + *
> + * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
> + * Written by David Howells ([email protected])
> + */
> +
> +#ifndef _LINUX_FSINFO_H
> +#define _LINUX_FSINFO_H
> +
> +#ifdef CONFIG_FSINFO
> +
> +#include <uapi/linux/fsinfo.h>
> +
> +#define FSINFO_NORMAL_ATTR_MAX_SIZE 4096
> +#define FSINFO_SCRATCH_BUFFER_SIZE 4096
> +
> +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 (if overlarge=T) */
> + unsigned int buf_size; /* Size of ->buffer[] */
> + void *buffer; /* Where to place the reply */
> + char *scratch_buffer; /* 4K scratch buffer (if 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.
Hm, I'm not sure that "capabilities" is a good name here. This is
potentially misleading because of other uses of "capabilities" we
already have. Like, I don't want thes capabilities to pop up when I do
git grep capabilities. Just a short way until someone also speaks of
"fscaps" or "fsinfocaps" and then confusion is basically guaranteed. :)
Maybe "features" would be better?
> + * @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..958ac427ff37 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 *pathname,
> + struct fsinfo_params __user *params,
> + void __user *buffer, size_t buf_size);
>
> /*
> * Architecture-specific system calls
> diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
> index a87904daf103..50ddf5f25122 100644
> --- a/include/uapi/asm-generic/unistd.h
> +++ b/include/uapi/asm-generic/unistd.h
> @@ -844,9 +844,11 @@ __SYSCALL(__NR_fsconfig, sys_fsconfig)
> __SYSCALL(__NR_fsmount, sys_fsmount)
> #define __NR_fspick 433
> __SYSCALL(__NR_fspick, sys_fspick)
> +#define __NR_fsinfo 434
> +__SYSCALL(__NR_fsinfo, sys_fsinfo)
>
> #undef __NR_syscalls
> -#define __NR_syscalls 434
> +#define __NR_syscalls 435
>
> /*
> * 32 bit systems traditionally used different
> diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
> new file mode 100644
> index 000000000000..cc7e13a9b95f
> --- /dev/null
> +++ b/include/uapi/linux/fsinfo.h
> @@ -0,0 +1,219 @@
> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
> +/* fsinfo() definitions.
> + *
> + * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
> + * Written by David Howells ([email protected])
> + */
> +#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 */
> +};
> +
> +struct fsinfo_u128 {
> +#if defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : defined(__BIG_ENDIAN)
> + __u64 hi;
> + __u64 lo;
> +#elif defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : defined(__LITTLE_ENDIAN)
> + __u64 lo;
> + __u64 hi;
> +#endif
> +};
> +
> +/*
> + * Information struct for fsinfo(fsinfo_attr_statfs).
> + * - This gives extended filesystem information.
> + */
> +struct fsinfo_statfs {
> + struct fsinfo_u128 f_blocks; /* Total number of blocks in fs */
> + struct fsinfo_u128 f_bfree; /* Total number of free blocks */
> + struct fsinfo_u128 f_bavail; /* Number of free blocks available to ordinary user */
> + struct fsinfo_u128 f_files; /* Total number of file nodes in fs */
> + struct fsinfo_u128 f_ffree; /* Number of free file nodes */
> + struct fsinfo_u128 f_favail; /* Number of file nodes available to ordinary user */
> + __u64 f_bsize; /* Optimal block size */
> + __u64 f_frsize; /* Fragment size */
> + __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]; /* Filesystem name */
> + __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 {
> + struct fsinfo_u128 max_file_size; /* Maximum file size */
> + struct fsinfo_u128 max_ino; /* Maximum inode number */
> + __u64 max_uid; /* Maximum UID supported */
> + __u64 max_gid; /* Maximum GID supported */
> + __u64 max_projid; /* Maximum project ID supported */
> + __u64 max_hard_links; /* Maximum number of hard links on a file */
> + __u64 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 max_dev_major; /* Maximum device major representable */
> + __u32 max_dev_minor; /* Maximum device minor representable */
> + __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 {
Again, something other than "capability" might bet better, e.g.
"features".
> + 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];
> +};
> +
> +struct fsinfo_timestamp_one {
> + __s64 minimum; /* Minimum timestamp value in seconds */
> + __u64 maximum; /* Maximum timestamp value in seconds */
> + __u16 gran_mantissa; /* Granularity(secs) = mant * 10^exp */
> + __s8 gran_exponent;
> + __u8 reserved[5];
> +};
> +
> +/*
> + * Information struct for fsinfo(fsinfo_attr_timestamp_info).
> + */
> +struct fsinfo_timestamp_info {
> + struct fsinfo_timestamp_one atime; /* Access time */
> + struct fsinfo_timestamp_one mtime; /* Modification time */
> + struct fsinfo_timestamp_one ctime; /* Change time */
> + struct fsinfo_timestamp_one btime; /* Birth/creation time */
> +};
> +
> +/*
> + * 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..93927072396c 100644
> --- a/kernel/sys_ni.c
> +++ b/kernel/sys_ni.c
> @@ -51,6 +51,7 @@ COND_SYSCALL_COMPAT(io_pgetevents);
> COND_SYSCALL(io_uring_setup);
> COND_SYSCALL(io_uring_enter);
> COND_SYSCALL(io_uring_register);
> +COND_SYSCALL(fsinfo);
>
> /* fs/xattr.c */
>
> 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..8cce1986df7e
> --- /dev/null
> +++ b/samples/vfs/test-fsinfo.c
> @@ -0,0 +1,551 @@
> +/* Test the fsinfo() system call
> + *
> + * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
> + * Written by David Howells ([email protected])
> + *
> + * 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
nit: Defining fsinfoat() implicitly or what's that supposed to do? If that's
the case wouldn't it be nicer to just explicitly declare fsinfoat()
> +#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))
See [1] above but here it's less of an issue since this is a test file.
I missed that in the first review. :)
> +
> +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.lo,
> + (unsigned long long)f->f_bfree.lo,
> + (unsigned long long)f->f_bavail.lo);
> +
> + printf("\tfiles : n=%llu fr=%llu av=%llu\n",
> + (unsigned long long)f->f_files.lo,
> + (unsigned long long)f->f_ffree.lo,
> + (unsigned long long)f->f_favail.lo);
> + printf("\tbsize : %llu\n", f->f_bsize);
> + printf("\tfrsize: %llu\n", f->f_frsize);
> + printf("\tmntfl : %llx\n", (unsigned long long)f->mnt_attrs);
> +}
> +
> +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%016llx\n",
> + (unsigned long long)f->max_file_size.hi,
> + (unsigned long long)f->max_file_size.lo);
> + printf("\tmax ino: %llx%016llx\n",
> + (unsigned long long)f->max_ino.hi,
> + (unsigned long long)f->max_ino.lo);
> + 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 : %llx\n",
> + (unsigned long long)f->max_hard_links);
> + printf("\tmax xattr : n=%x b=%llx\n",
> + f->max_xattr_name_len,
> + (unsigned long long)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 print_time(struct fsinfo_timestamp_one *t, char stamp)
> +{
> + printf("\t%ctime : gran=%gs range=%llx-%llx\n",
> + stamp,
> + t->gran_mantissa * pow(10., t->gran_exponent),
> + (long long)t->minimum,
> + (long long)t->maximum);
> +}
> +
> +static void dump_attr_TIMESTAMP_INFO(union reply *r, int size)
> +{
> + struct fsinfo_timestamp_info *f = &r->timestamps;
> +
> + printf("\n");
> + print_time(&f->atime, 'a');
> + print_time(&f->mtime, 'm');
> + print_time(&f->ctime, 'c');
> + print_time(&f->btime, 'b');
> +}
> +
> +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;
> +}
>
Christian Brauner <[email protected]> wrote:
> > +config FSINFO
>
> Hm, any reason why we would hide that syscalls under a config option?
Rasmus Villemoes asked for it to be made conditional.
https://lore.kernel.org/lkml/[email protected]/
> Do we, not have any dumb helpers for scenarios like this?:
>
> #define strlen_literal(x) (sizeof(""x"") - 1)
> #define strlen_array(x) (sizeof(x) - 1)
git grep doesn't find them under this name.
> > + while (!signal_pending(current)) {
> > + params->usage = 0;
> > + ret = fsinfo(path, params);
> > + if (IS_ERR_VALUE((long)ret))
> > + return ret; /* Error */
> > + if ((unsigned int)ret <= params->buf_size)
>
> if ((size_t)ret ...? Just for the sake of clarity if for nothing else.
>
> > + return ret; /* It fitted */
>
> Ok, a little confused here, tbh. params->buf_size is size_t
It's "unsigned int".
> and this function returns an int. Forgot whether you mentioned this before,
> buf_size exceed can't exceed INT_MAX?
It's mentioned in the documentation (ie. fsinfo.rst). I'll mention it in the
comments adjacent to the attribute definition table also.
> Is it really wort it to have this code generating stuff in there?
From a readability PoV, yes, tabulation is awesome, IMO;-). Up to 5 lines per
attribute is too much vertical space and expanding it makes the whole thing
much less readable. Add to that that not all attributes will be the same
number of lines.
It would be easier if the I could get away with making the constant names
lower case, but the thou-shalt-capitalise-constantists dislike that, so, given
that I don't know of a way to make the C preprocessor change the case of a
symbol, I have to include both parts.
I have four pieces of information: type, depth, constant name, struct name (if
applicable), and I can fit them on one line this way.
You really find this:
static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
[FSINFO_ATTR_STATFS] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_statfs)
},
[FSINFO_ATTR_FSINFO] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_fsinfo)
},
[FSINFO_ATTR_IDS] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_ids)
},
[FSINFO_ATTR_LIMITS] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_limits)
},
[FSINFO_ATTR_CAPABILITIES] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_capabilities)
},
[FSINFO_ATTR_SUPPORTS] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_supports)
},
[FSINFO_ATTR_TIMESTAMP_INFO] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_timestamp_info)
},
[FSINFO_ATTR_VOLUME_ID] = {
.type = __FSINFO_STRING,
.flags = __FSINFO_SINGLE,
},
[FSINFO_ATTR_VOLUME_UUID] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_volume_uuid)
},
[FSINFO_ATTR_VOLUME_NAME] = {
.type = __FSINFO_STRING,
.flags = __FSINFO_SINGLE,
},
[FSINFO_ATTR_NAME_ENCODING] = {
.type = __FSINFO_STRING,
.flags = __FSINFO_SINGLE,
},
[FSINFO_ATTR_NAME_CODEPAGE] = {
.type = __FSINFO_STRING,
.flags = __FSINFO_SINGLE,
},
[FSINFO_ATTR_PARAM_DESCRIPTION] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_param_description)
},
[FSINFO_ATTR_PARAM_SPECIFICATION] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_N,
.size = sizeof(struct fsinfo_param_specification)
},
[FSINFO_ATTR_PARAM_ENUM] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_N,
.size = sizeof(struct fsinfo_param_enum)
},
[FSINFO_ATTR_PARAMETERS] = {
.type = __FSINFO_OPAQUE,
.flags = __FSINFO_SINGLE,
},
[FSINFO_ATTR_LSM_PARAMETERS] = {
.type = __FSINFO_OPAQUE,
.flags = __FSINFO_SINGLE,
},
[FSINFO_ATTR_SERVER_NAME] = {
.type = __FSINFO_STRING,
.flags = __FSINFO_N,
},
[FSINFO_ATTR_SERVER_ADDRESS] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_NM,
.size = sizeof(struct fsinfo_server_address)
},
[FSINFO_ATTR_AFS_CELL_NAME] = {
.type = __FSINFO_STRING,
.flags = __FSINFO_SINGLE,
},
[FSINFO_ATTR_MOUNT_INFO] = {
.type = __FSINFO_STRUCT,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_mount_info)
},
[FSINFO_ATTR_MOUNT_DEVNAME] = {
.type = __FSINFO_STRING,
.flags = __FSINFO_SINGLE,
},
[FSINFO_ATTR_MOUNT_CHILDREN] = {
.type = __FSINFO_STRUCT_ARRAY,
.flags = __FSINFO_SINGLE,
.size = sizeof(struct fsinfo_mount_child)
},
[FSINFO_ATTR_MOUNT_SUBMOUNT] = {
.type = __FSINFO_STRING,
.flags = __FSINFO_N,
},
};
is easier to read than this?:
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),
FSINFO_STRUCT (VOLUME_UUID, volume_uuid),
FSINFO_STRING (VOLUME_NAME),
FSINFO_STRING (NAME_ENCODING),
FSINFO_STRING (NAME_CODEPAGE),
FSINFO_STRUCT (PARAM_DESCRIPTION, param_description),
FSINFO_STRUCT_N (PARAM_SPECIFICATION, param_specification),
FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
FSINFO_OPAQUE (PARAMETERS),
FSINFO_OPAQUE (LSM_PARAMETERS),
FSINFO_STRING_N (SERVER_NAME),
FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
FSINFO_STRING (AFS_CELL_NAME),
FSINFO_STRUCT (MOUNT_INFO, mount_info),
FSINFO_STRING (MOUNT_DEVNAME),
FSINFO_STRUCT_ARRAY (MOUNT_CHILDREN, mount_child),
FSINFO_STRING_N (MOUNT_SUBMOUNT),
};
The latter also has the advantage that I can take this and drop it into the
test program and change the helper macros to make it do other things. With
the fully expanded code, that isn't possible.
One thing I will grant you, though, I can simplify:
#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) [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) [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) [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) [FSINFO_ATTR_##X] = _Z(OPAQUE, 0, 0)
#define FSINFO_STRUCT_ARRAY(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT_ARRAY, 0, sizeof(struct fsinfo_##Y))
a bit:
#define __FSINFO_STRUCT 0
#define __FSINFO_STRING 1
#define __FSINFO_OPAQUE 2
#define __FSINFO_STRUCT_ARRAY 3
#define __FSINFO_N 0x01
#define __FSINFO_NM 0x02
#define _Z(T, S) { .type = __FSINFO_##T, .flags = 0, .size = S }
#define _Z_N(T, S) { .type = __FSINFO_##T, .flags = __FSINFO_N, .size = S }
#define _Z_NM(T, S) { .type = __FSINFO_##T, .flags = __FSINFO_NM, .size = S }
#define FSINFO_STRING(X) [FSINFO_ATTR_##X] = _Z(STRING, 0)
#define FSINFO_STRUCT(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT, sizeof(struct fsinfo_##Y))
#define FSINFO_STRING_N(X) [FSINFO_ATTR_##X] = _Z_N(STRING, 0)
#define FSINFO_STRUCT_N(X,Y) [FSINFO_ATTR_##X] = _Z_N(STRUCT, sizeof(struct fsinfo_##Y))
#define FSINFO_STRING_NM(X) [FSINFO_ATTR_##X] = _Z_NM(STRING, 0)
#define FSINFO_STRUCT_NM(X,Y) [FSINFO_ATTR_##X] = _Z_NM(STRUCT, sizeof(struct fsinfo_##Y))
#define FSINFO_OPAQUE(X) [FSINFO_ATTR_##X] = _Z(OPAQUE, 0)
#define FSINFO_STRUCT_ARRAY(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT_ARRAY, sizeof(struct fsinfo_##Y))
> I urge you to think about git grep users. For them this is an absolute
> nightmare. :)
That's a valid point, but it's a problem all over the kernel. We use
macroisation everywhere. See all the declaration and define macros that nest
layers deep.
If that's your main worry, The attribute type name could be fully expanded in
the table, eg.:
FSINFO_STRUCT (FSINFO_ATTR_CAPABILITIES, capabilities),
FSINFO_STRING_N (FSINFO_ATTR_MOUNT_SUBMOUNT),
> > + unsigned int result_size;
>
> Wouldn't it be better if this could be a size_t?
Why? size_t takes more space on a 64-bit system, but I'm not allowing the
filesystem to return that much data, mainly because I don't really want to be
allocating a >2G buffer.
In fact, for large objects there's something to be said for writing directly
to userspace rather than going through a buffer, but for the fact that I want
to hold, say, the RCU readlock across the entire transaction in some
instances.
> > + if (!user_buffer || !user_buf_size) {
>
> Maybe we could be a little more strict and require both be set to their
> respective zero values, i.e. only support reporting the size if
> !user_buffer && user_buf_size = 0 for that to work. If only one of them
> is set to their zero value we report EINVAL.
That's an option, certainly.
> Hm, I'm not sure that "capabilities" is a good name here. This is
> potentially misleading because of other uses of "capabilities" we
> already have. Like, I don't want thes capabilities to pop up when I do
> git grep capabilities. Just a short way until someone also speaks of
> "fscaps" or "fsinfocaps" and then confusion is basically guaranteed. :)
>
> Maybe "features" would be better?
Yeah - that's probably better. The only issue is that it doesn't have a nice
short hypocoristicon like "cap", though I could use "feat" I guess.
> > +#define _ATFILE_SOURCE
>
> nit: Defining fsinfoat() implicitly or what's that supposed to do? If that's
> the case wouldn't it be nicer to just explicitly declare fsinfoat()
Um... fsinfo() takes AT_* flags. It's fsinfoat(), ffsinfo() and lfsinfo()
all rolled into one, plus a couple of extra bits. It doesn't really need an
at-suffix on the name as there's no at-less original.
David
On Mon, Jul 01, 2019 at 02:13:16PM +0100, David Howells wrote:
> Christian Brauner <[email protected]> wrote:
>
> > > +config FSINFO
> >
> > Hm, any reason why we would hide that syscalls under a config option?
>
> Rasmus Villemoes asked for it to be made conditional.
Ah, ok. I guess this is another case of "what about embedded users".
Fair enough.
>
> https://lore.kernel.org/lkml/[email protected]/
>
> > Do we, not have any dumb helpers for scenarios like this?:
> >
> > #define strlen_literal(x) (sizeof(""x"") - 1)
> > #define strlen_array(x) (sizeof(x) - 1)
>
> git grep doesn't find them under this name.
Yeah, than we don't have that. Might be worth having such helpers at
some point.
>
> > > + while (!signal_pending(current)) {
> > > + params->usage = 0;
> > > + ret = fsinfo(path, params);
> > > + if (IS_ERR_VALUE((long)ret))
> > > + return ret; /* Error */
> > > + if ((unsigned int)ret <= params->buf_size)
> >
> > if ((size_t)ret ...? Just for the sake of clarity if for nothing else.
> >
> > > + return ret; /* It fitted */
> >
> > Ok, a little confused here, tbh. params->buf_size is size_t
>
> It's "unsigned int".
Ok, good.
>
> > and this function returns an int. Forgot whether you mentioned this before,
> > buf_size exceed can't exceed INT_MAX?
>
> It's mentioned in the documentation (ie. fsinfo.rst). I'll mention it in the
> comments adjacent to the attribute definition table also.
Thanks! I missed that apparently.
>
> > Is it really wort it to have this code generating stuff in there?
>
> From a readability PoV, yes, tabulation is awesome, IMO;-). Up to 5 lines per
> attribute is too much vertical space and expanding it makes the whole thing
> much less readable. Add to that that not all attributes will be the same
> number of lines.
>
> It would be easier if the I could get away with making the constant names
> lower case, but the thou-shalt-capitalise-constantists dislike that, so, given
> that I don't know of a way to make the C preprocessor change the case of a
> symbol, I have to include both parts.
>
> I have four pieces of information: type, depth, constant name, struct name (if
> applicable), and I can fit them on one line this way.
>
> You really find this:
>
> static const struct fsinfo_attr_info fsinfo_buffer_info[FSINFO_ATTR__NR] = {
> [FSINFO_ATTR_STATFS] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_statfs)
> },
> [FSINFO_ATTR_FSINFO] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_fsinfo)
> },
> [FSINFO_ATTR_IDS] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_ids)
> },
> [FSINFO_ATTR_LIMITS] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_limits)
> },
> [FSINFO_ATTR_CAPABILITIES] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_capabilities)
> },
> [FSINFO_ATTR_SUPPORTS] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_supports)
> },
> [FSINFO_ATTR_TIMESTAMP_INFO] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_timestamp_info)
> },
> [FSINFO_ATTR_VOLUME_ID] = {
> .type = __FSINFO_STRING,
> .flags = __FSINFO_SINGLE,
> },
> [FSINFO_ATTR_VOLUME_UUID] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_volume_uuid)
> },
> [FSINFO_ATTR_VOLUME_NAME] = {
> .type = __FSINFO_STRING,
> .flags = __FSINFO_SINGLE,
> },
> [FSINFO_ATTR_NAME_ENCODING] = {
> .type = __FSINFO_STRING,
> .flags = __FSINFO_SINGLE,
> },
> [FSINFO_ATTR_NAME_CODEPAGE] = {
> .type = __FSINFO_STRING,
> .flags = __FSINFO_SINGLE,
> },
> [FSINFO_ATTR_PARAM_DESCRIPTION] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_param_description)
> },
> [FSINFO_ATTR_PARAM_SPECIFICATION] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_N,
> .size = sizeof(struct fsinfo_param_specification)
> },
> [FSINFO_ATTR_PARAM_ENUM] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_N,
> .size = sizeof(struct fsinfo_param_enum)
> },
> [FSINFO_ATTR_PARAMETERS] = {
> .type = __FSINFO_OPAQUE,
> .flags = __FSINFO_SINGLE,
> },
> [FSINFO_ATTR_LSM_PARAMETERS] = {
> .type = __FSINFO_OPAQUE,
> .flags = __FSINFO_SINGLE,
> },
> [FSINFO_ATTR_SERVER_NAME] = {
> .type = __FSINFO_STRING,
> .flags = __FSINFO_N,
> },
> [FSINFO_ATTR_SERVER_ADDRESS] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_NM,
> .size = sizeof(struct fsinfo_server_address)
> },
> [FSINFO_ATTR_AFS_CELL_NAME] = {
> .type = __FSINFO_STRING,
> .flags = __FSINFO_SINGLE,
> },
> [FSINFO_ATTR_MOUNT_INFO] = {
> .type = __FSINFO_STRUCT,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_mount_info)
> },
> [FSINFO_ATTR_MOUNT_DEVNAME] = {
> .type = __FSINFO_STRING,
> .flags = __FSINFO_SINGLE,
> },
> [FSINFO_ATTR_MOUNT_CHILDREN] = {
> .type = __FSINFO_STRUCT_ARRAY,
> .flags = __FSINFO_SINGLE,
> .size = sizeof(struct fsinfo_mount_child)
> },
> [FSINFO_ATTR_MOUNT_SUBMOUNT] = {
> .type = __FSINFO_STRING,
> .flags = __FSINFO_N,
> },
> };
>
> is easier to read than this?:
Yes, very much so imho. :)
>
> 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),
> FSINFO_STRUCT (VOLUME_UUID, volume_uuid),
> FSINFO_STRING (VOLUME_NAME),
> FSINFO_STRING (NAME_ENCODING),
> FSINFO_STRING (NAME_CODEPAGE),
> FSINFO_STRUCT (PARAM_DESCRIPTION, param_description),
> FSINFO_STRUCT_N (PARAM_SPECIFICATION, param_specification),
> FSINFO_STRUCT_N (PARAM_ENUM, param_enum),
> FSINFO_OPAQUE (PARAMETERS),
> FSINFO_OPAQUE (LSM_PARAMETERS),
> FSINFO_STRING_N (SERVER_NAME),
> FSINFO_STRUCT_NM (SERVER_ADDRESS, server_address),
> FSINFO_STRING (AFS_CELL_NAME),
> FSINFO_STRUCT (MOUNT_INFO, mount_info),
> FSINFO_STRING (MOUNT_DEVNAME),
> FSINFO_STRUCT_ARRAY (MOUNT_CHILDREN, mount_child),
> FSINFO_STRING_N (MOUNT_SUBMOUNT),
> };
>
> The latter also has the advantage that I can take this and drop it into the
> test program and change the helper macros to make it do other things. With
> the fully expanded code, that isn't possible.
>
> One thing I will grant you, though, I can simplify:
>
> #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) [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) [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) [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) [FSINFO_ATTR_##X] = _Z(OPAQUE, 0, 0)
> #define FSINFO_STRUCT_ARRAY(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT_ARRAY, 0, sizeof(struct fsinfo_##Y))
>
> a bit:
>
> #define __FSINFO_STRUCT 0
> #define __FSINFO_STRING 1
> #define __FSINFO_OPAQUE 2
> #define __FSINFO_STRUCT_ARRAY 3
> #define __FSINFO_N 0x01
> #define __FSINFO_NM 0x02
>
> #define _Z(T, S) { .type = __FSINFO_##T, .flags = 0, .size = S }
> #define _Z_N(T, S) { .type = __FSINFO_##T, .flags = __FSINFO_N, .size = S }
> #define _Z_NM(T, S) { .type = __FSINFO_##T, .flags = __FSINFO_NM, .size = S }
> #define FSINFO_STRING(X) [FSINFO_ATTR_##X] = _Z(STRING, 0)
> #define FSINFO_STRUCT(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT, sizeof(struct fsinfo_##Y))
> #define FSINFO_STRING_N(X) [FSINFO_ATTR_##X] = _Z_N(STRING, 0)
> #define FSINFO_STRUCT_N(X,Y) [FSINFO_ATTR_##X] = _Z_N(STRUCT, sizeof(struct fsinfo_##Y))
> #define FSINFO_STRING_NM(X) [FSINFO_ATTR_##X] = _Z_NM(STRING, 0)
> #define FSINFO_STRUCT_NM(X,Y) [FSINFO_ATTR_##X] = _Z_NM(STRUCT, sizeof(struct fsinfo_##Y))
> #define FSINFO_OPAQUE(X) [FSINFO_ATTR_##X] = _Z(OPAQUE, 0)
> #define FSINFO_STRUCT_ARRAY(X,Y) [FSINFO_ATTR_##X] = _Z(STRUCT_ARRAY, sizeof(struct fsinfo_##Y))
>
> > I urge you to think about git grep users. For them this is an absolute
> > nightmare. :)
>
> That's a valid point, but it's a problem all over the kernel. We use
> macroisation everywhere. See all the declaration and define macros that nest
> layers deep.
Well maybe we can stop doing it (at least for some stuff). :)
>
> If that's your main worry, The attribute type name could be fully expanded in
> the table, eg.:
>
> FSINFO_STRUCT (FSINFO_ATTR_CAPABILITIES, capabilities),
> FSINFO_STRING_N (FSINFO_ATTR_MOUNT_SUBMOUNT),
>
> > > + unsigned int result_size;
> >
> > Wouldn't it be better if this could be a size_t?
>
> Why? size_t takes more space on a 64-bit system, but I'm not allowing the
> filesystem to return that much data, mainly because I don't really want to be
> allocating a >2G buffer.
>
> In fact, for large objects there's something to be said for writing directly
> to userspace rather than going through a buffer, but for the fact that I want
> to hold, say, the RCU readlock across the entire transaction in some
> instances.
>
> > > + if (!user_buffer || !user_buf_size) {
> >
> > Maybe we could be a little more strict and require both be set to their
> > respective zero values, i.e. only support reporting the size if
> > !user_buffer && user_buf_size = 0 for that to work. If only one of them
> > is set to their zero value we report EINVAL.
>
> That's an option, certainly.
Ok, up to you. I find my suggestion a little cleaner.
>
> > Hm, I'm not sure that "capabilities" is a good name here. This is
> > potentially misleading because of other uses of "capabilities" we
> > already have. Like, I don't want thes capabilities to pop up when I do
> > git grep capabilities. Just a short way until someone also speaks of
> > "fscaps" or "fsinfocaps" and then confusion is basically guaranteed. :)
> >
> > Maybe "features" would be better?
>
> Yeah - that's probably better. The only issue is that it doesn't have a nice
> short hypocoristicon like "cap", though I could use "feat" I guess.
FEAT is probably ok. Not pretty but "cap" isn't either.
>
> > > +#define _ATFILE_SOURCE
> >
> > nit: Defining fsinfoat() implicitly or what's that supposed to do? If that's
> > the case wouldn't it be nicer to just explicitly declare fsinfoat()
>
> Um... fsinfo() takes AT_* flags. It's fsinfoat(), ffsinfo() and lfsinfo()
> all rolled into one, plus a couple of extra bits. It doesn't really need an
> at-suffix on the name as there's no at-less original.
Ah, seems like a very ancient macro...
On Fri, Jun 28, 2019 at 04:43:45PM +0100, David Howells wrote:
> 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;
> __u64 __reserved[3];
> };
>
> 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 <[email protected]>
> cc: [email protected]
> ---
>
> 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 | 545 ++++++++++++++++++++++++++++++++
> include/linux/fs.h | 5
> include/linux/fsinfo.h | 65 ++++
> include/linux/syscalls.h | 4
> include/uapi/asm-generic/unistd.h | 4
> include/uapi/linux/fsinfo.h | 219 +++++++++++++
> kernel/sys_ni.c | 1
> samples/vfs/Makefile | 4
> samples/vfs/test-fsinfo.c | 551 ++++++++++++++++++++++++++++++++
> 13 files changed, 1407 insertions(+), 1 deletion(-)
> 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..09e743b16235
> --- /dev/null
> +++ b/fs/fsinfo.c
> @@ -0,0 +1,545 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Filesystem information query.
> + *
> + * Copyright (C) 2019 Red Hat, Inc. All Rights Reserved.
> + * Written by David Howells ([email protected])
> + */
> +#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)
I totally forgot to mention this:
I had a patchset that extended statfs to also report back when a
mountpoint is shared, slave, private, or unbindable to avoid parsing
/proc/1/mountinfo which is unreliable and slow. I've given a lengthier
argument in the patchset I sent more than a year ago:
https://lkml.org/lkml/2018/5/25/397
Pretty please, make it possible to retrieve propagation attributes with
fsinfo(). We desperately need this and it's trivial to add imho.
> +{
> + 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;
> +}
On Fri, Jun 28, 2019 at 04:43:37PM +0100, David Howells wrote:
>
> 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 4096, no NUL).
>
> (3) Array of fixed-length structures (up to INT_MAX size).
>
> (4) Opaque blob (up to INT_MAX size).
>
> Attributes can have multiple values either as a sequence of values or a
> sequence-of-sequences of values and all the values of a particular
> attribute must be of the same type.
>
> Note that the values of an attribute *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) Information about what the fsinfo() syscall itself supports, including
> the number of attibutes supported and the number of capability bits
> supported.
>
> (5) Future patches will include information about the mount topology.
>
> 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_AFS_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-core
>
>
Where are the tests and man page for this system call? "Tests" meaning actual
automated tests in a commonly used test suite (e.g. LTP, kselftests, or
xfstests), not just a sample program.
- Eric
Here's a manpage for fsinfo(). It needs a little updating, but I've applied
some review comments that will require this updating anyway.
David
---
'\" t
.\" Copyright (c) 2018 David Howells <[email protected]>
.\"
.\" %%%LICENSE_START(VERBATIM)
.\" Permission is granted to make and distribute verbatim copies of this
.\" manual provided the copyright notice and this permission notice are
.\" preserved on all copies.
.\"
.\" Permission is granted to copy and distribute modified versions of this
.\" manual under the conditions for verbatim copying, provided that the
.\" entire resulting derived work is distributed under the terms of a
.\" permission notice identical to this one.
.\"
.\" Since the Linux kernel and libraries are constantly changing, this
.\" manual page may be incorrect or out-of-date. The author(s) assume no
.\" responsibility for errors or omissions, or for damages resulting from
.\" the use of the information contained herein. The author(s) may not
.\" have taken the same level of care in the production of this manual,
.\" which is licensed free of charge, as they might when working
.\" professionally.
.\"
.\" Formatted or processed versions of this manual, if unaccompanied by
.\" the source, must acknowledge the copyright and authors of this work.
.\" %%%LICENSE_END
.\"
.TH FSINFO 2 2018-06-06 "Linux" "Linux Programmer's Manual"
.SH NAME
fsinfo \- Get filesystem information
.SH SYNOPSIS
.nf
.B #include <sys/types.h>
.br
.B #include <sys/fsinfo.h>
.br
.B #include <unistd.h>
.br
.BR "#include <fcntl.h> " "/* Definition of AT_* constants */"
.PP
.BI "int fsinfo(int " dirfd ", const char *" pathname ","
.BI " struct fsinfo_params *" params ","
.BI " void *" buffer ", size_t " buf_size );
.fi
.PP
.IR Note :
There is no glibc wrapper for
.BR fsinfo ();
see NOTES.
.SH DESCRIPTION
.PP
fsinfo() retrieves the desired filesystem attribute, as selected by the
parameters pointed to by
.IR params ,
and stores its value in the buffer pointed to by
.IR buffer .
.PP
The parameter structure is optional, defaulting to all the parameters being 0
if the pointer is NULL. The structure looks like the following:
.PP
.in +4n
.nf
struct fsinfo_params {
__u32 at_flags; /* AT_SYMLINK_NOFOLLOW and similar flags */
__u32 request; /* Requested attribute */
__u32 Nth; /* Instance of attribute */
__u32 Mth; /* Subinstance of Nth instance */
__u32 __reserved[6]; /* Reserved params; all must be 0 */
};
.fi
.in
.PP
The filesystem to be queried is looked up using a combination of
.IR dfd ", " pathname " and " params->at_flags.
This is discussed in more detail below.
.PP
The desired attribute is indicated by
.IR params->request .
If
.I params
is NULL, this will default to
.BR FSINFO_ATTR_STATFS ,
which retrieves some of the information returned by
.BR statfs ().
The available attributes are described below in the "THE ATTRIBUTES" section.
.PP
Some attributes can have multiple values and some can even have multiple
instances with multiple values. For example, a network filesystem might use
multiple servers. The names of each of these servers can be retrieved by
using
.I params->Nth
to iterate through all the instances until error
.B ENODATA
occurs, indicating the end of the list. Further, each server might have
multiple addresses available; these can be enumerated using
.I params->Nth
to iterate the servers and
.I params->Mth
to iterate the addresses of the Nth server.
.PP
The amount of data written into the buffer depends on the attribute selected.
Some attributes return variable-length strings and some return fixed-size
structures. If either
.IR buffer " is NULL or " buf_size " is 0"
then the size of the attribute value will be returned and nothing will be
written into the buffer.
.PP
The
.I params->__reserved
parameters must all be 0.
.\"_______________________________________________________
.SS
Allowance for Future Attribute Expansion
.PP
To allow for the future expansion and addition of fields to any fixed-size
structure attribute,
.BR fsinfo ()
makes the following guarantees:
.RS 4m
.IP (1) 4m
It will always clear any excess space in the buffer.
.IP (2) 4m
It will always return the actual size of the data.
.IP (3) 4m
It will truncate the data to fit it into the buffer rather than giving an
error.
.IP (4) 4m
Any new version of a structure will incorporate all the fields from the old
version at same offsets.
.RE
.PP
So, for example, if the caller is running on an older version of the kernel
with an older, smaller version of the structure than was asked for, the kernel
will write the smaller version into the buffer and will clear the remainder of
the buffer to make sure any additional fields are set to 0. The function will
return the actual size of the data.
.PP
On the other hand, if the caller is running on a newer version of the kernel
with a newer version of the structure that is larger than the buffer, the write
to the buffer will be truncated to fit as necessary and the actual size of the
data will be returned.
.PP
Note that this doesn't apply to variable-length string attributes.
.\"_______________________________________________________
.SS
Invoking \fBfsinfo\fR():
.PP
To access a file's status, no permissions are required on the file itself, but
in the case of
.BR fsinfo ()
with a path, execute (search) permission is required on all of the directories
in
.I pathname
that lead to the file.
.PP
.BR fsinfo ()
uses
.IR pathname ", " dirfd " and " params->at_flags
to locate the target file in one of a variety of ways:
.TP
[*] By absolute path.
.I pathname
points to an absolute path and
.I dirfd
is ignored. The file is looked up by name, starting from the root of the
filesystem as seen by the calling process.
.TP
[*] By cwd-relative path.
.I pathname
points to a relative path and
.IR dirfd " is " AT_FDCWD .
The file is looked up by name, starting from the current working directory.
.TP
[*] By dir-relative path.
.I pathname
points to relative path and
.I dirfd
indicates a file descriptor pointing to a directory. The file is looked up by
name, starting from the directory specified by
.IR dirfd .
.TP
[*] By file descriptor.
.IR pathname " is " NULL " and " dirfd
indicates a file descriptor. The file attached to the file descriptor is
queried directly. The file descriptor may point to any type of file, not just
a directory.
.PP
.I flags
can be used to influence a path-based lookup. A value for
.I flags
is constructed by OR'ing together zero or more of the following constants:
.TP
.BR AT_EMPTY_PATH
.\" commit 65cfc6722361570bfe255698d9cd4dccaf47570d
If
.I pathname
is an empty string, operate on the file referred to by
.IR dirfd
(which may have been obtained using the
.BR open (2)
.B O_PATH
flag).
If
.I dirfd
is
.BR AT_FDCWD ,
the call operates on the current working directory.
In this case,
.I dirfd
can refer to any type of file, not just a directory.
This flag is Linux-specific; define
.B _GNU_SOURCE
.\" Before glibc 2.16, defining _ATFILE_SOURCE sufficed
to obtain its definition.
.TP
.BR AT_NO_AUTOMOUNT
Don't automount the terminal ("basename") component of
.I pathname
if it is a directory that is an automount point. This allows the caller to
gather attributes of the filesystem holding an automount point (rather than
the filesystem it would mount). This flag can be used in tools that scan
directories to prevent mass-automounting of a directory of automount points.
The
.B AT_NO_AUTOMOUNT
flag has no effect if the mount point has already been mounted over.
This flag is Linux-specific; define
.B _GNU_SOURCE
.\" Before glibc 2.16, defining _ATFILE_SOURCE sufficed
to obtain its definition.
.TP
.B AT_SYMLINK_NOFOLLOW
If
.I pathname
is a symbolic link, do not dereference it:
instead return information about the link itself, like
.BR lstat ().
.SH THE ATTRIBUTES
.PP
There is a range of attributes that can be selected from. These are:
.\" __________________ FSINFO_ATTR_STATFS __________________
.TP
.B fsinfo_attr_statfs
This retrieves the "dynamic"
.B statfs
information, such as block and file counts, that are expected to change whilst
a filesystem is being used. This fills in the following structure:
.PP
.RS
.in +4n
.nf
struct fsinfo_statfs {
__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 free file nodes available to ordinary user */
__u32 f_bsize; /* Optimal block size */
__u32 f_frsize; /* Fragment size */
};
.fi
.in
.RE
.IP
The fields correspond to those of the same name returned by
.BR statfs ().
.\" __________________ FSINFO_ATTR_FSINFO __________________
.TP
.B FSINFO_ATTR_FSINFO
This retrieves information about the
.BR fsinfo ()
system call itself. This fills in the following structure:
.PP
.RS
.in +4n
.nf
struct fsinfo_fsinfo {
__u32 max_attr;
__u32 max_cap;
};
.fi
.in
.RE
.IP
The
.I max_attr
value indicates the number of attributes supported by the
.BR fsinfo ()
system call, and
.I max_cap
indicates the number of capability bits supported by the
.B FSINFO_ATTR_CAPABILITIES
attribute. The first corresponds to
.I fsinfo_attr__nr
and the second to
.I fsinfo_cap__nr
in the header file.
.\" __________________ FSINFO_ATTR_IDS __________________
.TP
.B FSINFO_ATTR_IDS
This retrieves a number of fixed IDs and other static information otherwise
available through
.BR statfs ().
The following structure is filled in:
.PP
.RS
.in +4n
.nf
struct fsinfo_ids {
char f_fs_name[15 + 1]; /* Filesystem name */
__u64 f_flags; /* Filesystem mount flags (MS_*) */
__u64 f_fsid; /* Short 64-bit Filesystem ID */
__u64 f_sb_id; /* Internal superblock ID */
__u32 f_fstype; /* Filesystem type from linux/magic.h */
__u32 f_dev_major; /* As st_dev_* from struct statx */
__u32 f_dev_minor;
};
.fi
.in
.RE
.IP
Most of these are filled in as for
.BR statfs (),
with the addition of the filesystem's symbolic name in
.I f_fs_name
and an identifier for use in notifications in
.IR f_sb_id .
.\" __________________ FSINFO_ATTR_LIMITS __________________
.TP
.B FSINFO_ATTR_LIMITS
This retrieves information about the limits of what a filesystem can support.
The following structure is filled in:
.PP
.RS
.in +4n
.nf
struct fsinfo_limits {
__u64 max_file_size;
__u64 max_uid;
__u64 max_gid;
__u64 max_projid;
__u32 max_dev_major;
__u32 max_dev_minor;
__u32 max_hard_links;
__u32 max_xattr_body_len;
__u16 max_xattr_name_len;
__u16 max_filename_len;
__u16 max_symlink_len;
__u16 __reserved[1];
};
.fi
.in
.RE
.IP
These indicate the maximum supported sizes for a variety of filesystem objects,
including the file size, the extended attribute name length and body length,
the filename length and the symlink body length.
.IP
It also indicates the maximum representable values for a User ID, a Group ID,
a Project ID, a device major number and a device minor number.
.IP
And finally, it indicates the maximum number of hard links that can be made to
a file.
.IP
Note that some of these values may be zero if the underlying object or concept
is not supported by the filesystem or the medium.
.\" __________________ FSINFO_ATTR_SUPPORTS __________________
.TP
.B FSINFO_ATTR_SUPPORTS
This retrieves information about what bits a filesystem supports in various
masks. The following structure is filled in:
.PP
.RS
.in +4n
.nf
struct fsinfo_supports {
__u64 stx_attributes;
__u32 stx_mask;
__u32 ioc_flags;
__u32 win_file_attrs;
__u32 __reserved[1];
};
.fi
.in
.RE
.IP
The
.IR stx_attributes " and " stx_mask
fields indicate what bits in the struct statx fields of the matching names
are supported by the filesystem.
.IP
The
.I ioc_flags
field indicates what FS_*_FL flag bits as used through the FS_IOC_GET/SETFLAGS
ioctls are supported by the filesystem.
.IP
The
.I win_file_attrs
indicates what DOS/Windows file attributes a filesystem supports, if any.
.\" __________________ FSINFO_ATTR_CAPABILITIES __________________
.TP
.B FSINFO_ATTR_CAPABILITIES
This retrieves information about what features a filesystem supports as a
series of single bit indicators. The following structure is filled in:
.PP
.RS
.in +4n
.nf
struct fsinfo_capabilities {
__u8 capabilities[(fsinfo_cap__nr + 7) / 8];
};
.fi
.in
.RE
.IP
where the bit of interest can be found by:
.PP
.RS
.in +4n
.nf
p->capabilities[bit / 8] & (1 << (bit % 8)))
.fi
.in
.RE
.IP
The bits are listed by
.I enum fsinfo_capability
and
.B fsinfo_cap__nr
is one more than the last capability bit listed in the header file.
.IP
Note that the number of capability bits actually supported by the kernel can be
found using the
.B FSINFO_ATTR_FSINFO
attribute.
.IP
The capability bits and their meanings are listed below in the "THE
CAPABILITIES" section.
.\" __________________ FSINFO_ATTR_TIMESTAMP_INFO __________________
.TP
.B FSINFO_ATTR_TIMESTAMP_INFO
This retrieves information about what timestamp resolution and scope is
supported by a filesystem for each of the file timestamps. The following
structure is filled in:
.PP
.RS
.in +4n
.nf
struct fsinfo_timestamp_info {
__s64 minimum_timestamp;
__s64 maximum_timestamp;
__u16 atime_gran_mantissa;
__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];
};
.fi
.in
.RE
.IP
where
.IR minimum_timestamp " and " maximum_timestamp
are the limits on the timestamps that the filesystem supports and
.IR *time_gran_mantissa " and " *time_gran_exponent
indicate the granularity of each timestamp in terms of seconds, using the
formula:
.PP
.RS
.in +4n
.nf
mantissa * pow(10, exponent) Seconds
.fi
.in
.RE
.IP
where exponent may be negative and the result may be a fraction of a second.
.IP
Four timestamps are detailed: \fBA\fPccess time, \fBB\fPirth/creation time,
\fBC\fPhange time and \fBM\fPodification time. Capability bits are defined
that specify whether each of these exist in the filesystem or not.
.IP
Note that the timestamp description may be approximated or inaccurate if the
file is actually remote or is the union of multiple objects.
.\" __________________ FSINFO_ATTR_VOLUME_ID __________________
.TP
.B FSINFO_ATTR_VOLUME_ID
This retrieves the system's superblock volume identifier as a variable-length
string. This does not necessarily represent a value stored in the medium but
might be constructed on the fly.
.IP
For instance, for a block device this is the block device identifier
(eg. "sdb2"); for AFS this would be the numeric volume identifier.
.\" __________________ FSINFO_ATTR_VOLUME_UUID __________________
.TP
.B FSINFO_ATTR_VOLUME_UUID
This retrieves the volume UUID, if there is one, as a little-endian binary
UUID. This fills in the following structure:
.PP
.RS
.in +4n
.nf
struct fsinfo_volume_uuid {
__u8 uuid[16];
};
.fi
.in
.RE
.IP
.\" __________________ FSINFO_ATTR_VOLUME_NAME __________________
.TP
.B FSINFO_ATTR_VOLUME_NAME
This retrieves the filesystem's volume name as a variable-length string. This
is expected to represent a name stored in the medium.
.IP
For a block device, this might be a label stored in the superblock. For a
network filesystem, this might be a logical volume name of some sort.
.\" __________________ FSINFO_ATTR_CELL/DOMAIN __________________
.PP
.B FSINFO_ATTR_CELL_NAME
.br
.B FSINFO_ATTR_DOMAIN_NAME
.br
.IP
These two attributes are variable-length string attributes that may be used to
obtain information about network filesystems. An AFS volume, for instance,
belongs to a named cell. CIFS shares may belong to a domain.
.\" __________________ FSINFO_ATTR_REALM_NAME __________________
.TP
.B FSINFO_ATTR_REALM_NAME
This attribute is variable-length string that indicates the Kerberos realm that
a filesystem's authentication tokens should come from.
.\" __________________ FSINFO_ATTR_SERVER_NAME __________________
.TP
.B FSINFO_ATTR_SERVER_NAME
This attribute is a multiple-value attribute that lists the names of the
servers that are backing a network filesystem. Each value is a variable-length
string. The values are enumerated by calling
.BR fsinfo ()
multiple times, incrementing
.I params->Nth
each time until an ENODATA error occurs, thereby indicating the end of the
list.
.\" __________________ FSINFO_ATTR_SERVER_ADDRESS __________________
.TP
.B FSINFO_ATTR_SERVER_ADDRESS
This attribute is a multiple-instance, multiple-value attribute that lists the
addresses of the servers that are backing a network filesystem. Each value is
a structure of the following type:
.PP
.RS
.in +4n
.nf
struct fsinfo_server_address {
struct __kernel_sockaddr_storage address;
};
.fi
.in
.RE
.IP
Where the address may be AF_INET, AF_INET6, AF_RXRPC or any other type as
appropriate to the filesystem.
.IP
The values are enumerated by calling
.IR fsinfo ()
multiple times, incrementing
.I params->Nth
to step through the servers and
.I params->Mth
to step through the addresses of the Nth server each time until ENODATA errors
occur, thereby indicating either the end of a server's address list or the end
of the server list.
.IP
Barring the server list changing whilst being accessed, it is expected that the
.I params->Nth
will correspond to
.I params->Nth
for
.BR FSINFO_ATTR_SERVER_NAME .
.\" __________________ FSINFO_ATTR_PARAMETER __________________
.TP
.B FSINFO_ATTR_PARAMETER
This attribute is a 2D multiple-value attribute that lists the values of the
mount parameters for a filesystem as variable-length strings.
.IP
The parameters are enumerated by calling
.BR fsinfo ()
multiple times, incrementing
.IR params->Nth and params->Mth
to step through them until error ENODATA is given.
.IP
Parameter strings are presented in a form akin to the way they're passed to the
context created by the
.BR fsopen ()
system call. For example, straight text parameters will be rendered as
something like:
.PP
.RS
.in +4n
.nf
"source=/dev/sda1"
"data=journal"
"noquota"
.fi
.in
.RE
.IP
where the first parameters correspond on a 1-to-1 basis by
.I params->Nth
with the parameters defined by
.IR FSINFO_ATTR_PARAM_SPECIFICATION .
Additional parameters may also be presented. Further, any particular parameter
may have multiple values (multiple sources for example). These can be
enumerated with params->Mth.
.\" __________________ FSINFO_ATTR_NAME_ENCODING __________________
.TP
.B FSINFO_ATTR_NAME_ENCODING
This attribute is variable-length string that indicates the filename encoding
used by the filesystem. The default is "utf8". Note that this may indicate a
non-8-bit encoding if that's what the underlying filesystem actually supports.
.\" __________________ FSINFO_ATTR_NAME_CODEPAGE __________________
.TP
.B FSINFO_ATTR_NAME_CODEPAGE
This attribute is variable-length string that indicates the codepage used to
translate filenames from the filesystem to the system if this is applicable to
the filesystem.
.\" __________________ FSINFO_ATTR_IO_SIZE __________________
.TP
.B FSINFO_ATTR_IO_SIZE
This retrieves information about the I/O sizes supported by the filesystem.
The following structure is filled in:
.PP
.RS
.in +4n
.nf
struct fsinfo_io_size {
__u32 dio_size_gran;
__u32 dio_mem_align;
};
.fi
.in
.RE
.IP
Where
.I dio_size_gran
indicate the fundamental I/O block size that the size of O_DIRECT read/write
calls must be a multiple of and
.I dio_mem_align
indicates the memory alignment requirements of the data buffer in any O_DIRECT
read/write call.
.IP
Note that any of these may be zero if inapplicable or indeterminable.
.\" __________________ FSINFO_ATTR_PARAM_DESCRIPTION __________________
.TP
.B FSINFO_ATTR_PARAM_DESCRIPTION
This retrieves basic information about the superblock configuration parameters
used by the filesystem. The value returned is of the following type:
.PP
.RS
.in +4n
.nf
struct fsinfo_param_description {
__u32 nr_params; /* Number of individual parameters */
__u32 nr_names; /* Number of parameter names */
__u32 nr_enum_names; /* Number of enum names */
};
.fi
.in
.RE
.IP
Where
.I nr_params indicates the number of described parameters (it's possible for
the configuration to take more than this - cgroup-v1 for example);
.I nr_names
indicates the number of parameter names that there are defined (nr_names can be
more than nr_params if there are synonyms); and
.I nr_enum_names
indicates the number of enum value names that there are defined.
.\" __________________ FSINFO_ATTR_PARAM_SPECIFICATION __________________
.TP
.B FSINFO_ATTR_PARAM_SPECIFICATION
This retrieves information about the Nth superblock configuration parameter
available in the filesystem. This is enumerated by incrementing
.I params->Nth
each time. Each value is a structure of the following type:
.PP
.RS
.in +4n
.nf
struct fsinfo_param_specification {
__u32 type;
__u32 flags;
};
.fi
.in
.RE
.IP
Where
.I type
indicates the type of value by way of one of the following constants:
.PP
.RS
.in +4n
.nf
FSINFO_PARAM_SPEC_NOT_DEFINED
FSINFO_PARAM_SPEC_TAKES_NO_VALUE
FSINFO_PARAM_SPEC_IS_BOOL
FSINFO_PARAM_SPEC_IS_U32
FSINFO_PARAM_SPEC_IS_U32_OCTAL
FSINFO_PARAM_SPEC_IS_U32_HEX
FSINFO_PARAM_SPEC_IS_S32
FSINFO_PARAM_SPEC_IS_ENUM
FSINFO_PARAM_SPEC_IS_STRING
FSINFO_PARAM_SPEC_IS_BLOB
FSINFO_PARAM_SPEC_IS_BLOCKDEV
FSINFO_PARAM_SPEC_IS_PATH
FSINFO_PARAM_SPEC_IS_FD
.fi
.in
.RE
.IP
depending on whether the kernel (incorrectly) didn't define the type, the
parameter takes no value, or takes a bool, one of a number of integers, a named
enum value, a string, a binary blob, a blockdev, an arbitrary path or a file
descriptor.
.PP
.I flags
qualifies the form of the value accepted:
.PP
.RS
.in +4n
.nf
FSINFO_PARAM_SPEC_VALUE_IS_OPTIONAL
FSINFO_PARAM_SPEC_PREFIX_NO_IS_NEG
FSINFO_PARAM_SPEC_EMPTY_STRING_IS_NEG
FSINFO_PARAM_SPEC_DEPRECATED
.fi
.in
.RE
.IP
These indicate whether the value is optional, the parameter can be made
negative by prefixing with 'no' or giving it an empty value or whether the
parameter is deprecated and a warning issued if it used.
.\" __________________ FSINFO_ATTR_PARAM_NAME __________________
.TP
.B FSINFO_ATTR_PARAM_NAME
This retrieves information about the Nth superblock configuration parameter
available in the filesystem. This is enumerated by incrementing
.I params->Nth
each time. Each value is a structure of the following type:
.PP
.RS
.in +4n
.nf
struct fsinfo_param_name {
__u32 param_index;
char name[252];
};
.fi
.in
.RE
.IP
Where
.I param_index
is refers to the Nth parameter returned by FSINFO_ATTR_PARAM_SPECIFICATION and
.I name
is a name string that maps to the specified parameter.
.\" __________________ FSINFO_ATTR_PARAM_ENUMs __________________
.TP
.B FSINFO_ATTR_PARAM_ENUM
This can be used to list all the enum value symbols available for all the
configuration parameters available in the filesystem. This is enumerated by
incrementing
.I params->Nth
each time. Each value is a structure of the following type:
.PP
.RS
.in +4n
.nf
struct fsinfo_param_enum {
__u32 param_index; /* Index of the relevant parameter specification */
char name[252]; /* Name of the enum value */
};
.fi
.in
.RE
.IP
Where
.I param_index
indicates the enumeration-type parameter to which this value corresponds and
.I name
is the symbolic name. Note that all the enum values from all the enum
parameters are in one list together
.SH THE CAPABILITIES
.PP
There are number of capability bits in a bit array that can be retrieved using
.BR fsinfo_attr_capabilities .
These give information about features of the filesystem driver and the specific
filesystem.
.\" __________________ FSINFO_CAP_IS_*_FS __________________
.PP
.B FSINFO_CAP_IS_KERNEL_FS
.br
.B FSINFO_CAP_IS_BLOCK_FS
.br
.B FSINFO_CAP_IS_FLASH_FS
.br
.B FSINFO_CAP_IS_NETWORK_FS
.br
.B FSINFO_CAP_IS_AUTOMOUNTER_FS
.IP
These indicate the primary type of the filesystem.
.B kernel
filesystems are special communication interfaces that substitute files for
system calls; examples include procfs and sysfs.
.B block
filesystems require a block device on which to operate; examples include ext4
and XFS.
.B flash
filesystems require an MTD device on which to operate; examples include JFFS2.
.B network
filesystems require access to the network and contact one or more servers;
examples include NFS and AFS.
.B automounter
filesystems are kernel special filesystems that host automount points and
triggers to dynamically create automount points. Examples include autofs and
AFS's dynamic root.
.\" __________________ FSINFO_CAP_AUTOMOUNTS __________________
.TP
.B FSINFO_CAP_AUTOMOUNTS
The filesystem may have automount points that can be triggered by pathwalk.
.\" __________________ FSINFO_CAP_ADV_LOCKS __________________
.TP
.B FSINFO_CAP_ADV_LOCKS
The filesystem supports advisory file locks. For a network filesystem, this
indicates that the advisory file locks are cross-client (and also between
server and its local filesystem on something like NFS).
.\" __________________ FSINFO_CAP_MAND_LOCKS __________________
.TP
.B FSINFO_CAP_MAND_LOCKS
The filesystem supports mandatory file locks. For a network filesystem, this
indicates that the mandatory file locks are cross-client (and also between
server and its local filesystem on something like NFS).
.\" __________________ FSINFO_CAP_LEASES __________________
.TP
.B FSINFO_CAP_LEASES
The filesystem supports leases. For a network filesystem, this means that the
server will tell the client to clean up its state on a file before passing the
lease to another client.
.\" __________________ FSINFO_CAP_*IDS __________________
.PP
.B FSINFO_CAP_UIDS
.br
.B FSINFO_CAP_GIDS
.br
.B FSINFO_CAP_PROJIDS
.IP
These indicate that the filesystem supports numeric user IDs, group IDs and
project IDs respectively.
.\" __________________ FSINFO_CAP_ID_* __________________
.PP
.B FSINFO_CAP_ID_NAMES
.br
.B FSINFO_CAP_ID_GUIDS
.IP
These indicate that the filesystem employs textual names and/or GUIDs as
identifiers.
.\" __________________ FSINFO_CAP_WINDOWS_ATTRS __________________
.TP
.B FSINFO_CAP_WINDOWS_ATTRS
Indicates that the filesystem supports some Windows FILE_* attributes.
.\" __________________ FSINFO_CAP_*_QUOTAS __________________
.PP
.B FSINFO_CAP_USER_QUOTAS
.br
.B FSINFO_CAP_GROUP_QUOTAS
.br
.B FSINFO_CAP_PROJECT_QUOTAS
.IP
These indicate that the filesystem supports quotas for users, groups and
projects respectively.
.\" __________________ FSINFO_CAP_XATTRS/FILETYPES __________________
.PP
.B FSINFO_CAP_XATTRS
.br
.B FSINFO_CAP_SYMLINKS
.br
.B FSINFO_CAP_HARD_LINKS
.br
.B FSINFO_CAP_HARD_LINKS_1DIR
.br
.B FSINFO_CAP_DEVICE_FILES
.br
.B FSINFO_CAP_UNIX_SPECIALS
.IP
These indicate that the filesystem supports respectively extended attributes;
symbolic links; hard links spanning direcories; hard links, but only within a
directory; block and character device files; and UNIX special files, such as
FIFO and socket.
.\" __________________ FSINFO_CAP_*JOURNAL* __________________
.PP
.B FSINFO_CAP_JOURNAL
.br
.B FSINFO_CAP_DATA_IS_JOURNALLED
.IP
The first of these indicates that the filesystem has a journal and the second
that the file data changes are being journalled.
.\" __________________ FSINFO_CAP_O_* __________________
.PP
.B FSINFO_CAP_O_SYNC
.br
.B FSINFO_CAP_O_DIRECT
.IP
These indicate that O_SYNC and O_DIRECT are supported respectively.
.\" __________________ FSINFO_CAP_* for names __________________
.PP
.B FSINFO_CAP_VOLUME_ID
.br
.B FSINFO_CAP_VOLUME_UUID
.br
.B FSINFO_CAP_VOLUME_NAME
.br
.B FSINFO_CAP_VOLUME_FSID
.br
.B FSINFO_CAP_CELL_NAME
.br
.B FSINFO_CAP_DOMAIN_NAME
.br
.B FSINFO_CAP_REALM_NAME
.IP
These indicate if various attributes are supported by the filesystem, where
.B FSINFO_CAP_X
here corresponds to
.BR fsinfo_attr_X .
.\" __________________ FSINFO_CAP_IVER_* __________________
.PP
.B FSINFO_CAP_IVER_ALL_CHANGE
.br
.B FSINFO_CAP_IVER_DATA_CHANGE
.br
.B FSINFO_CAP_IVER_MONO_INCR
.IP
These indicate if
.I i_version
on an inode in the filesystem is supported and
how it behaves.
.B all_change
indicates that i_version is incremented on metadata changes as well as data
changes.
.B data_change
indicates that i_version is only incremented on data changes, including
truncation.
.B mono_incr
indicates that i_version is incremented by exactly 1 for each change made.
.\" __________________ FSINFO_CAP_RESOURCE_FORKS __________________
.TP
.B FSINFO_CAP_RESOURCE_FORKS
This indicates that the filesystem supports some sort of resource fork or
alternate data stream on a file. This isn't the same as an extended attribute.
.\" __________________ FSINFO_CAP_NAME_* __________________
.PP
.B FSINFO_CAP_NAME_CASE_INDEP
.br
.B FSINFO_CAP_NAME_NON_UTF8
.br
.B FSINFO_CAP_NAME_HAS_CODEPAGE
.IP
These indicate certain facts about the filenames in a filesystem: whether
they're case-independent; if they're not UTF-8; and if there's a codepage
employed to map the names.
.\" __________________ FSINFO_CAP_SPARSE __________________
.TP
.B FSINFO_CAP_SPARSE
This indicates that the filesystem supports sparse files.
.\" __________________ FSINFO_CAP_NOT_PERSISTENT __________________
.TP
.B FSINFO_CAP_NOT_PERSISTENT
This indicates that the filesystem is not persistent, and that any data stored
here will not be saved in the event that the filesystem is unmounted, the
machine is rebooted or the machine loses power.
.\" __________________ FSINFO_CAP_NO_UNIX_MODE __________________
.TP
.B FSINFO_CAP_NO_UNIX_MODE
This indicates that the filesystem doesn't support the UNIX mode permissions
bits.
.\" __________________ FSINFO_CAP_HAS_*TIME __________________
.PP
.B FSINFO_CAP_HAS_ATIME
.br
.B FSINFO_CAP_HAS_BTIME
.br
.B FSINFO_CAP_HAS_CTIME
.br
.B FSINFO_CAP_HAS_MTIME
.IP
These indicate as to what timestamps a filesystem supports, including: Access
time, Birth/creation time, Change time (metadata and data) and Modification
time (data only).
.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.\"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.SH RETURN VALUE
On success, the size of the value that the kernel has available is returned,
irrespective of whether the buffer is large enough to hold that. The data
written to the buffer will be truncated if it is not. On error, \-1 is
returned, and
.I errno
is set appropriately.
.SH ERRORS
.TP
.B EACCES
Search permission is denied for one of the directories
in the path prefix of
.IR pathname .
(See also
.BR path_resolution (7).)
.TP
.B EBADF
.I dirfd
is not a valid open file descriptor.
.TP
.B EFAULT
.I pathname
is NULL or
.IR pathname ", " params " or " buffer
point to a location outside the process's accessible address space.
.TP
.B EINVAL
Reserved flag specified in
.IR params->at_flags " or one of " params->__reserved[]
is not 0.
.TP
.B EOPNOTSUPP
Unsupported attribute requested in
.IR params->request .
This may be beyond the limit of the supported attribute set or may just not be
one that's supported by the filesystem.
.TP
.B ENODATA
Unavailable attribute value requested by
.IR params->Nth " and/or " params->Mth .
.TP
.B ELOOP
Too many symbolic links encountered while traversing the pathname.
.TP
.B ENAMETOOLONG
.I pathname
is too long.
.TP
.B ENOENT
A component of
.I pathname
does not exist, or
.I pathname
is an empty string and
.B AT_EMPTY_PATH
was not specified in
.IR params->at_flags .
.TP
.B ENOMEM
Out of memory (i.e., kernel memory).
.TP
.B ENOTDIR
A component of the path prefix of
.I pathname
is not a directory or
.I pathname
is relative and
.I dirfd
is a file descriptor referring to a file other than a directory.
.SH VERSIONS
.BR fsinfo ()
was added to Linux in kernel 4.18.
.SH CONFORMING TO
.BR fsinfo ()
is Linux-specific.
.SH NOTES
Glibc does not (yet) provide a wrapper for the
.BR fsinfo ()
system call; call it using
.BR syscall (2).
.SH SEE ALSO
.BR ioctl_iflags (2),
.BR statx (2),
.BR statfs (2)