blob: 18b533b79ceab532c24e73419759a7f5cd131a6b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/* Filesystem information query.
*
* Copyright (C) 2020 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (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"
/**
* fsinfo_opaque - Store opaque blob as an fsinfo attribute value.
* @s: The blob to store (may be NULL)
* @ctx: The parameter context
* @len: The length of the blob
*/
int fsinfo_opaque(const void *s, struct fsinfo_context *ctx, unsigned int len)
{
void *p = ctx->buffer;
int ret = 0;
if (s) {
if (!ctx->want_size_only)
memcpy(p, s, len);
ret = len;
}
return ret;
}
EXPORT_SYMBOL(fsinfo_opaque);
/**
* fsinfo_string - Store a NUL-terminated 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)
{
if (!s)
return 1;
return fsinfo_opaque(s, ctx, min_t(size_t, strlen(s) + 1, ctx->buf_size));
}
EXPORT_SYMBOL(fsinfo_string);
/*
* Get basic filesystem stats from statfs.
*/
static int fsinfo_generic_statfs(struct path *path, struct fsinfo_context *ctx)
{
struct fsinfo_statfs *p = ctx->buffer;
struct kstatfs buf;
int ret;
ret = vfs_statfs(path, &buf);
if (ret < 0)
return ret;
p->f_blocks.lo = buf.f_blocks;
p->f_bfree.lo = buf.f_bfree;
p->f_bavail.lo = buf.f_bavail;
p->f_files.lo = buf.f_files;
p->f_ffree.lo = buf.f_ffree;
p->f_favail.lo = buf.f_ffree;
p->f_bsize = buf.f_bsize;
p->f_frsize = buf.f_frsize;
return sizeof(*p);
}
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;
if (ret == 0)
memcpy(&p->f_fsid, &buf.f_fsid, sizeof(p->f_fsid));
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);
p->f_sb_id = sb->s_unique_id;
strlcpy(p->f_fs_name, sb->s_type->name, sizeof(p->f_fs_name));
return sizeof(*p);
}
int fsinfo_generic_limits(struct path *path, struct fsinfo_context *ctx)
{
struct fsinfo_limits *p = ctx->buffer;
struct super_block *sb = path->dentry->d_sb;
p->max_file_size.hi = 0;
p->max_file_size.lo = sb->s_maxbytes;
p->max_ino.hi = 0;
p->max_ino.lo = UINT_MAX;
p->max_hard_links = sb->s_max_links;
p->max_uid = UINT_MAX;
p->max_gid = UINT_MAX;
p->max_projid = UINT_MAX;
p->max_filename_len = NAME_MAX;
p->max_symlink_len = PATH_MAX;
p->max_xattr_name_len = XATTR_NAME_MAX;
p->max_xattr_body_len = XATTR_SIZE_MAX;
p->max_dev_major = 0xffffff;
p->max_dev_minor = 0xff;
return sizeof(*p);
}
EXPORT_SYMBOL(fsinfo_generic_limits);
int fsinfo_generic_supports(struct path *path, struct fsinfo_context *ctx)
{
struct fsinfo_supports *p = ctx->buffer;
struct super_block *sb = path->dentry->d_sb;
p->stx_mask = STATX_BASIC_STATS;
if (sb->s_d_op && sb->s_d_op->d_automount)
p->stx_attributes |= STATX_ATTR_AUTOMOUNT;
return sizeof(*p);
}
EXPORT_SYMBOL(fsinfo_generic_supports);
int fsinfo_generic_features(struct path *path, struct fsinfo_context *ctx)
{
struct fsinfo_features *p = ctx->buffer;
struct super_block *sb = path->dentry->d_sb;
fsinfo_init_features(p);
if (sb->s_mtd)
fsinfo_set_feature(p, FSINFO_FEAT_IS_FLASH_FS);
else if (sb->s_bdev)
fsinfo_set_feature(p, FSINFO_FEAT_IS_BLOCK_FS);
if (sb->s_quota_types & QTYPE_MASK_USR)
fsinfo_set_feature(p, FSINFO_FEAT_USER_QUOTAS);
if (sb->s_quota_types & QTYPE_MASK_GRP)
fsinfo_set_feature(p, FSINFO_FEAT_GROUP_QUOTAS);
if (sb->s_quota_types & QTYPE_MASK_PRJ)
fsinfo_set_feature(p, FSINFO_FEAT_PROJECT_QUOTAS);
if (sb->s_d_op && sb->s_d_op->d_automount)
fsinfo_set_feature(p, FSINFO_FEAT_AUTOMOUNTS);
if (sb->s_id[0])
fsinfo_set_feature(p, FSINFO_FEAT_VOLUME_ID);
if (sb->s_flags & SB_MANDLOCK)
fsinfo_set_feature(p, FSINFO_FEAT_MAND_LOCKS);
if (sb->s_flags & SB_POSIXACL)
fsinfo_set_feature(p, FSINFO_FEAT_HAS_ACL);
fsinfo_set_feature(p, FSINFO_FEAT_HAS_ATIME);
fsinfo_set_feature(p, FSINFO_FEAT_HAS_CTIME);
fsinfo_set_feature(p, FSINFO_FEAT_HAS_MTIME);
return sizeof(*p);
}
EXPORT_SYMBOL(fsinfo_generic_features);
static const struct fsinfo_timestamp_info fsinfo_default_timestamp_info = {
.atime = {
.minimum = S64_MIN,
.maximum = S64_MAX,
.gran_mantissa = 1,
.gran_exponent = 0,
},
.mtime = {
.minimum = S64_MIN,
.maximum = S64_MAX,
.gran_mantissa = 1,
.gran_exponent = 0,
},
.ctime = {
.minimum = S64_MIN,
.maximum = S64_MAX,
.gran_mantissa = 1,
.gran_exponent = 0,
},
.btime = {
.minimum = S64_MIN,
.maximum = S64_MAX,
.gran_mantissa = 1,
.gran_exponent = 0,
},
};
int fsinfo_generic_timestamp_info(struct path *path, struct fsinfo_context *ctx)
{
struct fsinfo_timestamp_info *p = ctx->buffer;
struct super_block *sb = path->dentry->d_sb;
s8 exponent;
*p = fsinfo_default_timestamp_info;
if (sb->s_time_gran < 1000000000) {
if (sb->s_time_gran < 1000)
exponent = -9;
else if (sb->s_time_gran < 1000000)
exponent = -6;
else
exponent = -3;
p->atime.gran_exponent = exponent;
p->mtime.gran_exponent = exponent;
p->ctime.gran_exponent = exponent;
p->btime.gran_exponent = exponent;
}
return sizeof(*p);
}
EXPORT_SYMBOL(fsinfo_generic_timestamp_info);
static int fsinfo_generic_volume_uuid(struct path *path, struct fsinfo_context *ctx)
{
struct fsinfo_volume_uuid *p = ctx->buffer;
struct super_block *sb = path->dentry->d_sb;
memcpy(p, &sb->s_uuid, sizeof(*p));
return sizeof(*p);
}
static int fsinfo_generic_volume_id(struct path *path, struct fsinfo_context *ctx)
{
return fsinfo_string(path->dentry->d_sb->s_id, ctx);
}
/*
* Retrieve the superblock configuration (mount options) as a comma-separated
* string. The initial comma is stripped off and NUL termination is added.
*/
static int fsinfo_generic_seq_read(struct path *path, struct fsinfo_context *ctx)
{
struct super_block *sb = path->dentry->d_sb;
struct seq_file m = {
.buf = ctx->buffer,
.size = ctx->buf_size - 1,
};
int ret = 0;
switch (ctx->requested_attr) {
case FSINFO_ATTR_CONFIGURATION:
seq_puts(&m, sb_rdonly(sb) ? "ro" : "rw");
ret = security_sb_show_options(&m, sb);
if (!ret && sb->s_op->show_options)
ret = sb->s_op->show_options(&m, path->mnt->mnt_root);
break;
case FSINFO_ATTR_MOUNT_PATH:
if (sb->s_op->show_path)
ret = sb->s_op->show_path(&m, path->mnt->mnt_root);
else
seq_dentry(&m, path->mnt->mnt_root, " \t\n\\");
break;
case FSINFO_ATTR_FS_STATISTICS:
if (sb->s_op->show_stats)
ret = sb->s_op->show_stats(&m, path->mnt->mnt_root);
break;
}
if (ret < 0)
return ret;
if (seq_has_overflowed(&m))
return ctx->buf_size + PAGE_SIZE;
((char *)ctx->buffer)[ctx->skip + m.count] = 0;
return m.count + 1;
}
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 (FSINFO_ATTR_FEATURES, fsinfo_generic_features),
FSINFO_STRING (FSINFO_ATTR_SOURCE, fsinfo_generic_mount_source),
FSINFO_STRING (FSINFO_ATTR_CONFIGURATION, fsinfo_generic_seq_read),
FSINFO_STRING (FSINFO_ATTR_FS_STATISTICS, fsinfo_generic_seq_read),
FSINFO_LIST (FSINFO_ATTR_FSINFO_ATTRIBUTES, (void *)123UL),
FSINFO_VSTRUCT_N(FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO, (void *)123UL),
FSINFO_VSTRUCT (FSINFO_ATTR_MOUNT_INFO, fsinfo_generic_mount_info),
FSINFO_VSTRUCT (FSINFO_ATTR_MOUNT_TOPOLOGY, fsinfo_generic_mount_topology),
FSINFO_STRING (FSINFO_ATTR_MOUNT_PATH, fsinfo_generic_seq_read),
FSINFO_STRING (FSINFO_ATTR_MOUNT_POINT, fsinfo_generic_mount_point),
FSINFO_STRING (FSINFO_ATTR_MOUNT_POINT_FULL, fsinfo_generic_mount_point_full),
FSINFO_LIST (FSINFO_ATTR_MOUNT_CHILDREN, fsinfo_generic_mount_children),
FSINFO_LIST (FSINFO_ATTR_MOUNT_ALL, fsinfo_generic_mount_all),
{}
};
/*
* Determine an attribute's minimum buffer size and, if the buffer is large
* enough, get the attribute value.
*/
static int fsinfo_get_this_attribute(struct path *path,
struct fsinfo_context *ctx,
const struct fsinfo_attribute *attr)
{
int buf_size;
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;
switch (attr->type) {
case FSINFO_TYPE_VSTRUCT:
ctx->clear_tail = true;
buf_size = attr->size;
break;
case FSINFO_TYPE_STRING:
case FSINFO_TYPE_OPAQUE:
case FSINFO_TYPE_LIST:
buf_size = 4096;
break;
default:
return -ENOPKG;
}
if (ctx->buf_size < buf_size)
return buf_size;
return attr->get(path, ctx);
}
static void fsinfo_attributes_insert(struct fsinfo_context *ctx,
const struct fsinfo_attribute *attr)
{
__u32 *p = ctx->buffer;
unsigned int i;
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 (p[i] == attr->attr_id)
return;
p[i] = attr->attr_id;
ctx->usage += sizeof(__u32);
}
static int fsinfo_list_attributes(struct path *path,
struct fsinfo_context *ctx,
const struct fsinfo_attribute *attributes)
{
const struct fsinfo_attribute *a;
for (a = attributes; a->get; a++)
fsinfo_attributes_insert(ctx, a);
return -EOPNOTSUPP; /* We want to go through all the lists */
}
static int fsinfo_get_attribute_info(struct path *path,
struct fsinfo_context *ctx,
const struct fsinfo_attribute *attributes)
{
const struct fsinfo_attribute *a;
struct fsinfo_attribute_info *p = ctx->buffer;
if (!ctx->buf_size)
return sizeof(*p);
for (a = attributes; a->get; a++) {
if (a->attr_id == ctx->Nth) {
p->attr_id = a->attr_id;
p->type = a->type;
p->flags = a->flags;
p->size = a->size;
p->size = a->size;
return sizeof(*p);
}
}
return -EOPNOTSUPP; /* We want to go through all the lists */
}
/**
* fsinfo_get_attribute - Look up and handle an attribute
* @path: The object to query
* @params: Parameters to define a request and place to store result
* @attributes: List of attributes to search.
*
* Look through a list of attributes for one that matches the requested
* attribute then call the handler for it.
*/
int fsinfo_get_attribute(struct path *path, struct fsinfo_context *ctx,
const struct fsinfo_attribute *attributes)
{
const struct fsinfo_attribute *a;
switch (ctx->requested_attr) {
case FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO:
return fsinfo_get_attribute_info(path, ctx, attributes);
case FSINFO_ATTR_FSINFO_ATTRIBUTES:
return fsinfo_list_attributes(path, ctx, attributes);
default:
for (a = attributes; a->get; a++)
if (a->attr_id == ctx->requested_attr)
return fsinfo_get_this_attribute(path, ctx, a);
return -EOPNOTSUPP;
}
}
EXPORT_SYMBOL(fsinfo_get_attribute);
/**
* generic_fsinfo - Handle an fsinfo attribute generically
* @path: The object to query
* @params: Parameters to define a request and place to store result
*/
static int fsinfo_call(struct path *path, struct fsinfo_context *ctx)
{
int ret;
if (path->dentry->d_sb->s_op->fsinfo) {
ret = path->dentry->d_sb->s_op->fsinfo(path, ctx);
if (ret != -EOPNOTSUPP)
return ret;
}
ret = fsinfo_get_attribute(path, ctx, fsinfo_common_attributes);
if (ret != -EOPNOTSUPP)
return ret;
switch (ctx->requested_attr) {
case FSINFO_ATTR_FSINFO_ATTRIBUTE_INFO:
return -ENODATA;
case FSINFO_ATTR_FSINFO_ATTRIBUTES:
return ctx->usage;
default:
return -EOPNOTSUPP;
}
}
/**
* 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)
{
struct dentry *dentry = path->dentry;
int ret;
ret = security_sb_statfs(dentry);
if (ret)
return ret;
/* Call the handler to find out the buffer size required. */
ctx->buf_size = 0;
ret = fsinfo_call(path, ctx);
if (ret < 0 || ctx->want_size_only)
return ret;
ctx->buf_size = ret;
do {
/* Allocate a buffer of the requested size. */
if (ctx->buf_size > INT_MAX)
return -EMSGSIZE;
ctx->buffer = kvzalloc(ctx->buf_size, GFP_KERNEL);
if (!ctx->buffer)
return -ENOMEM;
ctx->usage = 0;
ctx->skip = 0;
ret = fsinfo_call(path, ctx);
if (IS_ERR_VALUE((long)ret))
return ret;
if ((unsigned int)ret <= ctx->buf_size)
return ret; /* It fitted */
/* We need to resize the buffer */
ctx->buf_size = roundup(ret, PAGE_SIZE);
kvfree(ctx->buffer);
ctx->buffer = NULL;
} while (!signal_pending(current));
return -ERESTARTSYS;
}
static int vfs_fsinfo_path(int dfd, const char __user *pathname,
const struct fsinfo_params *up,
struct fsinfo_context *ctx)
{
struct path path;
unsigned lookup_flags = LOOKUP_FOLLOW | LOOKUP_AUTOMOUNT;
int ret = -EINVAL;
if (up->resolve_flags & ~VALID_RESOLVE_FLAGS)
return -EINVAL;
if (up->at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT |
AT_EMPTY_PATH))
return -EINVAL;
if (up->resolve_flags & RESOLVE_NO_XDEV)
lookup_flags |= LOOKUP_NO_XDEV;
if (up->resolve_flags & RESOLVE_NO_MAGICLINKS)
lookup_flags |= LOOKUP_NO_MAGICLINKS;
if (up->resolve_flags & RESOLVE_NO_SYMLINKS)
lookup_flags |= LOOKUP_NO_SYMLINKS;
if (up->resolve_flags & RESOLVE_BENEATH)
lookup_flags |= LOOKUP_BENEATH;
if (up->resolve_flags & RESOLVE_IN_ROOT)
lookup_flags |= LOOKUP_IN_ROOT;
if (up->at_flags & AT_SYMLINK_NOFOLLOW)
lookup_flags &= ~LOOKUP_FOLLOW;
if (up->at_flags & AT_NO_AUTOMOUNT)
lookup_flags &= ~LOOKUP_AUTOMOUNT;
if (up->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;
}
/*
* Look up the root of a mount object. This allows access to mount objects
* (and their attached superblocks) that can't be retrieved by path because
* they're entirely covered.
*
* We only permit access to a mount that has a direct path between either the
* dentry pointed to by dfd or to our chroot (if dfd is AT_FDCWD).
*/
static int vfs_fsinfo_mount(int dfd, const char __user *filename,
struct fsinfo_context *ctx)
{
struct path path;
struct fd f = {};
char *name;
long mnt_id;
int ret;
if (!filename)
return -EINVAL;
name = strndup_user(filename, 32);
if (IS_ERR(name))
return PTR_ERR(name);
ret = kstrtoul(name, 0, &mnt_id);
if (ret < 0)
goto out_name;
if (mnt_id > INT_MAX)
goto out_name;
if (dfd != AT_FDCWD) {
ret = -EBADF;
f = fdget_raw(dfd);
if (!f.file)
goto out_name;
}
ret = lookup_mount_object(f.file ? &f.file->f_path : NULL,
mnt_id, &path);
if (ret < 0)
goto out_fd;
ret = vfs_fsinfo(&path, ctx);
path_put(&path);
out_fd:
fdput(f);
out_name:
kfree(name);
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 (NULL: FSINFO_ATTR_STATFS).
* @params_size: Size of parameter buffer.
* @result_buffer: Result buffer.
* @result_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
* @result_buf_size is 0 or @result_buffer is NULL, only the size is returned.
* If the size of the value is larger than @result_buf_size, it will be
* truncated by the copy. If the size of the value is smaller than
* @result_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_DEFINE6(fsinfo,
int, dfd,
const char __user *, pathname,
const struct fsinfo_params __user *, params,
size_t, params_size,
void __user *, result_buffer,
size_t, result_buf_size)
{
struct fsinfo_context ctx;
struct fsinfo_params user_params;
unsigned int result_size;
void *r;
int ret;
if ((!params && params_size) ||
( params && !params_size) ||
(!result_buffer && result_buf_size) ||
( result_buffer && !result_buf_size))
return -EINVAL;
if (result_buf_size > UINT_MAX)
return -EOVERFLOW;
memset(&ctx, 0, sizeof(ctx));
ctx.requested_attr = FSINFO_ATTR_STATFS;
ctx.flags = FSINFO_FLAGS_QUERY_PATH;
ctx.want_size_only = (result_buf_size == 0);
if (params) {
ret = copy_struct_from_user(&user_params, sizeof(user_params),
params, params_size);
if (ret < 0)
return ret;
if (user_params.flags & ~FSINFO_FLAGS_QUERY_MASK)
return -EINVAL;
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_MASK) {
case FSINFO_FLAGS_QUERY_PATH:
ret = vfs_fsinfo_path(dfd, pathname, &user_params, &ctx);
break;
case FSINFO_FLAGS_QUERY_FD:
if (pathname)
return -EINVAL;
ret = vfs_fsinfo_fd(dfd, &ctx);
break;
case FSINFO_FLAGS_QUERY_MOUNT:
ret = vfs_fsinfo_mount(dfd, pathname, &ctx);
break;
default:
return -EINVAL;
}
if (ret < 0)
goto error;
r = ctx.buffer + ctx.skip;
result_size = min_t(size_t, ret, result_buf_size);
if (result_size > 0 &&
copy_to_user(result_buffer, r, 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 &&
result_buf_size > result_size &&
clear_user(result_buffer + result_size,
result_buf_size - result_size) != 0) {
ret = -EFAULT;
goto error;
}
error:
kvfree(ctx.buffer);
return ret;
}