blob: 91ae97570b39fdc5e06a7d0385825d53ccc4b56a [file] [log] [blame]
Lorenz Brun1d801752020-04-02 09:24:51 +02001From c91b96ad3f53de22e11d6692d2d8a80ed611a5a5 Mon Sep 17 00:00:00 2001
2From: David Howells <dhowells@redhat.com>
3Date: Fri, 6 Mar 2020 14:59:51 +0000
4Subject: [PATCH 3/4] fsinfo: Add fsinfo() syscall to query filesystem
5 information
6
7Add a system call to allow filesystem information to be queried. A request
8value can be given to indicate the desired attribute. Support is provided
9for enumerating multi-value attributes.
10
11===============
12NEW SYSTEM CALL
13===============
14
15The new system call looks like:
16
17 int ret = fsinfo(int dfd,
18 const char *pathname,
19 const struct fsinfo_params *params,
20 size_t params_size,
21 void *result_buffer,
22 size_t result_buf_size);
23
24The params parameter optionally points to a block of parameters:
25
26 struct fsinfo_params {
27 __u64 resolve_flags;
28 __u32 at_flags;
29 __u32 flags;
30 __u32 request;
31 __u32 Nth;
32 __u32 Mth;
33 };
34
35If params is NULL, the default is that params->request is
36FSINFO_ATTR_STATFS and all the other fields are 0. params_size indicates
37the size of the parameter struct. If the parameter block is short compared
38to what the kernel expects, the missing length will be set to 0; if the
39parameter block is longer, an error will be given if the excess is not all
40zeros.
41
42The object to be queried is specified as follows - part param->flags
43indicates the type of reference:
44
45 (1) FSINFO_FLAGS_QUERY_PATH - dfd, pathname and at_flags indicate a
46 filesystem object to query.
47
48 There is no separate system call providing an analogue of lstat() -
49 AT_SYMLINK_NOFOLLOW should be set in at_flags instead.
50 AT_NO_AUTOMOUNT can also be used to an allow automount point to be
51 queried without triggering it.
52
53 RESOLVE_* flags can also be set in resolve_flags to further restrict
54 the patchwalk.
55
56 (2) FSINFO_FLAGS_QUERY_FD - dfd indicates a file descriptor pointing to
57 the filesystem object to query. pathname should be NULL.
58
59 (3) FSINFO_FLAGS_QUERY_MOUNT - pathname indicates the numeric ID of the
60 mountpoint to query as a string. dfd is used to constrain which
61 mounts can be accessed. If dfd is AT_FDCWD, the mount must be within
62 the subtree rooted at chroot, otherwise the mount must be within the
63 subtree rooted at the directory specified by dfd.
64
65 (4) In the future FSINFO_FLAGS_QUERY_FSCONTEXT will be added - dfd will
66 indicate a context handle fd obtained from fsopen() or fspick(),
67 allowing that to be queried before the target superblock is attached
68 to the filesystem or even created.
69
70params->request indicates the attribute/attributes to be queried. This can
71be one of:
72
73 FSINFO_ATTR_STATFS - statfs-style info
74 FSINFO_ATTR_IDS - Filesystem IDs
75 FSINFO_ATTR_LIMITS - Filesystem limits
76 FSINFO_ATTR_SUPPORTS - Support for statx, ioctl, etc.
77 FSINFO_ATTR_TIMESTAMP_INFO - Inode timestamp info
78 FSINFO_ATTR_VOLUME_ID - Volume ID (string)
79 FSINFO_ATTR_VOLUME_UUID - Volume UUID
80 FSINFO_ATTR_VOLUME_NAME - Volume name (string)
81 FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO - Information about attr Nth
82 FSINFO_ATTR_FSINFO_ATTRIBUTES - List of supported attrs
83
84Some attributes (such as the servers backing a network filesystem) can have
85multiple values. These can be enumerated by setting params->Nth and
86params->Mth to 0, 1, ... until ENODATA is returned.
87
88result_buffer and result_buf_size point to the reply buffer. The buffer is
89filled up to the specified size, even if this means truncating the reply.
90The size of the full reply is returned, irrespective of the amount data
91that was copied. In future versions, this will allow extra fields to be
92tacked on to the end of the reply, but anyone not expecting them will only
93get the subset they're expecting. If either buffer of result_buf_size are
940, no copy will take place and the data size will be returned.
95
96Backported for Linux 5.6 by Lorenz Brun <lorenz@nexantic.com>
97
98Signed-off-by: David Howells <dhowells@redhat.com>
99cc: linux-api@vger.kernel.org
100---
101 arch/alpha/kernel/syscalls/syscall.tbl | 1 +
102 arch/arm/tools/syscall.tbl | 1 +
103 arch/arm64/include/asm/unistd.h | 2 +-
104 arch/arm64/include/asm/unistd32.h | 2 +
105 arch/ia64/kernel/syscalls/syscall.tbl | 1 +
106 arch/m68k/kernel/syscalls/syscall.tbl | 1 +
107 arch/microblaze/kernel/syscalls/syscall.tbl | 1 +
108 arch/mips/kernel/syscalls/syscall_n32.tbl | 1 +
109 arch/mips/kernel/syscalls/syscall_n64.tbl | 1 +
110 arch/mips/kernel/syscalls/syscall_o32.tbl | 1 +
111 arch/parisc/kernel/syscalls/syscall.tbl | 1 +
112 arch/powerpc/kernel/syscalls/syscall.tbl | 1 +
113 arch/s390/kernel/syscalls/syscall.tbl | 1 +
114 arch/sh/kernel/syscalls/syscall.tbl | 1 +
115 arch/sparc/kernel/syscalls/syscall.tbl | 1 +
116 arch/x86/entry/syscalls/syscall_32.tbl | 1 +
117 arch/x86/entry/syscalls/syscall_64.tbl | 1 +
118 arch/xtensa/kernel/syscalls/syscall.tbl | 1 +
119 fs/Kconfig | 7 +
120 fs/Makefile | 1 +
121 fs/fsinfo.c | 586 ++++++++++++++++++
122 include/linux/fs.h | 4 +
123 include/linux/fsinfo.h | 73 +++
124 include/linux/syscalls.h | 4 +
125 include/uapi/asm-generic/unistd.h | 4 +-
126 include/uapi/linux/fsinfo.h | 187 ++++++
127 kernel/sys_ni.c | 1 +
128 samples/vfs/Makefile | 5 +
129 samples/vfs/test-fsinfo.c | 633 ++++++++++++++++++++
130 29 files changed, 1523 insertions(+), 2 deletions(-)
131 create mode 100644 fs/fsinfo.c
132 create mode 100644 include/linux/fsinfo.h
133 create mode 100644 include/uapi/linux/fsinfo.h
134 create mode 100644 samples/vfs/test-fsinfo.c
135
136diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl
137index 36d42da7466a..59388edc444a 100644
138--- a/arch/alpha/kernel/syscalls/syscall.tbl
139+++ b/arch/alpha/kernel/syscalls/syscall.tbl
140@@ -477,3 +477,4 @@
141 # 545 reserved for clone3
142 547 common openat2 sys_openat2
143 548 common pidfd_getfd sys_pidfd_getfd
144+551 common fsinfo sys_fsinfo
145diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl
146index 4d1cf74a2caa..a670f6add5ab 100644
147--- a/arch/arm/tools/syscall.tbl
148+++ b/arch/arm/tools/syscall.tbl
149@@ -451,3 +451,4 @@
150 435 common clone3 sys_clone3
151 437 common openat2 sys_openat2
152 438 common pidfd_getfd sys_pidfd_getfd
153+441 common fsinfo sys_fsinfo
154diff --git a/arch/arm64/include/asm/unistd.h b/arch/arm64/include/asm/unistd.h
155index 803039d504de..86a9d7b3eabe 100644
156--- a/arch/arm64/include/asm/unistd.h
157+++ b/arch/arm64/include/asm/unistd.h
158@@ -38,7 +38,7 @@
159 #define __ARM_NR_compat_set_tls (__ARM_NR_COMPAT_BASE + 5)
160 #define __ARM_NR_COMPAT_END (__ARM_NR_COMPAT_BASE + 0x800)
161
162-#define __NR_compat_syscalls 439
163+#define __NR_compat_syscalls 442
164 #endif
165
166 #define __ARCH_WANT_SYS_CLONE
167diff --git a/arch/arm64/include/asm/unistd32.h b/arch/arm64/include/asm/unistd32.h
168index c1c61635f89c..1f7d2c8d481a 100644
169--- a/arch/arm64/include/asm/unistd32.h
170+++ b/arch/arm64/include/asm/unistd32.h
171@@ -883,6 +883,8 @@ __SYSCALL(__NR_clone3, sys_clone3)
172 __SYSCALL(__NR_openat2, sys_openat2)
173 #define __NR_pidfd_getfd 438
174 __SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd)
175+#define __NR_fsinfo 441
176+__SYSCALL(__NR_fsinfo, sys_fsinfo)
177
178 /*
179 * Please add new compat syscalls above this comment and update
180diff --git a/arch/ia64/kernel/syscalls/syscall.tbl b/arch/ia64/kernel/syscalls/syscall.tbl
181index 042911e670b8..2a4aea2f1050 100644
182--- a/arch/ia64/kernel/syscalls/syscall.tbl
183+++ b/arch/ia64/kernel/syscalls/syscall.tbl
184@@ -358,3 +358,4 @@
185 # 435 reserved for clone3
186 437 common openat2 sys_openat2
187 438 common pidfd_getfd sys_pidfd_getfd
188+441 common fsinfo sys_fsinfo
189diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl
190index f4f49fcb76d0..9e254f0ef8ea 100644
191--- a/arch/m68k/kernel/syscalls/syscall.tbl
192+++ b/arch/m68k/kernel/syscalls/syscall.tbl
193@@ -437,3 +437,4 @@
194 435 common clone3 __sys_clone3
195 437 common openat2 sys_openat2
196 438 common pidfd_getfd sys_pidfd_getfd
197+441 common fsinfo sys_fsinfo
198diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl
199index 4c67b11f9c9e..75924284ee3b 100644
200--- a/arch/microblaze/kernel/syscalls/syscall.tbl
201+++ b/arch/microblaze/kernel/syscalls/syscall.tbl
202@@ -443,3 +443,4 @@
203 435 common clone3 sys_clone3
204 437 common openat2 sys_openat2
205 438 common pidfd_getfd sys_pidfd_getfd
206+441 common fsinfo sys_fsinfo
207diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl
208index 1f9e8ad636cc..4e03df1d67d0 100644
209--- a/arch/mips/kernel/syscalls/syscall_n32.tbl
210+++ b/arch/mips/kernel/syscalls/syscall_n32.tbl
211@@ -376,3 +376,4 @@
212 435 n32 clone3 __sys_clone3
213 437 n32 openat2 sys_openat2
214 438 n32 pidfd_getfd sys_pidfd_getfd
215+441 n32 fsinfo sys_fsinfo
216diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl
217index c0b9d802dbf6..fdcc5ca0a776 100644
218--- a/arch/mips/kernel/syscalls/syscall_n64.tbl
219+++ b/arch/mips/kernel/syscalls/syscall_n64.tbl
220@@ -352,3 +352,4 @@
221 435 n64 clone3 __sys_clone3
222 437 n64 openat2 sys_openat2
223 438 n64 pidfd_getfd sys_pidfd_getfd
224+441 n64 fsinfo sys_fsinfo
225diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl
226index ac586774c980..17fb101db45c 100644
227--- a/arch/mips/kernel/syscalls/syscall_o32.tbl
228+++ b/arch/mips/kernel/syscalls/syscall_o32.tbl
229@@ -425,3 +425,4 @@
230 435 o32 clone3 __sys_clone3
231 437 o32 openat2 sys_openat2
232 438 o32 pidfd_getfd sys_pidfd_getfd
233+441 o32 fsinfo sys_fsinfo
234diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl
235index 52a15f5cd130..daa7f0c22da0 100644
236--- a/arch/parisc/kernel/syscalls/syscall.tbl
237+++ b/arch/parisc/kernel/syscalls/syscall.tbl
238@@ -435,3 +435,4 @@
239 435 common clone3 sys_clone3_wrapper
240 437 common openat2 sys_openat2
241 438 common pidfd_getfd sys_pidfd_getfd
242+441 common fsinfo sys_fsinfo
243diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl
244index 35b61bfc1b1a..ad1de6e3e866 100644
245--- a/arch/powerpc/kernel/syscalls/syscall.tbl
246+++ b/arch/powerpc/kernel/syscalls/syscall.tbl
247@@ -519,3 +519,4 @@
248 435 nospu clone3 ppc_clone3
249 437 common openat2 sys_openat2
250 438 common pidfd_getfd sys_pidfd_getfd
251+441 common fsinfo sys_fsinfo
252diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl
253index bd7bd3581a0f..915ee9824956 100644
254--- a/arch/s390/kernel/syscalls/syscall.tbl
255+++ b/arch/s390/kernel/syscalls/syscall.tbl
256@@ -440,3 +440,4 @@
257 435 common clone3 sys_clone3 sys_clone3
258 437 common openat2 sys_openat2 sys_openat2
259 438 common pidfd_getfd sys_pidfd_getfd sys_pidfd_getfd
260+441 common fsinfo sys_fsinfo sys_fsinfo
261diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl
262index c7a30fcd135f..35facfae37c9 100644
263--- a/arch/sh/kernel/syscalls/syscall.tbl
264+++ b/arch/sh/kernel/syscalls/syscall.tbl
265@@ -440,3 +440,4 @@
266 # 435 reserved for clone3
267 437 common openat2 sys_openat2
268 438 common pidfd_getfd sys_pidfd_getfd
269+441 common fsinfo sys_fsinfo
270diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl
271index f13615ecdecc..45adfd34b654 100644
272--- a/arch/sparc/kernel/syscalls/syscall.tbl
273+++ b/arch/sparc/kernel/syscalls/syscall.tbl
274@@ -483,3 +483,4 @@
275 # 435 reserved for clone3
276 437 common openat2 sys_openat2
277 438 common pidfd_getfd sys_pidfd_getfd
278+441 common fsinfo sys_fsinfo
279diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl
280index c17cb77eb150..37aa4ed3dbef 100644
281--- a/arch/x86/entry/syscalls/syscall_32.tbl
282+++ b/arch/x86/entry/syscalls/syscall_32.tbl
283@@ -442,3 +442,4 @@
284 435 i386 clone3 sys_clone3 __ia32_sys_clone3
285 437 i386 openat2 sys_openat2 __ia32_sys_openat2
286 438 i386 pidfd_getfd sys_pidfd_getfd __ia32_sys_pidfd_getfd
287+441 i386 fsinfo sys_fsinfo __ia32_sys_fsinfo
288diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
289index 44d510bc9b78..eacf0b7c5c3d 100644
290--- a/arch/x86/entry/syscalls/syscall_64.tbl
291+++ b/arch/x86/entry/syscalls/syscall_64.tbl
292@@ -359,6 +359,7 @@
293 435 common clone3 __x64_sys_clone3/ptregs
294 437 common openat2 __x64_sys_openat2
295 438 common pidfd_getfd __x64_sys_pidfd_getfd
296+441 common fsinfo __x64_sys_fsinfo
297
298 #
299 # x32-specific system call numbers start at 512 to avoid cache impact
300diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl
301index 85a9ab1bc04d..4d7803550754 100644
302--- a/arch/xtensa/kernel/syscalls/syscall.tbl
303+++ b/arch/xtensa/kernel/syscalls/syscall.tbl
304@@ -408,3 +408,4 @@
305 435 common clone3 sys_clone3
306 437 common openat2 sys_openat2
307 438 common pidfd_getfd sys_pidfd_getfd
308+441 common fsinfo sys_fsinfo
309diff --git a/fs/Kconfig b/fs/Kconfig
310index 708ba336e689..1d1b48059ec9 100644
311--- a/fs/Kconfig
312+++ b/fs/Kconfig
313@@ -15,6 +15,13 @@ config VALIDATE_FS_PARSER
314 Enable this to perform validation of the parameter description for a
315 filesystem when it is registered.
316
317+config FSINFO
318+ bool "Enable the fsinfo() system call"
319+ help
320+ Enable the file system information querying system call to allow
321+ comprehensive information to be retrieved about a filesystem,
322+ superblock or mount object.
323+
324 if BLOCK
325
326 config FS_IOMAP
327diff --git a/fs/Makefile b/fs/Makefile
328index 505e51166973..b5cc9bcd17a4 100644
329--- a/fs/Makefile
330+++ b/fs/Makefile
331@@ -54,6 +54,7 @@ obj-$(CONFIG_COREDUMP) += coredump.o
332 obj-$(CONFIG_SYSCTL) += drop_caches.o
333
334 obj-$(CONFIG_FHANDLE) += fhandle.o
335+obj-$(CONFIG_FSINFO) += fsinfo.o
336 obj-y += iomap/
337
338 obj-y += quota/
339diff --git a/fs/fsinfo.c b/fs/fsinfo.c
340new file mode 100644
341index 000000000000..1830c73f37a7
342--- /dev/null
343+++ b/fs/fsinfo.c
344@@ -0,0 +1,586 @@
345+// SPDX-License-Identifier: GPL-2.0
346+/* Filesystem information query.
347+ *
348+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
349+ * Written by David Howells (dhowells@redhat.com)
350+ */
351+#include <linux/syscalls.h>
352+#include <linux/fs.h>
353+#include <linux/file.h>
354+#include <linux/mount.h>
355+#include <linux/namei.h>
356+#include <linux/statfs.h>
357+#include <linux/security.h>
358+#include <linux/uaccess.h>
359+#include <linux/fsinfo.h>
360+#include <uapi/linux/mount.h>
361+#include "internal.h"
362+
363+/**
364+ * fsinfo_string - Store a NUL-terminated string as an fsinfo attribute value.
365+ * @s: The string to store (may be NULL)
366+ * @ctx: The parameter context
367+ */
368+int fsinfo_string(const char *s, struct fsinfo_context *ctx)
369+{
370+ unsigned int len;
371+ char *p = ctx->buffer;
372+ int ret = 0;
373+
374+ if (s) {
375+ len = min_t(size_t, strlen(s), ctx->buf_size - 1);
376+ if (!ctx->want_size_only) {
377+ memcpy(p, s, len);
378+ p[len] = 0;
379+ }
380+ ret = len;
381+ }
382+
383+ return ret;
384+}
385+EXPORT_SYMBOL(fsinfo_string);
386+
387+/*
388+ * Get basic filesystem stats from statfs.
389+ */
390+static int fsinfo_generic_statfs(struct path *path, struct fsinfo_context *ctx)
391+{
392+ struct fsinfo_statfs *p = ctx->buffer;
393+ struct kstatfs buf;
394+ int ret;
395+
396+ ret = vfs_statfs(path, &buf);
397+ if (ret < 0)
398+ return ret;
399+
400+ p->f_blocks.lo = buf.f_blocks;
401+ p->f_bfree.lo = buf.f_bfree;
402+ p->f_bavail.lo = buf.f_bavail;
403+ p->f_files.lo = buf.f_files;
404+ p->f_ffree.lo = buf.f_ffree;
405+ p->f_favail.lo = buf.f_ffree;
406+ p->f_bsize = buf.f_bsize;
407+ p->f_frsize = buf.f_frsize;
408+ return sizeof(*p);
409+}
410+
411+static int fsinfo_generic_ids(struct path *path, struct fsinfo_context *ctx)
412+{
413+ struct fsinfo_ids *p = ctx->buffer;
414+ struct super_block *sb;
415+ struct kstatfs buf;
416+ int ret;
417+
418+ ret = vfs_statfs(path, &buf);
419+ if (ret < 0 && ret != -ENOSYS)
420+ return ret;
421+ if (ret == 0)
422+ memcpy(&p->f_fsid, &buf.f_fsid, sizeof(p->f_fsid));
423+
424+ sb = path->dentry->d_sb;
425+ p->f_fstype = sb->s_magic;
426+ p->f_dev_major = MAJOR(sb->s_dev);
427+ p->f_dev_minor = MINOR(sb->s_dev);
428+ p->f_sb_id = sb->s_unique_id;
429+ strlcpy(p->f_fs_name, sb->s_type->name, sizeof(p->f_fs_name));
430+ return sizeof(*p);
431+}
432+
433+int fsinfo_generic_limits(struct path *path, struct fsinfo_context *ctx)
434+{
435+ struct fsinfo_limits *p = ctx->buffer;
436+ struct super_block *sb = path->dentry->d_sb;
437+
438+ p->max_file_size.hi = 0;
439+ p->max_file_size.lo = sb->s_maxbytes;
440+ p->max_ino.hi = 0;
441+ p->max_ino.lo = UINT_MAX;
442+ p->max_hard_links = sb->s_max_links;
443+ p->max_uid = UINT_MAX;
444+ p->max_gid = UINT_MAX;
445+ p->max_projid = UINT_MAX;
446+ p->max_filename_len = NAME_MAX;
447+ p->max_symlink_len = PATH_MAX;
448+ p->max_xattr_name_len = XATTR_NAME_MAX;
449+ p->max_xattr_body_len = XATTR_SIZE_MAX;
450+ p->max_dev_major = 0xffffff;
451+ p->max_dev_minor = 0xff;
452+ return sizeof(*p);
453+}
454+EXPORT_SYMBOL(fsinfo_generic_limits);
455+
456+int fsinfo_generic_supports(struct path *path, struct fsinfo_context *ctx)
457+{
458+ struct fsinfo_supports *p = ctx->buffer;
459+ struct super_block *sb = path->dentry->d_sb;
460+
461+ p->stx_mask = STATX_BASIC_STATS;
462+ if (sb->s_d_op && sb->s_d_op->d_automount)
463+ p->stx_attributes |= STATX_ATTR_AUTOMOUNT;
464+ return sizeof(*p);
465+}
466+EXPORT_SYMBOL(fsinfo_generic_supports);
467+
468+static const struct fsinfo_timestamp_info fsinfo_default_timestamp_info = {
469+ .atime = {
470+ .minimum = S64_MIN,
471+ .maximum = S64_MAX,
472+ .gran_mantissa = 1,
473+ .gran_exponent = 0,
474+ },
475+ .mtime = {
476+ .minimum = S64_MIN,
477+ .maximum = S64_MAX,
478+ .gran_mantissa = 1,
479+ .gran_exponent = 0,
480+ },
481+ .ctime = {
482+ .minimum = S64_MIN,
483+ .maximum = S64_MAX,
484+ .gran_mantissa = 1,
485+ .gran_exponent = 0,
486+ },
487+ .btime = {
488+ .minimum = S64_MIN,
489+ .maximum = S64_MAX,
490+ .gran_mantissa = 1,
491+ .gran_exponent = 0,
492+ },
493+};
494+
495+int fsinfo_generic_timestamp_info(struct path *path, struct fsinfo_context *ctx)
496+{
497+ struct fsinfo_timestamp_info *p = ctx->buffer;
498+ struct super_block *sb = path->dentry->d_sb;
499+ s8 exponent;
500+
501+ *p = fsinfo_default_timestamp_info;
502+
503+ if (sb->s_time_gran < 1000000000) {
504+ if (sb->s_time_gran < 1000)
505+ exponent = -9;
506+ else if (sb->s_time_gran < 1000000)
507+ exponent = -6;
508+ else
509+ exponent = -3;
510+
511+ p->atime.gran_exponent = exponent;
512+ p->mtime.gran_exponent = exponent;
513+ p->ctime.gran_exponent = exponent;
514+ p->btime.gran_exponent = exponent;
515+ }
516+
517+ return sizeof(*p);
518+}
519+EXPORT_SYMBOL(fsinfo_generic_timestamp_info);
520+
521+static int fsinfo_generic_volume_uuid(struct path *path, struct fsinfo_context *ctx)
522+{
523+ struct fsinfo_volume_uuid *p = ctx->buffer;
524+ struct super_block *sb = path->dentry->d_sb;
525+
526+ memcpy(p, &sb->s_uuid, sizeof(*p));
527+ return sizeof(*p);
528+}
529+
530+static int fsinfo_generic_volume_id(struct path *path, struct fsinfo_context *ctx)
531+{
532+ return fsinfo_string(path->dentry->d_sb->s_id, ctx);
533+}
534+
535+static const struct fsinfo_attribute fsinfo_common_attributes[] = {
536+ FSINFO_VSTRUCT (FSINFO_ATTR_STATFS, fsinfo_generic_statfs),
537+ FSINFO_VSTRUCT (FSINFO_ATTR_IDS, fsinfo_generic_ids),
538+ FSINFO_VSTRUCT (FSINFO_ATTR_LIMITS, fsinfo_generic_limits),
539+ FSINFO_VSTRUCT (FSINFO_ATTR_SUPPORTS, fsinfo_generic_supports),
540+ FSINFO_VSTRUCT (FSINFO_ATTR_TIMESTAMP_INFO, fsinfo_generic_timestamp_info),
541+ FSINFO_STRING (FSINFO_ATTR_VOLUME_ID, fsinfo_generic_volume_id),
542+ FSINFO_VSTRUCT (FSINFO_ATTR_VOLUME_UUID, fsinfo_generic_volume_uuid),
543+
544+ FSINFO_LIST (FSINFO_ATTR_FSINFO_ATTRIBUTES, (void *)123UL),
545+ FSINFO_VSTRUCT_N(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO, (void *)123UL),
546+ {}
547+};
548+
549+/*
550+ * Determine an attribute's minimum buffer size and, if the buffer is large
551+ * enough, get the attribute value.
552+ */
553+static int fsinfo_get_this_attribute(struct path *path,
554+ struct fsinfo_context *ctx,
555+ const struct fsinfo_attribute *attr)
556+{
557+ int buf_size;
558+
559+ if (ctx->Nth != 0 && !(attr->flags & (FSINFO_FLAGS_N | FSINFO_FLAGS_NM)))
560+ return -ENODATA;
561+ if (ctx->Mth != 0 && !(attr->flags & FSINFO_FLAGS_NM))
562+ return -ENODATA;
563+
564+ switch (attr->type) {
565+ case FSINFO_TYPE_VSTRUCT:
566+ ctx->clear_tail = true;
567+ buf_size = attr->size;
568+ break;
569+ case FSINFO_TYPE_STRING:
570+ case FSINFO_TYPE_OPAQUE:
571+ case FSINFO_TYPE_LIST:
572+ buf_size = 4096;
573+ break;
574+ default:
575+ return -ENOPKG;
576+ }
577+
578+ if (ctx->buf_size < buf_size)
579+ return buf_size;
580+
581+ return attr->get(path, ctx);
582+}
583+
584+static void fsinfo_attributes_insert(struct fsinfo_context *ctx,
585+ const struct fsinfo_attribute *attr)
586+{
587+ __u32 *p = ctx->buffer;
588+ unsigned int i;
589+
590+ if (ctx->usage >= ctx->buf_size ||
591+ ctx->buf_size - ctx->usage < sizeof(__u32)) {
592+ ctx->usage += sizeof(__u32);
593+ return;
594+ }
595+
596+ for (i = 0; i < ctx->usage / sizeof(__u32); i++)
597+ if (p[i] == attr->attr_id)
598+ return;
599+
600+ p[i] = attr->attr_id;
601+ ctx->usage += sizeof(__u32);
602+}
603+
604+static int fsinfo_list_attributes(struct path *path,
605+ struct fsinfo_context *ctx,
606+ const struct fsinfo_attribute *attributes)
607+{
608+ const struct fsinfo_attribute *a;
609+
610+ for (a = attributes; a->get; a++)
611+ fsinfo_attributes_insert(ctx, a);
612+ return -EOPNOTSUPP; /* We want to go through all the lists */
613+}
614+
615+static int fsinfo_get_attribute_info(struct path *path,
616+ struct fsinfo_context *ctx,
617+ const struct fsinfo_attribute *attributes)
618+{
619+ const struct fsinfo_attribute *a;
620+ struct fsinfo_attribute_info *p = ctx->buffer;
621+
622+ if (!ctx->buf_size)
623+ return sizeof(*p);
624+
625+ for (a = attributes; a->get; a++) {
626+ if (a->attr_id == ctx->Nth) {
627+ p->attr_id = a->attr_id;
628+ p->type = a->type;
629+ p->flags = a->flags;
630+ p->size = a->size;
631+ p->size = a->size;
632+ return sizeof(*p);
633+ }
634+ }
635+ return -EOPNOTSUPP; /* We want to go through all the lists */
636+}
637+
638+/**
639+ * fsinfo_get_attribute - Look up and handle an attribute
640+ * @path: The object to query
641+ * @params: Parameters to define a request and place to store result
642+ * @attributes: List of attributes to search.
643+ *
644+ * Look through a list of attributes for one that matches the requested
645+ * attribute then call the handler for it.
646+ */
647+int fsinfo_get_attribute(struct path *path, struct fsinfo_context *ctx,
648+ const struct fsinfo_attribute *attributes)
649+{
650+ const struct fsinfo_attribute *a;
651+
652+ switch (ctx->requested_attr) {
653+ case FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO:
654+ return fsinfo_get_attribute_info(path, ctx, attributes);
655+ case FSINFO_ATTR_FSINFO_ATTRIBUTES:
656+ return fsinfo_list_attributes(path, ctx, attributes);
657+ default:
658+ for (a = attributes; a->get; a++)
659+ if (a->attr_id == ctx->requested_attr)
660+ return fsinfo_get_this_attribute(path, ctx, a);
661+ return -EOPNOTSUPP;
662+ }
663+}
664+EXPORT_SYMBOL(fsinfo_get_attribute);
665+
666+/**
667+ * generic_fsinfo - Handle an fsinfo attribute generically
668+ * @path: The object to query
669+ * @params: Parameters to define a request and place to store result
670+ */
671+static int fsinfo_call(struct path *path, struct fsinfo_context *ctx)
672+{
673+ int ret;
674+
675+ if (path->dentry->d_sb->s_op->fsinfo) {
676+ ret = path->dentry->d_sb->s_op->fsinfo(path, ctx);
677+ if (ret != -EOPNOTSUPP)
678+ return ret;
679+ }
680+ ret = fsinfo_get_attribute(path, ctx, fsinfo_common_attributes);
681+ if (ret != -EOPNOTSUPP)
682+ return ret;
683+
684+ switch (ctx->requested_attr) {
685+ case FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO:
686+ return -ENODATA;
687+ case FSINFO_ATTR_FSINFO_ATTRIBUTES:
688+ return ctx->usage;
689+ default:
690+ return -EOPNOTSUPP;
691+ }
692+}
693+
694+/**
695+ * vfs_fsinfo - Retrieve filesystem information
696+ * @path: The object to query
697+ * @params: Parameters to define a request and place to store result
698+ *
699+ * Get an attribute on a filesystem or an object within a filesystem. The
700+ * filesystem attribute to be queried is indicated by @ctx->requested_attr, and
701+ * if it's a multi-valued attribute, the particular value is selected by
702+ * @ctx->Nth and then @ctx->Mth.
703+ *
704+ * For common attributes, a value may be fabricated if it is not supported by
705+ * the filesystem.
706+ *
707+ * On success, the size of the attribute's value is returned (0 is a valid
708+ * size). A buffer will have been allocated and will be pointed to by
709+ * @ctx->buffer. The caller must free this with kvfree().
710+ *
711+ * Errors can also be returned: -ENOMEM if a buffer cannot be allocated, -EPERM
712+ * or -EACCES if permission is denied by the LSM, -EOPNOTSUPP if an attribute
713+ * doesn't exist for the specified object or -ENODATA if the attribute exists,
714+ * but the Nth,Mth value does not exist. -EMSGSIZE indicates that the value is
715+ * unmanageable internally and -ENOPKG indicates other internal failure.
716+ *
717+ * Errors such as -EIO may also come from attempts to access media or servers
718+ * to obtain the requested information if it's not immediately to hand.
719+ *
720+ * [*] Note that the caller may set @ctx->want_size_only if it only wants the
721+ * size of the value and not the data. If this is set, a buffer may not be
722+ * allocated under some circumstances. This is intended for size query by
723+ * userspace.
724+ *
725+ * [*] Note that @ctx->clear_tail will be returned set if the data should be
726+ * padded out with zeros when writing it to userspace.
727+ */
728+static int vfs_fsinfo(struct path *path, struct fsinfo_context *ctx)
729+{
730+ struct dentry *dentry = path->dentry;
731+ int ret;
732+
733+ ret = security_sb_statfs(dentry);
734+ if (ret)
735+ return ret;
736+
737+ /* Call the handler to find out the buffer size required. */
738+ ctx->buf_size = 0;
739+ ret = fsinfo_call(path, ctx);
740+ if (ret < 0 || ctx->want_size_only)
741+ return ret;
742+ ctx->buf_size = ret;
743+
744+ do {
745+ /* Allocate a buffer of the requested size. */
746+ if (ctx->buf_size > INT_MAX)
747+ return -EMSGSIZE;
748+ ctx->buffer = kvzalloc(ctx->buf_size, GFP_KERNEL);
749+ if (!ctx->buffer)
750+ return -ENOMEM;
751+
752+ ctx->usage = 0;
753+ ctx->skip = 0;
754+ ret = fsinfo_call(path, ctx);
755+ if (IS_ERR_VALUE((long)ret))
756+ return ret;
757+ if ((unsigned int)ret <= ctx->buf_size)
758+ return ret; /* It fitted */
759+
760+ /* We need to resize the buffer */
761+ ctx->buf_size = roundup(ret, PAGE_SIZE);
762+ kvfree(ctx->buffer);
763+ ctx->buffer = NULL;
764+ } while (!signal_pending(current));
765+
766+ return -ERESTARTSYS;
767+}
768+
769+static int vfs_fsinfo_path(int dfd, const char __user *pathname,
770+ const struct fsinfo_params *up,
771+ struct fsinfo_context *ctx)
772+{
773+ struct path path;
774+ unsigned lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
775+ int ret = -EINVAL;
776+
777+ if (up->resolve_flags & ~VALID_RESOLVE_FLAGS)
778+ return -EINVAL;
779+ if (up->at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
780+ AT_EMPTY_PATH))
781+ return -EINVAL;
782+
783+ if (up->resolve_flags & RESOLVE_NO_XDEV)
784+ lookup_flags |= LOOKUP_NO_XDEV;
785+ if (up->resolve_flags & RESOLVE_NO_MAGICLINKS)
786+ lookup_flags |= LOOKUP_NO_MAGICLINKS;
787+ if (up->resolve_flags & RESOLVE_NO_SYMLINKS)
788+ lookup_flags |= LOOKUP_NO_SYMLINKS;
789+ if (up->resolve_flags & RESOLVE_BENEATH)
790+ lookup_flags |= LOOKUP_BENEATH;
791+ if (up->resolve_flags & RESOLVE_IN_ROOT)
792+ lookup_flags |= LOOKUP_IN_ROOT;
793+ if (up->at_flags & AT_SYMLINK_NOFOLLOW)
794+ lookup_flags &= ~LOOKUP_FOLLOW;
795+ if (up->at_flags & AT_NO_AUTOMOUNT)
796+ lookup_flags &= ~LOOKUP_AUTOMOUNT;
797+ if (up->at_flags & AT_EMPTY_PATH)
798+ lookup_flags |= LOOKUP_EMPTY;
799+
800+retry:
801+ ret = user_path_at(dfd, pathname, lookup_flags, &path);
802+ if (ret)
803+ goto out;
804+
805+ ret = vfs_fsinfo(&path, ctx);
806+ path_put(&path);
807+ if (retry_estale(ret, lookup_flags)) {
808+ lookup_flags |= LOOKUP_REVAL;
809+ goto retry;
810+ }
811+out:
812+ return ret;
813+}
814+
815+static int vfs_fsinfo_fd(unsigned int fd, struct fsinfo_context *ctx)
816+{
817+ struct fd f = fdget_raw(fd);
818+ int ret = -EBADF;
819+
820+ if (f.file) {
821+ ret = vfs_fsinfo(&f.file->f_path, ctx);
822+ fdput(f);
823+ }
824+ return ret;
825+}
826+
827+/**
828+ * sys_fsinfo - System call to get filesystem information
829+ * @dfd: Base directory to pathwalk from or fd referring to filesystem.
830+ * @pathname: Filesystem to query or NULL.
831+ * @params: Parameters to define request (NULL: FSINFO_ATTR_STATFS).
832+ * @params_size: Size of parameter buffer.
833+ * @result_buffer: Result buffer.
834+ * @result_buf_size: Size of result buffer.
835+ *
836+ * Get information on a filesystem. The filesystem attribute to be queried is
837+ * indicated by @_params->request, and some of the attributes can have multiple
838+ * values, indexed by @_params->Nth and @_params->Mth. If @_params is NULL,
839+ * then the 0th fsinfo_attr_statfs attribute is queried. If an attribute does
840+ * not exist, EOPNOTSUPP is returned; if the Nth,Mth value does not exist,
841+ * ENODATA is returned.
842+ *
843+ * On success, the size of the attribute's value is returned. If
844+ * @result_buf_size is 0 or @result_buffer is NULL, only the size is returned.
845+ * If the size of the value is larger than @result_buf_size, it will be
846+ * truncated by the copy. If the size of the value is smaller than
847+ * @result_buf_size then the excess buffer space will be cleared. The full
848+ * size of the value will be returned, irrespective of how much data is
849+ * actually placed in the buffer.
850+ */
851+SYSCALL_DEFINE6(fsinfo,
852+ int, dfd,
853+ const char __user *, pathname,
854+ const struct fsinfo_params __user *, params,
855+ size_t, params_size,
856+ void __user *, result_buffer,
857+ size_t, result_buf_size)
858+{
859+ struct fsinfo_context ctx;
860+ struct fsinfo_params user_params;
861+ unsigned int result_size;
862+ void *r;
863+ int ret;
864+
865+ if ((!params && params_size) ||
866+ ( params && !params_size) ||
867+ (!result_buffer && result_buf_size) ||
868+ ( result_buffer && !result_buf_size))
869+ return -EINVAL;
870+ if (result_buf_size > UINT_MAX)
871+ return -EOVERFLOW;
872+
873+ memset(&ctx, 0, sizeof(ctx));
874+ ctx.requested_attr = FSINFO_ATTR_STATFS;
875+ ctx.flags = FSINFO_FLAGS_QUERY_PATH;
876+ ctx.want_size_only = (result_buf_size == 0);
877+
878+ if (params) {
879+ ret = copy_struct_from_user(&user_params, sizeof(user_params),
880+ params, params_size);
881+ if (ret < 0)
882+ return ret;
883+ if (user_params.flags & ~FSINFO_FLAGS_QUERY_MASK)
884+ return -EINVAL;
885+ ctx.flags = user_params.flags;
886+ ctx.requested_attr = user_params.request;
887+ ctx.Nth = user_params.Nth;
888+ ctx.Mth = user_params.Mth;
889+ }
890+
891+ switch (ctx.flags & FSINFO_FLAGS_QUERY_MASK) {
892+ case FSINFO_FLAGS_QUERY_PATH:
893+ ret = vfs_fsinfo_path(dfd, pathname, &user_params, &ctx);
894+ break;
895+ case FSINFO_FLAGS_QUERY_FD:
896+ if (pathname)
897+ return -EINVAL;
898+ ret = vfs_fsinfo_fd(dfd, &ctx);
899+ break;
900+ default:
901+ return -EINVAL;
902+ }
903+
904+ if (ret < 0)
905+ goto error;
906+
907+ r = ctx.buffer + ctx.skip;
908+ result_size = min_t(size_t, ret, result_buf_size);
909+ if (result_size > 0 &&
910+ copy_to_user(result_buffer, r, result_size) != 0) {
911+ ret = -EFAULT;
912+ goto error;
913+ }
914+
915+ /* Clear any part of the buffer that we won't fill if we're putting a
916+ * struct in there. Strings, opaque objects and arrays are expected to
917+ * be variable length.
918+ */
919+ if (ctx.clear_tail &&
920+ result_buf_size > result_size &&
921+ clear_user(result_buffer + result_size,
922+ result_buf_size - result_size) != 0) {
923+ ret = -EFAULT;
924+ goto error;
925+ }
926+
927+error:
928+ kvfree(ctx.buffer);
929+ return ret;
930+}
931diff --git a/include/linux/fs.h b/include/linux/fs.h
932index fb0db8474141..13270f1a8663 100644
933--- a/include/linux/fs.h
934+++ b/include/linux/fs.h
935@@ -68,6 +68,7 @@ struct fsverity_info;
936 struct fsverity_operations;
937 struct fs_context;
938 struct fs_parameter_spec;
939+struct fsinfo_context;
940
941 extern void __init inode_init(void);
942 extern void __init inode_init_early(void);
943@@ -1958,6 +1959,9 @@ struct super_operations {
944 int (*thaw_super) (struct super_block *);
945 int (*unfreeze_fs) (struct super_block *);
946 int (*statfs) (struct dentry *, struct kstatfs *);
947+#ifdef CONFIG_FSINFO
948+ int (*fsinfo)(struct path *, struct fsinfo_context *);
949+#endif
950 int (*remount_fs) (struct super_block *, int *, char *);
951 void (*umount_begin) (struct super_block *);
952
953diff --git a/include/linux/fsinfo.h b/include/linux/fsinfo.h
954new file mode 100644
955index 000000000000..bf806669b4fb
956--- /dev/null
957+++ b/include/linux/fsinfo.h
958@@ -0,0 +1,73 @@
959+// SPDX-License-Identifier: GPL-2.0
960+/* Filesystem information query
961+ *
962+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
963+ * Written by David Howells (dhowells@redhat.com)
964+ */
965+
966+#ifndef _LINUX_FSINFO_H
967+#define _LINUX_FSINFO_H
968+
969+#ifdef CONFIG_FSINFO
970+
971+#include <uapi/linux/fsinfo.h>
972+
973+struct path;
974+
975+#define FSINFO_NORMAL_ATTR_MAX_SIZE 4096
976+
977+struct fsinfo_context {
978+ __u32 flags; /* [in] FSINFO_FLAGS_* */
979+ __u32 requested_attr; /* [in] What is being asking for */
980+ __u32 Nth; /* [in] Instance of it (some may have multiple) */
981+ __u32 Mth; /* [in] Subinstance */
982+ bool want_size_only; /* [in] Just want to know the size, not the data */
983+ bool clear_tail; /* [out] T if tail of buffer should be cleared */
984+ unsigned int skip; /* [out] Number of bytes to skip in buffer */
985+ unsigned int usage; /* [tmp] Amount of buffer used (if large) */
986+ unsigned int buf_size; /* [tmp] Size of ->buffer[] */
987+ void *buffer; /* [out] The reply buffer */
988+};
989+
990+/*
991+ * A filesystem information attribute definition.
992+ */
993+struct fsinfo_attribute {
994+ unsigned int attr_id; /* The ID of the attribute */
995+ enum fsinfo_value_type type:8; /* The type of the attribute's value(s) */
996+ unsigned int flags:8;
997+ unsigned int size:16; /* - Value size (FSINFO_STRUCT/LIST) */
998+ int (*get)(struct path *path, struct fsinfo_context *params);
999+};
1000+
1001+#define __FSINFO(A, T, S, G, F) \
1002+ { .attr_id = A, .type = T, .flags = F, .size = S, .get = G }
1003+
1004+#define _FSINFO(A, T, S, G) __FSINFO(A, T, S, G, 0)
1005+#define _FSINFO_N(A, T, S, G) __FSINFO(A, T, S, G, FSINFO_FLAGS_N)
1006+#define _FSINFO_NM(A, T, S, G) __FSINFO(A, T, S, G, FSINFO_FLAGS_NM)
1007+
1008+#define _FSINFO_VSTRUCT(A,S,G) _FSINFO (A, FSINFO_TYPE_VSTRUCT, sizeof(S), G)
1009+#define _FSINFO_VSTRUCT_N(A,S,G) _FSINFO_N (A, FSINFO_TYPE_VSTRUCT, sizeof(S), G)
1010+#define _FSINFO_VSTRUCT_NM(A,S,G) _FSINFO_NM(A, FSINFO_TYPE_VSTRUCT, sizeof(S), G)
1011+
1012+#define FSINFO_VSTRUCT(A,G) _FSINFO_VSTRUCT (A, A##__STRUCT, G)
1013+#define FSINFO_VSTRUCT_N(A,G) _FSINFO_VSTRUCT_N (A, A##__STRUCT, G)
1014+#define FSINFO_VSTRUCT_NM(A,G) _FSINFO_VSTRUCT_NM(A, A##__STRUCT, G)
1015+#define FSINFO_STRING(A,G) _FSINFO (A, FSINFO_TYPE_STRING, 0, G)
1016+#define FSINFO_STRING_N(A,G) _FSINFO_N (A, FSINFO_TYPE_STRING, 0, G)
1017+#define FSINFO_STRING_NM(A,G) _FSINFO_NM(A, FSINFO_TYPE_STRING, 0, G)
1018+#define FSINFO_OPAQUE(A,G) _FSINFO (A, FSINFO_TYPE_OPAQUE, 0, G)
1019+#define FSINFO_LIST(A,G) _FSINFO (A, FSINFO_TYPE_LIST, sizeof(A##__STRUCT), G)
1020+#define FSINFO_LIST_N(A,G) _FSINFO_N (A, FSINFO_TYPE_LIST, sizeof(A##__STRUCT), G)
1021+
1022+extern int fsinfo_string(const char *, struct fsinfo_context *);
1023+extern int fsinfo_generic_timestamp_info(struct path *, struct fsinfo_context *);
1024+extern int fsinfo_generic_supports(struct path *, struct fsinfo_context *);
1025+extern int fsinfo_generic_limits(struct path *, struct fsinfo_context *);
1026+extern int fsinfo_get_attribute(struct path *, struct fsinfo_context *,
1027+ const struct fsinfo_attribute *);
1028+
1029+#endif /* CONFIG_FSINFO */
1030+
1031+#endif /* _LINUX_FSINFO_H */
1032diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
1033index 1815065d52f3..623f61dcdb9b 100644
1034--- a/include/linux/syscalls.h
1035+++ b/include/linux/syscalls.h
1036@@ -47,6 +47,7 @@ struct stat64;
1037 struct statfs;
1038 struct statfs64;
1039 struct statx;
1040+struct fsinfo_params;
1041 struct __sysctl_args;
1042 struct sysinfo;
1043 struct timespec;
1044@@ -1003,6 +1004,9 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
1045 siginfo_t __user *info,
1046 unsigned int flags);
1047 asmlinkage long sys_pidfd_getfd(int pidfd, int fd, unsigned int flags);
1048+asmlinkage long sys_fsinfo(int dfd, const char __user *pathname,
1049+ struct fsinfo_params __user *params, size_t params_size,
1050+ void __user *result_buffer, size_t result_buf_size);
1051
1052 /*
1053 * Architecture-specific system calls
1054diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h
1055index 3a3201e4618e..1708d24cf98d 100644
1056--- a/include/uapi/asm-generic/unistd.h
1057+++ b/include/uapi/asm-generic/unistd.h
1058@@ -855,9 +855,11 @@ __SYSCALL(__NR_clone3, sys_clone3)
1059 __SYSCALL(__NR_openat2, sys_openat2)
1060 #define __NR_pidfd_getfd 438
1061 __SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd)
1062+#define __NR_fsinfo 441
1063+__SYSCALL(__NR_fsinfo, sys_fsinfo)
1064
1065 #undef __NR_syscalls
1066-#define __NR_syscalls 439
1067+#define __NR_syscalls 442
1068
1069 /*
1070 * 32 bit systems traditionally used different
1071diff --git a/include/uapi/linux/fsinfo.h b/include/uapi/linux/fsinfo.h
1072new file mode 100644
1073index 000000000000..e9b35b9b7629
1074--- /dev/null
1075+++ b/include/uapi/linux/fsinfo.h
1076@@ -0,0 +1,187 @@
1077+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
1078+/* fsinfo() definitions.
1079+ *
1080+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
1081+ * Written by David Howells (dhowells@redhat.com)
1082+ */
1083+#ifndef _UAPI_LINUX_FSINFO_H
1084+#define _UAPI_LINUX_FSINFO_H
1085+
1086+#include <linux/types.h>
1087+#include <linux/socket.h>
1088+#include <linux/openat2.h>
1089+
1090+/*
1091+ * The filesystem attributes that can be requested. Note that some attributes
1092+ * may have multiple instances which can be switched in the parameter block.
1093+ */
1094+#define FSINFO_ATTR_STATFS 0x00 /* statfs()-style state */
1095+#define FSINFO_ATTR_IDS 0x01 /* Filesystem IDs */
1096+#define FSINFO_ATTR_LIMITS 0x02 /* Filesystem limits */
1097+#define FSINFO_ATTR_SUPPORTS 0x03 /* What's supported in statx, iocflags, ... */
1098+#define FSINFO_ATTR_TIMESTAMP_INFO 0x04 /* Inode timestamp info */
1099+#define FSINFO_ATTR_VOLUME_ID 0x05 /* Volume ID (string) */
1100+#define FSINFO_ATTR_VOLUME_UUID 0x06 /* Volume UUID (LE uuid) */
1101+#define FSINFO_ATTR_VOLUME_NAME 0x07 /* Volume name (string) */
1102+
1103+#define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO 0x100 /* Information about attr N (for path) */
1104+#define FSINFO_ATTR_FSINFO_ATTRIBUTES 0x101 /* List of supported attrs (for path) */
1105+
1106+/*
1107+ * Optional fsinfo() parameter structure.
1108+ *
1109+ * If this is not given, it is assumed that fsinfo_attr_statfs instance 0,0 is
1110+ * desired.
1111+ */
1112+struct fsinfo_params {
1113+ __u64 resolve_flags; /* RESOLVE_* flags */
1114+ __u32 at_flags; /* AT_* flags */
1115+ __u32 flags; /* Flags controlling fsinfo() specifically */
1116+#define FSINFO_FLAGS_QUERY_MASK 0x0007 /* What object should fsinfo() query? */
1117+#define FSINFO_FLAGS_QUERY_PATH 0x0000 /* - path, specified by dirfd,pathname,AT_EMPTY_PATH */
1118+#define FSINFO_FLAGS_QUERY_FD 0x0001 /* - fd specified by dirfd */
1119+ __u32 request; /* ID of requested attribute */
1120+ __u32 Nth; /* Instance of it (some may have multiple) */
1121+ __u32 Mth; /* Subinstance of Nth instance */
1122+};
1123+
1124+enum fsinfo_value_type {
1125+ FSINFO_TYPE_VSTRUCT = 0, /* Version-lengthed struct (up to 4096 bytes) */
1126+ FSINFO_TYPE_STRING = 1, /* NUL-term var-length string (up to 4095 chars) */
1127+ FSINFO_TYPE_OPAQUE = 2, /* Opaque blob (unlimited size) */
1128+ FSINFO_TYPE_LIST = 3, /* List of ints/structs (unlimited size) */
1129+};
1130+
1131+/*
1132+ * Information struct for fsinfo(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO).
1133+ *
1134+ * This gives information about the attributes supported by fsinfo for the
1135+ * given path.
1136+ */
1137+struct fsinfo_attribute_info {
1138+ unsigned int attr_id; /* The ID of the attribute */
1139+ enum fsinfo_value_type type; /* The type of the attribute's value(s) */
1140+ unsigned int flags;
1141+#define FSINFO_FLAGS_N 0x01 /* - Attr has a set of values */
1142+#define FSINFO_FLAGS_NM 0x02 /* - Attr has a set of sets of values */
1143+ unsigned int size; /* - Value size (FSINFO_STRUCT/FSINFO_LIST) */
1144+};
1145+
1146+#define FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO__STRUCT struct fsinfo_attribute_info
1147+#define FSINFO_ATTR_FSINFO_ATTRIBUTES__STRUCT __u32
1148+
1149+struct fsinfo_u128 {
1150+#if defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : defined(__BIG_ENDIAN)
1151+ __u64 hi;
1152+ __u64 lo;
1153+#elif defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : defined(__LITTLE_ENDIAN)
1154+ __u64 lo;
1155+ __u64 hi;
1156+#endif
1157+};
1158+
1159+/*
1160+ * Information struct for fsinfo(FSINFO_ATTR_STATFS).
1161+ * - This gives extended filesystem information.
1162+ */
1163+struct fsinfo_statfs {
1164+ struct fsinfo_u128 f_blocks; /* Total number of blocks in fs */
1165+ struct fsinfo_u128 f_bfree; /* Total number of free blocks */
1166+ struct fsinfo_u128 f_bavail; /* Number of free blocks available to ordinary user */
1167+ struct fsinfo_u128 f_files; /* Total number of file nodes in fs */
1168+ struct fsinfo_u128 f_ffree; /* Number of free file nodes */
1169+ struct fsinfo_u128 f_favail; /* Number of file nodes available to ordinary user */
1170+ __u64 f_bsize; /* Optimal block size */
1171+ __u64 f_frsize; /* Fragment size */
1172+};
1173+
1174+#define FSINFO_ATTR_STATFS__STRUCT struct fsinfo_statfs
1175+
1176+/*
1177+ * Information struct for fsinfo(FSINFO_ATTR_IDS).
1178+ *
1179+ * List of basic identifiers as is normally found in statfs().
1180+ */
1181+struct fsinfo_ids {
1182+ char f_fs_name[15 + 1]; /* Filesystem name */
1183+ __u64 f_fsid; /* Short 64-bit Filesystem ID (as statfs) */
1184+ __u64 f_sb_id; /* Internal superblock ID for sbnotify()/mntnotify() */
1185+ __u32 f_fstype; /* Filesystem type from linux/magic.h [uncond] */
1186+ __u32 f_dev_major; /* As st_dev_* from struct statx [uncond] */
1187+ __u32 f_dev_minor;
1188+ __u32 __padding[1];
1189+};
1190+
1191+#define FSINFO_ATTR_IDS__STRUCT struct fsinfo_ids
1192+
1193+/*
1194+ * Information struct for fsinfo(FSINFO_ATTR_LIMITS).
1195+ *
1196+ * List of supported filesystem limits.
1197+ */
1198+struct fsinfo_limits {
1199+ struct fsinfo_u128 max_file_size; /* Maximum file size */
1200+ struct fsinfo_u128 max_ino; /* Maximum inode number */
1201+ __u64 max_uid; /* Maximum UID supported */
1202+ __u64 max_gid; /* Maximum GID supported */
1203+ __u64 max_projid; /* Maximum project ID supported */
1204+ __u64 max_hard_links; /* Maximum number of hard links on a file */
1205+ __u64 max_xattr_body_len; /* Maximum xattr content length */
1206+ __u32 max_xattr_name_len; /* Maximum xattr name length */
1207+ __u32 max_filename_len; /* Maximum filename length */
1208+ __u32 max_symlink_len; /* Maximum symlink content length */
1209+ __u32 max_dev_major; /* Maximum device major representable */
1210+ __u32 max_dev_minor; /* Maximum device minor representable */
1211+ __u32 __padding[1];
1212+};
1213+
1214+#define FSINFO_ATTR_LIMITS__STRUCT struct fsinfo_limits
1215+
1216+/*
1217+ * Information struct for fsinfo(FSINFO_ATTR_SUPPORTS).
1218+ *
1219+ * What's supported in various masks, such as statx() attribute and mask bits
1220+ * and IOC flags.
1221+ */
1222+struct fsinfo_supports {
1223+ __u64 stx_attributes; /* What statx::stx_attributes are supported */
1224+ __u32 stx_mask; /* What statx::stx_mask bits are supported */
1225+ __u32 fs_ioc_getflags; /* What FS_IOC_GETFLAGS may return */
1226+ __u32 fs_ioc_setflags_set; /* What FS_IOC_SETFLAGS may set */
1227+ __u32 fs_ioc_setflags_clear; /* What FS_IOC_SETFLAGS may clear */
1228+ __u32 win_file_attrs; /* What DOS/Windows FILE_* attributes are supported */
1229+ __u32 __padding[1];
1230+};
1231+
1232+#define FSINFO_ATTR_SUPPORTS__STRUCT struct fsinfo_supports
1233+
1234+struct fsinfo_timestamp_one {
1235+ __s64 minimum; /* Minimum timestamp value in seconds */
1236+ __s64 maximum; /* Maximum timestamp value in seconds */
1237+ __u16 gran_mantissa; /* Granularity(secs) = mant * 10^exp */
1238+ __s8 gran_exponent;
1239+ __u8 __padding[5];
1240+};
1241+
1242+/*
1243+ * Information struct for fsinfo(FSINFO_ATTR_TIMESTAMP_INFO).
1244+ */
1245+struct fsinfo_timestamp_info {
1246+ struct fsinfo_timestamp_one atime; /* Access time */
1247+ struct fsinfo_timestamp_one mtime; /* Modification time */
1248+ struct fsinfo_timestamp_one ctime; /* Change time */
1249+ struct fsinfo_timestamp_one btime; /* Birth/creation time */
1250+};
1251+
1252+#define FSINFO_ATTR_TIMESTAMP_INFO__STRUCT struct fsinfo_timestamp_info
1253+
1254+/*
1255+ * Information struct for fsinfo(FSINFO_ATTR_VOLUME_UUID).
1256+ */
1257+struct fsinfo_volume_uuid {
1258+ __u8 uuid[16];
1259+};
1260+
1261+#define FSINFO_ATTR_VOLUME_UUID__STRUCT struct fsinfo_volume_uuid
1262+
1263+#endif /* _UAPI_LINUX_FSINFO_H */
1264diff --git a/kernel/sys_ni.c b/kernel/sys_ni.c
1265index 3b69a560a7ac..58246e6b5603 100644
1266--- a/kernel/sys_ni.c
1267+++ b/kernel/sys_ni.c
1268@@ -51,6 +51,7 @@ COND_SYSCALL_COMPAT(io_pgetevents);
1269 COND_SYSCALL(io_uring_setup);
1270 COND_SYSCALL(io_uring_enter);
1271 COND_SYSCALL(io_uring_register);
1272+COND_SYSCALL(fsinfo);
1273
1274 /* fs/xattr.c */
1275
1276diff --git a/samples/vfs/Makefile b/samples/vfs/Makefile
1277index 65acdde5c117..9159ad1d7fc5 100644
1278--- a/samples/vfs/Makefile
1279+++ b/samples/vfs/Makefile
1280@@ -1,10 +1,15 @@
1281 # SPDX-License-Identifier: GPL-2.0-only
1282 # List of programs to build
1283+
1284 hostprogs := \
1285+ test-fsinfo \
1286 test-fsmount \
1287 test-statx
1288
1289 always-y := $(hostprogs)
1290
1291+HOSTCFLAGS_test-fsinfo.o += -I$(objtree)/usr/include
1292+HOSTLDLIBS_test-fsinfo += -static -lm
1293+
1294 HOSTCFLAGS_test-fsmount.o += -I$(objtree)/usr/include
1295 HOSTCFLAGS_test-statx.o += -I$(objtree)/usr/include
1296diff --git a/samples/vfs/test-fsinfo.c b/samples/vfs/test-fsinfo.c
1297new file mode 100644
1298index 000000000000..2b53c735d330
1299--- /dev/null
1300+++ b/samples/vfs/test-fsinfo.c
1301@@ -0,0 +1,633 @@
1302+// SPDX-License-Identifier: GPL-2.0-or-later
1303+/* Test the fsinfo() system call
1304+ *
1305+ * Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
1306+ * Written by David Howells (dhowells@redhat.com)
1307+ */
1308+
1309+#define _GNU_SOURCE
1310+#define _ATFILE_SOURCE
1311+#include <stdbool.h>
1312+#include <stdio.h>
1313+#include <stdlib.h>
1314+#include <stdint.h>
1315+#include <string.h>
1316+#include <unistd.h>
1317+#include <ctype.h>
1318+#include <errno.h>
1319+#include <time.h>
1320+#include <math.h>
1321+#include <fcntl.h>
1322+#include <sys/syscall.h>
1323+#include <linux/fsinfo.h>
1324+#include <linux/socket.h>
1325+#include <sys/stat.h>
1326+#include <arpa/inet.h>
1327+
1328+#ifndef __NR_fsinfo
1329+#define __NR_fsinfo -1
1330+#endif
1331+
1332+static bool debug = 0;
1333+static bool list_last;
1334+
1335+static __attribute__((unused))
1336+ssize_t fsinfo(int dfd, const char *filename,
1337+ struct fsinfo_params *params, size_t params_size,
1338+ void *result_buffer, size_t result_buf_size)
1339+{
1340+ return syscall(__NR_fsinfo, dfd, filename,
1341+ params, params_size,
1342+ result_buffer, result_buf_size);
1343+}
1344+
1345+struct fsinfo_attribute {
1346+ unsigned int attr_id;
1347+ enum fsinfo_value_type type;
1348+ unsigned int size;
1349+ const char *name;
1350+ void (*dump)(void *reply, unsigned int size);
1351+};
1352+
1353+static const struct fsinfo_attribute fsinfo_attributes[];
1354+
1355+static ssize_t get_fsinfo(const char *, const char *, struct fsinfo_params *, void **);
1356+
1357+static void dump_hex(unsigned int *data, int from, int to)
1358+{
1359+ unsigned offset, print_offset = 1, col = 0;
1360+
1361+ from /= 4;
1362+ to = (to + 3) / 4;
1363+
1364+ for (offset = from; offset < to; offset++) {
1365+ if (print_offset) {
1366+ printf("%04x: ", offset * 8);
1367+ print_offset = 0;
1368+ }
1369+ printf("%08x", data[offset]);
1370+ col++;
1371+ if ((col & 3) == 0) {
1372+ printf("\n");
1373+ print_offset = 1;
1374+ } else {
1375+ printf(" ");
1376+ }
1377+ }
1378+
1379+ if (!print_offset)
1380+ printf("\n");
1381+}
1382+
1383+static void dump_attribute_info(void *reply, unsigned int size)
1384+{
1385+ struct fsinfo_attribute_info *attr_info = reply;
1386+ const struct fsinfo_attribute *attr;
1387+ char type[32], val_size[32];
1388+
1389+ switch (attr_info->type) {
1390+ case FSINFO_TYPE_VSTRUCT: strcpy(type, "V-STRUCT"); break;
1391+ case FSINFO_TYPE_STRING: strcpy(type, "STRING"); break;
1392+ case FSINFO_TYPE_OPAQUE: strcpy(type, "OPAQUE"); break;
1393+ case FSINFO_TYPE_LIST: strcpy(type, "LIST"); break;
1394+ default:
1395+ sprintf(type, "type-%x", attr_info->type);
1396+ break;
1397+ }
1398+
1399+ if (attr_info->flags & FSINFO_FLAGS_N)
1400+ strcat(type, " x N");
1401+ else if (attr_info->flags & FSINFO_FLAGS_NM)
1402+ strcat(type, " x NM");
1403+
1404+ for (attr = fsinfo_attributes; attr->name; attr++)
1405+ if (attr->attr_id == attr_info->attr_id)
1406+ break;
1407+
1408+ if (attr_info->size)
1409+ sprintf(val_size, "%u", attr_info->size);
1410+ else
1411+ strcpy(val_size, "-");
1412+
1413+ printf("%8x %-12s %08x %5s %s\n",
1414+ attr_info->attr_id,
1415+ type,
1416+ attr_info->flags,
1417+ val_size,
1418+ attr->name ? attr->name : "");
1419+}
1420+
1421+static void dump_fsinfo_generic_statfs(void *reply, unsigned int size)
1422+{
1423+ struct fsinfo_statfs *f = reply;
1424+
1425+ printf("\n");
1426+ printf("\tblocks : n=%llu fr=%llu av=%llu\n",
1427+ (unsigned long long)f->f_blocks.lo,
1428+ (unsigned long long)f->f_bfree.lo,
1429+ (unsigned long long)f->f_bavail.lo);
1430+
1431+ printf("\tfiles : n=%llu fr=%llu av=%llu\n",
1432+ (unsigned long long)f->f_files.lo,
1433+ (unsigned long long)f->f_ffree.lo,
1434+ (unsigned long long)f->f_favail.lo);
1435+ printf("\tbsize : %llu\n", f->f_bsize);
1436+ printf("\tfrsize : %llu\n", f->f_frsize);
1437+}
1438+
1439+static void dump_fsinfo_generic_ids(void *reply, unsigned int size)
1440+{
1441+ struct fsinfo_ids *f = reply;
1442+
1443+ printf("\n");
1444+ printf("\tdev : %02x:%02x\n", f->f_dev_major, f->f_dev_minor);
1445+ printf("\tfs : type=%x name=%s\n", f->f_fstype, f->f_fs_name);
1446+ printf("\tfsid : %llx\n", (unsigned long long)f->f_fsid);
1447+ printf("\tsbid : %llx\n", (unsigned long long)f->f_sb_id);
1448+}
1449+
1450+static void dump_fsinfo_generic_limits(void *reply, unsigned int size)
1451+{
1452+ struct fsinfo_limits *f = reply;
1453+
1454+ printf("\n");
1455+ printf("\tmax file size: %llx%016llx\n",
1456+ (unsigned long long)f->max_file_size.hi,
1457+ (unsigned long long)f->max_file_size.lo);
1458+ printf("\tmax ino : %llx%016llx\n",
1459+ (unsigned long long)f->max_ino.hi,
1460+ (unsigned long long)f->max_ino.lo);
1461+ printf("\tmax ids : u=%llx g=%llx p=%llx\n",
1462+ (unsigned long long)f->max_uid,
1463+ (unsigned long long)f->max_gid,
1464+ (unsigned long long)f->max_projid);
1465+ printf("\tmax dev : maj=%x min=%x\n",
1466+ f->max_dev_major, f->max_dev_minor);
1467+ printf("\tmax links : %llx\n",
1468+ (unsigned long long)f->max_hard_links);
1469+ printf("\tmax xattr : n=%x b=%llx\n",
1470+ f->max_xattr_name_len,
1471+ (unsigned long long)f->max_xattr_body_len);
1472+ printf("\tmax len : file=%x sym=%x\n",
1473+ f->max_filename_len, f->max_symlink_len);
1474+}
1475+
1476+static void dump_fsinfo_generic_supports(void *reply, unsigned int size)
1477+{
1478+ struct fsinfo_supports *f = reply;
1479+
1480+ printf("\n");
1481+ printf("\tstx_attr : %llx\n", (unsigned long long)f->stx_attributes);
1482+ printf("\tstx_mask : %x\n", f->stx_mask);
1483+ printf("\tfs_ioc_*flags: get=%x set=%x clr=%x\n",
1484+ f->fs_ioc_getflags, f->fs_ioc_setflags_set, f->fs_ioc_setflags_clear);
1485+ printf("\twin_fattrs : %x\n", f->win_file_attrs);
1486+}
1487+
1488+static void print_time(struct fsinfo_timestamp_one *t, char stamp)
1489+{
1490+ printf("\t%ctime : gran=%gs range=%llx-%llx\n",
1491+ stamp,
1492+ t->gran_mantissa * pow(10., t->gran_exponent),
1493+ (long long)t->minimum,
1494+ (long long)t->maximum);
1495+}
1496+
1497+static void dump_fsinfo_generic_timestamp_info(void *reply, unsigned int size)
1498+{
1499+ struct fsinfo_timestamp_info *f = reply;
1500+
1501+ printf("\n");
1502+ print_time(&f->atime, 'a');
1503+ print_time(&f->mtime, 'm');
1504+ print_time(&f->ctime, 'c');
1505+ print_time(&f->btime, 'b');
1506+}
1507+
1508+static void dump_fsinfo_generic_volume_uuid(void *reply, unsigned int size)
1509+{
1510+ struct fsinfo_volume_uuid *f = reply;
1511+
1512+ printf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x"
1513+ "-%02x%02x%02x%02x%02x%02x\n",
1514+ f->uuid[ 0], f->uuid[ 1],
1515+ f->uuid[ 2], f->uuid[ 3],
1516+ f->uuid[ 4], f->uuid[ 5],
1517+ f->uuid[ 6], f->uuid[ 7],
1518+ f->uuid[ 8], f->uuid[ 9],
1519+ f->uuid[10], f->uuid[11],
1520+ f->uuid[12], f->uuid[13],
1521+ f->uuid[14], f->uuid[15]);
1522+}
1523+
1524+static void dump_string(void *reply, unsigned int size)
1525+{
1526+ char *s = reply, *p;
1527+ bool nl = false, last_nl = false;
1528+
1529+ p = s;
1530+ if (size >= 4096) {
1531+ size = 4096;
1532+ p[4092] = '.';
1533+ p[4093] = '.';
1534+ p[4094] = '.';
1535+ p[4095] = 0;
1536+ } else {
1537+ p[size] = 0;
1538+ }
1539+
1540+ for (p = s; *p; p++) {
1541+ if (*p == '\n') {
1542+ last_nl = nl = true;
1543+ continue;
1544+ }
1545+ last_nl = false;
1546+ if (!isprint(*p) && *p != '\t')
1547+ *p = '?';
1548+ }
1549+
1550+ if (nl)
1551+ putchar('\n');
1552+ printf("%s", s);
1553+ if (!last_nl)
1554+ putchar('\n');
1555+}
1556+
1557+#define dump_fsinfo_meta_attribute_info (void *)0x123
1558+#define dump_fsinfo_meta_attributes (void *)0x123
1559+
1560+/*
1561+ *
1562+ */
1563+#define __FSINFO(A, T, S, G, F, N) \
1564+ { .attr_id = A, .type = T, .size = S, .name = N, .dump = dump_##G }
1565+
1566+#define _FSINFO(A,T,S,G,N) __FSINFO(A, T, S, G, 0, N)
1567+#define _FSINFO_N(A,T,S,G,N) __FSINFO(A, T, S, G, FSINFO_FLAGS_N, N)
1568+#define _FSINFO_NM(A,T,S,G,N) __FSINFO(A, T, S, G, FSINFO_FLAGS_NM, N)
1569+
1570+#define _FSINFO_VSTRUCT(A,S,G,N) _FSINFO (A, FSINFO_TYPE_VSTRUCT, sizeof(S), G, N)
1571+#define _FSINFO_VSTRUCT_N(A,S,G,N) _FSINFO_N (A, FSINFO_TYPE_VSTRUCT, sizeof(S), G, N)
1572+#define _FSINFO_VSTRUCT_NM(A,S,G,N) _FSINFO_NM(A, FSINFO_TYPE_VSTRUCT, sizeof(S), G, N)
1573+
1574+#define FSINFO_VSTRUCT(A,G) _FSINFO_VSTRUCT (A, A##__STRUCT, G, #A)
1575+#define FSINFO_VSTRUCT_N(A,G) _FSINFO_VSTRUCT_N (A, A##__STRUCT, G, #A)
1576+#define FSINFO_VSTRUCT_NM(A,G) _FSINFO_VSTRUCT_NM(A, A##__STRUCT, G, #A)
1577+#define FSINFO_STRING(A,G) _FSINFO (A, FSINFO_TYPE_STRING, 0, G, #A)
1578+#define FSINFO_STRING_N(A,G) _FSINFO_N (A, FSINFO_TYPE_STRING, 0, G, #A)
1579+#define FSINFO_STRING_NM(A,G) _FSINFO_NM(A, FSINFO_TYPE_STRING, 0, G, #A)
1580+#define FSINFO_OPAQUE(A,G) _FSINFO (A, FSINFO_TYPE_OPAQUE, 0, G, #A)
1581+#define FSINFO_LIST(A,G) _FSINFO (A, FSINFO_TYPE_LIST, sizeof(A##__STRUCT), G, #A)
1582+#define FSINFO_LIST_N(A,G) _FSINFO_N (A, FSINFO_TYPE_LIST, sizeof(A##__STRUCT), G, #A)
1583+
1584+static const struct fsinfo_attribute fsinfo_attributes[] = {
1585+ FSINFO_VSTRUCT (FSINFO_ATTR_STATFS, fsinfo_generic_statfs),
1586+ FSINFO_VSTRUCT (FSINFO_ATTR_IDS, fsinfo_generic_ids),
1587+ FSINFO_VSTRUCT (FSINFO_ATTR_LIMITS, fsinfo_generic_limits),
1588+ FSINFO_VSTRUCT (FSINFO_ATTR_SUPPORTS, fsinfo_generic_supports),
1589+ FSINFO_VSTRUCT (FSINFO_ATTR_TIMESTAMP_INFO, fsinfo_generic_timestamp_info),
1590+ FSINFO_STRING (FSINFO_ATTR_VOLUME_ID, string),
1591+ FSINFO_VSTRUCT (FSINFO_ATTR_VOLUME_UUID, fsinfo_generic_volume_uuid),
1592+ FSINFO_STRING (FSINFO_ATTR_VOLUME_NAME, string),
1593+ FSINFO_VSTRUCT_N(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO, fsinfo_meta_attribute_info),
1594+ FSINFO_LIST (FSINFO_ATTR_FSINFO_ATTRIBUTES, fsinfo_meta_attributes),
1595+ {}
1596+};
1597+
1598+static void dump_value(unsigned int attr_id,
1599+ const struct fsinfo_attribute *attr,
1600+ const struct fsinfo_attribute_info *attr_info,
1601+ void *reply, unsigned int size)
1602+{
1603+ if (!attr || !attr->dump) {
1604+ printf("<no dumper>\n");
1605+ return;
1606+ }
1607+
1608+ if (attr->type == FSINFO_TYPE_VSTRUCT && size < attr->size) {
1609+ printf("<short data %u/%u>\n", size, attr->size);
1610+ return;
1611+ }
1612+
1613+ attr->dump(reply, size);
1614+}
1615+
1616+static void dump_list(unsigned int attr_id,
1617+ const struct fsinfo_attribute *attr,
1618+ const struct fsinfo_attribute_info *attr_info,
1619+ void *reply, unsigned int size)
1620+{
1621+ size_t elem_size = attr_info->size;
1622+ unsigned int ix = 0;
1623+
1624+ printf("\n");
1625+ if (!attr || !attr->dump) {
1626+ printf("<no dumper>\n");
1627+ return;
1628+ }
1629+
1630+ if (attr->type == FSINFO_TYPE_VSTRUCT && size < attr->size) {
1631+ printf("<short data %u/%u>\n", size, attr->size);
1632+ return;
1633+ }
1634+
1635+ list_last = false;
1636+ while (size >= elem_size) {
1637+ printf("\t[%02x] ", ix);
1638+ if (size == elem_size)
1639+ list_last = true;
1640+ attr->dump(reply, size);
1641+ reply += elem_size;
1642+ size -= elem_size;
1643+ ix++;
1644+ }
1645+}
1646+
1647+/*
1648+ * Call fsinfo, expanding the buffer as necessary.
1649+ */
1650+static ssize_t get_fsinfo(const char *file, const char *name,
1651+ struct fsinfo_params *params, void **_r)
1652+{
1653+ ssize_t ret;
1654+ size_t buf_size = 4096;
1655+ void *r;
1656+
1657+ for (;;) {
1658+ r = malloc(buf_size);
1659+ if (!r) {
1660+ perror("malloc");
1661+ exit(1);
1662+ }
1663+ memset(r, 0xbd, buf_size);
1664+
1665+ errno = 0;
1666+ ret = fsinfo(AT_FDCWD, file, params, sizeof(*params), r, buf_size - 1);
1667+ if (ret == -1)
1668+ goto error;
1669+
1670+ if (ret <= buf_size - 1)
1671+ break;
1672+ buf_size = (ret + 4096 - 1) & ~(4096 - 1);
1673+ }
1674+
1675+ if (debug)
1676+ printf("fsinfo(%s,%s,%u,%u) = %zd\n",
1677+ file, name, params->Nth, params->Mth, ret);
1678+
1679+ ((char *)r)[ret] = 0;
1680+ *_r = r;
1681+ return ret;
1682+
1683+error:
1684+ *_r = NULL;
1685+ free(r);
1686+ if (debug)
1687+ printf("fsinfo(%s,%s,%u,%u) = %m\n",
1688+ file, name, params->Nth, params->Mth);
1689+ return ret;
1690+}
1691+
1692+/*
1693+ * Try one subinstance of an attribute.
1694+ */
1695+static int try_one(const char *file, struct fsinfo_params *params,
1696+ const struct fsinfo_attribute_info *attr_info, bool raw)
1697+{
1698+ const struct fsinfo_attribute *attr;
1699+ const char *name;
1700+ size_t size = 4096;
1701+ char namebuf[32];
1702+ void *r;
1703+
1704+ for (attr = fsinfo_attributes; attr->name; attr++) {
1705+ if (attr->attr_id == params->request) {
1706+ name = attr->name;
1707+ if (strncmp(name, "fsinfo_generic_", 15) == 0)
1708+ name += 15;
1709+ goto found;
1710+ }
1711+ }
1712+
1713+ sprintf(namebuf, "<unknown-%x>", params->request);
1714+ name = namebuf;
1715+ attr = NULL;
1716+
1717+found:
1718+ size = get_fsinfo(file, name, params, &r);
1719+
1720+ if (size == -1) {
1721+ if (errno == ENODATA) {
1722+ if (!(attr_info->flags & (FSINFO_FLAGS_N | FSINFO_FLAGS_NM)) &&
1723+ params->Nth == 0 && params->Mth == 0) {
1724+ fprintf(stderr,
1725+ "Unexpected ENODATA (0x%x{%u}{%u})\n",
1726+ params->request, params->Nth, params->Mth);
1727+ exit(1);
1728+ }
1729+ free(r);
1730+ return (params->Mth == 0) ? 2 : 1;
1731+ }
1732+ if (errno == EOPNOTSUPP) {
1733+ if (params->Nth > 0 || params->Mth > 0) {
1734+ fprintf(stderr,
1735+ "Should return -ENODATA (0x%x{%u}{%u})\n",
1736+ params->request, params->Nth, params->Mth);
1737+ exit(1);
1738+ }
1739+ //printf("\e[33m%s\e[m: <not supported>\n",
1740+ // fsinfo_attr_names[attr]);
1741+ free(r);
1742+ return 2;
1743+ }
1744+ perror(file);
1745+ exit(1);
1746+ }
1747+
1748+ if (raw) {
1749+ if (size > 4096)
1750+ size = 4096;
1751+ dump_hex(r, 0, size);
1752+ free(r);
1753+ return 0;
1754+ }
1755+
1756+ switch (attr_info->flags & (FSINFO_FLAGS_N | FSINFO_FLAGS_NM)) {
1757+ case 0:
1758+ printf("\e[33m%s\e[m: ", name);
1759+ break;
1760+ case FSINFO_FLAGS_N:
1761+ printf("\e[33m%s{%u}\e[m: ", name, params->Nth);
1762+ break;
1763+ case FSINFO_FLAGS_NM:
1764+ printf("\e[33m%s{%u,%u}\e[m: ", name, params->Nth, params->Mth);
1765+ break;
1766+ }
1767+
1768+ switch (attr_info->type) {
1769+ case FSINFO_TYPE_VSTRUCT:
1770+ case FSINFO_TYPE_STRING:
1771+ dump_value(params->request, attr, attr_info, r, size);
1772+ free(r);
1773+ return 0;
1774+
1775+ case FSINFO_TYPE_LIST:
1776+ dump_list(params->request, attr, attr_info, r, size);
1777+ free(r);
1778+ return 0;
1779+
1780+ case FSINFO_TYPE_OPAQUE:
1781+ free(r);
1782+ return 0;
1783+
1784+ default:
1785+ fprintf(stderr, "Fishy about %u 0x%x,%x,%x\n",
1786+ params->request, attr_info->type, attr_info->flags, attr_info->size);
1787+ exit(1);
1788+ }
1789+}
1790+
1791+static int cmp_u32(const void *a, const void *b)
1792+{
1793+ return *(const int *)a - *(const int *)b;
1794+}
1795+
1796+/*
1797+ *
1798+ */
1799+int main(int argc, char **argv)
1800+{
1801+ struct fsinfo_attribute_info attr_info;
1802+ struct fsinfo_params params = {
1803+ .at_flags = AT_SYMLINK_NOFOLLOW,
1804+ .flags = FSINFO_FLAGS_QUERY_PATH,
1805+ };
1806+ unsigned int *attrs, ret, nr, i;
1807+ bool meta = false;
1808+ int raw = 0, opt, Nth, Mth;
1809+
1810+ while ((opt = getopt(argc, argv, "Madlr"))) {
1811+ switch (opt) {
1812+ case 'M':
1813+ meta = true;
1814+ continue;
1815+ case 'a':
1816+ params.at_flags |= AT_NO_AUTOMOUNT;
1817+ params.flags = FSINFO_FLAGS_QUERY_PATH;
1818+ continue;
1819+ case 'd':
1820+ debug = true;
1821+ continue;
1822+ case 'l':
1823+ params.at_flags &= ~AT_SYMLINK_NOFOLLOW;
1824+ params.flags = FSINFO_FLAGS_QUERY_PATH;
1825+ continue;
1826+ case 'r':
1827+ raw = 1;
1828+ continue;
1829+ }
1830+ break;
1831+ }
1832+
1833+ argc -= optind;
1834+ argv += optind;
1835+
1836+ if (argc != 1) {
1837+ printf("Format: test-fsinfo [-Madlr] <path>\n");
1838+ exit(2);
1839+ }
1840+
1841+ /* Retrieve a list of supported attribute IDs */
1842+ params.request = FSINFO_ATTR_FSINFO_ATTRIBUTES;
1843+ params.Nth = 0;
1844+ params.Mth = 0;
1845+ ret = get_fsinfo(argv[0], "attributes", &params, (void **)&attrs);
1846+ if (ret == -1) {
1847+ fprintf(stderr, "Unable to get attribute list: %m\n");
1848+ exit(1);
1849+ }
1850+
1851+ if (ret % sizeof(attrs[0])) {
1852+ fprintf(stderr, "Bad length of attribute list (0x%x)\n", ret);
1853+ exit(2);
1854+ }
1855+
1856+ nr = ret / sizeof(attrs[0]);
1857+ qsort(attrs, nr, sizeof(attrs[0]), cmp_u32);
1858+
1859+ if (meta) {
1860+ printf("ATTR ID TYPE FLAGS SIZE NAME\n");
1861+ printf("======== ============ ======== ===== =========\n");
1862+ for (i = 0; i < nr; i++) {
1863+ params.request = FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO;
1864+ params.Nth = attrs[i];
1865+ params.Mth = 0;
1866+ ret = fsinfo(AT_FDCWD, argv[0],
1867+ &params, sizeof(params),
1868+ &attr_info, sizeof(attr_info));
1869+ if (ret == -1) {
1870+ fprintf(stderr, "Can't get info for attribute %x: %m\n", attrs[i]);
1871+ exit(1);
1872+ }
1873+
1874+ dump_attribute_info(&attr_info, ret);
1875+ }
1876+ exit(0);
1877+ }
1878+
1879+ for (i = 0; i < nr; i++) {
1880+ params.request = FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO;
1881+ params.Nth = attrs[i];
1882+ params.Mth = 0;
1883+ ret = fsinfo(AT_FDCWD, argv[0],
1884+ &params, sizeof(params),
1885+ &attr_info, sizeof(attr_info));
1886+ if (ret == -1) {
1887+ fprintf(stderr, "Can't get info for attribute %x: %m\n", attrs[i]);
1888+ exit(1);
1889+ }
1890+
1891+ if (attrs[i] == FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO ||
1892+ attrs[i] == FSINFO_ATTR_FSINFO_ATTRIBUTES)
1893+ continue;
1894+
1895+ if (attrs[i] != attr_info.attr_id) {
1896+ fprintf(stderr, "ID for %03x returned %03x\n",
1897+ attrs[i], attr_info.attr_id);
1898+ break;
1899+ }
1900+ Nth = 0;
1901+ do {
1902+ Mth = 0;
1903+ do {
1904+ params.request = attrs[i];
1905+ params.Nth = Nth;
1906+ params.Mth = Mth;
1907+
1908+ switch (try_one(argv[0], &params, &attr_info, raw)) {
1909+ case 0:
1910+ continue;
1911+ case 1:
1912+ goto done_M;
1913+ case 2:
1914+ goto done_N;
1915+ }
1916+ } while (++Mth < 100);
1917+
1918+ done_M:
1919+ if (Mth >= 100) {
1920+ fprintf(stderr, "Fishy: Mth %x[%u][%u]\n", attrs[i], Nth, Mth);
1921+ break;
1922+ }
1923+
1924+ } while (++Nth < 100);
1925+
1926+ done_N:
1927+ if (Nth >= 100) {
1928+ fprintf(stderr, "Fishy: Nth %x[%u]\n", attrs[i], Nth);
1929+ break;
1930+ }
1931+ }
1932+
1933+ return 0;
1934+}
1935--
19362.20.1
1937