Allow fsinfo() to retrieve information about a superblock, including the values configured by the parameters passed at superblock creation. Signed-off-by: David Howells <dhowells@xxxxxxxxxx> --- fs/nfs/fs_context.c | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++ fs/nfs/internal.h | 6 ++ fs/nfs/nfs4super.c | 3 + fs/nfs/super.c | 77 ++++++++++++++++++++++++ 4 files changed, 248 insertions(+), 1 deletion(-) diff --git a/fs/nfs/fs_context.c b/fs/nfs/fs_context.c index d05271b91e38..f550b0e54833 100644 --- a/fs/nfs/fs_context.c +++ b/fs/nfs/fs_context.c @@ -17,6 +17,8 @@ #include <linux/fs.h> #include <linux/fs_context.h> #include <linux/fs_parser.h> +#include <linux/fsinfo.h> +#include <linux/mount.h> #include <linux/nfs_fs.h> #include <linux/nfs_mount.h> #include <linux/nfs4_mount.h> @@ -1407,3 +1409,164 @@ MODULE_ALIAS_FS("nfs4"); MODULE_ALIAS("nfs4"); EXPORT_SYMBOL_GPL(nfs4_fs_type); #endif /* CONFIG_NFS_V4 */ + +#ifdef CONFIG_FSINFO +/* + * Allow the filesystem parameters to be queried. + */ +int nfs_fsinfo_parameters(struct fsinfo_kparams *params, struct path *path, + const struct nfs_server *server) +{ + const struct nfs_client *client = server->nfs_client; + const struct sockaddr *sap = (const struct sockaddr *)&server->mountd_address; + unsigned int version = client->rpc_ops->version; + unsigned int sf = server->flags; + const char *b; + char *e; + int i; + + static const struct proc_nfs_info { + int flag; + const char *str; + const char *nostr; + } nfs_info[] = { + { NFS_MOUNT_SOFT, "soft", "hard" }, + { NFS_MOUNT_POSIX, "posix", "" }, + { NFS_MOUNT_NOCTO, "nocto", "" }, + { NFS_MOUNT_NOAC, "noac", "" }, + { NFS_MOUNT_NONLM, "nolock", "" }, + { NFS_MOUNT_NOACL, "noacl", "" }, + { NFS_MOUNT_NORDIRPLUS, "nordirplus", "" }, + { NFS_MOUNT_UNSHARED, "nosharecache", "" }, + { NFS_MOUNT_NORESVPORT, "noresvport", "" }, + }; + + rcu_read_lock(); + + b = params->scratch_buffer; + b = nfs_path(&e, path->mnt->mnt_root, params->scratch_buffer, params->buf_size, 0); + if (b < e) + fsinfo_note_param(params, "source", b); + + if (version == 4) + fsinfo_note_paramf(params, "vers", "4.%u", client->cl_minorversion); + else + fsinfo_note_paramf(params, "vers", "%u", version); + + fsinfo_note_paramf(params, "rsize", "%u", server->rsize); + fsinfo_note_paramf(params, "wsize", "%u", server->wsize); + if (server->bsize) + fsinfo_note_paramf(params, "bsize", "%u", server->bsize); + fsinfo_note_paramf(params, "namlen", "%u", server->namelen); + + if (server->acregmin != NFS_DEF_ACREGMIN*HZ) + fsinfo_note_paramf(params, "acregmin", "%u", server->acregmin/HZ); + if (server->acregmax != NFS_DEF_ACREGMAX*HZ) + fsinfo_note_paramf(params, "acregmin", "%u", server->acregmax/HZ); + if (server->acdirmin != NFS_DEF_ACDIRMIN*HZ) + fsinfo_note_paramf(params, "acdirmin", "%u", server->acdirmin/HZ); + if (server->acdirmax != NFS_DEF_ACDIRMAX*HZ) + fsinfo_note_paramf(params, "acdirmin", "%u", server->acdirmax/HZ); + + for (i = 0; i < ARRAY_SIZE(nfs_info); i++) { + if (sf & nfs_info[i].flag) + b = nfs_info[i].str; + else + b = nfs_info[i].nostr; + if (b[0]) + fsinfo_note_param(params, b, NULL); + } + + fsinfo_note_param(params, "proto", + rpc_peeraddr2str(server->client, RPC_DISPLAY_NETID)); + if (version != 4 || server->port != NFS_PORT) + fsinfo_note_paramf(params, "port", "%u", server->port); + + fsinfo_note_paramf(params, "timeo", "%lu", + 10U * server->client->cl_timeout->to_initval / HZ); + fsinfo_note_paramf(params, "retrans", "%u", + server->client->cl_timeout->to_retries); + fsinfo_note_param(params, "sec", + nfs_pseudoflavour_to_name(server->client->cl_auth->au_flavor)); + + if (server->options & NFS_OPTION_FSCACHE) + fsinfo_note_param(params, "fsc", NULL); + if (server->options & NFS_OPTION_MIGRATION) + fsinfo_note_param(params, "migration", NULL); + + if (server->flags & NFS_MOUNT_LOOKUP_CACHE_NONEG) { + if (server->flags & NFS_MOUNT_LOOKUP_CACHE_NONE) + fsinfo_note_param(params, "lookupcache", "none"); + else + fsinfo_note_param(params, "lookupcache", "pos"); + } + + switch (server->flags & (NFS_MOUNT_LOCAL_FLOCK | NFS_MOUNT_LOCAL_FCNTL)) { + case 0: b = "none"; break; + case NFS_MOUNT_LOCAL_FLOCK: b = "flock"; break; + case NFS_MOUNT_LOCAL_FCNTL: b = "posix"; break; + default: b = "all"; break; + } + fsinfo_note_param(params, "local_lock", b); + + if (version == 4) + fsinfo_note_param(params, "clientaddr", client->cl_ipaddr); + + if (version != 4 && !(server->flags & NFS_MOUNT_LEGACY_INTERFACE)) { + switch (sap->sa_family) { + case AF_INET: { + struct sockaddr_in *sin = (struct sockaddr_in *)sap; + fsinfo_note_paramf(params, "mountaddr", "%pI4", + &sin->sin_addr.s_addr); + break; + } + case AF_INET6: { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; + fsinfo_note_paramf(params, "mountaddr", "%pI6c", + &sin6->sin6_addr); + break; + } + } + + if (server->mountd_port && + server->mountd_port != (unsigned short)NFS_UNSPEC_PORT) + fsinfo_note_paramf(params, "mountport", "%u", server->mountd_port); + + switch (sap->sa_family) { + case AF_INET: + switch (server->mountd_protocol) { + case IPPROTO_UDP: + b = RPCBIND_NETID_UDP; + break; + case IPPROTO_TCP: + b = RPCBIND_NETID_TCP; + break; + } + break; + case AF_INET6: + switch (server->mountd_protocol) { + case IPPROTO_UDP: + b = RPCBIND_NETID_UDP6; + break; + case IPPROTO_TCP: + b = RPCBIND_NETID_TCP6; + break; + } + break; + } + + if (b) + fsinfo_note_param(params, "mountproto", b); + + if (server->mountd_version) + fsinfo_note_paramf(params, "mountvers", "%u", + server->mountd_version); + } + + fsinfo_note_param(params, "addr", + rpc_peeraddr2str(server->nfs_client->cl_rpcclient, RPC_DISPLAY_ADDR)); + + rcu_read_unlock(); + return params->usage; +} +#endif /* CONFIG_FSINFO */ diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h index da088f5611f0..c218e715881d 100644 --- a/fs/nfs/internal.h +++ b/fs/nfs/internal.h @@ -244,6 +244,10 @@ extern const struct svc_version nfs4_callback_version4; /* fs_context.c */ extern struct file_system_type nfs_fs_type; +#ifdef CONFIG_FSINFO +extern int nfs_fsinfo_parameters(struct fsinfo_kparams *params, struct path *path, + const struct nfs_server *server); +#endif /* pagelist.c */ extern int __init nfs_init_nfspagecache(void); @@ -408,6 +412,7 @@ bool nfs_auth_info_match(const struct nfs_auth_info *, rpc_authflavor_t); int nfs_try_get_tree(struct fs_context *); int nfs_get_tree_common(struct fs_context *); void nfs_kill_super(struct super_block *); +const char *nfs_pseudoflavour_to_name(rpc_authflavor_t); extern struct rpc_stat nfs_rpcstat; @@ -455,6 +460,7 @@ extern void nfs_pageio_reset_read_mds(struct nfs_pageio_descriptor *pgio); /* super.c */ void nfs_umount_begin(struct super_block *); int nfs_statfs(struct dentry *, struct kstatfs *); +int nfs_fsinfo(struct path *, struct fsinfo_kparams *); int nfs_show_options(struct seq_file *, struct dentry *); int nfs_show_devname(struct seq_file *, struct dentry *); int nfs_show_path(struct seq_file *, struct dentry *); diff --git a/fs/nfs/nfs4super.c b/fs/nfs/nfs4super.c index 0240429ec596..22d8f2842ac1 100644 --- a/fs/nfs/nfs4super.c +++ b/fs/nfs/nfs4super.c @@ -31,6 +31,9 @@ static const struct super_operations nfs4_sops = { .show_devname = nfs_show_devname, .show_path = nfs_show_path, .show_stats = nfs_show_stats, +#ifdef CONFIG_FSINFO + .fsinfo = nfs_fsinfo, +#endif }; struct nfs_subversion nfs_v4 = { diff --git a/fs/nfs/super.c b/fs/nfs/super.c index c455ebeeadc9..dde6c59d0210 100644 --- a/fs/nfs/super.c +++ b/fs/nfs/super.c @@ -54,6 +54,7 @@ #include <linux/parser.h> #include <linux/nsproxy.h> #include <linux/rcupdate.h> +#include <linux/fsinfo.h> #include <linux/uaccess.h> @@ -81,6 +82,9 @@ const struct super_operations nfs_sops = { .show_devname = nfs_show_devname, .show_path = nfs_show_path, .show_stats = nfs_show_stats, +#ifdef CONFIG_FSINFO + .fsinfo = nfs_fsinfo, +#endif }; EXPORT_SYMBOL_GPL(nfs_sops); @@ -241,10 +245,81 @@ int nfs_statfs(struct dentry *dentry, struct kstatfs *buf) } EXPORT_SYMBOL_GPL(nfs_statfs); +#ifdef CONFIG_FSINFO +/* + * Get filesystem information. + */ +int nfs_fsinfo(struct path *path, struct fsinfo_kparams *params) +{ + struct fsinfo_server_address *addr; + struct fsinfo_capabilities *caps; + struct nfs_server *server = NFS_SB(path->dentry->d_sb); + struct nfs_client *client = server->nfs_client; + struct rpc_clnt *clnt; + struct rpc_xprt *xprt; + const char *str; + unsigned int version = client->rpc_ops->version; + + switch (params->request) { + case FSINFO_ATTR_CAPABILITIES: + caps = params->buffer; + fsinfo_set_cap(caps, FSINFO_CAP_IS_NETWORK_FS); + fsinfo_set_cap(caps, FSINFO_CAP_AUTOMOUNTS); + fsinfo_set_cap(caps, FSINFO_CAP_ADV_LOCKS); + fsinfo_set_cap(caps, FSINFO_CAP_UIDS); + fsinfo_set_cap(caps, FSINFO_CAP_GIDS); + fsinfo_set_cap(caps, FSINFO_CAP_O_SYNC); + fsinfo_set_cap(caps, FSINFO_CAP_O_DIRECT); + fsinfo_set_cap(caps, FSINFO_CAP_SYMLINKS); + fsinfo_set_cap(caps, FSINFO_CAP_HARD_LINKS); + fsinfo_set_cap(caps, FSINFO_CAP_DEVICE_FILES); + fsinfo_set_cap(caps, FSINFO_CAP_UNIX_SPECIALS); + fsinfo_set_cap(caps, FSINFO_CAP_HAS_ATIME); + fsinfo_set_cap(caps, FSINFO_CAP_HAS_CTIME); + fsinfo_set_cap(caps, FSINFO_CAP_HAS_MTIME); + if (version == 4) { + fsinfo_set_cap(caps, FSINFO_CAP_LEASES); + fsinfo_set_cap(caps, FSINFO_CAP_IVER_ALL_CHANGE); + } + return sizeof(*caps); + + case FSINFO_ATTR_SERVER_NAME: + if (params->Nth || params->Mth) + return -ENODATA; + str = client->cl_hostname; + goto string; + + case FSINFO_ATTR_SERVER_ADDRESS: + if (params->Nth || params->Mth) + return -ENODATA; + addr = params->buffer; + clnt = client->cl_rpcclient; + rcu_read_lock(); + xprt = rcu_dereference(clnt->cl_xprt); + memcpy(&addr->address, &xprt->addr, xprt->addrlen); + rcu_read_unlock(); + return sizeof(*addr); + + case FSINFO_ATTR_PARAMETERS: + return nfs_fsinfo_parameters(params, path, server); + + default: + return generic_fsinfo(path, params); + } + +string: + if (!str) + return 0; + strcpy(params->buffer, str); + return strlen(params->buffer); +} +EXPORT_SYMBOL_GPL(nfs_fsinfo); +#endif /* CONFIG_FSINFO */ + /* * Map the security flavour number to a name */ -static const char *nfs_pseudoflavour_to_name(rpc_authflavor_t flavour) +const char *nfs_pseudoflavour_to_name(rpc_authflavor_t flavour) { static const struct { rpc_authflavor_t flavour;