From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:05:11
Here are a set of patches that adds system calls, that (a) allow
information about the VFS, mount topology, superblock and files to be
retrieved and (b) allow for notifications of mount topology rearrangement
events, mount and superblock attribute changes and other superblock events,
such as errors.
============================
FILESYSTEM INFORMATION QUERY
============================
The first system call, fsinfo(), allows information about the filesystem at
a particular path point to be queried as a set of attributes, some of which
may have more than one value.
Attribute values are of four basic types:
(1) Version dependent-length structure (size defined by type).
(2) Variable-length string (up to 4096, including NUL).
(3) List of 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, but all the values of an attribute have to be of the same type.
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, the filesystem will
always see a sufficiently-sized buffer allocated. 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:
- 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.
- The amount of space/free space in a filesystem (as statfs()).
- Superblock notification counter.
(2) Filesystem-specific superblock attributes:
- Superblock-level timestamps.
- Cell name.
- Server names and addresses.
- Filesystem-specific information.
(3) VFS information:
- Mount topology information.
- Mount attributes.
- Mount notification counter.
(4) Information about what the fsinfo() syscall itself supports, including
the type and struct/element size of attributes.
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.
fsinfo() may be called like the following, for example:
struct fsinfo_params params = {
.at_flags = AT_SYMLINK_NOFOLLOW,
.flags = FSINFO_FLAGS_QUERY_PATH,
.request = FSINFO_ATTR_AFS_SERVER_ADDRESSES,
.Nth = 2,
};
struct fsinfo_server_address address;
len = fsinfo(AT_FDCWD, "/afs/grand.central.org/doc", ¶ms,
&address, sizeof(address));
The above example would query an AFS filesystem to retrieve the address
list for the 3rd server, and:
struct fsinfo_params params = {
.at_flags = AT_SYMLINK_NOFOLLOW,
.flags = FSINFO_FLAGS_QUERY_PATH,
.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.
In future, I want to make fsinfo() capable of querying a context created by
fsopen() or fspick(), e.g.:
fd = fsopen("ext4", 0);
struct fsinfo_params params = {
.flags = FSINFO_FLAGS_QUERY_FSCONTEXT,
.request = FSINFO_ATTR_PARAMETERS;
};
char buffer[65536];
fsinfo(fd, NULL, ¶ms, &buffer, sizeof(buffer));
even if that context doesn't currently have a superblock attached. I would
prefer this to contain length-prefixed strings so that there's no need to
insert escaping, especially as any character, including '\', can be used as
the separator in cifs and so that binary parameters can be returned (though
that is a lesser issue).
========================
FILESYSTEM NOTIFICATIONS
========================
The second system call, watch_mount(), places a watch on a point in the
mount topology specified by the dirfd, path and at_flags parameters. All
mount topology change and mount attribute change notifications in the
subtree rooted at that point can be intercepted by the watch. Watches are
ducted through pipes:
int fd[2];
pipe2(fd, O_NOTIFICATION_PIPE);
ioctl(fd[0], IOC_WATCH_QUEUE_SET_SIZE, BUF_SIZE);
watch_mount(AT_FDCWD, "/", 0, fd[0], 0x02);
Events include:
- New mount made
- Mount unmounted
- Mount expired
- R/O state changed
- Other attribute changed
- Mount moved from
- Mount moved to
Using filtering, this may be limited in various ways (single mount watch vs
subtree watch, recursive vs non-recursive changes, to-R/O vs to-R/W, mount
vs submount).
Each mount now has a change counter. Whenever a mount is changed, this
gets incremented. It can be queried by fsinfo() using either
FSINFO_ATTR_MOUNT_INFO or FSINFO_ATTR_MOUNT_CHILDREN. The ID of the mount
on which the notification is generated is placed into the notification
message (triggered_on). If the event involves a second mount as well, such
as creation of a new mount, that gets returned too (changed_mount).
The third system call, watch_sb(), places a watch on the superblock
specified by the dirfd, path and at_flags parameters. This allows various
superblock events to be monitored for, such as:
- Transition between R/W and R/O
- Filesystem errors
- Quota overrun
- Network status changes
Each superblock now gets a 64-bit unique superblock identifier and a
notification counter. The counter is incremented each time one of these
notifications would be generated. This attributes can be queried using
fsinfo() with FSINFO_ATTR_SB_NOTIFICATIONS. The identifier is placed into
notification messages.
Two sample programs are provided, one to query filesystem attributes and
the other to display a mount subtree. Both of them can be given a path or
a mount ID to start at. Further, the watch_test sample program now watches
for mount events under "/" and for superblock events on whatever superblock
is backing "/mnt" when it the program is started.
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 #16:
(*) Split the features bits out of the fsinfo() core into their own patch
and got rid of the name encoding attributes.
(*) Renamed the 'array' type to 'list' and made AFS use it for returning
server address lists.
(*) Changed the ->fsinfo() method into an ->fsinfo_attributes[] table,
where each attribute has a ->get() method to deal with it. These
tables can then be returned with an fsinfo meta attribute.
(*) Dropped the fscontext query and parameter/description retrieval
attributes for now.
(*) Picked the mount topology attributes into this branch.
(*) Picked the mount notifications into this branch and rebased on top of
notifications-pipe-core.
(*) Picked the superblock notifications into this branch.
(*) Add sample code for Ext4 and NFS.
David
---
David Howells (19):
vfs: syscall: Add fsinfo() to query filesystem information
fsinfo: Add syscalls to other arches
fsinfo: Provide a bitmap of supported features
vfs: Add mount change counter
vfs: Introduce a non-repeating system-unique superblock ID
vfs: Allow fsinfo() to look up a mount object by ID
vfs: Allow mount information to be queried by fsinfo()
vfs: fsinfo sample: Mount listing program
fsinfo: Allow the mount topology propogation flags to be retrieved
fsinfo: Add API documentation
afs: Support fsinfo()
security: Add hooks to rule on setting a superblock or mount watch
vfs: Add a mount-notification facility
notifications: sample: Display mount tree change notifications
vfs: Add superblock notifications
fsinfo: Provide superblock notification counter
notifications: sample: Display superblock notifications
ext4: Add example fsinfo information
nfs: Add example filesystem information
Documentation/filesystems/fsinfo.rst | 490 +++++++++++++++
arch/alpha/kernel/syscalls/syscall.tbl | 3
arch/arm/tools/syscall.tbl | 3
arch/arm64/include/asm/unistd.h | 2
arch/arm64/include/asm/unistd32.h | 2
arch/ia64/kernel/syscalls/syscall.tbl | 3
arch/m68k/kernel/syscalls/syscall.tbl | 4
arch/microblaze/kernel/syscalls/syscall.tbl | 3
arch/mips/kernel/syscalls/syscall_n32.tbl | 3
arch/mips/kernel/syscalls/syscall_n64.tbl | 3
arch/mips/kernel/syscalls/syscall_o32.tbl | 3
arch/parisc/kernel/syscalls/syscall.tbl | 3
arch/powerpc/kernel/syscalls/syscall.tbl | 3
arch/s390/kernel/syscalls/syscall.tbl | 3
arch/sh/kernel/syscalls/syscall.tbl | 3
arch/sparc/kernel/syscalls/syscall.tbl | 3
arch/x86/entry/syscalls/syscall_32.tbl | 3
arch/x86/entry/syscalls/syscall_64.tbl | 3
arch/xtensa/kernel/syscalls/syscall.tbl | 3
fs/Kconfig | 28 +
fs/Makefile | 2
fs/afs/internal.h | 1
fs/afs/super.c | 229 +++++++
fs/d_path.c | 2
fs/ext4/Makefile | 1
fs/ext4/ext4.h | 9
fs/ext4/fsinfo.c | 40 +
fs/ext4/super.c | 1
fs/fsinfo.c | 635 ++++++++++++++++++++
fs/internal.h | 12
fs/mount.h | 31 +
fs/mount_notify.c | 188 ++++++
fs/namespace.c | 323 ++++++++++
fs/nfs/Makefile | 1
fs/nfs/internal.h | 8
fs/nfs/nfs4super.c | 1
fs/nfs/super.c | 1
fs/super.c | 149 +++++
include/linux/dcache.h | 1
include/linux/fs.h | 89 +++
include/linux/fsinfo.h | 102 +++
include/linux/lsm_hooks.h | 24 +
include/linux/security.h | 16 +
include/linux/syscalls.h | 8
include/uapi/asm-generic/unistd.h | 8
include/uapi/linux/fsinfo.h | 371 ++++++++++++
include/uapi/linux/mount.h | 10
include/uapi/linux/watch_queue.h | 61 ++
kernel/sys_ni.c | 3
samples/vfs/Makefile | 7
samples/vfs/test-fsinfo.c | 858 +++++++++++++++++++++++++++
samples/vfs/test-mntinfo.c | 243 ++++++++
samples/watch_queue/watch_test.c | 76 ++
security/security.c | 14
54 files changed, 4081 insertions(+), 15 deletions(-)
create mode 100644 Documentation/filesystems/fsinfo.rst
create mode 100644 fs/ext4/fsinfo.c
create mode 100644 fs/fsinfo.c
create mode 100644 fs/mount_notify.c
create mode 100644 include/linux/fsinfo.h
create mode 100644 include/uapi/linux/fsinfo.h
create mode 100644 samples/vfs/test-fsinfo.c
create mode 100644 samples/vfs/test-mntinfo.c
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:05:26
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_IDS - Filesystem IDs
FSINFO_ATTR_LIMITS - Filesystem limits
FSINFO_ATTR_SUPPORTS - What's supported in statx(), IOC flags
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_FSINFO_ATTRIBUTE_INFO - Information about attr Nth
FSINFO_ATTR_FSINFO_ATTRIBUTES - List of supported attrs
Some attributes (such as the servers backing a network filesystem) can have
multiple values. These can be enumerated by setting params->Nth and
params->Mth to 0, 1, ... until ENODATA is returned.
buffer and buf_size point to the reply buffer. The buffer is filled up to
the specified size, even if this means truncating the reply. The full size
of the reply is returned. In future versions, this will allow extra fields
to be tacked on to the end of the reply, but anyone not expecting them will
only get the subset they're expecting. If either buffer of buf_size are 0,
no copy will take place and the data size will be returned.
At the moment, this will only work on x86_64 and i386 as it requires the
system call to be wired up.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: linux-api@vger.kernel.org
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 7
fs/Makefile | 1
fs/fsinfo.c | 525 ++++++++++++++++++++++++++++
include/linux/fs.h | 5
include/linux/fsinfo.h | 70 ++++
include/linux/syscalls.h | 4
include/uapi/asm-generic/unistd.h | 4
include/uapi/linux/fsinfo.h | 186 ++++++++++
kernel/sys_ni.c | 1
samples/vfs/Makefile | 5
samples/vfs/test-fsinfo.c | 599 ++++++++++++++++++++++++++++++++
13 files changed, 1408 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
@@ -359,6 +359,7 @@ 435 common clone3 __x64_sys_clone3/ptregs 437 common openat2 __x64_sys_openat2 438 common pidfd_getfd __x64_sys_pidfd_getfd+439 common fsinfo __x64_sys_fsinfo # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -15,6 +15,13 @@ config VALIDATE_FS_PARSEREnablethistoperformvalidationoftheparameterdescriptionforafilesystemwhenitisregistered.+configFSINFO+bool"Enable the fsinfo() system call"+help+Enablethefilesysteminformationqueryingsystemcalltoallow+comprehensiveinformationtoberetrievedaboutafilesystem,+superblockormountobject.+ifBLOCKconfigFS_IOMAP
@@ -0,0 +1,525 @@+// SPDX-License-Identifier: GPL-2.0+/* Filesystem information query.+*+*Copyright(C)2020RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*/+#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"++staticconststructfsinfo_attributefsinfo_common_attributes[];++/**+*fsinfo_string-Storeastringasanfsinfoattributevalue.+*@s:Thestringtostore(maybeNULL)+*@ctx:Theparametercontext+*/+intfsinfo_string(constchar*s,structfsinfo_context*ctx)+{+intret=0;++if(s){+ret=strlen(s);+memcpy(ctx->buffer,s,ret);+}++returnret;+}+EXPORT_SYMBOL(fsinfo_string);++/*+*Getbasicfilesystemstatsfromstatfs.+*/+staticintfsinfo_generic_statfs(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_statfs*p=ctx->buffer;+structkstatfsbuf;+intret;++ret=vfs_statfs(path,&buf);+if(ret<0)+returnret;++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;+returnsizeof(*p);+}++staticintfsinfo_generic_ids(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_ids*p=ctx->buffer;+structsuper_block*sb;+structkstatfsbuf;+intret;++ret=vfs_statfs(path,&buf);+if(ret<0&&ret!=-ENOSYS)+returnret;++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));+returnsizeof(*p);+}++staticintfsinfo_generic_limits(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_limits*lim=ctx->buffer;+structsuper_block*sb=path->dentry->d_sb;++lim->max_file_size.hi=0;+lim->max_file_size.lo=sb->s_maxbytes;+lim->max_ino.hi=0;+lim->max_ino.lo=UINT_MAX;+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;+returnsizeof(*lim);+}++staticintfsinfo_generic_supports(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_supports*c=ctx->buffer;+structsuper_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;+returnsizeof(*c);+}++staticconststructfsinfo_timestamp_infofsinfo_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,+},+};++staticintfsinfo_generic_timestamp_info(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_timestamp_info*ts=ctx->buffer;+structsuper_block*sb=path->dentry->d_sb;+s8exponent;++*ts=fsinfo_default_timestamp_info;++if(sb->s_time_gran<1000000000){+if(sb->s_time_gran<1000)+exponent=-9;+elseif(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;+}++returnsizeof(*ts);+}++staticintfsinfo_generic_volume_uuid(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_volume_uuid*vu=ctx->buffer;+structsuper_block*sb=path->dentry->d_sb;++memcpy(vu,&sb->s_uuid,sizeof(*vu));+returnsizeof(*vu);+}++staticintfsinfo_generic_volume_id(structpath*path,structfsinfo_context*ctx)+{+returnfsinfo_string(path->dentry->d_sb->s_id,ctx);+}++staticintfsinfo_attribute_info(structpath*path,structfsinfo_context*ctx)+{+conststructfsinfo_attribute*attr;+structfsinfo_attribute_info*info=ctx->buffer;+structdentry*dentry=path->dentry;++if(dentry->d_sb->s_op->fsinfo_attributes)+for(attr=dentry->d_sb->s_op->fsinfo_attributes;attr->get;attr++)+if(attr->attr_id==ctx->Nth)+gotofound;+for(attr=fsinfo_common_attributes;attr->get;attr++)+if(attr->attr_id==ctx->Nth)+gotofound;+return-ENODATA;++found:+info->attr_id=attr->attr_id;+info->type=attr->type;+info->flags=attr->flags;+info->size=attr->size;+info->element_size=attr->element_size;+returnsizeof(*attr);+}++staticvoidfsinfo_attributes_insert(structfsinfo_context*ctx,+conststructfsinfo_attribute*attr)+{+__u32*buffer=ctx->buffer;+unsignedinti;++if(ctx->usage>ctx->buf_size||+ctx->buf_size-ctx->usage<sizeof(__u32)){+ctx->usage+=sizeof(__u32);+return;+}++for(i=0;i<ctx->usage/sizeof(__u32);i++)+if(buffer[i]==attr->attr_id)+return;++buffer[i]=attr->attr_id;+ctx->usage+=sizeof(__u32);+}++staticintfsinfo_attributes(structpath*path,structfsinfo_context*ctx)+{+conststructfsinfo_attribute*attr;+structsuper_block*sb=path->dentry->d_sb;++if(sb->s_op->fsinfo_attributes)+for(attr=sb->s_op->fsinfo_attributes;attr->get;attr++)+fsinfo_attributes_insert(ctx,attr);+for(attr=fsinfo_common_attributes;attr->get;attr++)+fsinfo_attributes_insert(ctx,attr);+returnctx->usage;+}++staticconststructfsinfo_attributefsinfo_common_attributes[]={+FSINFO_VSTRUCT(FSINFO_ATTR_STATFS,fsinfo_generic_statfs),+FSINFO_VSTRUCT(FSINFO_ATTR_IDS,fsinfo_generic_ids),+FSINFO_VSTRUCT(FSINFO_ATTR_LIMITS,fsinfo_generic_limits),+FSINFO_VSTRUCT(FSINFO_ATTR_SUPPORTS,fsinfo_generic_supports),+FSINFO_VSTRUCT(FSINFO_ATTR_TIMESTAMP_INFO,fsinfo_generic_timestamp_info),+FSINFO_STRING(FSINFO_ATTR_VOLUME_ID,fsinfo_generic_volume_id),+FSINFO_VSTRUCT(FSINFO_ATTR_VOLUME_UUID,fsinfo_generic_volume_uuid),+FSINFO_VSTRUCT_N(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO,fsinfo_attribute_info),+FSINFO_LIST(FSINFO_ATTR_FSINFO_ATTRIBUTES,fsinfo_attributes),+{}+};++/*+*Retrievelargefilesysteminformation,suchasanopaquebloborarrayof+*structelementswherethevalueisn'tlimitedtothesizeofapage.+*/+staticintvfs_fsinfo_large(structpath*path,structfsinfo_context*ctx,+conststructfsinfo_attribute*attr)+{+intret;++while(!signal_pending(current)){+ctx->usage=0;+ret=attr->get(path,ctx);+if(IS_ERR_VALUE((long)ret))+returnret;/* Error */+if((unsignedint)ret<=ctx->buf_size)+returnret;/* It fitted */++/* We need to resize the buffer */+kvfree(ctx->buffer);+ctx->buffer=NULL;+ctx->buf_size=roundup(ret,PAGE_SIZE);+if(ctx->buf_size>INT_MAX)+return-EMSGSIZE;+ctx->buffer=kvmalloc(ctx->buf_size,GFP_KERNEL);+if(!ctx->buffer)+return-ENOMEM;+}++return-ERESTARTSYS;+}++staticintvfs_do_fsinfo(structpath*path,structfsinfo_context*ctx,+conststructfsinfo_attribute*attr)+{+if(ctx->Nth!=0&&!(attr->flags&(FSINFO_FLAGS_N|FSINFO_FLAGS_NM)))+return-ENODATA;+if(ctx->Mth!=0&&!(attr->flags&FSINFO_FLAGS_NM))+return-ENODATA;++ctx->buf_size=attr->size;+if(ctx->want_size_only&&attr->type==FSINFO_TYPE_VSTRUCT)+returnattr->size;++ctx->buffer=kvzalloc(ctx->buf_size,GFP_KERNEL);+if(!ctx->buffer)+return-ENOMEM;++switch(attr->type){+caseFSINFO_TYPE_VSTRUCT:+ctx->clear_tail=true;+/* Fall through */+caseFSINFO_TYPE_STRING:+returnattr->get(path,ctx);++caseFSINFO_TYPE_OPAQUE:+caseFSINFO_TYPE_LIST:+returnvfs_fsinfo_large(path,ctx,attr);++default:+return-ENOPKG;+}+}++/**+*vfs_fsinfo-Retrievefilesysteminformation+*@path:Theobjecttoquery+*@params:Parameterstodefinearequestandplacetostoreresult+*+*Getanattributeonafilesystemoranobjectwithinafilesystem.The+*filesystemattributetobequeriedisindicatedby@ctx->requested_attr,and+*ifit'samulti-valuedattribute,theparticularvalueisselectedby+*@ctx->Nthandthen@ctx->Mth.+*+*Forcommonattributes,avaluemaybefabricatedifitisnotsupportedby+*thefilesystem.+*+*Onsuccess,thesizeoftheattribute'svalueisreturned(0isavalid+*size).Abufferwillhavebeenallocatedandwillbepointedtoby+*@ctx->buffer.Thecallermustfreethiswithkvfree().+*+*Errorscanalsobereturned:-ENOMEMifabuffercannotbeallocated,-EPERM+*or-EACCESifpermissionisdeniedbytheLSM,-EOPNOTSUPPifanattribute+*doesn'texistforthespecifiedobjector-ENODATAiftheattributeexists,+*buttheNth,Mthvaluedoesnotexist.-EMSGSIZEindicatesthatthevalueis+*unmanageableinternallyand-ENOPKGindicatesotherinternalfailure.+*+*Errorssuchas-EIOmayalsocomefromattemptstoaccessmediaorservers+*toobtaintherequestedinformationifit'snotimmediatelytohand.+*+*[*]Notethatthecallermayset@ctx->want_size_onlyifitonlywantsthe+*sizeofthevalueandnotthedata.Ifthisisset,abuffermaynotbe+*allocatedundersomecircumstances.Thisisintendedforsizequeryby+*userspace.+*+*[*]Notethat@ctx->clear_tailwillbereturnedsetifthedatashouldbe+*paddedoutwithzeroswhenwritingittouserspace.+*/+staticintvfs_fsinfo(structpath*path,structfsinfo_context*ctx)+{+conststructfsinfo_attribute*attr;+structdentry*dentry=path->dentry;+intret;++if(dentry->d_sb->s_op->fsinfo_attributes)+for(attr=dentry->d_sb->s_op->fsinfo_attributes;attr->get;attr++)+if(attr->attr_id==ctx->requested_attr)+gotofound;+for(attr=fsinfo_common_attributes;attr->get;attr++)+if(attr->attr_id==ctx->requested_attr)+gotofound;+return-EOPNOTSUPP;++found:+ret=security_sb_statfs(dentry);+if(ret)+returnret;++returnvfs_do_fsinfo(path,ctx,attr);+}++staticintvfs_fsinfo_path(intdfd,constchar__user*pathname,+unsignedintat_flags,structfsinfo_context*ctx)+{+structpathpath;+unsignedlookup_flags=LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT;+intret=-EINVAL;++if((at_flags&~(AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT|+AT_EMPTY_PATH))!=0)+return-EINVAL;++if(at_flags&AT_SYMLINK_NOFOLLOW)+lookup_flags&=~LOOKUP_FOLLOW;+if(at_flags&AT_NO_AUTOMOUNT)+lookup_flags&=~LOOKUP_AUTOMOUNT;+if(at_flags&AT_EMPTY_PATH)+lookup_flags|=LOOKUP_EMPTY;++retry:+ret=user_path_at(dfd,pathname,lookup_flags,&path);+if(ret)+gotoout;++ret=vfs_fsinfo(&path,ctx);+path_put(&path);+if(retry_estale(ret,lookup_flags)){+lookup_flags|=LOOKUP_REVAL;+gotoretry;+}+out:+returnret;+}++staticintvfs_fsinfo_fd(unsignedintfd,structfsinfo_context*ctx)+{+structfdf=fdget_raw(fd);+intret=-EBADF;++if(f.file){+ret=vfs_fsinfo(&f.file->f_path,ctx);+fdput(f);+}+returnret;+}++/**+*sys_fsinfo-Systemcalltogetfilesysteminformation+*@dfd:Basedirectorytopathwalkfromorfdreferringtofilesystem.+*@pathname:FilesystemtoqueryorNULL.+*@_params:Parameterstodefinerequest(orNULLforenhancedstatfs).+*@user_buffer:Resultbuffer.+*@user_buf_size:Sizeofresultbuffer.+*+*Getinformationonafilesystem.Thefilesystemattributetobequeriedis+*indicatedby@_params->request,andsomeoftheattributescanhavemultiple+*values,indexedby@_params->Nthand@_params->Mth.If@_paramsisNULL,+*thenthe0thfsinfo_attr_statfsattributeisqueried.Ifanattributedoes+*notexist,EOPNOTSUPPisreturned;iftheNth,Mthvaluedoesnotexist,+*ENODATAisreturned.+*+*Onsuccess,thesizeoftheattribute'svalueisreturned.If+*@user_buf_sizeis0or@user_bufferisNULL,onlythesizeisreturned.If+*thesizeofthevalueislargerthan@user_buf_size,itwillbetruncatedby+*thecopy.Ifthesizeofthevalueissmallerthan@user_buf_sizethenthe+*excessbufferspacewillbecleared.Thefullsizeofthevaluewillbe+*returned,irrespectiveofhowmuchdataisactuallyplacedinthebuffer.+*/+SYSCALL_DEFINE5(fsinfo,+int,dfd,constchar__user*,pathname,+structfsinfo_params__user*,params,+void__user*,user_buffer,size_t,user_buf_size)+{+structfsinfo_contextctx;+structfsinfo_paramsuser_params;+unsignedintat_flags=0,result_size;+intret;++if(!user_buffer&&user_buf_size)+return-EINVAL;+if(user_buffer&&!user_buf_size)+return-EINVAL;+if(user_buf_size>UINT_MAX)+return-EOVERFLOW;++memset(&ctx,0,sizeof(ctx));+ctx.requested_attr=FSINFO_ATTR_STATFS;+if(user_buf_size==0)+ctx.want_size_only=true;++if(params){+if(copy_from_user(&user_params,params,sizeof(user_params)))+return-EFAULT;+if(user_params.__reserved32[0]||+user_params.__reserved[0]||+user_params.__reserved[1]||+user_params.__reserved[2]||+user_params.flags&~FSINFO_FLAGS_QUERY_TYPE)+return-EINVAL;+at_flags=user_params.at_flags;+ctx.flags=user_params.flags;+ctx.requested_attr=user_params.request;+ctx.Nth=user_params.Nth;+ctx.Mth=user_params.Mth;+}++switch(ctx.flags&FSINFO_FLAGS_QUERY_TYPE){+caseFSINFO_FLAGS_QUERY_PATH:+ret=vfs_fsinfo_path(dfd,pathname,at_flags,&ctx);+break;+caseFSINFO_FLAGS_QUERY_FD:+if(pathname)+return-EINVAL;+ret=vfs_fsinfo_fd(dfd,&ctx);+break;+default:+return-EINVAL;+}++if(ret<0)+gotoerror;++result_size=ret;+if(result_size>user_buf_size)+result_size=user_buf_size;++if(result_size>0&&+copy_to_user(user_buffer,ctx.buffer,result_size)!=0){+ret=-EFAULT;+gotoerror;+}++/* Clear any part of the buffer that we won't fill if we're putting a+*structinthere.Strings,opaqueobjectsandarraysareexpectedto+*bevariablelength.+*/+if(ctx.clear_tail&&+user_buf_size>result_size&&+clear_user(user_buffer+result_size,user_buf_size-result_size)!=0){+ret=-EFAULT;+gotoerror;+}++error:+kvfree(ctx.buffer);+returnret;+}
@@ -1003,6 +1004,9 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,siginfo_t__user*info,unsignedintflags);asmlinkagelongsys_pidfd_getfd(intpidfd,intfd,unsignedintflags);+asmlinkagelongsys_fsinfo(intdfd,constchar__user*pathname,+structfsinfo_params__user*params,+void__user*buffer,size_tbuf_size);/**Architecture-specificsystemcalls
@@ -0,0 +1,186 @@+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */+/* fsinfo() definitions.+*+*Copyright(C)2020RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*/+#ifndef _UAPI_LINUX_FSINFO_H+#define _UAPI_LINUX_FSINFO_H++#include<linux/types.h>+#include<linux/socket.h>++/*+*Thefilesystemattributesthatcanberequested.Notethatsomeattributes+*mayhavemultipleinstanceswhichcanbeswitchedintheparameterblock.+*/+#define FSINFO_ATTR_STATFS 0x00 /* statfs()-style state */+#define FSINFO_ATTR_IDS 0x01 /* Filesystem IDs */+#define FSINFO_ATTR_LIMITS 0x02 /* Filesystem limits */+#define FSINFO_ATTR_SUPPORTS 0x03 /* What's supported in statx, iocflags, ... */+#define FSINFO_ATTR_TIMESTAMP_INFO 0x04 /* Inode timestamp info */+#define FSINFO_ATTR_VOLUME_ID 0x05 /* Volume ID (string) */+#define FSINFO_ATTR_VOLUME_UUID 0x06 /* Volume UUID (LE uuid) */+#define FSINFO_ATTR_VOLUME_NAME 0x07 /* Volume name (string) */++#define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO 0x100 /* Information about attr N (for path) */+#define FSINFO_ATTR_FSINFO_ATTRIBUTES 0x101 /* List of supported attrs (for path) */++/*+*Optionalfsinfo()parameterstructure.+*+*Ifthisisnotgiven,itisassumedthatfsinfo_attr_statfsinstance0,0is+*desired.+*/+structfsinfo_params{+__u32at_flags;/* AT_SYMLINK_NOFOLLOW and similar flags */+__u32flags;/* Flags controlling fsinfo() specifically */+#define FSINFO_FLAGS_QUERY_TYPE 0x0007 /* What object should fsinfo() query? */+#define FSINFO_FLAGS_QUERY_PATH 0x0000 /* - path, specified by dirfd,pathname,AT_EMPTY_PATH */+#define FSINFO_FLAGS_QUERY_FD 0x0001 /* - fd specified by dirfd */+__u32request;/* ID of requested attribute */+__u32Nth;/* Instance of it (some may have multiple) */+__u32Mth;/* Subinstance of Nth instance */+__u32__reserved32[1];/* Reserved params; all must be 0 */+__u64__reserved[3];+};++enumfsinfo_value_type{+FSINFO_TYPE_VSTRUCT=0,/* Version-lengthed struct (up to 4096 bytes) */+FSINFO_TYPE_STRING=1,/* NUL-term var-length string (up to 4095 chars) */+FSINFO_TYPE_OPAQUE=2,/* Opaque blob (unlimited size) */+FSINFO_TYPE_LIST=3,/* List of ints/structs (unlimited size) */+};++/*+*Informationstructforfsinfo(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO).+*+*Thisgivesinformationabouttheattributessupportedbyfsinfoforthe+*givenpath.+*/+structfsinfo_attribute_info{+unsignedintattr_id;/* The ID of the attribute */+enumfsinfo_value_typetype;/* The type of the attribute's value(s) */+unsignedintflags;+#define FSINFO_FLAGS_N 0x01 /* - Attr has a set of values */+#define FSINFO_FLAGS_NM 0x02 /* - Attr has a set of sets of values */+unsignedintsize;/* - Value size (FSINFO_STRUCT) */+unsignedintelement_size;/* - Element size (FSINFO_LIST) */+};++#define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO__STRUCT struct fsinfo_attribute_info+#define FSINFO_ATTR_FSINFO_ATTRIBUTES__STRUCT __u32++structfsinfo_u128{+#if defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : defined(__BIG_ENDIAN)+__u64hi;+__u64lo;+#elif defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : defined(__LITTLE_ENDIAN)+__u64lo;+__u64hi;+#endif+};++/*+*Informationstructforfsinfo(FSINFO_ATTR_STATFS).+*-Thisgivesextendedfilesysteminformation.+*/+structfsinfo_statfs{+structfsinfo_u128f_blocks;/* Total number of blocks in fs */+structfsinfo_u128f_bfree;/* Total number of free blocks */+structfsinfo_u128f_bavail;/* Number of free blocks available to ordinary user */+structfsinfo_u128f_files;/* Total number of file nodes in fs */+structfsinfo_u128f_ffree;/* Number of free file nodes */+structfsinfo_u128f_favail;/* Number of file nodes available to ordinary user */+__u64f_bsize;/* Optimal block size */+__u64f_frsize;/* Fragment size */+};++#define FSINFO_ATTR_STATFS__STRUCT struct fsinfo_statfs++/*+*Informationstructforfsinfo(FSINFO_ATTR_IDS).+*+*Listofbasicidentifiersasisnormallyfoundinstatfs().+*/+structfsinfo_ids{+charf_fs_name[15+1];/* Filesystem name */+__u64f_fsid;/* Short 64-bit Filesystem ID (as statfs) */+__u64f_sb_id;/* Internal superblock ID for sbnotify()/mntnotify() */+__u32f_fstype;/* Filesystem type from linux/magic.h [uncond] */+__u32f_dev_major;/* As st_dev_* from struct statx [uncond] */+__u32f_dev_minor;+__u32__reserved[1];+};++#define FSINFO_ATTR_IDS__STRUCT struct fsinfo_ids++/*+*Informationstructforfsinfo(FSINFO_ATTR_LIMITS).+*+*Listofsupportedfilesystemlimits.+*/+structfsinfo_limits{+structfsinfo_u128max_file_size;/* Maximum file size */+structfsinfo_u128max_ino;/* Maximum inode number */+__u64max_uid;/* Maximum UID supported */+__u64max_gid;/* Maximum GID supported */+__u64max_projid;/* Maximum project ID supported */+__u64max_hard_links;/* Maximum number of hard links on a file */+__u64max_xattr_body_len;/* Maximum xattr content length */+__u32max_xattr_name_len;/* Maximum xattr name length */+__u32max_filename_len;/* Maximum filename length */+__u32max_symlink_len;/* Maximum symlink content length */+__u32max_dev_major;/* Maximum device major representable */+__u32max_dev_minor;/* Maximum device minor representable */+__u32__reserved[1];+};++#define FSINFO_ATTR_LIMITS__STRUCT struct fsinfo_limits++/*+*Informationstructforfsinfo(FSINFO_ATTR_SUPPORTS).+*+*What'ssupportedinvariousmasks,suchasstatx()attributeandmaskbits+*andIOCflags.+*/+structfsinfo_supports{+__u64stx_attributes;/* What statx::stx_attributes are supported */+__u32stx_mask;/* What statx::stx_mask bits are supported */+__u32ioc_flags;/* What FS_IOC_* flags are supported */+__u32win_file_attrs;/* What DOS/Windows FILE_* attributes are supported */+__u32__reserved[1];+};++#define FSINFO_ATTR_SUPPORTS__STRUCT struct fsinfo_supports++structfsinfo_timestamp_one{+__s64minimum;/* Minimum timestamp value in seconds */+__u64maximum;/* Maximum timestamp value in seconds */+__u16gran_mantissa;/* Granularity(secs) = mant * 10^exp */+__s8gran_exponent;+__u8reserved[5];+};++/*+*Informationstructforfsinfo(FSINFO_ATTR_TIMESTAMP_INFO).+*/+structfsinfo_timestamp_info{+structfsinfo_timestamp_oneatime;/* Access time */+structfsinfo_timestamp_onemtime;/* Modification time */+structfsinfo_timestamp_onectime;/* Change time */+structfsinfo_timestamp_onebtime;/* Birth/creation time */+};++#define FSINFO_ATTR_TIMESTAMP_INFO__STRUCT struct fsinfo_timestamp_info++/*+*Informationstructforfsinfo(FSINFO_ATTR_VOLUME_UUID).+*/+structfsinfo_volume_uuid{+__u8uuid[16];+};++#define FSINFO_ATTR_VOLUME_UUID__STRUCT struct fsinfo_volume_uuid++#endif /* _UAPI_LINUX_FSINFO_H */
@@ -1,10 +1,15 @@# SPDX-License-Identifier: GPL-2.0-only# List of programs to build+hostprogs:=\+test-fsinfo\test-fsmount\test-statxalways-y:=$(hostprogs)+HOSTCFLAGS_test-fsinfo.o+=-I$(objtree)/usr/include+HOSTLDLIBS_test-fsinfo+=-static-lm+HOSTCFLAGS_test-fsmount.o+=-I$(objtree)/usr/includeHOSTCFLAGS_test-statx.o+=-I$(objtree)/usr/include
From: Darrick J. Wong <hidden> Date: 2020-02-19 16:31:59
On Tue, Feb 18, 2020 at 05:05:02PM +0000, David Howells wrote:
quoted hunk
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_IDS - Filesystem IDs
FSINFO_ATTR_LIMITS - Filesystem limits
FSINFO_ATTR_SUPPORTS - What's supported in statx(), IOC flags
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_FSINFO_ATTRIBUTE_INFO - Information about attr Nth
FSINFO_ATTR_FSINFO_ATTRIBUTES - List of supported attrs
Some attributes (such as the servers backing a network filesystem) can have
multiple values. These can be enumerated by setting params->Nth and
params->Mth to 0, 1, ... until ENODATA is returned.
buffer and buf_size point to the reply buffer. The buffer is filled up to
the specified size, even if this means truncating the reply. The full size
of the reply is returned. In future versions, this will allow extra fields
to be tacked on to the end of the reply, but anyone not expecting them will
only get the subset they're expecting. If either buffer of buf_size are 0,
no copy will take place and the data size will be returned.
At the moment, this will only work on x86_64 and i386 as it requires the
system call to be wired up.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: linux-api@vger.kernel.org
---
arch/x86/entry/syscalls/syscall_32.tbl | 1
arch/x86/entry/syscalls/syscall_64.tbl | 1
fs/Kconfig | 7
fs/Makefile | 1
fs/fsinfo.c | 525 ++++++++++++++++++++++++++++
include/linux/fs.h | 5
include/linux/fsinfo.h | 70 ++++
include/linux/syscalls.h | 4
include/uapi/asm-generic/unistd.h | 4
include/uapi/linux/fsinfo.h | 186 ++++++++++
kernel/sys_ni.c | 1
samples/vfs/Makefile | 5
samples/vfs/test-fsinfo.c | 599 ++++++++++++++++++++++++++++++++
13 files changed, 1408 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
@@ -359,6 +359,7 @@ 435 common clone3 __x64_sys_clone3/ptregs 437 common openat2 __x64_sys_openat2 438 common pidfd_getfd __x64_sys_pidfd_getfd+439 common fsinfo __x64_sys_fsinfo # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -15,6 +15,13 @@ config VALIDATE_FS_PARSEREnablethistoperformvalidationoftheparameterdescriptionforafilesystemwhenitisregistered.+configFSINFO+bool"Enable the fsinfo() system call"+help+Enablethefilesysteminformationqueryingsystemcalltoallow+comprehensiveinformationtoberetrievedaboutafilesystem,+superblockormountobject.+ifBLOCKconfigFS_IOMAP
@@ -0,0 +1,525 @@+// SPDX-License-Identifier: GPL-2.0+/* Filesystem information query.+*+*Copyright(C)2020RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*/+#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"++staticconststructfsinfo_attributefsinfo_common_attributes[];++/**+*fsinfo_string-Storeastringasanfsinfoattributevalue.+*@s:Thestringtostore(maybeNULL)+*@ctx:Theparametercontext+*/+intfsinfo_string(constchar*s,structfsinfo_context*ctx)+{+intret=0;++if(s){+ret=strlen(s);+memcpy(ctx->buffer,s,ret);+}++returnret;+}+EXPORT_SYMBOL(fsinfo_string);++/*+*Getbasicfilesystemstatsfromstatfs.+*/+staticintfsinfo_generic_statfs(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_statfs*p=ctx->buffer;+structkstatfsbuf;+intret;++ret=vfs_statfs(path,&buf);+if(ret<0)+returnret;++p->f_blocks.hi=0;+p->f_blocks.lo=buf.f_blocks;
Er... are there filesystems (besides that (xfs++)++ one) that require
u128 counters? I suspect that the Very Large Fields are for future
expandability, but I also wonder about the whether it's worth the
complexity of doing this, since the structures can always be
version-revved later.
(I'm not opposed to u128, I'm just curious about it. :))
...so is the usage model here that XFS should call fsinfo_generic_limits
to fill out the fsinfo_limits structure, modify the values in
ctx->buffer as appropriate for XFS, and then return the structure size?
+ FSINFO_VSTRUCT (FSINFO_ATTR_VOLUME_UUID, fsinfo_generic_volume_uuid),
+ FSINFO_VSTRUCT_N(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO, fsinfo_attribute_info),
+ FSINFO_LIST (FSINFO_ATTR_FSINFO_ATTRIBUTES, fsinfo_attributes),
+ {}
+};
+
+/*
+ * Retrieve large filesystem information, such as an opaque blob or array of
+ * struct elements where the value isn't limited to the size of a page.
+ */
+static int vfs_fsinfo_large(struct path *path, struct fsinfo_context *ctx,
+ const struct fsinfo_attribute *attr)
+{
+ int ret;
+
+ while (!signal_pending(current)) {
+ ctx->usage = 0;
+ ret = attr->get(path, ctx);
+ if (IS_ERR_VALUE((long)ret))
+ return ret; /* Error */
+ if ((unsigned int)ret <= ctx->buf_size)
+ return ret; /* It fitted */
+
+ /* We need to resize the buffer */
+ kvfree(ctx->buffer);
+ ctx->buffer = NULL;
+ ctx->buf_size = roundup(ret, PAGE_SIZE);
+ if (ctx->buf_size > INT_MAX)
+ return -EMSGSIZE;
+ ctx->buffer = kvmalloc(ctx->buf_size, GFP_KERNEL);
+ if (!ctx->buffer)
+ return -ENOMEM;
+ }
+
+ return -ERESTARTSYS;
+}
+
+static int vfs_do_fsinfo(struct path *path, struct fsinfo_context *ctx,
+ const struct fsinfo_attribute *attr)
+{
+ if (ctx->Nth != 0 && !(attr->flags & (FSINFO_FLAGS_N | FSINFO_FLAGS_NM)))
+ return -ENODATA;
+ if (ctx->Mth != 0 && !(attr->flags & FSINFO_FLAGS_NM))
+ return -ENODATA;
+
+ ctx->buf_size = attr->size;
+ if (ctx->want_size_only && attr->type == FSINFO_TYPE_VSTRUCT)
+ return attr->size;
+
+ ctx->buffer = kvzalloc(ctx->buf_size, GFP_KERNEL);
+ if (!ctx->buffer)
+ return -ENOMEM;
+
+ switch (attr->type) {
+ case FSINFO_TYPE_VSTRUCT:
+ ctx->clear_tail = true;
+ /* Fall through */
+ case FSINFO_TYPE_STRING:
+ return attr->get(path, ctx);
+
+ case FSINFO_TYPE_OPAQUE:
+ case FSINFO_TYPE_LIST:
+ return vfs_fsinfo_large(path, ctx, attr);
+
+ default:
+ return -ENOPKG;
+ }
+}
+
+/**
+ * vfs_fsinfo - Retrieve filesystem information
+ * @path: The object to query
+ * @params: Parameters to define a request and place to store result
+ *
+ * Get an attribute on a filesystem or an object within a filesystem. The
+ * filesystem attribute to be queried is indicated by @ctx->requested_attr, and
+ * if it's a multi-valued attribute, the particular value is selected by
+ * @ctx->Nth and then @ctx->Mth.
+ *
+ * For common attributes, a value may be fabricated if it is not supported by
+ * the filesystem.
+ *
+ * On success, the size of the attribute's value is returned (0 is a valid
+ * size). A buffer will have been allocated and will be pointed to by
+ * @ctx->buffer. The caller must free this with kvfree().
+ *
+ * Errors can also be returned: -ENOMEM if a buffer cannot be allocated, -EPERM
+ * or -EACCES if permission is denied by the LSM, -EOPNOTSUPP if an attribute
+ * doesn't exist for the specified object or -ENODATA if the attribute exists,
+ * but the Nth,Mth value does not exist. -EMSGSIZE indicates that the value is
+ * unmanageable internally and -ENOPKG indicates other internal failure.
+ *
+ * Errors such as -EIO may also come from attempts to access media or servers
+ * to obtain the requested information if it's not immediately to hand.
+ *
+ * [*] Note that the caller may set @ctx->want_size_only if it only wants the
+ * size of the value and not the data. If this is set, a buffer may not be
+ * allocated under some circumstances. This is intended for size query by
+ * userspace.
+ *
+ * [*] Note that @ctx->clear_tail will be returned set if the data should be
+ * padded out with zeros when writing it to userspace.
+ */
+static int vfs_fsinfo(struct path *path, struct fsinfo_context *ctx)
+{
+ const struct fsinfo_attribute *attr;
+ struct dentry *dentry = path->dentry;
+ int ret;
+
+ if (dentry->d_sb->s_op->fsinfo_attributes)
+ for (attr = dentry->d_sb->s_op->fsinfo_attributes; attr->get; attr++)
+ if (attr->attr_id == ctx->requested_attr)
+ goto found;
+ for (attr = fsinfo_common_attributes; attr->get; attr++)
+ if (attr->attr_id == ctx->requested_attr)
+ goto found;
+ return -EOPNOTSUPP;
+
+found:
+ ret = security_sb_statfs(dentry);
+ if (ret)
+ return ret;
+
+ return vfs_do_fsinfo(path, ctx, attr);
+}
+
+static int vfs_fsinfo_path(int dfd, const char __user *pathname,
+ unsigned int at_flags, struct fsinfo_context *ctx)
+{
+ struct path path;
+ unsigned lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
+ int ret = -EINVAL;
+
+ if ((at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
+ AT_EMPTY_PATH)) != 0)
+ return -EINVAL;
+
+ if (at_flags & AT_SYMLINK_NOFOLLOW)
+ lookup_flags &= ~LOOKUP_FOLLOW;
+ if (at_flags & AT_NO_AUTOMOUNT)
+ lookup_flags &= ~LOOKUP_AUTOMOUNT;
+ if (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, ctx);
+ 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_context *ctx)
+{
+ struct fd f = fdget_raw(fd);
+ int ret = -EBADF;
+
+ if (f.file) {
+ ret = vfs_fsinfo(&f.file->f_path, ctx);
+ fdput(f);
+ }
+ return ret;
+}
+
+/**
+ * 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_context ctx;
+ struct fsinfo_params user_params;
+ unsigned int at_flags = 0, result_size;
+ int ret;
+
+ if (!user_buffer && user_buf_size)
+ return -EINVAL;
+ if (user_buffer && !user_buf_size)
+ return -EINVAL;
+ if (user_buf_size > UINT_MAX)
+ return -EOVERFLOW;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.requested_attr = FSINFO_ATTR_STATFS;
+ if (user_buf_size == 0)
+ ctx.want_size_only = true;
+
+ if (params) {
+ if (copy_from_user(&user_params, params, sizeof(user_params)))
+ return -EFAULT;
+ if (user_params.__reserved32[0] ||
+ user_params.__reserved[0] ||
+ user_params.__reserved[1] ||
+ user_params.__reserved[2] ||
+ user_params.flags & ~FSINFO_FLAGS_QUERY_TYPE)
+ return -EINVAL;
+ at_flags = user_params.at_flags;
+ ctx.flags = user_params.flags;
+ ctx.requested_attr = user_params.request;
+ ctx.Nth = user_params.Nth;
+ ctx.Mth = user_params.Mth;
+ }
+
+ switch (ctx.flags & FSINFO_FLAGS_QUERY_TYPE) {
+ case FSINFO_FLAGS_QUERY_PATH:
+ ret = vfs_fsinfo_path(dfd, pathname, at_flags, &ctx);
+ break;
+ case FSINFO_FLAGS_QUERY_FD:
+ if (pathname)
+ return -EINVAL;
+ ret = vfs_fsinfo_fd(dfd, &ctx);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (ret < 0)
+ goto error;
+
+ result_size = ret;
+ if (result_size > user_buf_size)
+ result_size = user_buf_size;
+
+ if (result_size > 0 &&
+ copy_to_user(user_buffer, ctx.buffer, result_size) != 0) {
+ ret = -EFAULT;
+ goto error;
+ }
+
+ /* 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 (ctx.clear_tail &&
+ user_buf_size > result_size &&
+ clear_user(user_buffer + result_size, user_buf_size - result_size) != 0) {
+ ret = -EFAULT;
+ goto error;
+ }
+
+error:
+ kvfree(ctx.buffer);
+ return ret;
+}
@@ -1003,6 +1004,9 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,siginfo_t__user*info,unsignedintflags);asmlinkagelongsys_pidfd_getfd(intpidfd,intfd,unsignedintflags);+asmlinkagelongsys_fsinfo(intdfd,constchar__user*pathname,+structfsinfo_params__user*params,+void__user*buffer,size_tbuf_size);/**Architecture-specificsystemcalls
I think I've muttered about the distinction between volume id and
volume name before, but I'm still wondering how confusing that will be
for users? Let me check my assumptions, though:
Volume ID is whatever's in super_block.s_id, which (at least for xfs and
ext4) is the device name (e.g. "sda1"). I guess that's useful for
correlating a thing you can call fsinfo() on against strings that were
logged in dmesg.
Volume name I think is the fs label (e.g. "home"), which I think will
have to be implemented separately by each filesystem, and that's why
there's no generic vfs implementation.
Do I have that correct?
+
+#define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO 0x100 /* Information about attr N (for path) */
+#define FSINFO_ATTR_FSINFO_ATTRIBUTES 0x101 /* List of supported attrs (for path) */
+
+/*
+ * 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 flags; /* Flags controlling fsinfo() specifically */
+#define FSINFO_FLAGS_QUERY_TYPE 0x0007 /* What object should fsinfo() query? */
+#define FSINFO_FLAGS_QUERY_PATH 0x0000 /* - path, specified by dirfd,pathname,AT_EMPTY_PATH */
+#define FSINFO_FLAGS_QUERY_FD 0x0001 /* - fd specified by dirfd */
The 7 -> 0 -> 1 sequence here confused me until I figured out that
QUERY_TYPE is the mask for QUERY_{PATH,FD}.
+ __u32 request; /* ID of requested attribute */
+ __u32 Nth; /* Instance of it (some may have multiple) */
+ __u32 Mth; /* Subinstance of Nth instance */
+ __u32 __reserved32[1]; /* Reserved params; all must be 0 */
+ __u64 __reserved[3];
+};
+
+enum fsinfo_value_type {
+ FSINFO_TYPE_VSTRUCT = 0, /* Version-lengthed struct (up to 4096 bytes) */
+ FSINFO_TYPE_STRING = 1, /* NUL-term var-length string (up to 4095 chars) */
+ FSINFO_TYPE_OPAQUE = 2, /* Opaque blob (unlimited size) */
+ FSINFO_TYPE_LIST = 3, /* List of ints/structs (unlimited size) */
+};
+
+/*
+ * Information struct for fsinfo(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO).
+ *
+ * This gives information about the attributes supported by fsinfo for the
+ * given path.
+ */
+struct fsinfo_attribute_info {
+ unsigned int attr_id; /* The ID of the attribute */
+ enum fsinfo_value_type type; /* The type of the attribute's value(s) */
+ unsigned int flags;
+#define FSINFO_FLAGS_N 0x01 /* - Attr has a set of values */
+#define FSINFO_FLAGS_NM 0x02 /* - Attr has a set of sets of values */
+ unsigned int size; /* - Value size (FSINFO_STRUCT) */
+ unsigned int element_size; /* - Element size (FSINFO_LIST) */
+};
+
+#define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO__STRUCT struct fsinfo_attribute_info
+#define FSINFO_ATTR_FSINFO_ATTRIBUTES__STRUCT __u32
+
+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 */
+};
+
+#define FSINFO_ATTR_STATFS__STRUCT struct fsinfo_statfs
+
+/*
+ * 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];
+};
+
+#define FSINFO_ATTR_IDS__STRUCT struct fsinfo_ids
+
+/*
+ * 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];
I wonder if these structures ought to reserve more space than a single u32...
+};
+
+#define FSINFO_ATTR_LIMITS__STRUCT struct fsinfo_limits
+
+/*
+ * 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 */
"IOC"? That just means 'ioctl'. Is this field supposed to return the
supported FS_IOC_GETFLAGS flags, or the supported FS_IOC_FSGETXATTR
flags?
I suspect it would also be a big help to be able to tell userspace which
of the flags can be set, and which can be cleared.
+ __u32 win_file_attrs; /* What DOS/Windows FILE_* attributes are supported */
+ __u32 __reserved[1];
+};
+
+#define FSINFO_ATTR_SUPPORTS__STRUCT struct fsinfo_supports
+
+struct fsinfo_timestamp_one {
+ __s64 minimum; /* Minimum timestamp value in seconds */
+ __u64 maximum; /* Maximum timestamp value in seconds */
Given that time64_t is s64, why is the maximum here u64?
@@ -1,10 +1,15 @@# SPDX-License-Identifier: GPL-2.0-only# List of programs to build+hostprogs:=\+test-fsinfo\test-fsmount\test-statxalways-y:=$(hostprogs)+HOSTCFLAGS_test-fsinfo.o+=-I$(objtree)/usr/include+HOSTLDLIBS_test-fsinfo+=-static-lm+HOSTCFLAGS_test-fsmount.o+=-I$(objtree)/usr/includeHOSTCFLAGS_test-statx.o+=-I$(objtree)/usr/include
Er... are there filesystems (besides that (xfs++)++ one) that require
u128 counters? I suspect that the Very Large Fields are for future
expandability, but I also wonder about the whether it's worth the
complexity of doing this, since the structures can always be
version-revved later.
I'm making a relatively cheap allowance for future expansion. Dave Chinner
has mentioned at one of the LSFs that 16EiB may be exceeded soon (though I
hate to think of fscking such a beastie). I know that the YFS variant of AFS
supports 96-bit vnode numbers (which I translate to inode numbers). What I'm
trying to avoid is the problem we have with stat/statfs where under some
circumstances we have to return an error (ERANGE?) because we can't represent
the number if someone asks for an older version of the struct.
Since the buffer is (meant to be) pre-cleared, the filesystem can just ignore
the high word if it's never going to set it. In fact, fsinfo_generic_statfs
doesn't need to set them either.
XFS inodes are u64 values...
...and the max symlink target length is 1k, not PAGE_SIZE...
Yeah, and AFS(YFS) has 96-bit inode numbers. The filesystem's fsinfo table is
read first so that the filesystem can override this.
...so is the usage model here that XFS should call fsinfo_generic_limits
to fill out the fsinfo_limits structure, modify the values in
ctx->buffer as appropriate for XFS, and then return the structure size?
Actually, I should export some these so that you can do that. I'll export
fsinfo_generic_{timestamp_info,supports,limits} for now.
I think I've muttered about the distinction between volume id and
volume name before, but I'm still wondering how confusing that will be
for users? Let me check my assumptions, though:
Volume ID is whatever's in super_block.s_id, which (at least for xfs and
ext4) is the device name (e.g. "sda1"). I guess that's useful for
correlating a thing you can call fsinfo() on against strings that were
logged in dmesg.
Volume name I think is the fs label (e.g. "home"), which I think will
have to be implemented separately by each filesystem, and that's why
there's no generic vfs implementation.
Yes. For AFS, for example, this would be the name of the volume (which may be
changed), whereas the volume ID is the number in the protocol that actually
refers to the volume (which cannot be changed).
And, as you say, for blockdev mounts, the ID is the device name and the volume
name is filesystem specific.
The 7 -> 0 -> 1 sequence here confused me until I figured out that
QUERY_TYPE is the mask for QUERY_{PATH,FD}.
I wonder if these structures ought to reserve more space than a single u32...
No need. Part of the way the interface is designed is that the version number
for a particular VSTRUCT-type attribute is also the length. So a newer
version is also longer. All the old fields must be retained and filled in.
New fields are tagged on the end.
If userspace asks for an older version than is supported, it gets a truncated
return. If it asks for a newer version, the extra fields it is expecting are
all set to 0. Either way, the length (and thus the version) the kernel
supports is returned - not the length copied.
The __reserved fields are there because they represent padding (the struct is
going to be aligned/padded according to __u64 in this case). Ideally, I'd
mark the structs __packed, but this messes with the alignment and may make the
compiler do funny tricks to get out any field larger than a byte.
I've renamed them to __padding.
quoted
+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 */
"IOC"? That just means 'ioctl'. Is this field supposed to return the
supported FS_IOC_GETFLAGS flags, or the supported FS_IOC_FSGETXATTR
flags?
FS_IOC_[GS]ETFLAGS is what I meant.
I suspect it would also be a big help to be able to tell userspace which
of the flags can be set, and which can be cleared.
How about:
__u32 fs_ioc_getflags; /* What FS_IOC_GETFLAGS may return */
__u32 fs_ioc_setflags_set; /* What FS_IOC_SETFLAGS may set */
__u32 fs_ioc_setflags_clear; /* What FS_IOC_SETFLAGS may clear */
quoted
+struct fsinfo_timestamp_one {
+ __s64 minimum; /* Minimum timestamp value in seconds */
+ __u64 maximum; /* Maximum timestamp value in seconds */
Given that time64_t is s64, why is the maximum here u64?
Well, I assume it extremely unlikely that the maximum can be before 1970, so
there doesn't seem any need to allow the maximum to be negative. Furthermore,
it would be feasible that you could encounter a filesystem with a u64
filesystem that doesn't support dates before 1970.
On the other hand, if Linux is still going when __s64 seconds from 1970 wraps,
it will be impressive, but I'm not sure we'll be around to see it... Someone
will have to cast a resurrection spell on Arnd to fix that one.
However, since signed/unsigned comparisons may have issues, I can turn it into
an __s64 also if that is preferred.
David
Er... are there filesystems (besides that (xfs++)++ one) that require
u128 counters? I suspect that the Very Large Fields are for future
expandability, but I also wonder about the whether it's worth the
complexity of doing this, since the structures can always be
version-revved later.
I'm making a relatively cheap allowance for future expansion. Dave Chinner
has mentioned at one of the LSFs that 16EiB may be exceeded soon (though I
hate to think of fscking such a beastie). I know that the YFS variant of AFS
supports 96-bit vnode numbers (which I translate to inode numbers). What I'm
Aha, you /already/ have a use for larger-than-64bit numbers. Got it. :)
trying to avoid is the problem we have with stat/statfs where under some
circumstances we have to return an error (ERANGE?) because we can't represent
the number if someone asks for an older version of the struct.
Since the buffer is (meant to be) pre-cleared, the filesystem can just ignore
the high word if it's never going to set it. In fact, fsinfo_generic_statfs
doesn't need to set them either.
quoted
XFS inodes are u64 values...
...and the max symlink target length is 1k, not PAGE_SIZE...
Yeah, and AFS(YFS) has 96-bit inode numbers. The filesystem's fsinfo table is
read first so that the filesystem can override this.
quoted
...so is the usage model here that XFS should call fsinfo_generic_limits
to fill out the fsinfo_limits structure, modify the values in
ctx->buffer as appropriate for XFS, and then return the structure size?
Actually, I should export some these so that you can do that. I'll export
fsinfo_generic_{timestamp_info,supports,limits} for now.
I think I've muttered about the distinction between volume id and
volume name before, but I'm still wondering how confusing that will be
for users? Let me check my assumptions, though:
Volume ID is whatever's in super_block.s_id, which (at least for xfs and
ext4) is the device name (e.g. "sda1"). I guess that's useful for
correlating a thing you can call fsinfo() on against strings that were
logged in dmesg.
Volume name I think is the fs label (e.g. "home"), which I think will
have to be implemented separately by each filesystem, and that's why
there's no generic vfs implementation.
Yes. For AFS, for example, this would be the name of the volume (which may be
changed), whereas the volume ID is the number in the protocol that actually
refers to the volume (which cannot be changed).
And, as you say, for blockdev mounts, the ID is the device name and the volume
name is filesystem specific.
quoted
The 7 -> 0 -> 1 sequence here confused me until I figured out that
QUERY_TYPE is the mask for QUERY_{PATH,FD}.
I wonder if these structures ought to reserve more space than a single u32...
No need. Part of the way the interface is designed is that the version number
for a particular VSTRUCT-type attribute is also the length. So a newer
version is also longer. All the old fields must be retained and filled in.
New fields are tagged on the end.
If userspace asks for an older version than is supported, it gets a truncated
return. If it asks for a newer version, the extra fields it is expecting are
all set to 0. Either way, the length (and thus the version) the kernel
supports is returned - not the length copied.
The __reserved fields are there because they represent padding (the struct is
going to be aligned/padded according to __u64 in this case). Ideally, I'd
mark the structs __packed, but this messes with the alignment and may make the
compiler do funny tricks to get out any field larger than a byte.
I've renamed them to __padding.
quoted
quoted
+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 */
"IOC"? That just means 'ioctl'. Is this field supposed to return the
supported FS_IOC_GETFLAGS flags, or the supported FS_IOC_FSGETXATTR
flags?
FS_IOC_[GS]ETFLAGS is what I meant.
quoted
I suspect it would also be a big help to be able to tell userspace which
of the flags can be set, and which can be cleared.
How about:
__u32 fs_ioc_getflags; /* What FS_IOC_GETFLAGS may return */
__u32 fs_ioc_setflags_set; /* What FS_IOC_SETFLAGS may set */
__u32 fs_ioc_setflags_clear; /* What FS_IOC_SETFLAGS may clear */
Looks good to me. At some point it'll be good to add another fsinfo
verb or something to query exactly what parts of struct fsxattr can be
set, cleared, or returned, but that can wait.
quoted
quoted
+struct fsinfo_timestamp_one {
+ __s64 minimum; /* Minimum timestamp value in seconds */
+ __u64 maximum; /* Maximum timestamp value in seconds */
Given that time64_t is s64, why is the maximum here u64?
Well, I assume it extremely unlikely that the maximum can be before 1970, so
there doesn't seem any need to allow the maximum to be negative. Furthermore,
it would be feasible that you could encounter a filesystem with a u64
filesystem that doesn't support dates before 1970.
On the other hand, if Linux is still going when __s64 seconds from 1970 wraps,
it will be impressive, but I'm not sure we'll be around to see it... Someone
will have to cast a resurrection spell on Arnd to fix that one.
I fear we /all/ will be around, having forcibly been uploaded to The
Cloud because nobody else knows how to maintain the Linux kernel, and we
can't have the drinks dispenser on B deck going bonkers twice per stardate.
(j/k)
However, since signed/unsigned comparisons may have issues, I can turn it into
an __s64 also if that is preferred.
It /would/ be more consistent with the rest of the kernel, and since the
kernel & userspace don't support timestamps beyond 8Es, there's no point
in advertising a maximum beyond that.
--D
On Tue, Feb 18, 2020 at 6:05 PM David Howells [off-list ref] 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.
[...]
+static const struct fsinfo_attribute fsinfo_common_attributes[];
+
+/**
+ * fsinfo_string - Store a string as an fsinfo attribute value.
+ * @s: The string to store (may be NULL)
+ * @ctx: The parameter context
+ */
+int fsinfo_string(const char *s, struct fsinfo_context *ctx)
+{
+ int ret = 0;
+
+ if (s) {
+ ret = strlen(s);
+ memcpy(ctx->buffer, s, ret);
+ }
+
+ return ret;
+}
Please add a check here to ensure that "ret" actually fits into the
buffer (and use WARN_ON() if you think the check should never fire).
Otherwise I think this is too fragile.
[...]
+static int fsinfo_generic_ids(struct path *path, struct fsinfo_context *ctx)
+{
+ struct fsinfo_ids *p = ctx->buffer;
+ struct super_block *sb;
+ struct kstatfs buf;
+ int ret;
+
+ ret = vfs_statfs(path, &buf);
+ if (ret < 0 && ret != -ENOSYS)
+ return ret;
What's going on here? If vfs_statfs() returns -ENOSYS, we just use the
(AFAICS uninitialized) buf.f_fsid anyway in the memcpy() below and
return it to userspace?
It is kind of weird that you have to return the ctx->usage everywhere
even though the caller already has ctx...
+}
+
+static const struct fsinfo_attribute fsinfo_common_attributes[] = {
+ FSINFO_VSTRUCT (FSINFO_ATTR_STATFS, fsinfo_generic_statfs),
+ FSINFO_VSTRUCT (FSINFO_ATTR_IDS, fsinfo_generic_ids),
+ FSINFO_VSTRUCT (FSINFO_ATTR_LIMITS, fsinfo_generic_limits),
+ FSINFO_VSTRUCT (FSINFO_ATTR_SUPPORTS, fsinfo_generic_supports),
+ FSINFO_VSTRUCT (FSINFO_ATTR_TIMESTAMP_INFO, fsinfo_generic_timestamp_info),
+ FSINFO_STRING (FSINFO_ATTR_VOLUME_ID, fsinfo_generic_volume_id),
+ FSINFO_VSTRUCT (FSINFO_ATTR_VOLUME_UUID, fsinfo_generic_volume_uuid),
+ FSINFO_VSTRUCT_N(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO, fsinfo_attribute_info),
+ FSINFO_LIST (FSINFO_ATTR_FSINFO_ATTRIBUTES, fsinfo_attributes),
+ {}
+};
+
+/*
+ * Retrieve large filesystem information, such as an opaque blob or array of
+ * struct elements where the value isn't limited to the size of a page.
+ */
+static int vfs_fsinfo_large(struct path *path, struct fsinfo_context *ctx,
+ const struct fsinfo_attribute *attr)
+{
+ int ret;
+
+ while (!signal_pending(current)) {
+ ctx->usage = 0;
+ ret = attr->get(path, ctx);
+ if (IS_ERR_VALUE((long)ret))
+ return ret; /* Error */
+ if ((unsigned int)ret <= ctx->buf_size)
+ return ret; /* It fitted */
+
+ /* We need to resize the buffer */
+ kvfree(ctx->buffer);
+ ctx->buffer = NULL;
+ ctx->buf_size = roundup(ret, PAGE_SIZE);
+ if (ctx->buf_size > INT_MAX)
+ return -EMSGSIZE;
+ ctx->buffer = kvmalloc(ctx->buf_size, GFP_KERNEL);
ctx->buffer is _almost_ always pre-zeroed (see vfs_do_fsinfo() below),
except if we have FSINFO_TYPE_OPAQUE or FSINFO_TYPE_LIST with a size
bigger than what the attribute's ->size field said? Is that
intentional?
This is "result_size = min_t(size_t, ret, user_buf_size)".
[...]
+/*
+ * A filesystem information attribute definition.
+ */
+struct fsinfo_attribute {
+ unsigned int attr_id; /* The ID of the attribute */
+ enum fsinfo_value_type type:8; /* The type of the attribute's value(s) */
+ unsigned int flags:8;
+ unsigned int size:16; /* - Value size (FSINFO_STRUCT) */
+ unsigned int element_size:16; /* - Element size (FSINFO_LIST) */
+ int (*get)(struct path *path, struct fsinfo_context *params);
+};
Why the bitfields? It doesn't look like that's going to help you much,
you'll just end up with 6 bytes of holes on x86-64:
$ cat fsinfo_attribute_layout.c
enum fsinfo_value_type {
FSINFO_TYPE_VSTRUCT = 0, /* Version-lengthed struct (up to
4096 bytes) */
FSINFO_TYPE_STRING = 1, /* NUL-term var-length string (up to
4095 chars) */
FSINFO_TYPE_OPAQUE = 2, /* Opaque blob (unlimited size) */
FSINFO_TYPE_LIST = 3, /* List of ints/structs (unlimited size) */
};
struct fsinfo_attribute {
unsigned int attr_id; /* The ID of the attribute */
enum fsinfo_value_type type:8; /* The type of the
attribute's value(s) */
unsigned int flags:8;
unsigned int size:16; /* - Value size (FSINFO_STRUCT) */
unsigned int element_size:16; /* - Element size (FSINFO_LIST) */
void *get;
};
void *blah(struct fsinfo_attribute *p) {
return p->get;
}
$ gcc -c -o fsinfo_attribute_layout.o fsinfo_attribute_layout.c -ggdb
$ pahole -C fsinfo_attribute -E --hex fsinfo_attribute_layout.o
struct fsinfo_attribute {
unsigned int attr_id; /* 0 0x4 */
enum fsinfo_value_type type:8; /* 0x4: 0 0x4 */
unsigned int flags:8; /* 0x4:0x8 0x4 */
unsigned int size:16; /* 0x4:0x10 0x4 */
unsigned int element_size:16; /* 0x8: 0 0x4 */
/* XXX 16 bits hole, try to pack */
/* XXX 4 bytes hole, try to pack */
void * get; /* 0x10 0x8 */
/* size: 24, cachelines: 1, members: 6 */
/* sum members: 12, holes: 1, sum holes: 4 */
/* sum bitfield members: 48 bits, bit holes: 1, sum bit holes:
16 bits */
/* last cacheline: 24 bytes */
};
...
Please add a check here to ensure that "ret" actually fits into the
buffer (and use WARN_ON() if you think the check should never fire).
Otherwise I think this is too fragile.
How about:
int fsinfo_string(const char *s, struct fsinfo_context *ctx)
{
unsigned int len;
char *p = ctx->buffer;
int ret = 0;
if (s) {
len = strlen(s);
if (len > ctx->buf_size - 1)
len = ctx->buf_size;
if (!ctx->want_size_only) {
memcpy(p, s, len);
p[len] = 0;
}
ret = len;
}
return ret;
}
I've also added a check to eliminate the copy if userspace didn't actually
supply a buffer.
quoted
+ ret = vfs_statfs(path, &buf);
+ if (ret < 0 && ret != -ENOSYS)
+ return ret;
What's going on here? If vfs_statfs() returns -ENOSYS, we just use the
(AFAICS uninitialized) buf.f_fsid anyway in the memcpy() below and
return it to userspace?
Good point. I've made the access to the buffer contingent on ret==0. If I
don't set it, it will just be left pre-cleared.
quoted
+ return sizeof(*attr);
I think you meant sizeof(*info).
Yes. I've renamed the buffer point to "p" in all cases so that it's more
obvious.
quoted
+ return ctx->usage;
It is kind of weird that you have to return the ctx->usage everywhere
even though the caller already has ctx...
At this point, it's only used and returned by fsinfo_attributes() and really
is only for the use of the attribute getter function.
I could, I suppose, return the amount of data in ctx->usage and then preset it
for VSTRUCT-type objects. Unfortunately, I can't make the getter return void
since it might have to return an error.
ctx->buffer is _almost_ always pre-zeroed (see vfs_do_fsinfo() below),
except if we have FSINFO_TYPE_OPAQUE or FSINFO_TYPE_LIST with a size
bigger than what the attribute's ->size field said? Is that
intentional?
Fixed.
quoted
+struct fsinfo_attribute {
+ unsigned int attr_id; /* The ID of the attribute */
+ enum fsinfo_value_type type:8; /* The type of the attribute's value(s) */
+ unsigned int flags:8;
+ unsigned int size:16; /* - Value size (FSINFO_STRUCT) */
+ unsigned int element_size:16; /* - Element size (FSINFO_LIST) */
+ int (*get)(struct path *path, struct fsinfo_context *params);
+};
Why the bitfields? It doesn't look like that's going to help you much,
you'll just end up with 6 bytes of holes on x86-64:
Expanding them to non-bitfields will require an extra 10 bytes, making the
struct 8 bytes bigger with 4 bytes of padding. I can do that if you'd rather.
David
...
Please add a check here to ensure that "ret" actually fits into the
buffer (and use WARN_ON() if you think the check should never fire).
Otherwise I think this is too fragile.
How about:
int fsinfo_string(const char *s, struct fsinfo_context *ctx)
{
unsigned int len;
char *p = ctx->buffer;
int ret = 0;
if (s) {
len = strlen(s);
if (len > ctx->buf_size - 1)
len = ctx->buf_size;
if (!ctx->want_size_only) {
memcpy(p, s, len);
p[len] = 0;
I think this is off-by-one? If len was too big, it is set to
ctx->buf_size, so in that case this effectively becomes
`ctx->buffer[ctx->buf_size] = 0`, which is one byte out of bounds,
right?
Maybe use something like `len = min_t(size_t, strlen(s), ctx->buf_size-1)` ?
Looks good apart from that, I think.
}
ret = len;
}
return ret;
}
[...]
quoted
quoted
+ return ctx->usage;
It is kind of weird that you have to return the ctx->usage everywhere
even though the caller already has ctx...
At this point, it's only used and returned by fsinfo_attributes() and really
is only for the use of the attribute getter function.
I could, I suppose, return the amount of data in ctx->usage and then preset it
for VSTRUCT-type objects. Unfortunately, I can't make the getter return void
since it might have to return an error.
Yeah, then you'd be passing around the error separately from the
length... I don't know whether that'd make things better or worse.
[...]
quoted
quoted
+struct fsinfo_attribute {
+ unsigned int attr_id; /* The ID of the attribute */
+ enum fsinfo_value_type type:8; /* The type of the attribute's value(s) */
+ unsigned int flags:8;
+ unsigned int size:16; /* - Value size (FSINFO_STRUCT) */
+ unsigned int element_size:16; /* - Element size (FSINFO_LIST) */
+ int (*get)(struct path *path, struct fsinfo_context *params);
+};
Why the bitfields? It doesn't look like that's going to help you much,
you'll just end up with 6 bytes of holes on x86-64:
Expanding them to non-bitfields will require an extra 10 bytes, making the
struct 8 bytes bigger with 4 bytes of padding. I can do that if you'd rather.
Wouldn't this still have the same total size?
struct fsinfo_attribute {
unsigned int attr_id; /* 0x0-0x3 */
enum fsinfo_value_type type; /* 0x4-0x7 */
u8 flags; /* 0x8-0x8 */
/* 1-byte hole */
u16 size; /* 0xa-0xb */
u16 element_size; /* 0xc-0xd */
/* 2-byte hole */
int (*get)(...); /* 0x10-0x18 */
};
But it's not like I really care about this detail all that much, feel
free to leave it as-is.
...
Please add a check here to ensure that "ret" actually fits into the
buffer (and use WARN_ON() if you think the check should never fire).
Otherwise I think this is too fragile.
How about:
int fsinfo_string(const char *s, struct fsinfo_context *ctx)
{
unsigned int len;
char *p = ctx->buffer;
int ret = 0;
if (s) {
len = strlen(s);
if (len > ctx->buf_size - 1)
len = ctx->buf_size;
if (!ctx->want_size_only) {
memcpy(p, s, len);
p[len] = 0;
I think this is off-by-one? If len was too big, it is set to
ctx->buf_size, so in that case this effectively becomes
`ctx->buffer[ctx->buf_size] = 0`, which is one byte out of bounds,
right?
Maybe use something like `len = min_t(size_t, strlen(s), ctx->buf_size-1)` ?
Looks good apart from that, I think.
quoted
}
ret = len;
}
return ret;
}
[...]
quoted
quoted
quoted
+ return ctx->usage;
It is kind of weird that you have to return the ctx->usage everywhere
even though the caller already has ctx...
At this point, it's only used and returned by fsinfo_attributes() and really
is only for the use of the attribute getter function.
I could, I suppose, return the amount of data in ctx->usage and then preset it
for VSTRUCT-type objects. Unfortunately, I can't make the getter return void
since it might have to return an error.
Yeah, then you'd be passing around the error separately from the
length... I don't know whether that'd make things better or worse.
[...]
quoted
quoted
quoted
+struct fsinfo_attribute {
+ unsigned int attr_id; /* The ID of the attribute */
+ enum fsinfo_value_type type:8; /* The type of the attribute's value(s) */
+ unsigned int flags:8;
+ unsigned int size:16; /* - Value size (FSINFO_STRUCT) */
+ unsigned int element_size:16; /* - Element size (FSINFO_LIST) */
+ int (*get)(struct path *path, struct fsinfo_context *params);
+};
Why the bitfields? It doesn't look like that's going to help you much,
you'll just end up with 6 bytes of holes on x86-64:
Expanding them to non-bitfields will require an extra 10 bytes, making the
struct 8 bytes bigger with 4 bytes of padding. I can do that if you'd rather.
Wouldn't this still have the same total size?
struct fsinfo_attribute {
unsigned int attr_id; /* 0x0-0x3 */
enum fsinfo_value_type type; /* 0x4-0x7 */
u8 flags; /* 0x8-0x8 */
/* 1-byte hole */
u16 size; /* 0xa-0xb */
u16 element_size; /* 0xc-0xd */
/* 2-byte hole */
int (*get)(...); /* 0x10-0x18 */
};
But it's not like I really care about this detail all that much, feel
free to leave it as-is.
I was thinking, why not just have unsigned int flags from the start?
That replaces the padding holes with usable flag space, though I guess
this is in-core only so I'm not that passionate. I doubt we're going to
have millions of fsinfo attributes. :)
--D
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:05:30
Provide a bitmap of features that a filesystem may provide for the path
being queried. Features include such things as:
(1) The general class of filesystem, such as kernel-interface,
block-based, flash-based, network-based.
(2) Supported inode features, such as which timestamps are supported,
whether simple numeric user, group or project IDs are supported and
whether user identification is actually more complex behind the
scenes.
(3) Supported volume features, such as it having a UUID, a name or a
filesystem ID.
(4) Supported filesystem features, such as what types of file are
supported, whether sparse files, extended attributes and quotas are
supported.
(5) Supported interface features, such as whether locking and leases are
supported, what open flags are honoured and how i_version is managed.
For some filesystems, this may be an immutable set and can just be memcpy'd
into the reply buffer.
---
fs/fsinfo.c | 41 +++++++++++++++++++
include/linux/fsinfo.h | 32 +++++++++++++++
include/uapi/linux/fsinfo.h | 78 ++++++++++++++++++++++++++++++++++++
samples/vfs/test-fsinfo.c | 93 ++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 242 insertions(+), 2 deletions(-)
@@ -22,9 +22,11 @@#define FSINFO_ATTR_VOLUME_ID 0x05 /* Volume ID (string) */#define FSINFO_ATTR_VOLUME_UUID 0x06 /* Volume UUID (LE uuid) */#define FSINFO_ATTR_VOLUME_NAME 0x07 /* Volume name (string) */+#define FSINFO_ATTR_FEATURES 0x08 /* Filesystem features (bits) */#define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO 0x100 /* Information about attr N (for path) */#define FSINFO_ATTR_FSINFO_ATTRIBUTES 0x101 /* List of supported attrs (for path) */+#define FSINFO_ATTR_FSINFO 0x102 /* Information about fsinfo() as a whole *//**Optionalfsinfo()parameterstructure.
@@ -45,6 +47,17 @@ struct fsinfo_params {__u64__reserved[3];};+/*+*Informationstructforfsinfo(FSINFO_ATTR_FSINFO).+*+*Thisgivesinformationaboutfsinfo()itself.+*/+structfsinfo_fsinfo{+__u32max_features;/* Number of supported features (fsinfo_features__nr) */+};++#define FSINFO_ATTR_FSINFO__STRUCT struct fsinfo_fsinfo+enumfsinfo_value_type{FSINFO_TYPE_VSTRUCT=0,/* Version-lengthed struct (up to 4096 bytes) */FSINFO_TYPE_STRING=1,/* NUL-term var-length string (up to 4095 chars) */
@@ -154,6 +167,71 @@ struct fsinfo_supports {#define FSINFO_ATTR_SUPPORTS__STRUCT struct fsinfo_supports+/*+*Informationstructforfsinfo(FSINFO_ATTR_FEATURES).+*+*Bitmaskindicatingfilesystemfeatureswhererenderableassinglebits.+*/+enumfsinfo_feature{+FSINFO_FEAT_IS_KERNEL_FS=0,/* fs is kernel-special filesystem */+FSINFO_FEAT_IS_BLOCK_FS=1,/* fs is block-based filesystem */+FSINFO_FEAT_IS_FLASH_FS=2,/* fs is flash filesystem */+FSINFO_FEAT_IS_NETWORK_FS=3,/* fs is network filesystem */+FSINFO_FEAT_IS_AUTOMOUNTER_FS=4,/* fs is automounter special filesystem */+FSINFO_FEAT_IS_MEMORY_FS=5,/* fs is memory-based filesystem */+FSINFO_FEAT_AUTOMOUNTS=6,/* fs supports automounts */+FSINFO_FEAT_ADV_LOCKS=7,/* fs supports advisory file locking */+FSINFO_FEAT_MAND_LOCKS=8,/* fs supports mandatory file locking */+FSINFO_FEAT_LEASES=9,/* fs supports file leases */+FSINFO_FEAT_UIDS=10,/* fs supports numeric uids */+FSINFO_FEAT_GIDS=11,/* fs supports numeric gids */+FSINFO_FEAT_PROJIDS=12,/* fs supports numeric project ids */+FSINFO_FEAT_STRING_USER_IDS=13,/* fs supports string user identifiers */+FSINFO_FEAT_GUID_USER_IDS=14,/* fs supports GUID user identifiers */+FSINFO_FEAT_WINDOWS_ATTRS=15,/* fs has windows attributes */+FSINFO_FEAT_USER_QUOTAS=16,/* fs has per-user quotas */+FSINFO_FEAT_GROUP_QUOTAS=17,/* fs has per-group quotas */+FSINFO_FEAT_PROJECT_QUOTAS=18,/* fs has per-project quotas */+FSINFO_FEAT_XATTRS=19,/* fs has xattrs */+FSINFO_FEAT_JOURNAL=20,/* fs has a journal */+FSINFO_FEAT_DATA_IS_JOURNALLED=21,/* fs is using data journalling */+FSINFO_FEAT_O_SYNC=22,/* fs supports O_SYNC */+FSINFO_FEAT_O_DIRECT=23,/* fs supports O_DIRECT */+FSINFO_FEAT_VOLUME_ID=24,/* fs has a volume ID */+FSINFO_FEAT_VOLUME_UUID=25,/* fs has a volume UUID */+FSINFO_FEAT_VOLUME_NAME=26,/* fs has a volume name */+FSINFO_FEAT_VOLUME_FSID=27,/* fs has a volume FSID */+FSINFO_FEAT_IVER_ALL_CHANGE=28,/* i_version represents data + meta changes */+FSINFO_FEAT_IVER_DATA_CHANGE=29,/* i_version represents data changes only */+FSINFO_FEAT_IVER_MONO_INCR=30,/* i_version incremented monotonically */+FSINFO_FEAT_DIRECTORIES=31,/* fs supports (sub)directories */+FSINFO_FEAT_SYMLINKS=32,/* fs supports symlinks */+FSINFO_FEAT_HARD_LINKS=33,/* fs supports hard links */+FSINFO_FEAT_HARD_LINKS_1DIR=34,/* fs supports hard links in same dir only */+FSINFO_FEAT_DEVICE_FILES=35,/* fs supports bdev, cdev */+FSINFO_FEAT_UNIX_SPECIALS=36,/* fs supports pipe, fifo, socket */+FSINFO_FEAT_RESOURCE_FORKS=37,/* fs supports resource forks/streams */+FSINFO_FEAT_NAME_CASE_INDEP=38,/* Filename case independence is mandatory */+FSINFO_FEAT_NAME_NON_UTF8=39,/* fs has non-utf8 names */+FSINFO_FEAT_NAME_HAS_CODEPAGE=40,/* fs has a filename codepage */+FSINFO_FEAT_SPARSE=41,/* fs supports sparse files */+FSINFO_FEAT_NOT_PERSISTENT=42,/* fs is not persistent */+FSINFO_FEAT_NO_UNIX_MODE=43,/* fs does not support unix mode bits */+FSINFO_FEAT_HAS_ATIME=44,/* fs supports access time */+FSINFO_FEAT_HAS_BTIME=45,/* fs supports birth/creation time */+FSINFO_FEAT_HAS_CTIME=46,/* fs supports change time */+FSINFO_FEAT_HAS_MTIME=47,/* fs supports modification time */+FSINFO_FEAT_HAS_ACL=48,/* fs supports ACLs of some sort */+FSINFO_FEAT_HAS_INODE_NUMBERS=49,/* fs has inode numbers */+FSINFO_FEAT__NR+};++structfsinfo_features{+__u8features[(FSINFO_FEAT__NR+7)/8];+};++#define FSINFO_ATTR_FEATURES__STRUCT struct fsinfo_features+structfsinfo_timestamp_one{__s64minimum;/* Minimum timestamp value in seconds */__u64maximum;/* Maximum timestamp value in seconds */
@@ -531,6 +607,18 @@ int main(int argc, char **argv)qsort(attrs,nr,sizeof(attrs[0]),cmp_u32);if(meta){+params.request=FSINFO_ATTR_FSINFO;+params.Nth=0;+params.Mth=0;+ret=fsinfo(AT_FDCWD,argv[0],¶ms,&fsinfo_info,sizeof(fsinfo_info));+if(ret==-1){+fprintf(stderr,"Unable to get fsinfo information: %m\n");+exit(1);+}++dump_fsinfo_info(&fsinfo_info,ret);+printf("\n");+printf("ATTR ID TYPE FLAGS SIZE ESIZE NAME\n");printf("======== ============ ======== ===== ===== =========\n");for(i=0;i<nr;i++){
@@ -558,7 +646,8 @@ int main(int argc, char **argv)exit(1);}-if(attrs[i]==FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO||+if(attrs[i]==FSINFO_ATTR_FSINFO||+attrs[i]==FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO||attrs[i]==FSINFO_ATTR_FSINFO_ATTRIBUTES)continue;
From: Darrick J. Wong <hidden> Date: 2020-02-19 16:37:24
On Tue, Feb 18, 2020 at 05:05:20PM +0000, David Howells wrote:
quoted hunk
Provide a bitmap of features that a filesystem may provide for the path
being queried. Features include such things as:
(1) The general class of filesystem, such as kernel-interface,
block-based, flash-based, network-based.
(2) Supported inode features, such as which timestamps are supported,
whether simple numeric user, group or project IDs are supported and
whether user identification is actually more complex behind the
scenes.
(3) Supported volume features, such as it having a UUID, a name or a
filesystem ID.
(4) Supported filesystem features, such as what types of file are
supported, whether sparse files, extended attributes and quotas are
supported.
(5) Supported interface features, such as whether locking and leases are
supported, what open flags are honoured and how i_version is managed.
For some filesystems, this may be an immutable set and can just be memcpy'd
into the reply buffer.
---
fs/fsinfo.c | 41 +++++++++++++++++++
include/linux/fsinfo.h | 32 +++++++++++++++
include/uapi/linux/fsinfo.h | 78 ++++++++++++++++++++++++++++++++++++
samples/vfs/test-fsinfo.c | 93 ++++++++++++++++++++++++++++++++++++++++++-
4 files changed, 242 insertions(+), 2 deletions(-)
@@ -22,9 +22,11 @@#define FSINFO_ATTR_VOLUME_ID 0x05 /* Volume ID (string) */#define FSINFO_ATTR_VOLUME_UUID 0x06 /* Volume UUID (LE uuid) */#define FSINFO_ATTR_VOLUME_NAME 0x07 /* Volume name (string) */+#define FSINFO_ATTR_FEATURES 0x08 /* Filesystem features (bits) */#define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO 0x100 /* Information about attr N (for path) */#define FSINFO_ATTR_FSINFO_ATTRIBUTES 0x101 /* List of supported attrs (for path) */+#define FSINFO_ATTR_FSINFO 0x102 /* Information about fsinfo() as a whole *//**Optionalfsinfo()parameterstructure.
@@ -45,6 +47,17 @@ struct fsinfo_params {__u64__reserved[3];};+/*+*Informationstructforfsinfo(FSINFO_ATTR_FSINFO).+*+*Thisgivesinformationaboutfsinfo()itself.+*/+structfsinfo_fsinfo{+__u32max_features;/* Number of supported features (fsinfo_features__nr) */+};++#define FSINFO_ATTR_FSINFO__STRUCT struct fsinfo_fsinfo+enumfsinfo_value_type{FSINFO_TYPE_VSTRUCT=0,/* Version-lengthed struct (up to 4096 bytes) */FSINFO_TYPE_STRING=1,/* NUL-term var-length string (up to 4095 chars) */
@@ -154,6 +167,71 @@ struct fsinfo_supports {#define FSINFO_ATTR_SUPPORTS__STRUCT struct fsinfo_supports+/*+*Informationstructforfsinfo(FSINFO_ATTR_FEATURES).+*+*Bitmaskindicatingfilesystemfeatureswhererenderableassinglebits.+*/+enumfsinfo_feature{+FSINFO_FEAT_IS_KERNEL_FS=0,/* fs is kernel-special filesystem */+FSINFO_FEAT_IS_BLOCK_FS=1,/* fs is block-based filesystem */+FSINFO_FEAT_IS_FLASH_FS=2,/* fs is flash filesystem */+FSINFO_FEAT_IS_NETWORK_FS=3,/* fs is network filesystem */+FSINFO_FEAT_IS_AUTOMOUNTER_FS=4,/* fs is automounter special filesystem */+FSINFO_FEAT_IS_MEMORY_FS=5,/* fs is memory-based filesystem */+FSINFO_FEAT_AUTOMOUNTS=6,/* fs supports automounts */+FSINFO_FEAT_ADV_LOCKS=7,/* fs supports advisory file locking */+FSINFO_FEAT_MAND_LOCKS=8,/* fs supports mandatory file locking */+FSINFO_FEAT_LEASES=9,/* fs supports file leases */+FSINFO_FEAT_UIDS=10,/* fs supports numeric uids */+FSINFO_FEAT_GIDS=11,/* fs supports numeric gids */+FSINFO_FEAT_PROJIDS=12,/* fs supports numeric project ids */+FSINFO_FEAT_STRING_USER_IDS=13,/* fs supports string user identifiers */+FSINFO_FEAT_GUID_USER_IDS=14,/* fs supports GUID user identifiers */+FSINFO_FEAT_WINDOWS_ATTRS=15,/* fs has windows attributes */+FSINFO_FEAT_USER_QUOTAS=16,/* fs has per-user quotas */+FSINFO_FEAT_GROUP_QUOTAS=17,/* fs has per-group quotas */+FSINFO_FEAT_PROJECT_QUOTAS=18,/* fs has per-project quotas */+FSINFO_FEAT_XATTRS=19,/* fs has xattrs */+FSINFO_FEAT_JOURNAL=20,/* fs has a journal */+FSINFO_FEAT_DATA_IS_JOURNALLED=21,/* fs is using data journalling */+FSINFO_FEAT_O_SYNC=22,/* fs supports O_SYNC */+FSINFO_FEAT_O_DIRECT=23,/* fs supports O_DIRECT */+FSINFO_FEAT_VOLUME_ID=24,/* fs has a volume ID */+FSINFO_FEAT_VOLUME_UUID=25,/* fs has a volume UUID */+FSINFO_FEAT_VOLUME_NAME=26,/* fs has a volume name */+FSINFO_FEAT_VOLUME_FSID=27,/* fs has a volume FSID */+FSINFO_FEAT_IVER_ALL_CHANGE=28,/* i_version represents data + meta changes */+FSINFO_FEAT_IVER_DATA_CHANGE=29,/* i_version represents data changes only */+FSINFO_FEAT_IVER_MONO_INCR=30,/* i_version incremented monotonically */+FSINFO_FEAT_DIRECTORIES=31,/* fs supports (sub)directories */+FSINFO_FEAT_SYMLINKS=32,/* fs supports symlinks */+FSINFO_FEAT_HARD_LINKS=33,/* fs supports hard links */+FSINFO_FEAT_HARD_LINKS_1DIR=34,/* fs supports hard links in same dir only */+FSINFO_FEAT_DEVICE_FILES=35,/* fs supports bdev, cdev */+FSINFO_FEAT_UNIX_SPECIALS=36,/* fs supports pipe, fifo, socket */+FSINFO_FEAT_RESOURCE_FORKS=37,/* fs supports resource forks/streams */+FSINFO_FEAT_NAME_CASE_INDEP=38,/* Filename case independence is mandatory */+FSINFO_FEAT_NAME_NON_UTF8=39,/* fs has non-utf8 names */+FSINFO_FEAT_NAME_HAS_CODEPAGE=40,/* fs has a filename codepage */+FSINFO_FEAT_SPARSE=41,/* fs supports sparse files */+FSINFO_FEAT_NOT_PERSISTENT=42,/* fs is not persistent */+FSINFO_FEAT_NO_UNIX_MODE=43,/* fs does not support unix mode bits */+FSINFO_FEAT_HAS_ATIME=44,/* fs supports access time */+FSINFO_FEAT_HAS_BTIME=45,/* fs supports birth/creation time */+FSINFO_FEAT_HAS_CTIME=46,/* fs supports change time */+FSINFO_FEAT_HAS_MTIME=47,/* fs supports modification time */+FSINFO_FEAT_HAS_ACL=48,/* fs supports ACLs of some sort */+FSINFO_FEAT_HAS_INODE_NUMBERS=49,/* fs has inode numbers */+FSINFO_FEAT__NR+};++structfsinfo_features{+__u8features[(FSINFO_FEAT__NR+7)/8];
Hm...the structure size is pretty small (56 bytes) and will expand as we
add new _FEAT flags. Is this ok because the fsinfo code will truncate
its response to userspace to whatever buffer size userspace tells it?
--D
quoted hunk
+};
+
+#define FSINFO_ATTR_FEATURES__STRUCT struct fsinfo_features
+
struct fsinfo_timestamp_one {
__s64 minimum; /* Minimum timestamp value in seconds */
__u64 maximum; /* Maximum timestamp value in seconds */
@@ -531,6 +607,18 @@ int main(int argc, char **argv)qsort(attrs,nr,sizeof(attrs[0]),cmp_u32);if(meta){+params.request=FSINFO_ATTR_FSINFO;+params.Nth=0;+params.Mth=0;+ret=fsinfo(AT_FDCWD,argv[0],¶ms,&fsinfo_info,sizeof(fsinfo_info));+if(ret==-1){+fprintf(stderr,"Unable to get fsinfo information: %m\n");+exit(1);+}++dump_fsinfo_info(&fsinfo_info,ret);+printf("\n");+printf("ATTR ID TYPE FLAGS SIZE ESIZE NAME\n");printf("======== ============ ======== ===== ===== =========\n");for(i=0;i<nr;i++){
@@ -558,7 +646,8 @@ int main(int argc, char **argv)exit(1);}-if(attrs[i]==FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO||+if(attrs[i]==FSINFO_ATTR_FSINFO||+attrs[i]==FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO||attrs[i]==FSINFO_ATTR_FSINFO_ATTRIBUTES)continue;
Hm...the structure size is pretty small (56 bytes) and will expand as we
add new _FEAT flags. Is this ok because the fsinfo code will truncate
its response to userspace to whatever buffer size userspace tells it?
Yes. Also, you can ask fsinfo how many feature bits it supports.
I should put this in the struct first rather than putting it elsewhere.
David
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:05:40
Add a change counter on each mount object so that the user can easily check
to see if a mount has changed its attributes or its children.
Future patches will:
(1) Provide this value through fsinfo() attributes.
(2) Implement a watch_mount() system call to provide a notification
interface for userspace monitoring.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/mount.h | 22 ++++++++++++++++++++++
fs/namespace.c | 11 +++++++++++
2 files changed, 33 insertions(+)
@@ -72,6 +72,7 @@ struct mount {intmnt_expiry_mark;/* true if marked for expiry */structhlist_headmnt_pins;structhlist_headmnt_stuck_children;+atomic_tmnt_change_counter;/* Number of changed applied */}__randomize_layout;#define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
@@ -153,3 +154,24 @@ static inline bool is_anon_ns(struct mnt_namespace *ns){returnns->seq==0;}++/*+*Typeofmounttopologychangenotification.+*/+enummount_notification_subtype{+NOTIFY_MOUNT_NEW_MOUNT=0,/* New mount added */+NOTIFY_MOUNT_UNMOUNT=1,/* Mount removed manually */+NOTIFY_MOUNT_EXPIRY=2,/* Automount expired */+NOTIFY_MOUNT_READONLY=3,/* Mount R/O state changed */+NOTIFY_MOUNT_SETATTR=4,/* Mount attributes changed */+NOTIFY_MOUNT_MOVE_FROM=5,/* Mount moved from here */+NOTIFY_MOUNT_MOVE_TO=6,/* Mount moved to here (compare op_id) */+};++staticinlinevoidnotify_mount(structmount*changed,+structmount*aux,+enummount_notification_subtypesubtype,+u32info_flags)+{+atomic_inc(&changed->mnt_change_counter);+}
@@ -498,6 +498,8 @@ static int mnt_make_readonly(struct mount *mnt)smp_wmb();mnt->mnt.mnt_flags&=~MNT_WRITE_HOLD;unlock_mount_hash();+if(ret==0)+notify_mount(mnt,NULL,NOTIFY_MOUNT_READONLY,0x10000);returnret;}
@@ -506,6 +508,7 @@ static int __mnt_unmake_readonly(struct mount *mnt)lock_mount_hash();mnt->mnt.mnt_flags&=~MNT_READONLY;unlock_mount_hash();+notify_mount(mnt,NULL,NOTIFY_MOUNT_READONLY,0);return0;}
@@ -819,6 +822,7 @@ static struct mountpoint *unhash_mnt(struct mount *mnt)*/staticvoidumount_mnt(structmount*mnt){+notify_mount(mnt->mnt_parent,mnt,NOTIFY_MOUNT_UNMOUNT,0);put_mountpoint(unhash_mnt(mnt));}
@@ -2078,7 +2083,10 @@ static int attach_recursive_mnt(struct mount *source_mnt,lock_mount_hash();}if(moving){+notify_mount(source_mnt->mnt_parent,source_mnt,+NOTIFY_MOUNT_MOVE_FROM,0);unhash_mnt(source_mnt);+notify_mount(dest_mnt,source_mnt,NOTIFY_MOUNT_MOVE_TO,0);attach_mnt(source_mnt,dest_mnt,dest_mp);touch_mnt_namespace(source_mnt->mnt_ns);}else{
@@ -2087,6 +2095,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,list_del_init(&source_mnt->mnt_ns->list);}mnt_set_mountpoint(dest_mnt,dest_mp,source_mnt);+notify_mount(dest_mnt,source_mnt,NOTIFY_MOUNT_NEW_MOUNT,0);commit_tree(source_mnt);}
@@ -2464,6 +2473,7 @@ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags)mnt->mnt.mnt_flags=mnt_flags;touch_mnt_namespace(mnt->mnt_ns);unlock_mount_hash();+notify_mount(mnt,NULL,NOTIFY_MOUNT_SETATTR,0);}staticvoidmnt_warn_timestamp_expiry(structpath*mountpoint,structvfsmount*mnt)
From: Christian Brauner <hidden> Date: 2020-02-21 14:49:07
On Tue, Feb 18, 2020 at 05:05:28PM +0000, David Howells wrote:
quoted hunk
Add a change counter on each mount object so that the user can easily check
to see if a mount has changed its attributes or its children.
Future patches will:
(1) Provide this value through fsinfo() attributes.
(2) Implement a watch_mount() system call to provide a notification
interface for userspace monitoring.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/mount.h | 22 ++++++++++++++++++++++
fs/namespace.c | 11 +++++++++++
2 files changed, 33 insertions(+)
@@ -72,6 +72,7 @@ struct mount {intmnt_expiry_mark;/* true if marked for expiry */structhlist_headmnt_pins;structhlist_headmnt_stuck_children;+atomic_tmnt_change_counter;/* Number of changed applied */
Nit: "Number of changes applied"?
quoted hunk
} __randomize_layout;
#define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
@@ -153,3 +154,24 @@ static inline bool is_anon_ns(struct mnt_namespace *ns) { return ns->seq == 0; }++/*+ * Type of mount topology change notification.+ */+enum mount_notification_subtype {+ NOTIFY_MOUNT_NEW_MOUNT = 0, /* New mount added */+ NOTIFY_MOUNT_UNMOUNT = 1, /* Mount removed manually */+ NOTIFY_MOUNT_EXPIRY = 2, /* Automount expired */+ NOTIFY_MOUNT_READONLY = 3, /* Mount R/O state changed */+ NOTIFY_MOUNT_SETATTR = 4, /* Mount attributes changed */+ NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */+ NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */+};++static inline void notify_mount(struct mount *changed,+ struct mount *aux,+ enum mount_notification_subtype subtype,+ u32 info_flags)+{+ atomic_inc(&changed->mnt_change_counter);+}
@@ -498,6 +498,8 @@ static int mnt_make_readonly(struct mount *mnt)smp_wmb();mnt->mnt.mnt_flags&=~MNT_WRITE_HOLD;unlock_mount_hash();+if(ret==0)+notify_mount(mnt,NULL,NOTIFY_MOUNT_READONLY,0x10000);
Why is 0x10000 hard-coded here?
Hm, maybe for future patches where this will be replaced with a #define
but not sure...
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:05:51
Introduce an (effectively) non-repeating system-unique superblock ID that
can be used to determine that two object are in the same superblock without
risking reuse of the ID in the meantime (as is possible with device IDs).
The ID is time-based to make it harder to use it as a covert communications
channel.
Also make it so that this ID can be fetched by the fsinfo() system call.
The ID added so that superblock notification messages will also be able to
be tagged with it.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 1 +
fs/super.c | 24 ++++++++++++++++++++++++
include/linux/fs.h | 3 +++
samples/vfs/test-fsinfo.c | 1 +
4 files changed, 29 insertions(+)
@@ -1550,6 +1550,9 @@ struct super_block {spinlock_ts_inode_wblist_lock;structlist_heads_inodes_wb;/* writeback inodes */++/* Superblock event notifications */+u64s_unique_id;}__randomize_layout;/* Helper functions so that in most cases filesystems will
From: Darrick J. Wong <hidden> Date: 2020-02-19 16:53:27
On Tue, Feb 18, 2020 at 05:05:35PM +0000, David Howells wrote:
quoted hunk
Introduce an (effectively) non-repeating system-unique superblock ID that
can be used to determine that two object are in the same superblock without
risking reuse of the ID in the meantime (as is possible with device IDs).
The ID is time-based to make it harder to use it as a covert communications
channel.
Also make it so that this ID can be fetched by the fsinfo() system call.
The ID added so that superblock notification messages will also be able to
be tagged with it.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 1 +
fs/super.c | 24 ++++++++++++++++++++++++
include/linux/fs.h | 3 +++
samples/vfs/test-fsinfo.c | 1 +
4 files changed, 29 insertions(+)
Ahah, this is what the f_sb_id field is for. I noticed a few patches
ago that it was in a header file but was never set.
I'm losing track of which IDs do what...
* f_fsid is that old int[2] thing that we used for statfs. It sucks but
we can't remove it because it's been in statfs since the beginning of
time.
* f_fs_name is a string coming from s_type, which is the name of the fs
(e.g. "XFS")?
* f_fstype comes from s_magic, which (for XFS) is 0x58465342.
* f_sb_id is basically an incore u64 cookie that one can use with the
mount events thing that comes later in this patchset?
* FSINFO_ATTR_VOLUME_ID comes from s_id, which tends to be the block
device name (at least for local filesystems)
* FSINFO_ATTR_VOLUME_UUID comes from s_uuid, which some filesystems fill
in at mount time.
* FSINFO_ATTR_VOLUME_NAME is ... left to individual filesystems to
implement, and (AFAICT) can be the label that one uses for things
like: "mount LABEL=foo /home" ?
Assuming I got all of that right, can we please capture what all of
these "IDs" mean in the documentation?
(Assuming I got all that right, the code looks ok.)
--D
@@ -1550,6 +1550,9 @@ struct super_block {spinlock_ts_inode_wblist_lock;structlist_heads_inodes_wb;/* writeback inodes */++/* Superblock event notifications */+u64s_unique_id;}__randomize_layout;/* Helper functions so that in most cases filesystems will
From: David Howells <dhowells@redhat.com> Date: 2020-02-20 12:45:44
Darrick J. Wong [off-list ref] wrote:
Ahah, this is what the f_sb_id field is for. I noticed a few patches
ago that it was in a header file but was never set.
I'm losing track of which IDs do what...
* f_fsid is that old int[2] thing that we used for statfs. It sucks but
we can't remove it because it's been in statfs since the beginning of
time.
* f_fs_name is a string coming from s_type, which is the name of the fs
(e.g. "XFS")?
* f_fstype comes from s_magic, which (for XFS) is 0x58465342.
* f_sb_id is basically an incore u64 cookie that one can use with the
mount events thing that comes later in this patchset?
* FSINFO_ATTR_VOLUME_ID comes from s_id, which tends to be the block
device name (at least for local filesystems)
* FSINFO_ATTR_VOLUME_UUID comes from s_uuid, which some filesystems fill
in at mount time.
* FSINFO_ATTR_VOLUME_NAME is ... left to individual filesystems to
implement, and (AFAICT) can be the label that one uses for things
like: "mount LABEL=foo /home" ?
Assuming I got all of that right, can we please capture what all of
these "IDs" mean in the documentation?
Basically, yes. Would it help if I:
(1) Put the ID generation into its own patch, first.
(2) Put the notification counter patches right after that.
(3) Renamed the fields a bit, say:
f_fsid -> fsid
f_fs_name -> filesystem_name
f_fstype -> filesystem_magic
f_sb_id -> superblock_id
f_dev_* -> backing_dev_*
David
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:06:01
Allow the fsinfo() syscall to look up a mount object by ID rather than by
pathname. This is necessary as there can be multiple mounts stacked up at
the same pathname and there's no way to look through them otherwise.
This is done by passing FSINFO_FLAGS_QUERY_MOUNT to fsinfo() in the
parameters and then passing the mount ID as a string to fsinfo() in place
of the filename:
struct fsinfo_params params = {
.flags = FSINFO_FLAGS_QUERY_MOUNT,
.request = FSINFO_ATTR_IDS,
};
ret = fsinfo(AT_FDCWD, "21", ¶ms, buffer, sizeof(buffer));
The caller is only permitted to query a mount object if the root directory
of that mount connects directly to the current chroot if dfd == AT_FDCWD[*]
or the directory specified by dfd otherwise. Note that this is not
available to the pathwalk of any other syscall.
[*] This needs to be something other than AT_FDCWD, perhaps AT_FDROOT.
[!] This probably needs an LSM hook.
[!] This might want to check the permissions on all the intervening dirs -
but it would have to do that under RCU conditions.
[!] This might want to check a CAP_* flag.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/fsinfo.c | 53 +++++++++++++++++++
fs/internal.h | 2 +
fs/namespace.c | 117 ++++++++++++++++++++++++++++++++++++++++++-
include/uapi/linux/fsinfo.h | 1
samples/vfs/test-fsinfo.c | 11 +++-
5 files changed, 179 insertions(+), 5 deletions(-)
@@ -63,7 +63,7 @@ static int __init set_mphash_entries(char *str)__setup("mphash_entries=",set_mphash_entries);staticu64event;-staticDEFINE_IDA(mnt_id_ida);+staticDEFINE_IDR(mnt_id_ida);staticDEFINE_IDA(mnt_group_ida);staticstructhlist_head*mount_hashtable__read_mostly;
@@ -104,17 +104,27 @@ static inline struct hlist_head *mp_hash(struct dentry *dentry)staticintmnt_alloc_id(structmount*mnt){-intres=ida_alloc(&mnt_id_ida,GFP_KERNEL);+intres;+/* Allocate an ID, but don't set the pointer back to the mount until+*later,asoncewedothat,wehavetofollowRCUprotocolstoget+*ridofthemountstruct.+*/+res=idr_alloc(&mnt_id_ida,NULL,0,INT_MAX,GFP_KERNEL);if(res<0)returnres;mnt->mnt_id=res;return0;}+staticvoidmnt_publish_id(structmount*mnt)+{+idr_replace(&mnt_id_ida,mnt,mnt->mnt_id);+}+staticvoidmnt_free_id(structmount*mnt){-ida_free(&mnt_id_ida,mnt->mnt_id);+idr_remove(&mnt_id_ida,mnt->mnt_id);}/*
From: Christian Brauner <hidden> Date: 2020-02-21 15:10:07
On Tue, Feb 18, 2020 at 05:05:43PM +0000, David Howells wrote:
Allow the fsinfo() syscall to look up a mount object by ID rather than by
pathname. This is necessary as there can be multiple mounts stacked up at
the same pathname and there's no way to look through them otherwise.
This is done by passing FSINFO_FLAGS_QUERY_MOUNT to fsinfo() in the
parameters and then passing the mount ID as a string to fsinfo() in place
of the filename:
struct fsinfo_params params = {
.flags = FSINFO_FLAGS_QUERY_MOUNT,
.request = FSINFO_ATTR_IDS,
};
ret = fsinfo(AT_FDCWD, "21", ¶ms, buffer, sizeof(buffer));
The caller is only permitted to query a mount object if the root directory
of that mount connects directly to the current chroot if dfd == AT_FDCWD[*]
or the directory specified by dfd otherwise. Note that this is not
available to the pathwalk of any other syscall.
[*] This needs to be something other than AT_FDCWD, perhaps AT_FDROOT.
@@ -437,3 +437,5 @@ 435 common clone3 __sys_clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+# 435 reserved for clone3+439 common fsinfo sys_fsinfo
@@ -435,3 +435,4 @@ 435 common clone3 sys_clone3_wrapper 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common fsinfo sys_fsinfo
From: Christian Brauner <hidden> Date: 2020-02-21 14:51:26
On Tue, Feb 18, 2020 at 05:05:13PM +0000, David Howells wrote:
Add the fsinfo syscall to the other arches.
Over the last couple of kernel releases we have established the
convention that we wire-up a syscall for all arches at the same time and
not e.g. x86 and then the other separately.
@@ -437,3 +437,5 @@ 435 common clone3 __sys_clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+# 435 reserved for clone3+439 common fsinfo sys_fsinfo
@@ -435,3 +435,4 @@ 435 common clone3 sys_clone3_wrapper 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+439 common fsinfo sys_fsinfo
On Fri, Feb 21, 2020 at 3:52 PM Christian Brauner
[off-list ref] wrote:
On Tue, Feb 18, 2020 at 05:05:13PM +0000, David Howells wrote:
quoted
Add the fsinfo syscall to the other arches.
Over the last couple of kernel releases we have established the
convention that we wire-up a syscall for all arches at the same time and
not e.g. x86 and then the other separately.
@@ -437,3 +437,5 @@ 435 common clone3 __sys_clone3 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd+# 435 reserved for clone3
Indeed, bogus line above (bad rebase?).
quoted
+439 common fsinfo sys_fsinfo
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:06:04
Allow mount information, including information about the topology tree to
be queried with the fsinfo() system call. Setting AT_FSINFO_QUERY_MOUNT
allows overlapping mounts to be queried by indicating that the syscall
should interpet the pathname as a number indicating the mount ID.
To this end, four fsinfo() attributes are provided:
(1) FSINFO_ATTR_MOUNT_INFO.
This is a structure providing information about a mount, including:
- Mounted superblock ID.
- Mount ID (can be used with AT_FSINFO_QUERY_MOUNT).
- Parent mount ID.
- Mount attributes (eg. R/O, NOEXEC).
- A change counter.
Note that the parent mount ID is overridden to the ID of the queried
mount if the parent lies outside of the chroot or dfd tree.
(2) FSINFO_ATTR_MOUNT_DEVNAME.
This a string providing the device name associated with the mount.
Note that the device name may be a path that lies outside of the root.
(3) FSINFO_ATTR_MOUNT_POINT.
This is a string indicating the name of the mountpoint within the
parent mount, limited to the parent's mounted root and the chroot.
(4) FSINFO_ATTR_MOUNT_CHILDREN.
This produces an array of structures, one for each child and capped
with one for the argument mount (checked after listing all the
children). Each element contains the mount ID and the change counter
of the respective mount object.
Signed-off-by: David Howells <dhowells@redhat.com>
---
fs/d_path.c | 2
fs/fsinfo.c | 4 +
fs/internal.h | 10 ++
fs/namespace.c | 179 +++++++++++++++++++++++++++++++++++++++++++
include/uapi/linux/fsinfo.h | 34 ++++++++
samples/vfs/test-fsinfo.c | 27 ++++++
6 files changed, 255 insertions(+), 1 deletion(-)
@@ -229,7 +229,7 @@ static int prepend_unreachable(char **buffer, int *buflen)returnprepend(buffer,buflen,"(unreachable)",13);}-staticvoidget_fs_root_rcu(structfs_struct*fs,structpath*root)+voidget_fs_root_rcu(structfs_struct*fs,structpath*root){unsignedseq;
@@ -4097,3 +4098,181 @@ int lookup_mount_object(struct path *root, int mnt_id, struct path *_mntpt)unlock_mount_hash();gotoout_unlock;}++#ifdef CONFIG_FSINFO+/*+*Retrieveinformationaboutthenominatedmount.+*/+intfsinfo_generic_mount_info(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_mount_info*p=ctx->buffer;+structsuper_block*sb;+structmount*m;+structpathroot;+unsignedintflags;++if(!path->mnt)+return-ENODATA;++m=real_mount(path->mnt);+sb=m->mnt.mnt_sb;++p->f_sb_id=sb->s_unique_id;+p->mnt_id=m->mnt_id;+p->parent_id=m->mnt_parent->mnt_id;+p->change_counter=atomic_read(&m->mnt_change_counter);++get_fs_root(current->fs,&root);+if(path->mnt==root.mnt){+p->parent_id=p->mnt_id;+}else{+rcu_read_lock();+if(!are_paths_connected(&root,path))+p->parent_id=p->mnt_id;+rcu_read_unlock();+}+if(IS_MNT_SHARED(m))+p->group_id=m->mnt_group_id;+if(IS_MNT_SLAVE(m)){+intmaster=m->mnt_master->mnt_group_id;+intdom=get_dominating_id(m,&root);+p->master_id=master;+if(dom&&dom!=master)+p->from_id=dom;+}+path_put(&root);++flags=READ_ONCE(m->mnt.mnt_flags);+if(flags&MNT_READONLY)+p->attr|=MOUNT_ATTR_RDONLY;+if(flags&MNT_NOSUID)+p->attr|=MOUNT_ATTR_NOSUID;+if(flags&MNT_NODEV)+p->attr|=MOUNT_ATTR_NODEV;+if(flags&MNT_NOEXEC)+p->attr|=MOUNT_ATTR_NOEXEC;+if(flags&MNT_NODIRATIME)+p->attr|=MOUNT_ATTR_NODIRATIME;++if(flags&MNT_NOATIME)+p->attr|=MOUNT_ATTR_NOATIME;+elseif(flags&MNT_RELATIME)+p->attr|=MOUNT_ATTR_RELATIME;+else+p->attr|=MOUNT_ATTR_STRICTATIME;+returnsizeof(*p);+}++intfsinfo_generic_mount_devname(structpath*path,structfsinfo_context*ctx)+{+if(!path->mnt)+return-ENODATA;++returnfsinfo_string(real_mount(path->mnt)->mnt_devname,ctx);+}++/*+*Returnthepathofthismountrelativetoitsparentandclippedto+*thecurrentchroot.+*/+intfsinfo_generic_mount_point(structpath*path,structfsinfo_context*ctx)+{+structmountpoint*mp;+structmount*m,*parent;+structpathmountpoint,root;+size_tlen;+void*p;++if(!path->mnt)+return-ENODATA;++rcu_read_lock();++m=real_mount(path->mnt);+parent=m->mnt_parent;+if(parent==m)+gotoskip;+mp=READ_ONCE(m->mnt_mp);+if(mp)+gotofound;+skip:+rcu_read_unlock();+return-ENODATA;++found:+mountpoint.mnt=&parent->mnt;+mountpoint.dentry=READ_ONCE(mp->m_dentry);++get_fs_root_rcu(current->fs,&root);+if(path->mnt==root.mnt){+rcu_read_unlock();+len=snprintf(ctx->buffer,ctx->buf_size,"/");+}else{+if(root.mnt!=&parent->mnt){+root.mnt=&parent->mnt;+root.dentry=parent->mnt.mnt_root;+}++p=__d_path(&mountpoint,&root,ctx->buffer,ctx->buf_size);+rcu_read_unlock();++if(IS_ERR(p))+returnPTR_ERR(p);+if(!p)+return-EPERM;++len=(ctx->buffer+ctx->buf_size)-p;+memmove(ctx->buffer,p,len);+}+returnlen;+}++/*+*Storeamountrecordintothefsinfobuffer.+*/+staticvoidstore_mount_fsinfo(structfsinfo_context*ctx,+structfsinfo_mount_child*child)+{+unsignedintusage=ctx->usage;+unsignedinttotal=sizeof(*child);++if(ctx->usage>=INT_MAX)+return;+ctx->usage=usage+total;+if(ctx->buffer&&ctx->usage<=ctx->buf_size)+memcpy(ctx->buffer+usage,child,total);+}++/*+*Returninformationaboutthesubmountsrelativetopath.+*/+intfsinfo_generic_mount_children(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_mount_childrecord;+structmount*m,*child;++if(!path->mnt)+return-ENODATA;++m=real_mount(path->mnt);++rcu_read_lock();+list_for_each_entry_rcu(child,&m->mnt_mounts,mnt_child){+if(child->mnt_parent!=m)+continue;+record.mnt_id=child->mnt_id;+record.change_counter=atomic_read(&child->mnt_change_counter);+store_mount_fsinfo(ctx,&record);+}+rcu_read_unlock();++/* End the list with a copy of the parameter mount's details so that+*userspacecanquicklycheckforchanges.+*/+record.mnt_id=m->mnt_id;+record.change_counter=atomic_read(&m->mnt_change_counter);+store_mount_fsinfo(ctx,&record);+returnctx->usage;+}++#endif /* CONFIG_FSINFO */
@@ -28,6 +28,11 @@#define FSINFO_ATTR_FSINFO_ATTRIBUTES 0x101 /* List of supported attrs (for path) */#define FSINFO_ATTR_FSINFO 0x102 /* Information about fsinfo() as a whole */+#define FSINFO_ATTR_MOUNT_INFO 0x200 /* Mount object information */+#define FSINFO_ATTR_MOUNT_DEVNAME 0x201 /* Mount object device name (string) */+#define FSINFO_ATTR_MOUNT_POINT 0x202 /* Relative path of mount in parent (string) */+#define FSINFO_ATTR_MOUNT_CHILDREN 0x203 /* Children of this mount (list) */+/**Optionalfsinfo()parameterstructure.*
@@ -95,6 +101,34 @@ struct fsinfo_u128 {#endif};+/*+*Informationstructforfsinfo(FSINFO_ATTR_MOUNT_INFO).+*/+structfsinfo_mount_info{+__u64f_sb_id;/* Superblock ID */+__u32mnt_id;/* Mount identifier (use with AT_FSINFO_MOUNTID_PATH) */+__u32parent_id;/* Parent mount identifier */+__u32group_id;/* Mount group ID */+__u32master_id;/* Slave master group ID */+__u32from_id;/* Slave propagated from ID */+__u32attr;/* MOUNT_ATTR_* flags */+__u32change_counter;/* Number of changes applied. */+__u32__reserved[1];+};++#define FSINFO_ATTR_MOUNT_INFO__STRUCT struct fsinfo_mount_info++/*+*Informationstructelementforfsinfo(FSINFO_ATTR_MOUNT_CHILDREN).+*-Anextraelementisplacedontheendrepresentingtheparentmount.+*/+structfsinfo_mount_child{+__u32mnt_id;/* Mount identifier (use with AT_FSINFO_MOUNTID_PATH) */+__u32change_counter;/* Number of changes applied to mount. */+};++#define FSINFO_ATTR_MOUNT_CHILDREN__STRUCT struct fsinfo_mount_child+/**Informationstructforfsinfo(FSINFO_ATTR_STATFS).*-Thisgivesextendedfilesysteminformation.
@@ -0,0 +1,490 @@+============================+Filesystem Information Query+============================++The fsinfo() system call allows the querying of filesystem and filesystem+security information beyond what stat(), statx() and statfs() can obtain. It+does not require a file to be opened as does ioctl().++fsinfo() may be called with a path, with open file descriptor or a with a mount+object identifier.++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, the IDs of+which can be found in include/uapi/linux/fsinfo.h::++ FSINFO_ATTR_STATFS - statfs()-style state+ FSINFO_ATTR_IDS - Filesystem IDs+ FSINFO_ATTR_LIMITS - Filesystem limits+ ...+ FSINFO_ATTR_FSINFO - Information about fsinfo() itself+ ...+ FSINFO_ATTR_MOUNT_INFO - Information about the mount topology+ ...++Each attribute can have zero or more values, which can be of one of the+following types:++*``VStruct``. 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 system call returns the 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.++*``Opaque``. This is a variable-length blob of indeterminate structure. It+ may be up to INT_MAX bytes in size.++*``List``. This is a variable-length list 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.++Value type is an inherent propery of an attribute and all the values of an+attribute must be of that type. Each attribute can have a single value, a+sequence of values or a sequence-of-sequences of values.+++Filesystem API+==============++If the filesystem wishes to provide a list of queryable attributes, it should+set the table pointer in the superblock::++ const struct fsinfo_attribute *fsinfo_attributes;++terminating it with a blank entry. Each entry is a ``struct fsinfo_attribute``+and these can be created with a set of helper macros::++ FSINFO_VSTRUCT(A,G)+ FSINFO_VSTRUCT_N(A,G)+ FSINFO_VSTRUCT_NM(A,G)+ FSINFO_STRING(A,G)+ FSINFO_STRING_N(A,G)+ FSINFO_STRING_NM(A,G)+ FSINFO_OPAQUE(A,G)+ FSINFO_LIST(A,G)+ FSINFO_LIST_N(A,G)++The names of the macro are a combination of type (vstruct, string, opaque and+list) and an optional qualifier, if the attribute has N values or N lots of M+values. ``A`` is the name of the attribute and ``G`` is a function to get a+value for that attribute.++For vstruct- and list-type attributes, it is expected that there is a macro+defined with the name ``A##__STRUCT`` that indicates the structure or element+type.++The get function needs to match the following type::++ int (*get)(struct path *path, struct fsinfo_context *ctx);++where "path" indicates the object to be queried and ctx is a context describing+the parameters and the output buffer. 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_context {+ __u32 requested_attr;+ __u32 Nth;+ __u32 Mth;+ bool want_size_only;+ unsigned int buf_size;+ unsigned int usage;+ void *buffer;+ ...+ };++The fields relevant to the filesystem are as follows:++*``requested_attr``++ 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, an attribute for a network filesystems that returns+ server addresses - each server may have one or more addresses. This could+ return the Mth address of the Nth server. ENODATA should be returned if the+ Nth set doesn't exist or the Mth element of the Nth set doesn't exist.++*``want_size_only``++ Is set to true if the caller only wants the size of the value so that the+ get function doesn't have to make expensive calculations or calls to+ retrieve the value.++*``buf_size``++ This indicates the current size of the buffer. For the list 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 list or+ opaque type attribute. This is updated by the fsinfo_note_param*()+ functions.++*``buffer``++ This points to the output buffer. For struct- and string-type attributes it+ will always be big enough; for list- and opaque-type, it will be buf_size in+ size and will be resized if the returned size is larger than this.++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:++*``void fsinfo_set_feature(struct fsinfo_features *ft,+ enum fsinfo_feature feature);``++ This function sets a feature flag.++*``void fsinfo_clear_feature(struct fsinfo_features *ft,+ enum fsinfo_feature feature);``++ This function clears a feature flag.++*``void fsinfo_set_unix_features(struct fsinfo_features *ft);``++ Set feature 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.+++Attribute Summary+=================++To summarise the attributes that are defined::++ Symbolic name Type+ ===================================== ===============+ FSINFO_ATTR_STATFS vstruct+ FSINFO_ATTR_IDS vstruct+ FSINFO_ATTR_LIMITS vstruct+ FSINFO_ATTR_SUPPORTS vstruct+ FSINFO_ATTR_FEATURES vstruct+ FSINFO_ATTR_TIMESTAMP_INFO vstruct+ FSINFO_ATTR_VOLUME_ID string+ FSINFO_ATTR_VOLUME_UUID vstruct+ FSINFO_ATTR_VOLUME_NAME string+ FSINFO_ATTR_NAME_ENCODING string+ FSINFO_ATTR_NAME_CODEPAGE string+ FSINFO_ATTR_FSINFO vstruct+ FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO vstruct+ FSINFO_ATTR_FSINFO_ATTRIBUTES list+ FSINFO_ATTR_MOUNT_INFO vstruct+ FSINFO_ATTR_MOUNT_DEVNAME string+ FSINFO_ATTR_MOUNT_POINT string+ FSINFO_ATTR_MOUNT_CHILDREN list+ FSINFO_ATTR_AFS_CELL_NAME string+ FSINFO_ATTR_AFS_SERVER_NAME N × string+ FSINFO_ATTR_AFS_SERVER_ADDRESS N × struct+++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_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_FEATURES``++ This is a special attribute, being a set of single-bit feature flags,+ formatted as struct-type attribute. The meanings of the feature bits are+ listed below - see the "Feature Bit Catalogue" section. The feature bits+ are grouped numerically into bytes, such that features 0-7 are in byte 0,+ 8-15 are in byte 1, 16-23 in byte 2 and so on.++ Any feature bit that's not supported by the kernel will be set to false if+ asked for. The highest supported feature can be obtained from attribute+ "FSINFO_ATTR_FSINFO".+++Some attributes give information about fsinfo itself:++*``FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO``++ This struct-type attribute gives metadata about the attribute with the ID+ specified by the Nth parameter, including its type, default size and+ element size.++*``FSINFO_ATTR_FSINFO_ATTRIBUTES``++ This list-type attribute gives a list of the attribute IDs available at the+ point of reference. FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO can then be used to+ query each attribute.++*``FSINFO_ATTR_FSINFO``++ This struct-type attribute gives information about the fsinfo() system call+ itself, including the maximum number of feature bits supported.+++Then there are filesystem-specific attributes, e.g.:++*``FSINFO_ATTR_AFS_CELL_NAME``++ This is a string-type attribute that retrieves the AFS cell name of the+ target object.++*``FSINFO_ATTR_AFS_SERVER_NAME``++ This is a string-type attribute that conveys the name of the Nth server+ backing a network-filesystem superblock.++*``FSINFO_ATTR_AFS_SERVER_ADDRESSES``++ This is a list-type attribute that conveys the Mth address of the Nth+ server, as returned by FSINFO_ATTR_SERVER_NAME.+++Feature Bit Catalogue+=====================++The feature bits convey single true/false assertions about a specific instance+of a filesystem (ie. a specific superblock). They are accessed using the+"FSINFO_ATTR_FEATURE" attribute:++*``FSINFO_FEAT_IS_KERNEL_FS``+*``FSINFO_FEAT_IS_BLOCK_FS``+*``FSINFO_FEAT_IS_FLASH_FS``+*``FSINFO_FEAT_IS_NETWORK_FS``+*``FSINFO_FEAT_IS_AUTOMOUNTER_FS``+*``FSINFO_FEAT_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_FEAT_AUTOMOUNTS``++ This indicate if a filesystem may have objects that are automount points.++*``FSINFO_FEAT_ADV_LOCKS``+*``FSINFO_FEAT_MAND_LOCKS``+*``FSINFO_FEAT_LEASES``++ These indicate if a filesystem supports advisory locks, mandatory locks or+ leases.++*``FSINFO_FEAT_UIDS``+*``FSINFO_FEAT_GIDS``+*``FSINFO_FEAT_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_FEAT_STRING_USER_IDS``++ This indicates if a filesystem supports/stores/transports string user+ identifiers.++*``FSINFO_FEAT_GUID_USER_IDS``++ This indicates if a filesystem supports/stores/transports Windows GUIDs as+ user identifiers (eg. ntfs).++*``FSINFO_FEAT_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_FEAT_USER_QUOTAS``+*``FSINFO_FEAT_GROUP_QUOTAS``+*``FSINFO_FEAT_PROJECT_QUOTAS``++ These indicate if a filesystem supports quotas for users, groups or+ projects.++*``FSINFO_FEAT_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_FEAT_JOURNAL``+*``FSINFO_FEAT_DATA_IS_JOURNALLED``++ These indicate whether the filesystem has a journal and whether data+ changes are logged to it.++*``FSINFO_FEAT_O_SYNC``+*``FSINFO_FEAT_O_DIRECT``++ These indicate whether the filesystem supports the O_SYNC and O_DIRECT+ flags.++*``FSINFO_FEAT_VOLUME_ID``+*``FSINFO_FEAT_VOLUME_UUID``+*``FSINFO_FEAT_VOLUME_NAME``+*``FSINFO_FEAT_VOLUME_FSID``++ These indicate whether ID, UUID, name and FSID identifiers actually exist+ in the filesystem and thus might be considered persistent.++*``FSINFO_FEAT_IVER_ALL_CHANGE``+*``FSINFO_FEAT_IVER_DATA_CHANGE``+*``FSINFO_FEAT_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_FEAT_HARD_LINKS``+*``FSINFO_FEAT_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_FEAT_DIRECTORIES``+*``FSINFO_FEAT_SYMLINKS``+*``FSINFO_FEAT_DEVICE_FILES``+*``FSINFO_FEAT_UNIX_SPECIALS``++ These indicate whether directories; symbolic links; device files; or pipes+ and sockets can be made within the filesystem.++*``FSINFO_FEAT_RESOURCE_FORKS``++ This indicates if the filesystem supports resource forks.++*``FSINFO_FEAT_NAME_CASE_INDEP``+*``FSINFO_FEAT_NAME_NON_UTF8``+*``FSINFO_FEAT_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_FEAT_SPARSE``++ This indicates if a filesystem supports sparse files.++*``FSINFO_FEAT_NOT_PERSISTENT``++ This indicates if a filesystem is not persistent.++*``FSINFO_FEAT_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_FEAT_HAS_ATIME``+*``FSINFO_FEAT_HAS_BTIME``+*``FSINFO_FEAT_HAS_CTIME``+*``FSINFO_FEAT_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).
@@ -117,4 +117,12 @@ enum fsconfig_command {#define MOUNT_ATTR_STRICTATIME 0x00000020 /* - Always perform atime updates */#define MOUNT_ATTR_NODIRATIME 0x00000080 /* Do not update directory access times */+/*+*Mountobjectpropogationattributes.+*/+#define MOUNT_PROPAGATION_UNBINDABLE 0x00000001 /* Mount is unbindable */+#define MOUNT_PROPAGATION_SLAVE 0x00000002 /* Mount is slave */+#define MOUNT_PROPAGATION_PRIVATE 0x00000000 /* Mount is private (ie. not shared) */+#define MOUNT_PROPAGATION_SHARED 0x00000004 /* Mount is shared */+#endif /* _UAPI_LINUX_MOUNT_H */
@@ -236,8 +236,8 @@ int main(int argc, char **argv)exit(2);}-printf("MOUNT MOUNT ID CHANGE# AT DEV TYPE\n");-printf("------------------------------------- ---------- -------- -- ----- --------\n");+printf("MOUNT MOUNT ID CHANGE# AT P DEV TYPE\n");+printf("------------------------------------- ---------- -------- -- - ----- --------\n");display_mount(mnt_id,0,path);return0;}
@@ -760,3 +787,195 @@ static int afs_statfs(struct dentry *dentry, struct kstatfs *buf)returnret;}++#ifdef CONFIG_FSINFO+staticconststructfsinfo_timestamp_infoafs_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,+},+};++staticintafs_fsinfo_get_timestamp(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_timestamp_info*tsinfo=ctx->buffer;+*tsinfo=afs_timestamp_info;+returnsizeof(*tsinfo);+}++staticintafs_fsinfo_get_limits(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_limits*lim=ctx->buffer;++lim->max_file_size.hi=0;+lim->max_file_size.lo=MAX_LFS_FILESIZE;+/* Inode numbers can be 96-bit on YFS, but that's hard to determine. */+lim->max_ino.hi=0;+lim->max_ino.lo=UINT_MAX;+lim->max_hard_links=UINT_MAX;+lim->max_uid=UINT_MAX;+lim->max_gid=UINT_MAX;+lim->max_filename_len=AFSNAMEMAX-1;+lim->max_symlink_len=AFSPATHMAX-1;+returnsizeof(*lim);+}++staticintafs_fsinfo_get_supports(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_supports*sup=ctx->buffer;++sup=ctx->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;+returnsizeof(*sup);+}++staticintafs_fsinfo_get_features(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_features*ft=ctx->buffer;++fsinfo_set_feature(ft,FSINFO_FEAT_IS_NETWORK_FS);+fsinfo_set_feature(ft,FSINFO_FEAT_AUTOMOUNTS);+fsinfo_set_feature(ft,FSINFO_FEAT_ADV_LOCKS);+fsinfo_set_feature(ft,FSINFO_FEAT_UIDS);+fsinfo_set_feature(ft,FSINFO_FEAT_GIDS);+fsinfo_set_feature(ft,FSINFO_FEAT_VOLUME_ID);+fsinfo_set_feature(ft,FSINFO_FEAT_VOLUME_NAME);+fsinfo_set_feature(ft,FSINFO_FEAT_IVER_MONO_INCR);+fsinfo_set_feature(ft,FSINFO_FEAT_SYMLINKS);+fsinfo_set_feature(ft,FSINFO_FEAT_HARD_LINKS_1DIR);+fsinfo_set_feature(ft,FSINFO_FEAT_HAS_MTIME);+fsinfo_set_feature(ft,FSINFO_FEAT_HAS_INODE_NUMBERS);+returnsizeof(*ft);+}++staticintafs_dyn_fsinfo_get_features(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_features*ft=ctx->buffer;++fsinfo_set_feature(ft,FSINFO_FEAT_IS_AUTOMOUNTER_FS);+fsinfo_set_feature(ft,FSINFO_FEAT_AUTOMOUNTS);+returnsizeof(*ft);+}++staticintafs_fsinfo_get_volume_name(structpath*path,structfsinfo_context*ctx)+{+structafs_super_info*as=AFS_FS_S(path->dentry->d_sb);+structafs_volume*volume=as->volume;++memcpy(ctx->buffer,volume->name,volume->name_len);+returnvolume->name_len;+}++staticintafs_fsinfo_get_cell_name(structpath*path,structfsinfo_context*ctx)+{+structafs_super_info*as=AFS_FS_S(path->dentry->d_sb);+structafs_cell*cell=as->cell;++memcpy(ctx->buffer,cell->name,cell->name_len);+returncell->name_len;+}++staticintafs_fsinfo_get_server_name(structpath*path,structfsinfo_context*ctx)+{+structafs_server_list*slist;+structafs_super_info*as=AFS_FS_S(path->dentry->d_sb);+structafs_volume*volume=as->volume;+structafs_server*server;+intret=-ENODATA;++read_lock(&volume->servers_lock);+slist=volume->servers;+if(slist){+if(ctx->Nth<slist->nr_servers){+server=slist->servers[ctx->Nth].server;+ret=sprintf(ctx->buffer,"%pU",&server->uuid);+}+}++read_unlock(&volume->servers_lock);+returnret;+}++staticintafs_fsinfo_get_server_address(structpath*path,structfsinfo_context*ctx)+{+structfsinfo_afs_server_address*addr=ctx->buffer;+structafs_server_list*slist;+structafs_super_info*as=AFS_FS_S(path->dentry->d_sb);+structafs_addr_list*alist;+structafs_volume*volume=as->volume;+structafs_server*server;+structafs_net*net=afs_d2net(path->dentry);+unsignedinti;+intret=-ENODATA;++read_lock(&volume->servers_lock);+slist=afs_get_serverlist(volume->servers);+read_unlock(&volume->servers_lock);++if(ctx->Nth>=slist->nr_servers)+gotoput_slist;+server=slist->servers[ctx->Nth].server;++read_lock(&server->fs_lock);+alist=afs_get_addrlist(rcu_access_pointer(server->addresses));+read_unlock(&server->fs_lock);+if(!alist)+gotoput_slist;++ret=alist->nr_addrs*sizeof(*addr);+if(ret<=ctx->buf_size){+for(i=0;i<alist->nr_addrs;i++)+memcpy(&addr[i].address,&alist->addrs[i],+sizeof(structsockaddr_rxrpc));+}++afs_put_addrlist(alist);+put_slist:+afs_put_serverlist(net,slist);+returnret;+}++staticconststructfsinfo_attributeafs_fsinfo_attributes[]={+FSINFO_VSTRUCT(FSINFO_ATTR_TIMESTAMP_INFO,afs_fsinfo_get_timestamp),+FSINFO_VSTRUCT(FSINFO_ATTR_LIMITS,afs_fsinfo_get_limits),+FSINFO_VSTRUCT(FSINFO_ATTR_SUPPORTS,afs_fsinfo_get_supports),+FSINFO_VSTRUCT(FSINFO_ATTR_FEATURES,afs_fsinfo_get_features),+FSINFO_STRING(FSINFO_ATTR_VOLUME_NAME,afs_fsinfo_get_volume_name),+FSINFO_STRING(FSINFO_ATTR_AFS_CELL_NAME,afs_fsinfo_get_cell_name),+FSINFO_STRING_N(FSINFO_ATTR_AFS_SERVER_NAME,afs_fsinfo_get_server_name),+FSINFO_LIST_N(FSINFO_ATTR_AFS_SERVER_ADDRESSES,afs_fsinfo_get_server_address),+{}+};++staticconststructfsinfo_attributeafs_dyn_fsinfo_attributes[]={+FSINFO_VSTRUCT(FSINFO_ATTR_TIMESTAMP_INFO,afs_fsinfo_get_timestamp),+FSINFO_VSTRUCT(FSINFO_ATTR_FEATURES,afs_dyn_fsinfo_get_features),+{}+};++#endif /* CONFIG_FSINFO */
@@ -33,6 +33,10 @@#define FSINFO_ATTR_MOUNT_POINT 0x202 /* Relative path of mount in parent (string) */#define FSINFO_ATTR_MOUNT_CHILDREN 0x203 /* Children of this mount (list) */+#define FSINFO_ATTR_AFS_CELL_NAME 0x300 /* AFS cell name (string) */+#define FSINFO_ATTR_AFS_SERVER_NAME 0x301 /* Name of the Nth server (string) */+#define FSINFO_ATTR_AFS_SERVER_ADDRESSES 0x302 /* List of addresses of the Nth server */+/**Optionalfsinfo()parameterstructure.*
Ewww. So basically, having one static set of .fsinfo_attributes is not
sufficiently flexible for everyone, but instead of allowing the
filesystem to dynamically provide a list of supported attributes, you
just duplicate the super_operations? Seems to me like it'd be cleaner
to add a function pointer to the super_operations that can dynamically
fill out the supported fsinfo attributes.
It seems to me like the current API is going to be a dead end if you
ever want to have decent passthrough of these things for e.g. FUSE, or
overlayfs, or VirtFS?
Documentation for rcu_access_pointer() says:
* Return the value of the specified RCU-protected pointer, but omit the
* lockdep checks for being in an RCU read-side critical section. This is
* useful when the value of this pointer is accessed, but the pointer is
* not dereferenced, for example, when testing an RCU-protected pointer
* against NULL. Although rcu_access_pointer() may also be used in cases
* where update-side locks prevent the value of the pointer from changing,
* you should instead use rcu_dereference_protected() for this use case.
*
* It is also permissible to use rcu_access_pointer() when read-side
* access to the pointer was removed at least one grace period ago, as
* is the case in the context of the RCU callback that is freeing up
* the data, or after a synchronize_rcu() returns. This can be useful
* when tearing down multi-linked structures after a grace period
* has elapsed.
From: David Howells <dhowells@redhat.com> Date: 2020-02-20 12:59:02
Jann Horn [off-list ref] wrote:
Ewww. So basically, having one static set of .fsinfo_attributes is not
sufficiently flexible for everyone, but instead of allowing the
filesystem to dynamically provide a list of supported attributes, you
just duplicate the super_operations? Seems to me like it'd be cleaner
to add a function pointer to the super_operations that can dynamically
fill out the supported fsinfo attributes.
It seems to me like the current API is going to be a dead end if you
ever want to have decent passthrough of these things for e.g. FUSE, or
overlayfs, or VirtFS?
Ummm...
Would it be sufficient to have a function that returns a list of attributes?
Or does it need to be able to call to vfs_do_fsinfo() if it supports an
attribute?
There are two things I want to be able to do:
(1) Do the buffer wrangling in the core - which means the core needs to see
the type of the attribute. That's fine if, say, afs_fsinfo() can call
vfs_do_fsinfo() with the definition for any attribute it wants to handle
and, say, return -ENOPKG otherways so that the core can then fall back to
its private list.
(2) Be able to retrieve the list of attributes and/or query an attribute.
Now, I can probably manage this even through the same interface. If,
say, seeing FSINFO_ATTR_FSINFO_ATTRIBUTES causes the handler to simply
append on the IDs of its own supported attributes (a helper can be
provided for that).
If it sees FSINFO_ATR_FSINFO_ATTRIBUTE_INFO, it can just look to see if
it has the attribute with the ID matching Nth and return that, else
ENOPKG - again a helper could be provided.
Chaining through overlayfs gets tricky. You end up with multiple contributory
filesystems with different properties - and any one of those layers could
perhaps be another overlay. Overlayfs would probably needs to integrate the
info and derive the lowest common set.
David
On Thu, Feb 20, 2020 at 1:59 PM David Howells [off-list ref] wrote:
Jann Horn [off-list ref] wrote:
quoted
Ewww. So basically, having one static set of .fsinfo_attributes is not
sufficiently flexible for everyone, but instead of allowing the
filesystem to dynamically provide a list of supported attributes, you
just duplicate the super_operations? Seems to me like it'd be cleaner
to add a function pointer to the super_operations that can dynamically
fill out the supported fsinfo attributes.
It seems to me like the current API is going to be a dead end if you
ever want to have decent passthrough of these things for e.g. FUSE, or
overlayfs, or VirtFS?
Ummm...
Would it be sufficient to have a function that returns a list of attributes?
Or does it need to be able to call to vfs_do_fsinfo() if it supports an
attribute?
There are two things I want to be able to do:
(1) Do the buffer wrangling in the core - which means the core needs to see
the type of the attribute. That's fine if, say, afs_fsinfo() can call
vfs_do_fsinfo() with the definition for any attribute it wants to handle
and, say, return -ENOPKG otherways so that the core can then fall back to
its private list.
(2) Be able to retrieve the list of attributes and/or query an attribute.
Now, I can probably manage this even through the same interface. If,
say, seeing FSINFO_ATTR_FSINFO_ATTRIBUTES causes the handler to simply
append on the IDs of its own supported attributes (a helper can be
provided for that).
If it sees FSINFO_ATR_FSINFO_ATTRIBUTE_INFO, it can just look to see if
it has the attribute with the ID matching Nth and return that, else
ENOPKG - again a helper could be provided.
Chaining through overlayfs gets tricky. You end up with multiple contributory
filesystems with different properties - and any one of those layers could
perhaps be another overlay. Overlayfs would probably needs to integrate the
info and derive the lowest common set.
Hm - I guess just returning a list of attributes ought to be fine?
Then AFS can just return one of its two statically-allocated attribute
lists there, and a filesystem with more complicated circumstances
(like FUSE or overlayfs or whatever) can compute a heap-allocated list
on mount that is freed when the superblock goes away, or something
like that?
From: David Howells <dhowells@redhat.com> Date: 2020-02-21 13:27:02
Jann Horn [off-list ref] wrote:
Hm - I guess just returning a list of attributes ought to be fine?
Then AFS can just return one of its two statically-allocated attribute
lists there, and a filesystem with more complicated circumstances
(like FUSE or overlayfs or whatever) can compute a heap-allocated list
on mount that is freed when the superblock goes away, or something
like that?
I've changed it so that the core calls into the filesystem with no buffer
allocated first. If the fs finds an appropriate attribute, it calls a helper
to handle it. As there's no buffer, this will just return the size.
If the fs doesn't have a handler, it returns -EOPNOTSUPP and the core looks
for a common attribute instead and calls the helper on that if found.
At this point, if a valid length was returned and if userspace didn't specify
a buffer, we just return the proposed size to userspace.
If userspace did specify a buffer, then core will allocate a buffer of the
requested size and call into the filesystem again. The helper will call the
->get() function to retrieve the value. The ->get() function returns the
size.
If the returned size exceeds the buffer size, a bigger buffer will be
allocated and it will repeat the last step.
A simple example looks like:
int ext4_fsinfo(struct path *path, struct fsinfo_context *ctx)
{
return fsinfo_get_attribute(path, ctx, ext4_fsinfo_attributes);
}
where the ext4_fsinfo_attributes is an array of attribute defs. The helper,
fsinfo_get_attribute() scans the list. The helper can be called multiple
times if there's more than one list to process. The caller should stop if one
doesn't return -EOPNOTSUPP.
When the attribute IDs are being listed, the helper will detect that and just
add all the IDs to the list, returning -EOPNOTSUPP when it's done so that all
the attributes get listed.
When the metadata for an attribute is being retrieved, the helper detects that
and searches the given table for that attribute. If it finds it, it will
return information about that attribute rather than calling the attribute
helper.
David
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:06:42
Add security hooks that will allow an LSM to rule on whether or not a watch
may be set on a mount or on a superblock. More than one hook is required
as the watches watch different types of object.
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Casey Schaufler <casey@schaufler-ca.com>
cc: Stephen Smalley <redacted>
cc: linux-security-module@vger.kernel.org
---
include/linux/lsm_hooks.h | 24 ++++++++++++++++++++++++
include/linux/security.h | 16 ++++++++++++++++
security/security.c | 14 ++++++++++++++
3 files changed, 54 insertions(+)
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:06:52
Add a mount notification facility whereby notifications about changes in
mount topology and configuration can be received. Note that this only
covers vfsmount topology changes and not superblock events. A separate
facility will be added for that.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_MOUNT_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_mount(AT_FDCWD, "/", 0, fd, 0x02);
In this case, it would let me monitor the mount topology subtree rooted at
"/" for events. Mount notifications propagate up the tree towards the
root, so a watch will catch all of the events happening in the subtree
rooted at the watch.
After setting the watch, records will be placed into the queue when, for
example, as superblock switches between read-write and read-only. Records
are of the following format:
struct mount_notification {
struct watch_notification watch;
__u32 triggered_on;
__u32 changed_mount;
} *n;
Where:
n->watch.type will be WATCH_TYPE_MOUNT_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_MOUNT_NEW_MOUNT.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
watch_mount(), shifted.
n->watch.info & NOTIFY_MOUNT_IN_SUBTREE if true indicates that the
notifcation was generated in the mount subtree rooted at the watch,
and not actually in the watch itself.
n->watch.info & NOTIFY_MOUNT_IS_RECURSIVE if true indicates that
the notifcation was generated by an event (eg. SETATTR) that was
applied recursively. The notification is only generated for the
object that initially triggered it.
n->watch.info & NOTIFY_MOUNT_IS_NOW_RO will be used for
NOTIFY_MOUNT_READONLY, being set if the superblock becomes R/O, and
being cleared otherwise, and for NOTIFY_MOUNT_NEW_MOUNT, being set
if the new mount is a submount (e.g. an automount).
n->watch.info & NOTIFY_MOUNT_IS_SUBMOUNT if true indicates that the
NOTIFY_MOUNT_NEW_MOUNT notification is in response to a mount
performed by the kernel (e.g. an automount).
n->triggered_on indicates the ID of the mount on which the watch
was installed.
n->changed_mount indicates the ID of the mount that was affected.
The mount IDs can be retrieved with the fsinfo() syscall, using the
fsinfo_mount_info and fsinfo_mount_child attributes. There are change
notification counters there too for when a buffer overrun occurs, thereby
allowing the mount tree to be quickly rescanned.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <dhowells@redhat.com>
---
arch/alpha/kernel/syscalls/syscall.tbl | 1
arch/arm/tools/syscall.tbl | 1
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 | 9 +
fs/Makefile | 1
fs/mount.h | 33 +++--
fs/mount_notify.c | 188 +++++++++++++++++++++++++++
fs/namespace.c | 17 ++
include/linux/dcache.h | 1
include/linux/syscalls.h | 2
include/uapi/asm-generic/unistd.h | 4 -
include/uapi/linux/watch_queue.h | 32 ++++-
kernel/sys_ni.c | 1
26 files changed, 287 insertions(+), 17 deletions(-)
create mode 100644 fs/mount_notify.c
@@ -478,3 +478,4 @@ 547 common openat2 sys_openat2 548 common pidfd_getfd sys_pidfd_getfd 549 common fsinfo sys_fsinfo+550 common watch_mount sys_watch_mount
@@ -452,3 +452,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -359,3 +359,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -439,3 +439,4 @@ 438 common pidfd_getfd sys_pidfd_getfd # 435 reserved for clone3 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -444,3 +444,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -436,3 +436,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -520,3 +520,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -441,3 +441,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -484,3 +484,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -360,6 +360,7 @@ 437 common openat2 __x64_sys_openat2 438 common pidfd_getfd __x64_sys_pidfd_getfd 439 common fsinfo __x64_sys_fsinfo+440 common watch_mount __x64_sys_watch_mount # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -409,3 +409,4 @@ 437 common openat2 sys_openat2 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo+440 common watch_mount sys_watch_mount
@@ -70,9 +71,13 @@ struct mount {intmnt_id;/* mount identifier */intmnt_group_id;/* peer group identifier */intmnt_expiry_mark;/* true if marked for expiry */+intmnt_nr_watchers;/* The number of subtree watches tracking this */structhlist_headmnt_pins;structhlist_headmnt_stuck_children;atomic_tmnt_change_counter;/* Number of changed applied */+#ifdef CONFIG_MOUNT_NOTIFICATIONS+structwatch_list*mnt_watchers;/* Watches on dentries within this mount */+#endif}__randomize_layout;#define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
@@ -155,18 +160,8 @@ static inline bool is_anon_ns(struct mnt_namespace *ns)returnns->seq==0;}-/*-*Typeofmounttopologychangenotification.-*/-enummount_notification_subtype{-NOTIFY_MOUNT_NEW_MOUNT=0,/* New mount added */-NOTIFY_MOUNT_UNMOUNT=1,/* Mount removed manually */-NOTIFY_MOUNT_EXPIRY=2,/* Automount expired */-NOTIFY_MOUNT_READONLY=3,/* Mount R/O state changed */-NOTIFY_MOUNT_SETATTR=4,/* Mount attributes changed */-NOTIFY_MOUNT_MOVE_FROM=5,/* Mount moved from here */-NOTIFY_MOUNT_MOVE_TO=6,/* Mount moved to here (compare op_id) */-};+externvoidpost_mount_notification(structmount*changed,+structmount_notification*notify);staticinlinevoidnotify_mount(structmount*changed,structmount*aux,
@@ -510,7 +510,8 @@ static int mnt_make_readonly(struct mount *mnt)mnt->mnt.mnt_flags&=~MNT_WRITE_HOLD;unlock_mount_hash();if(ret==0)-notify_mount(mnt,NULL,NOTIFY_MOUNT_READONLY,0x10000);+notify_mount(mnt,NULL,NOTIFY_MOUNT_READONLY,+NOTIFY_MOUNT_IS_NOW_RO);returnret;}
@@ -1176,6 +1177,11 @@ static void mntput_no_expire(struct mount *mnt)mnt->mnt.mnt_flags|=MNT_DOOMED;rcu_read_unlock();+#ifdef CONFIG_MOUNT_NOTIFICATIONS+if(mnt->mnt_watchers)+remove_watch_list(mnt->mnt_watchers,mnt->mnt_id);+#endif+list_del(&mnt->mnt_instance);if(unlikely(!list_empty(&mnt->mnt_mounts))){
@@ -2108,7 +2114,11 @@ static int attach_recursive_mnt(struct mount *source_mnt,list_del_init(&source_mnt->mnt_ns->list);}mnt_set_mountpoint(dest_mnt,dest_mp,source_mnt);-notify_mount(dest_mnt,source_mnt,NOTIFY_MOUNT_NEW_MOUNT,0);+notify_mount(dest_mnt,source_mnt,NOTIFY_MOUNT_NEW_MOUNT,+(source_mnt->mnt.mnt_sb->s_flags&SB_RDONLY?+NOTIFY_MOUNT_IS_NOW_RO:0)|+(source_mnt->mnt.mnt_sb->s_flags&SB_SUBMOUNT?+NOTIFY_MOUNT_IS_SUBMOUNT:0));commit_tree(source_mnt);}
@@ -2486,7 +2496,8 @@ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags)mnt->mnt.mnt_flags=mnt_flags;touch_mnt_namespace(mnt->mnt_ns);unlock_mount_hash();-notify_mount(mnt,NULL,NOTIFY_MOUNT_SETATTR,0);+notify_mount(mnt,NULL,NOTIFY_MOUNT_SETATTR,+(mnt_flags&SB_RDONLY?NOTIFY_MOUNT_IS_NOW_RO:0));}staticvoidmnt_warn_timestamp_expiry(structpath*mountpoint,structvfsmount*mnt)
@@ -1007,6 +1007,8 @@ asmlinkage long sys_pidfd_getfd(int pidfd, int fd, unsigned int flags);asmlinkagelongsys_fsinfo(intdfd,constchar__user*pathname,structfsinfo_params__user*params,void__user*buffer,size_tbuf_size);+asmlinkagelongsys_watch_mount(intdfd,constchar__user*path,+unsignedintat_flags,intwatch_fd,intwatch_id);/**Architecture-specificsystemcalls
@@ -14,7 +14,8 @@enumwatch_notification_type{WATCH_TYPE_META=0,/* Special record */WATCH_TYPE_KEY_NOTIFY=1,/* Key change event notification */-WATCH_TYPE__NR=2+WATCH_TYPE_MOUNT_NOTIFY=2,/* Mount topology change notification */+WATCH_TYPE___NR=3};enumwatch_meta_notification_subtype{
@@ -101,4 +102,33 @@ struct key_notification {__u32aux;/* Per-type auxiliary data */};+/*+*Typeofmounttopologychangenotification.+*/+enummount_notification_subtype{+NOTIFY_MOUNT_NEW_MOUNT=0,/* New mount added */+NOTIFY_MOUNT_UNMOUNT=1,/* Mount removed manually */+NOTIFY_MOUNT_EXPIRY=2,/* Automount expired */+NOTIFY_MOUNT_READONLY=3,/* Mount R/O state changed */+NOTIFY_MOUNT_SETATTR=4,/* Mount attributes changed */+NOTIFY_MOUNT_MOVE_FROM=5,/* Mount moved from here */+NOTIFY_MOUNT_MOVE_TO=6,/* Mount moved to here (compare op_id) */+};++#define NOTIFY_MOUNT_IN_SUBTREE WATCH_INFO_FLAG_0 /* Event not actually at watched dentry */+#define NOTIFY_MOUNT_IS_RECURSIVE WATCH_INFO_FLAG_1 /* Change applied recursively */+#define NOTIFY_MOUNT_IS_NOW_RO WATCH_INFO_FLAG_2 /* Mount changed to R/O */+#define NOTIFY_MOUNT_IS_SUBMOUNT WATCH_INFO_FLAG_3 /* New mount is submount */++/*+*Mounttopology/configurationchangenotificationrecord.+*-watch.type=WATCH_TYPE_MOUNT_NOTIFY+*-watch.subtype=enummount_notification_subtype+*/+structmount_notification{+structwatch_notificationwatch;/* WATCH_TYPE_MOUNT_NOTIFY */+__u32triggered_on;/* The mount that the notify was on */+__u32changed_mount;/* The mount that got changed */+};+#endif /* _UAPI_LINUX_WATCH_QUEUE_H */
On Tue, Feb 18, 2020 at 6:07 PM David Howells [off-list ref] wrote:
Add a mount notification facility whereby notifications about changes in
mount topology and configuration can be received. Note that this only
covers vfsmount topology changes and not superblock events. A separate
facility will be added for that.
[...]
quoted hunk
@@ -70,9 +71,13 @@ struct mount { int mnt_id; /* mount identifier */ int mnt_group_id; /* peer group identifier */ int mnt_expiry_mark; /* true if marked for expiry */+ int mnt_nr_watchers; /* The number of subtree watches tracking this */
You're never referencing this variable elsewhere in the patch, and it
also isn't gated by #ifdef.
struct hlist_head mnt_pins;
struct hlist_head mnt_stuck_children;
atomic_t mnt_change_counter; /* Number of changed applied */
+#ifdef CONFIG_MOUNT_NOTIFICATIONS
+ struct watch_list *mnt_watchers; /* Watches on dentries within this mount */
Please document lifetime semantics. Something like "This pointer can't
change once it has been set to a non-NULL value".
quoted hunk
+#endif
} __randomize_layout;
#define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
@@ -155,18 +160,8 @@ static inline bool is_anon_ns(struct mnt_namespace *ns) return ns->seq == 0; }-/*- * Type of mount topology change notification.- */-enum mount_notification_subtype {- NOTIFY_MOUNT_NEW_MOUNT = 0, /* New mount added */- NOTIFY_MOUNT_UNMOUNT = 1, /* Mount removed manually */- NOTIFY_MOUNT_EXPIRY = 2, /* Automount expired */- NOTIFY_MOUNT_READONLY = 3, /* Mount R/O state changed */- NOTIFY_MOUNT_SETATTR = 4, /* Mount attributes changed */- NOTIFY_MOUNT_MOVE_FROM = 5, /* Mount moved from here */- NOTIFY_MOUNT_MOVE_TO = 6, /* Mount moved to here (compare op_id) */-};
Is there a reason why you introduce these in "vfs: Add mount change
counter", then in this patch move them elsewhere?
[...]
If another thread concurrently runs close(watch_fd) at this point,
pipe_release -> put_pipe_info -> free_pipe_info -> watch_queue_clear
will run, correct? And then watch_queue_clear() will find the watch
that we've just created and call its ->release_watch() handler, which
causes dput() on path.dentry? At that point, we no longer hold any
reference to the dentry...
... but then here we call dget() on it?
In general, the following pattern indicates a bug unless a surrounding
lock provides the necessary protection:
ret = operation_that_hands_off_the_reference_on_success(ptr);
if (ret == SUCCESS) {
increment_refcount(ptr);
}
and should be replaced with the following pattern:
increment_refcount(ptr);
ret = operation_that_hands_off_the_reference_on_success(ptr);
if (ret == FAILURE) {
decrement_refcount(ptr);
}
What exactly is the implication of only using the dentry as key here
(and not the mount)? Does this mean that if you watch install watches
on two bind mounts of the same underlying filesystem, the notification
mechanism gets confused?
What exactly is the implication of only using the dentry as key here
(and not the mount)? Does this mean that if you watch install watches
on two bind mounts of the same underlying filesystem, the notification
mechanism gets confused?
Ah, nevermind, I understand this one now... this operation only
removes watches on this mount with that dentry, and so together, that
means it effectively removes watches by the full path.
adding some locking folks to the thread...
On Fri, Feb 21, 2020 at 6:06 PM David Howells [off-list ref] wrote:
Jann Horn [off-list ref] wrote:
quoted
On Fri, Feb 21, 2020 at 1:24 PM David Howells [off-list ref] wrote:
quoted
What's the best way to write a lockdep assertion?
BUG_ON(!lockdep_is_held(lock));
lockdep_assert_held(lock) is the normal way, I think - that will
WARN() if lockdep is enabled and the lock is not held.
Okay. But what's the best way with a seqlock_t? It has two dep maps in it.
Do I just ignore the one attached to the spinlock?
Uuuh... very good question. Looking at how the seqlock_t helpers use
the dep map of the seqlock, I don't think lockdep asserts work for
asserting that you're in the read side of a seqlock?
read_seqbegin_or_lock() -> read_seqbegin() -> read_seqcount_begin() ->
seqcount_lockdep_reader_access() does seqcount_acquire_read() (which
maps to lock_acquire_shared_recursive()), but immediately following
that calls seqcount_release() (which maps to lock_release())?
So I think lockdep won't consider you to be holding any locks after
read_seqbegin_or_lock() if the lock wasn't taken?
From: John Stultz <hidden> Date: 2020-02-21 18:02:38
On Fri, Feb 21, 2020 at 9:36 AM Jann Horn [off-list ref] wrote:
adding some locking folks to the thread...
On Fri, Feb 21, 2020 at 6:06 PM David Howells [off-list ref] wrote:
quoted
Jann Horn [off-list ref] wrote:
quoted
On Fri, Feb 21, 2020 at 1:24 PM David Howells [off-list ref] wrote:
quoted
What's the best way to write a lockdep assertion?
BUG_ON(!lockdep_is_held(lock));
lockdep_assert_held(lock) is the normal way, I think - that will
WARN() if lockdep is enabled and the lock is not held.
Okay. But what's the best way with a seqlock_t? It has two dep maps in it.
Do I just ignore the one attached to the spinlock?
Uuuh... very good question. Looking at how the seqlock_t helpers use
the dep map of the seqlock, I don't think lockdep asserts work for
asserting that you're in the read side of a seqlock?
read_seqbegin_or_lock() -> read_seqbegin() -> read_seqcount_begin() ->
seqcount_lockdep_reader_access() does seqcount_acquire_read() (which
maps to lock_acquire_shared_recursive()), but immediately following
that calls seqcount_release() (which maps to lock_release())?
So I think lockdep won't consider you to be holding any locks after
read_seqbegin_or_lock() if the lock wasn't taken?
Yea. It's a bit foggy now, but the main concern at the time was
wanting to catch seqlock readers that happened under a writer which
was a common cause of deadlocks between the timekeeping core and stuff
like printks (or anything we called out that might try to read the
time).
I think it was because writers can properly interrupt readers, we
couldn't hold the depmap across the read critical section? That's why
we just take and release the depmap, since that will still catch any
reads made while holding the write, which would deadlock.
But take that with a grain of salt, as its been awhile.
thanks
-john
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:07:07
Add a superblock event notification facility whereby notifications about
superblock events, such as I/O errors (EIO), quota limits being hit
(EDQUOT) and running out of space (ENOSPC) can be reported to a monitoring
process asynchronously. Note that this does not cover vfsmount topology
changes. watch_mount() is used for that.
Firstly, an event queue needs to be created:
fd = open("/dev/event_queue", O_RDWR);
ioctl(fd, IOC_WATCH_QUEUE_SET_SIZE, page_size << n);
then a notification can be set up to report notifications via that queue:
struct watch_notification_filter filter = {
.nr_filters = 1,
.filters = {
[0] = {
.type = WATCH_TYPE_SB_NOTIFY,
.subtype_filter[0] = UINT_MAX,
},
},
};
ioctl(fd, IOC_WATCH_QUEUE_SET_FILTER, &filter);
watch_sb(AT_FDCWD, "/home/dhowells", 0, fd, 0x03);
In this case, it would let me monitor my own homedir for events. After
setting the watch, records will be placed into the queue when, for example,
as superblock switches between read-write and read-only. Records are of
the following format:
struct superblock_notification {
struct watch_notification watch;
__u64 sb_id;
} *n;
Where:
n->watch.type will be WATCH_TYPE_SB_NOTIFY.
n->watch.subtype will indicate the type of event, such as
NOTIFY_SUPERBLOCK_READONLY.
n->watch.info & WATCH_INFO_LENGTH will indicate the length of the
record.
n->watch.info & WATCH_INFO_ID will be the fifth argument to
watch_sb(), shifted.
n->watch.info & NOTIFY_SUPERBLOCK_IS_NOW_RO will be used for
NOTIFY_SUPERBLOCK_READONLY, being set if the superblock becomes
R/O, and being cleared otherwise.
n->sb_id will be the ID of the superblock, as can be retrieved with
the fsinfo() syscall, as part of the fsinfo_sb_notifications
attribute in the the watch_id field.
Note that it is permissible for event records to be of variable length -
or, at least, the length may be dependent on the subtype. Note also that
the queue can be shared between multiple notifications of various types.
Signed-off-by: David Howells <dhowells@redhat.com>
---
arch/alpha/kernel/syscalls/syscall.tbl | 1
arch/arm/tools/syscall.tbl | 1
arch/arm64/include/asm/unistd.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 | 12 +++
fs/super.c | 125 +++++++++++++++++++++++++++
include/linux/fs.h | 77 +++++++++++++++++
include/linux/syscalls.h | 2
include/uapi/asm-generic/unistd.h | 4 +
include/uapi/linux/watch_queue.h | 31 ++++++-
kernel/sys_ni.c | 1
24 files changed, 267 insertions(+), 3 deletions(-)
@@ -479,3 +479,4 @@ 548 common pidfd_getfd sys_pidfd_getfd 549 common fsinfo sys_fsinfo 550 common watch_mount sys_watch_mount+551 common watch_sb sys_watch_sb
@@ -453,3 +453,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo 440 common watch_mount sys_watch_mount+441 common watch_sb sys_watch_sb
@@ -360,3 +360,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo 440 common watch_mount sys_watch_mount+441 common watch_sb sys_watch_sb
@@ -445,3 +445,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo 440 common watch_mount sys_watch_mount+441 common watch_sb sys_watch_sb
@@ -437,3 +437,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo 440 common watch_mount sys_watch_mount+441 common watch_sb sys_watch_sb
@@ -521,3 +521,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo 440 common watch_mount sys_watch_mount+441 common watch_sb sys_watch_sb
@@ -442,3 +442,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo 440 common watch_mount sys_watch_mount+441 common watch_sb sys_watch_sb
@@ -485,3 +485,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo 440 common watch_mount sys_watch_mount+441 common watch_sb sys_watch_sb
@@ -361,6 +361,7 @@ 438 common pidfd_getfd __x64_sys_pidfd_getfd 439 common fsinfo __x64_sys_fsinfo 440 common watch_mount __x64_sys_watch_mount+441 common watch_sb __x64_sys_watch_sb # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -410,3 +410,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common fsinfo sys_fsinfo 440 common watch_mount sys_watch_mount+441 common watch_sb sys_watch_sb
@@ -993,6 +999,8 @@ int reconfigure_super(struct fs_context *fc)/* Needs to be ordered wrt mnt_is_readonly() */smp_wmb();sb->s_readonly_remount=0;+notify_sb(sb,NOTIFY_SUPERBLOCK_READONLY,+remount_ro?NOTIFY_SUPERBLOCK_IS_NOW_RO:0);/**Somefilesystemsmodifytheirmetadataviasomeotherpaththanthe
@@ -1891,3 +1899,120 @@ int thaw_super(struct super_block *sb)returnthaw_super_locked(sb);}EXPORT_SYMBOL(thaw_super);++#ifdef CONFIG_SB_NOTIFICATIONS+/*+*Postsuperblocknotifications.+*/+voidpost_sb_notification(structsuper_block*s,structsuperblock_notification*n)+{+post_watch_notification(s->s_watchers,&n->watch,current_cred(),+s->s_unique_id);+}++/**+*sys_watch_sb-Watchforsuperblockevents.+*@dfd:Basedirectorytopathwalkfromorfdreferringtosuperblock.+*@filename:Pathtosuperblocktoplacethewatchupon+*@at_flags:Pathwalkcontrolflags+*@watch_fd:Thewatchqueuetosendnotificationsto.+*@watch_id:ThewatchIDtobeplacedinthenotification(-1toremovewatch)+*/+SYSCALL_DEFINE5(watch_sb,+int,dfd,+constchar__user*,filename,+unsignedint,at_flags,+int,watch_fd,+int,watch_id)+{+structwatch_queue*wqueue;+structsuper_block*s;+structwatch_list*wlist=NULL;+structwatch*watch=NULL;+structpathpath;+unsignedintlookup_flags=+LOOKUP_DIRECTORY|LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT;+intret;++if(watch_id<-1||watch_id>0xff)+return-EINVAL;+if((at_flags&~(AT_NO_AUTOMOUNT|AT_EMPTY_PATH))!=0)+return-EINVAL;+if(at_flags&AT_NO_AUTOMOUNT)+lookup_flags&=~LOOKUP_AUTOMOUNT;+if(at_flags&AT_EMPTY_PATH)+lookup_flags|=LOOKUP_EMPTY;++ret=user_path_at(dfd,filename,at_flags,&path);+if(ret)+returnret;++ret=inode_permission(path.dentry->d_inode,MAY_EXEC);+if(ret)+gotoerr_path;++wqueue=get_watch_queue(watch_fd);+if(IS_ERR(wqueue))+gotoerr_path;++s=path.dentry->d_sb;+if(watch_id>=0){+ret=-ENOMEM;+if(!s->s_watchers){+wlist=kzalloc(sizeof(*wlist),GFP_KERNEL);+if(!wlist)+gotoerr_wqueue;+init_watch_list(wlist,NULL);+}++watch=kzalloc(sizeof(*watch),GFP_KERNEL);+if(!watch)+gotoerr_wlist;++init_watch(watch,wqueue);+watch->id=s->s_unique_id;+watch->private=s;+watch->info_id=(u32)watch_id<<24;++ret=security_watch_sb(watch,s);+if(ret<0)+gotoerr_watch;++down_write(&s->s_umount);+ret=-EIO;+if(atomic_read(&s->s_active)){+if(!s->s_watchers){+s->s_watchers=wlist;+wlist=NULL;+}++ret=add_watch_to_object(watch,s->s_watchers);+if(ret==0){+spin_lock(&sb_lock);+s->s_count++;+spin_unlock(&sb_lock);+watch=NULL;+}+}+up_write(&s->s_umount);+}else{+ret=-EBADSLT;+if(READ_ONCE(s->s_watchers)){+down_write(&s->s_umount);+ret=remove_watch_from_object(s->s_watchers,wqueue,+s->s_unique_id,false);+up_write(&s->s_umount);+}+}++err_watch:+kfree(watch);+err_wlist:+kfree(wlist);+err_wqueue:+put_watch_queue(wqueue);+err_path:+path_put(&path);+returnret;+}+#endif
@@ -1553,6 +1554,10 @@ struct super_block {/* Superblock event notifications */u64s_unique_id;++#ifdef CONFIG_SB_NOTIFICATIONS+structwatch_list*s_watchers;+#endif}__randomize_layout;/* Helper functions so that in most cases filesystems will
On Tue, Feb 18, 2020 at 6:07 PM David Howells [off-list ref] wrote:
Add a superblock event notification facility whereby notifications about
superblock events, such as I/O errors (EIO), quota limits being hit
(EDQUOT) and running out of space (ENOSPC) can be reported to a monitoring
process asynchronously. Note that this does not cover vfsmount topology
changes. watch_mount() is used for that.
(Nit: I don't get why you do a lockless check here before taking the
lock - it'd be more straightforward to take the lock first, and it's
not like you want to optimize for the case where someone calls
sys_watch_sb() with invalid arguments...)
+/**
+ * notify_sb: Post simple superblock notification.
+ * @s: The superblock the notification is about.
+ * @subtype: The type of notification.
+ * @info: WATCH_INFO_FLAG_* flags to be set in the record.
+ */
+static inline void notify_sb(struct super_block *s,
+ enum superblock_notification_type subtype,
+ u32 info)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
READ_ONCE() ?
+ struct superblock_notification n = {
+ .watch.type = WATCH_TYPE_SB_NOTIFY,
+ .watch.subtype = subtype,
+ .watch.info = watch_sizeof(n) | info,
+ .sb_id = s->s_unique_id,
+ };
+
+ post_sb_notification(s, &n);
+ }
+
+#endif
+}
+
+/**
+ * notify_sb_error: Post superblock error notification.
+ * @s: The superblock the notification is about.
+ * @error: The error number to be recorded.
+ */
+static inline int notify_sb_error(struct super_block *s, int error)
+{
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
From: David Howells <dhowells@redhat.com> Date: 2020-02-21 14:24:05
Jann Horn [off-list ref] wrote:
quoted
+ if (!s->s_watchers) {
READ_ONCE() ?
I'm not sure it matters. It can only be set once, and the next time we read
it we're inside the lock. And at this point, I don't actually dereference it,
and if it's non-NULL, it's not going to change.
quoted
+ ret = add_watch_to_object(watch, s->s_watchers);
+ if (ret == 0) {
+ spin_lock(&sb_lock);
+ s->s_count++;
+ spin_unlock(&sb_lock);
Where is the corresponding decrement of s->s_count? I'm guessing that
it should be in the ->release_watch() handler, except that there isn't
one...
Um. Good question. I think this should do the job:
static void sb_release_watch(struct watch *watch)
{
put_super(watch->private);
}
And this then has to be set later:
init_watch_list(wlist, sb_release_watch);
quoted
+ } else {
+ ret = -EBADSLT;
+ if (READ_ONCE(s->s_watchers)) {
(Nit: I don't get why you do a lockless check here before taking the
lock - it'd be more straightforward to take the lock first, and it's
not like you want to optimize for the case where someone calls
sys_watch_sb() with invalid arguments...)
Fair enough. I'll remove it.
quoted
+#ifdef CONFIG_SB_NOTIFICATIONS
+ if (unlikely(s->s_watchers)) {
READ_ONCE() ?
Shouldn't matter. It's only read once and then a decision is made on it
immediately thereafter. And if it's non-NULL, the value cannot change
thereafter.
David
On Fri, Feb 21, 2020 at 3:24 PM David Howells [off-list ref] wrote:
Jann Horn [off-list ref] wrote:
quoted
quoted
+ if (!s->s_watchers) {
READ_ONCE() ?
I'm not sure it matters. It can only be set once, and the next time we read
it we're inside the lock. And at this point, I don't actually dereference it,
and if it's non-NULL, it's not going to change.
I'd really like these READ_ONCE() things to be *anywhere* the value
can concurrently change, for two reasons:
First, it tells the reader "keep in mind that this value may
concurrently change in some way, don't just assume that it'll stay the
same".
But also, it tells the compiler that if it generates multiple loads
here and assumes that they return the same value, *really* bad stuff
may happen. GCC has some really fun behavior when compiling a switch()
on a value that might change concurrently without using READ_ONCE():
It sometimes generates multiple loads, where the first load is used to
test whether the value is in a specific range and then the second load
is used for actually indexing into a table of jump destinations. If
the value is concurrently mutated from an in-bounds value to an
out-of-bounds value, this code will load a jump destination from
random out-of-bounds memory.
An example:
$ cat gcc-jump.c
int blah(int *x, int y) {
switch (*x) {
case 0: return y+1;
case 1: return y*2;
case 2: return y-3;
case 3: return y^1;
case 4: return y+6;
case 5: return y-5;
case 6: return y|1;
case 7: return y&4;
case 8: return y|5;
case 9: return y-3;
case 10: return y&8;
case 11: return y|9;
default: return y;
}
}
$ gcc-9 -O2 -c -o gcc-jump.o gcc-jump.c
$ objdump -dr gcc-jump.o
[...]
0000000000000000 <blah>:
0: 83 3f 0b cmpl $0xb,(%rdi)
3: 0f 87 00 00 00 00 ja 9 <blah+0x9>
5: R_X86_64_PC32 .text.unlikely-0x4
9: 8b 07 mov (%rdi),%eax
b: 48 8d 15 00 00 00 00 lea 0x0(%rip),%rdx # 12 <blah+0x12>
e: R_X86_64_PC32 .rodata-0x4
12: 48 63 04 82 movslq (%rdx,%rax,4),%rax
16: 48 01 d0 add %rdx,%rax
19: ff e0 jmpq *%rax
[...]
Or if you want to see a full example that actually crashes:
$ cat gcc-jump-crash.c
#include <pthread.h>
int mutating_number;
__attribute__((noinline)) int blah(int *x, int y) {
switch (*x) {
case 0: return y+1;
case 1: return y*2;
case 2: return y-3;
case 3: return y^1;
case 4: return y+6;
case 5: return y-5;
case 6: return y|1;
case 7: return y&4;
case 8: return y|5;
case 9: return y-3;
case 10: return y&8;
case 11: return y|9;
default: return y;
}
}
int blah_num;
void *thread_fn(void *dummy) {
while (1) {
blah_num = blah(&mutating_number, blah_num);
}
}
int main(void) {
pthread_t thread;
pthread_create(&thread, NULL, thread_fn, NULL);
while (1) {
*(volatile int *)&mutating_number = 1;
*(volatile int *)&mutating_number = 100000000;
}
}
$ gcc-9 -O2 -pthread -o gcc-jump-crash gcc-jump-crash.c -ggdb -Wall
$ gdb ./gcc-jump-crash
[...]
(gdb) run
[...]
Thread 2 "gcc-jump-crash" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7db6700 (LWP 33237)]
0x00005555555551a2 in blah (x=0x555555558034 <mutating_number>, y=0)
at gcc-jump-crash.c:6
6 switch (*x) {
(gdb) x/10i blah
0x555555555190 <blah>: cmp DWORD PTR [rdi],0xb
0x555555555193 <blah+3>: ja 0x555555555050 <blah+4294966976>
0x555555555199 <blah+9>: mov eax,DWORD PTR [rdi]
0x55555555519b <blah+11>: lea rdx,[rip+0xe62] # 0x555555556004
=> 0x5555555551a2 <blah+18>: movsxd rax,DWORD PTR [rdx+rax*4]
0x5555555551a6 <blah+22>: add rax,rdx
0x5555555551a9 <blah+25>: jmp rax
0x5555555551ab <blah+27>: nop DWORD PTR [rax+rax*1+0x0]
0x5555555551b0 <blah+32>: lea eax,[rsi-0x3]
0x5555555551b3 <blah+35>: ret
(gdb)
Here's a presentation from Felix Wilhelm, a security researcher who
managed to find a case in the Xen hypervisor where a switch() on a
value in shared memory was exploitable to compromise the hypervisor
from inside a guest (see slides 35 and following):
<https://www.blackhat.com/docs/us-16/materials/us-16-Wilhelm-Xenpwn-Breaking-Paravirtualized-Devices.pdf>
I realize that a compiler is extremely unlikely to make such an
optimization decision for a simple "if (!a->b)" branch; but still, I
would prefer to have READ_ONCE() everywhere where it is semantically
required, not just everywhere where you can think of a concrete
compiler optimization that will break stuff.
quoted
quoted
+ ret = add_watch_to_object(watch, s->s_watchers);
+ if (ret == 0) {
+ spin_lock(&sb_lock);
+ s->s_count++;
+ spin_unlock(&sb_lock);
Where is the corresponding decrement of s->s_count? I'm guessing that
it should be in the ->release_watch() handler, except that there isn't
one...
Um. Good question. I think this should do the job:
static void sb_release_watch(struct watch *watch)
{
put_super(watch->private);
}
And this then has to be set later:
init_watch_list(wlist, sb_release_watch);
(And as in the other case, the s->s_count increment will probably have
to be moved above the add_watch_to_object(), unless you hold the
sb_lock around it?)
From: David Howells <dhowells@redhat.com> Date: 2020-02-21 16:33:29
Jann Horn [off-list ref] wrote:
(And as in the other case, the s->s_count increment will probably have
to be moved above the add_watch_to_object(), unless you hold the
sb_lock around it?)
It shouldn't matter as I'm holding s->s_umount across the add and increment.
That prevents the watch from being removed: watch_sb() would have to get the
lock first to do that. It also deactivate_locked_super() from removing all
the watchers.
I can move it before, but I probably have to drop s_umount before I can call
put_super().
David
On Fri, Feb 21, 2020 at 5:33 PM David Howells [off-list ref] wrote:
Jann Horn [off-list ref] wrote:
quoted
(And as in the other case, the s->s_count increment will probably have
to be moved above the add_watch_to_object(), unless you hold the
sb_lock around it?)
It shouldn't matter as I'm holding s->s_umount across the add and increment.
That prevents the watch from being removed: watch_sb() would have to get the
lock first to do that. It also deactivate_locked_super() from removing all
the watchers.
Can't the same thing I already pointed out on "[PATCH 13/19] vfs: Add
a mount-notification facility [ver #16]" also happen here?
If another thread concurrently runs close(watch_fd) before the
spin_lock(&sb_lock), pipe_release -> put_pipe_info -> free_pipe_info
-> watch_queue_clear will run, correct? And then watch_queue_clear()
will find the watch that we've just created and call its
->release_watch() handler, which causes put_super(), potentially
dropping the refcount to zero? And then stuff will blow up.
I can move it before, but I probably have to drop s_umount before I can call
put_super().
From: David Howells <dhowells@redhat.com> Date: 2020-02-18 17:07:16
This is run like:
./watch_test
and watches "/" for changes to the mount topology and the attributes of
individual mount objects.
# mount -t tmpfs none /mnt
# mount -o remount,ro /mnt
# mount -o remount,rw /mnt
producing:
# ./watch_test
read() = 16
NOTIFY[000]: ty=000002 sy=00 i=02000010
MOUNT 00000060 change=0[new_mount] aux=416
read() = 16
NOTIFY[000]: ty=000002 sy=04 i=02010010
MOUNT 000001a0 change=4[setattr] aux=0
read() = 16
NOTIFY[000]: ty=000002 sy=04 i=02010010
MOUNT 000001a0 change=4[setattr] aux=0
Signed-off-by: David Howells <dhowells@redhat.com>
---
samples/watch_queue/watch_test.c | 39 +++++++++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
@@ -0,0 +1,40 @@+// SPDX-License-Identifier: GPL-2.0+/* Filesystem information for ext4+*+*Copyright(C)2020RedHat,Inc.AllRightsReserved.+*WrittenbyDavidHowells(dhowells@redhat.com)+*/++#include<linux/mount.h>+#include"ext4.h"++staticintext4_fsinfo_get_volume_name(structpath*path,structfsinfo_context*ctx)+{+conststructext4_sb_info*sbi=EXT4_SB(path->mnt->mnt_sb);+conststructext4_super_block*es=sbi->s_es;++memcpy(ctx->buffer,es->s_volume_name,sizeof(es->s_volume_name));+returnstrlen(ctx->buffer);+}++staticintext4_fsinfo_get_timestamps(structpath*path,structfsinfo_context*ctx)+{+conststructext4_sb_info*sbi=EXT4_SB(path->mnt->mnt_sb);+conststructext4_super_block*es=sbi->s_es;+structfsinfo_ext4_timestamps*ts=ctx->buffer;++#define Z(R,S) R = S | (((u64)S##_hi) << 32)+Z(ts->mkfs_time,es->s_mkfs_time);+Z(ts->mount_time,es->s_mtime);+Z(ts->write_time,es->s_wtime);+Z(ts->last_check_time,es->s_lastcheck);+Z(ts->first_error_time,es->s_first_error_time);+Z(ts->last_error_time,es->s_last_error_time);+returnsizeof(*ts);+}++conststructfsinfo_attributeext4_fsinfo_attributes[]={+FSINFO_STRING(FSINFO_ATTR_VOLUME_NAME,ext4_fsinfo_get_volume_name),+FSINFO_VSTRUCT(FSINFO_ATTR_EXT4_TIMESTAMPS,ext4_fsinfo_get_timestamps),+{}+};
@@ -38,6 +38,8 @@#define FSINFO_ATTR_AFS_SERVER_NAME 0x301 /* Name of the Nth server (string) */#define FSINFO_ATTR_AFS_SERVER_ADDRESSES 0x302 /* List of addresses of the Nth server */+#define FSINFO_ATTR_EXT4_TIMESTAMPS 0x400 /* Ext4 superblock timestamps */+/**Optionalfsinfo()parameterstructure.*