Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+197
View File
@@ -0,0 +1,197 @@
# SPDX-License-Identifier: GPL-2.0-only
config NFSD
tristate "NFS server support"
depends on INET
depends on FILE_LOCKING
depends on FSNOTIFY
select CRC32
select CRYPTO_LIB_MD5 if NFSD_LEGACY_CLIENT_TRACKING
select CRYPTO_LIB_SHA256 if NFSD_V4
select CRYPTO # required by RPCSEC_GSS_KRB5 and signed filehandles
select LOCKD
select SUNRPC
select EXPORTFS
select NFS_COMMON
select NFS_ACL_SUPPORT if NFSD_V2_ACL
select NFS_ACL_SUPPORT if NFSD_V3_ACL
depends on MULTIUSER
help
Choose Y here if you want to allow other computers to access
files residing on this system using Sun's Network File System
protocol. To compile the NFS server support as a module,
choose M here: the module will be called nfsd.
You may choose to use a user-space NFS server instead, in which
case you can choose N here.
To export local file systems using NFS, you also need to install
user space programs which can be found in the Linux nfs-utils
package, available from http://linux-nfs.org/. More detail about
the Linux NFS server implementation is available via the
exports(5) man page.
Below you can choose which versions of the NFS protocol are
available to clients mounting the NFS server on this system.
Support for NFS version 3 (RFC 1813) is always available when
CONFIG_NFSD is selected.
If unsure, say N.
config NFSD_V2
bool "NFS server support for NFS version 2 (DEPRECATED)"
depends on NFSD
default n
help
NFSv2 (RFC 1094) was the first publicly-released version of NFS.
Unless you are hosting ancient (1990's era) NFS clients, you don't
need this.
If unsure, say N.
config NFSD_V2_ACL
bool "NFS server support for the NFSv2 ACL protocol extension"
depends on NFSD_V2
config NFSD_V3_ACL
bool "NFS server support for the NFSv3 ACL protocol extension"
depends on NFSD
help
Solaris NFS servers support an auxiliary NFSv3 ACL protocol that
never became an official part of the NFS version 3 protocol.
This protocol extension allows applications on NFS clients to
manipulate POSIX Access Control Lists on files residing on NFS
servers. NFS servers enforce POSIX ACLs on local files whether
this protocol is available or not.
This option enables support in your system's NFS server for the
NFSv3 ACL protocol extension allowing NFS clients to manipulate
POSIX ACLs on files exported by your system's NFS server. NFS
clients which support the Solaris NFSv3 ACL protocol can then
access and modify ACLs on your NFS server.
To store ACLs on your NFS server, you also need to enable ACL-
related CONFIG options for your local file systems of choice.
If unsure, say N.
config NFSD_V4
bool "NFS server support for NFS version 4"
depends on NFSD && PROC_FS
select FS_POSIX_ACL
select RPCSEC_GSS_KRB5
select GRACE_PERIOD
select NFS_V4_2_SSC_HELPER if NFS_V4_2
help
This option enables support in your system's NFS server for
version 4 of the NFS protocol (RFC 3530).
To export files using NFSv4, you need to install additional user
space programs which can be found in the Linux nfs-utils package,
available from http://linux-nfs.org/.
If unsure, say N.
config NFSD_PNFS
bool
config NFSD_BLOCKLAYOUT
bool "NFSv4.1 server support for pNFS block layouts"
depends on NFSD_V4 && BLOCK
select NFSD_PNFS
select EXPORTFS_BLOCK_OPS
help
This option enables support for the exporting pNFS block layouts
in the kernel's NFS server. The pNFS block layout enables NFS
clients to directly perform I/O to block devices accessible to both
the server and the clients. See RFC 5663 for more details.
If unsure, say N.
config NFSD_SCSILAYOUT
bool "NFSv4.1 server support for pNFS SCSI layouts"
depends on NFSD_V4 && BLOCK
select NFSD_PNFS
select EXPORTFS_BLOCK_OPS
help
This option enables support for the exporting pNFS SCSI layouts
in the kernel's NFS server. The pNFS SCSI layout enables NFS
clients to directly perform I/O to SCSI devices accessible to both
the server and the clients. See draft-ietf-nfsv4-scsi-layout for
more details.
If unsure, say N.
config NFSD_FLEXFILELAYOUT
bool "NFSv4.1 server support for pNFS Flex File layouts"
depends on NFSD_V4
select NFSD_PNFS
help
This option enables support for the exporting pNFS Flex File
layouts in the kernel's NFS server. The pNFS Flex File layout
enables NFS clients to directly perform I/O to NFSv3 devices
accessible to both the server and the clients. See
draft-ietf-nfsv4-flex-files for more details.
Warning, this server implements the bare minimum functionality
to be a flex file server - it is for testing the client,
not for use in production.
If unsure, say N.
config NFSD_V4_2_INTER_SSC
bool "NFSv4.2 inter server to server COPY"
depends on NFSD_V4 && NFS_V4_2
help
This option enables support for NFSv4.2 inter server to
server copy where the destination server calls the NFSv4.2
client to read the data to copy from the source server.
If unsure, say N.
config NFSD_V4_SECURITY_LABEL
bool "Provide Security Label support for NFSv4 server"
depends on NFSD_V4 && SECURITY
help
Say Y here if you want enable fine-grained security label attribute
support for NFS version 4. Security labels allow security modules like
SELinux and Smack to label files to facilitate enforcement of their policies.
Without this an NFSv4 mount will have the same label on each file.
If you do not wish to enable fine-grained security labels SELinux or
Smack policies on NFSv4 files, say N.
config NFSD_LEGACY_CLIENT_TRACKING
bool "Support legacy NFSv4 client tracking methods (DEPRECATED)"
depends on NFSD_V4
default n
help
The NFSv4 server needs to store a small amount of information on
stable storage in order to handle state recovery after reboot. Most
modern deployments upcall to a userland daemon for this (nfsdcld),
but older NFS servers may store information directly in a
recoverydir, or spawn a process directly using a usermodehelper
upcall.
These legacy client tracking methods have proven to be problematic
and will be removed in the future. Say Y here if you need support
for them in the interim.
config NFSD_V4_POSIX_ACLS
bool "Support NFSv4 POSIX draft ACLs"
depends on NFSD_V4
default n
help
Include experimental support for POSIX Access Control Lists
(ACLs) in NFSv4 as specified in the IETF draft
draft-ietf-nfsv4-posix-acls. This protocol extension enables
NFSv4 clients to retrieve and modify POSIX ACLs on exported
filesystems that support them.
This feature is based on an unratified IETF draft
specification that may change in ways that impact
interoperability with existing clients. Enable only for
testing environments or when interoperability with specific
clients that implement this draft is required.
If unsure, say N.
+49
View File
@@ -0,0 +1,49 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Linux nfs server
#
ccflags-y += -I$(src) # needed for trace events
obj-$(CONFIG_NFSD) += nfsd.o
# this one should be compiled first, as the tracing macros can easily blow up
nfsd-y += trace.o
nfsd-y += nfssvc.o nfsctl.o nfsfh.o vfs.o \
export.o auth.o lockd.o nfscache.o \
stats.o filecache.o nfs3proc.o nfs3xdr.o \
netlink.o
nfsd-$(CONFIG_NFSD_V2) += nfsproc.o nfsxdr.o
nfsd-$(CONFIG_NFSD_V2_ACL) += nfs2acl.o
nfsd-$(CONFIG_NFSD_V3_ACL) += nfs3acl.o
nfsd-$(CONFIG_NFSD_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4idmap.o \
nfs4acl.o nfs4callback.o nfs4recover.o nfs4xdr_gen.o
nfsd-$(CONFIG_NFSD_PNFS) += nfs4layouts.o
nfsd-$(CONFIG_NFSD_BLOCKLAYOUT) += blocklayout.o blocklayoutxdr.o
nfsd-$(CONFIG_NFSD_SCSILAYOUT) += blocklayout.o blocklayoutxdr.o
nfsd-$(CONFIG_NFSD_FLEXFILELAYOUT) += flexfilelayout.o flexfilelayoutxdr.o
nfsd-$(CONFIG_NFS_LOCALIO) += localio.o
nfsd-$(CONFIG_DEBUG_FS) += debugfs.o
#
# XDR code generation (requires Python and additional packages)
#
# The generated *xdr_gen.{h,c} files are checked into git. Normal kernel
# builds do not require the xdrgen tool or its Python dependencies.
#
# Developers modifying .x files in Documentation/sunrpc/xdr/ should run
# "make xdrgen" to regenerate the affected files.
#
.PHONY: xdrgen
xdrgen: ../../include/linux/sunrpc/xdrgen/nfs4_1.h nfs4xdr_gen.h nfs4xdr_gen.c
../../include/linux/sunrpc/xdrgen/nfs4_1.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
../../tools/net/sunrpc/xdrgen/xdrgen definitions $< > $@
nfs4xdr_gen.h: ../../Documentation/sunrpc/xdr/nfs4_1.x
../../tools/net/sunrpc/xdrgen/xdrgen declarations $< > $@
nfs4xdr_gen.c: ../../Documentation/sunrpc/xdr/nfs4_1.x
../../tools/net/sunrpc/xdrgen/xdrgen source $< > $@
+54
View File
@@ -0,0 +1,54 @@
/*
* Common NFSv4 ACL handling definitions.
*
* Copyright (c) 2002 The Regents of the University of Michigan.
* All rights reserved.
*
* Marius Aamodt Eriksen <marius@umich.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef LINUX_NFS4_ACL_H
#define LINUX_NFS4_ACL_H
struct nfs4_acl;
struct svc_fh;
struct svc_rqst;
struct nfsd_attrs;
enum nfs_ftype4;
int nfs4_acl_bytes(int entries);
int nfs4_acl_get_whotype(char *, u32);
__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who);
int nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
struct nfs4_acl **acl);
__be32 nfsd4_acl_to_attr(enum nfs_ftype4 type, struct nfs4_acl *acl,
struct nfsd_attrs *attr);
void sort_pacl_range(struct posix_acl *pacl, int start, int end);
#endif /* LINUX_NFS4_ACL_H */
+89
View File
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> */
#include <linux/sched.h>
#include "nfsd.h"
#include "auth.h"
int nfsexp_flags(struct svc_cred *cred, struct svc_export *exp)
{
struct exp_flavor_info *f;
struct exp_flavor_info *end = exp->ex_flavors + exp->ex_nflavors;
for (f = exp->ex_flavors; f < end; f++) {
if (f->pseudoflavor == cred->cr_flavor)
return f->flags;
}
return exp->ex_flags;
}
int nfsd_setuser(struct svc_cred *cred, struct svc_export *exp)
{
struct group_info *rqgi;
struct group_info *gi;
struct cred *new;
int i;
int flags = nfsexp_flags(cred, exp);
/* discard any old override before preparing the new set */
put_cred(revert_creds(get_cred(current_real_cred())));
new = prepare_creds();
if (!new)
return -ENOMEM;
new->fsuid = cred->cr_uid;
new->fsgid = cred->cr_gid;
rqgi = cred->cr_group_info;
if (flags & NFSEXP_ALLSQUASH) {
new->fsuid = exp->ex_anon_uid;
new->fsgid = exp->ex_anon_gid;
gi = groups_alloc(0);
if (!gi)
goto oom;
} else if (flags & NFSEXP_ROOTSQUASH) {
if (uid_eq(new->fsuid, GLOBAL_ROOT_UID))
new->fsuid = exp->ex_anon_uid;
if (gid_eq(new->fsgid, GLOBAL_ROOT_GID))
new->fsgid = exp->ex_anon_gid;
gi = groups_alloc(rqgi->ngroups);
if (!gi)
goto oom;
for (i = 0; i < rqgi->ngroups; i++) {
if (gid_eq(GLOBAL_ROOT_GID, rqgi->gid[i]))
gi->gid[i] = exp->ex_anon_gid;
else
gi->gid[i] = rqgi->gid[i];
}
/* Each thread allocates its own gi, no race */
groups_sort(gi);
} else {
gi = get_group_info(rqgi);
}
if (uid_eq(new->fsuid, INVALID_UID))
new->fsuid = exp->ex_anon_uid;
if (gid_eq(new->fsgid, INVALID_GID))
new->fsgid = exp->ex_anon_gid;
set_groups(new, gi);
put_group_info(gi);
if (!uid_eq(new->fsuid, GLOBAL_ROOT_UID))
new->cap_effective = cap_drop_nfsd_set(new->cap_effective);
else
new->cap_effective = cap_raise_nfsd_set(new->cap_effective,
new->cap_permitted);
put_cred(override_creds(new));
return 0;
oom:
abort_creds(new);
return -ENOMEM;
}
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* nfsd-specific authentication stuff.
*
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
*/
#ifndef LINUX_NFSD_AUTH_H
#define LINUX_NFSD_AUTH_H
/*
* Set the current process's fsuid/fsgid etc to those of the NFS
* client user
*/
int nfsd_setuser(struct svc_cred *cred, struct svc_export *exp);
#endif /* LINUX_NFSD_AUTH_H */
+526
View File
@@ -0,0 +1,526 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2014-2016 Christoph Hellwig.
*/
#include <linux/exportfs_block.h>
#include <linux/iomap.h>
#include <linux/slab.h>
#include <linux/pr.h>
#include <linux/nfsd/debug.h>
#include "blocklayoutxdr.h"
#include "pnfs.h"
#include "filecache.h"
#include "vfs.h"
#include "trace.h"
#define NFSDDBG_FACILITY NFSDDBG_PNFS
/*
* Get an extent from the file system that starts at offset or below
* and may be shorter than the requested length.
*/
static __be32
nfsd4_block_map_extent(struct inode *inode, const struct svc_fh *fhp,
u64 offset, u64 length, u32 iomode, u64 minlength,
struct pnfs_block_extent *bex)
{
struct super_block *sb = inode->i_sb;
struct iomap iomap;
u32 device_generation = 0;
int error;
error = sb->s_export_op->block_ops->map_blocks(inode, offset, length,
&iomap, iomode != IOMODE_READ, &device_generation);
if (error) {
if (error == -ENXIO)
return nfserr_layoutunavailable;
return nfserrno(error);
}
switch (iomap.type) {
case IOMAP_MAPPED:
if (iomode == IOMODE_READ)
bex->es = PNFS_BLOCK_READ_DATA;
else
bex->es = PNFS_BLOCK_READWRITE_DATA;
bex->soff = iomap.addr;
break;
case IOMAP_UNWRITTEN:
if (iomode & IOMODE_RW) {
/*
* Crack monkey special case from section 2.3.1.
*/
if (minlength == 0) {
dprintk("pnfsd: no soup for you!\n");
return nfserr_layoutunavailable;
}
bex->es = PNFS_BLOCK_INVALID_DATA;
bex->soff = iomap.addr;
break;
}
fallthrough;
case IOMAP_HOLE:
if (iomode == IOMODE_READ) {
bex->es = PNFS_BLOCK_NONE_DATA;
break;
}
fallthrough;
case IOMAP_DELALLOC:
default:
WARN(1, "pnfsd: filesystem returned %d extent\n", iomap.type);
return nfserr_layoutunavailable;
}
error = nfsd4_set_deviceid(&bex->vol_id, fhp, device_generation);
if (error)
return nfserrno(error);
bex->foff = iomap.offset;
bex->len = iomap.length;
return nfs_ok;
}
static __be32
nfsd4_block_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode,
const struct svc_fh *fhp, struct nfsd4_layoutget *args)
{
struct nfsd4_layout_seg *seg = &args->lg_seg;
struct pnfs_block_layout *bl;
struct pnfs_block_extent *first_bex, *last_bex;
u64 offset = seg->offset, length = seg->length;
u32 i, nr_extents_max, block_size = i_blocksize(inode);
__be32 nfserr;
if (locks_in_grace(SVC_NET(rqstp)))
return nfserr_grace;
nfserr = nfserr_layoutunavailable;
if (seg->offset & (block_size - 1)) {
dprintk("pnfsd: I/O misaligned\n");
goto out_error;
}
/*
* RFC 8881, section 3.3.17:
* The layout4 data type defines a layout for a file.
*
* RFC 8881, section 18.43.3:
* The loga_maxcount field specifies the maximum layout size
* (in bytes) that the client can handle. If the size of the
* layout structure exceeds the size specified by maxcount,
* the metadata server will return the NFS4ERR_TOOSMALL error.
*/
nfserr = nfserr_toosmall;
if (args->lg_maxcount < PNFS_BLOCK_LAYOUT4_SIZE +
PNFS_BLOCK_EXTENT_SIZE)
goto out_error;
/*
* Limit the maximum layout size to avoid allocating
* a large buffer on the server for each layout request.
*/
nr_extents_max = (min(args->lg_maxcount, PAGE_SIZE) -
PNFS_BLOCK_LAYOUT4_SIZE) / PNFS_BLOCK_EXTENT_SIZE;
/*
* Some clients barf on non-zero block numbers for NONE or INVALID
* layouts, so make sure to zero the whole structure.
*/
nfserr = nfserrno(-ENOMEM);
bl = kzalloc_flex(*bl, extents, nr_extents_max);
if (!bl)
goto out_error;
bl->nr_extents = nr_extents_max;
args->lg_content = bl;
for (i = 0; i < bl->nr_extents; i++) {
struct pnfs_block_extent *bex = bl->extents + i;
u64 bex_length;
nfserr = nfsd4_block_map_extent(inode, fhp, offset, length,
seg->iomode, args->lg_minlength, bex);
if (nfserr != nfs_ok)
goto out_error;
bex_length = bex->len - (offset - bex->foff);
if (bex_length >= length) {
bl->nr_extents = i + 1;
break;
}
offset = bex->foff + bex->len;
length -= bex_length;
}
first_bex = bl->extents;
last_bex = bl->extents + bl->nr_extents - 1;
nfserr = nfserr_layoutunavailable;
length = last_bex->foff + last_bex->len - seg->offset;
if (length < args->lg_minlength) {
dprintk("pnfsd: extent smaller than minlength\n");
goto out_error;
}
seg->offset = first_bex->foff;
seg->length = last_bex->foff - first_bex->foff + last_bex->len;
return nfs_ok;
out_error:
seg->length = 0;
return nfserr;
}
static __be32
nfsd4_block_commit_blocks(struct inode *inode, struct nfsd4_layoutcommit *lcp,
struct iomap *iomaps, int nr_iomaps)
{
int error;
/*
* This ignores the client provided mtime in loca_time_modify, as a
* fully client specified mtime doesn't really fit into the Linux
* multi-grain timestamp architecture.
*
* RFC 8881 Section 18.42 makes it clear that the client provided
* timestamp is a "may" condition, and clients that want to force a
* specific timestamp should send a separate SETATTR in the compound.
*/
error = inode->i_sb->s_export_op->block_ops->commit_blocks(inode,
iomaps, nr_iomaps,
lcp->lc_size_chg ? lcp->lc_newsize : 0);
kfree(iomaps);
return nfserrno(error);
}
#ifdef CONFIG_NFSD_BLOCKLAYOUT
static int
nfsd4_block_get_device_info_simple(struct super_block *sb,
struct nfsd4_getdeviceinfo *gdp)
{
struct pnfs_block_deviceaddr *dev;
struct pnfs_block_volume *b;
dev = kzalloc_flex(*dev, volumes, 1);
if (!dev)
return -ENOMEM;
gdp->gd_device = dev;
dev->nr_volumes = 1;
b = &dev->volumes[0];
b->type = PNFS_BLOCK_VOLUME_SIMPLE;
b->simple.sig_len = PNFS_BLOCK_UUID_LEN;
return sb->s_export_op->block_ops->get_uuid(sb, b->simple.sig,
&b->simple.sig_len, &b->simple.offset);
}
static __be32
nfsd4_block_proc_getdeviceinfo(struct super_block *sb,
struct svc_rqst *rqstp,
struct nfs4_client *clp,
struct nfsd4_getdeviceinfo *gdp)
{
if (bdev_is_partition(sb->s_bdev))
return nfserr_inval;
return nfserrno(nfsd4_block_get_device_info_simple(sb, gdp));
}
static __be32
nfsd4_block_proc_layoutcommit(struct inode *inode, struct svc_rqst *rqstp,
struct nfsd4_layoutcommit *lcp)
{
struct iomap *iomaps;
int nr_iomaps;
__be32 nfserr;
rqstp->rq_arg = lcp->lc_up_layout;
svcxdr_init_decode(rqstp);
nfserr = nfsd4_block_decode_layoutupdate(&rqstp->rq_arg_stream,
&iomaps, &nr_iomaps, i_blocksize(inode));
if (nfserr != nfs_ok)
return nfserr;
return nfsd4_block_commit_blocks(inode, lcp, iomaps, nr_iomaps);
}
const struct nfsd4_layout_ops bl_layout_ops = {
/*
* Pretend that we send notification to the client. This is a blatant
* lie to force recent Linux clients to cache our device IDs.
* We rarely ever change the device ID, so the harm of leaking deviceids
* for a while isn't too bad. Unfortunately RFC5661 is a complete mess
* in this regard, but I filed errata 4119 for this a while ago, and
* hopefully the Linux client will eventually start caching deviceids
* without this again.
*/
.notify_types =
NOTIFY_DEVICEID4_DELETE | NOTIFY_DEVICEID4_CHANGE,
.proc_getdeviceinfo = nfsd4_block_proc_getdeviceinfo,
.encode_getdeviceinfo = nfsd4_block_encode_getdeviceinfo,
.proc_layoutget = nfsd4_block_proc_layoutget,
.encode_layoutget = nfsd4_block_encode_layoutget,
.proc_layoutcommit = nfsd4_block_proc_layoutcommit,
};
#endif /* CONFIG_NFSD_BLOCKLAYOUT */
#ifdef CONFIG_NFSD_SCSILAYOUT
#define NFSD_MDS_PR_FENCED XA_MARK_0
/*
* Clear the fence flag if the device already has an entry. This occurs
* when a client re-registers after a previous fence, allowing new
* layouts for this device.
*
* Insert only on first registration. This bounds cl_dev_fences to the
* count of devices this client has accessed, preventing unbounded growth.
*/
static inline int nfsd4_scsi_fence_insert(struct nfs4_client *clp,
dev_t device)
{
struct xarray *xa = &clp->cl_dev_fences;
int ret;
xa_lock(xa);
ret = __xa_insert(xa, device, XA_ZERO_ENTRY, GFP_KERNEL);
if (ret == -EBUSY) {
__xa_clear_mark(xa, device, NFSD_MDS_PR_FENCED);
ret = 0;
}
xa_unlock(xa);
clp->cl_fence_retry_warn = false;
return ret;
}
static inline bool nfsd4_scsi_fence_set(struct nfs4_client *clp, dev_t device)
{
struct xarray *xa = &clp->cl_dev_fences;
bool skip;
xa_lock(xa);
skip = xa_get_mark(xa, device, NFSD_MDS_PR_FENCED);
if (!skip)
__xa_set_mark(xa, device, NFSD_MDS_PR_FENCED);
xa_unlock(xa);
return skip;
}
static inline void nfsd4_scsi_fence_clear(struct nfs4_client *clp, dev_t device)
{
xa_clear_mark(&clp->cl_dev_fences, device, NFSD_MDS_PR_FENCED);
}
#define NFSD_MDS_PR_KEY 0x0100000000000000ULL
/*
* We use the client ID as a unique key for the reservations.
* This allows us to easily fence a client when recalls fail.
*/
static u64 nfsd4_scsi_pr_key(struct nfs4_client *clp)
{
return ((u64)clp->cl_clientid.cl_boot << 32) | clp->cl_clientid.cl_id;
}
static const u8 designator_types[] = {
PS_DESIGNATOR_EUI64,
PS_DESIGNATOR_NAA,
};
static int
nfsd4_block_get_unique_id(struct gendisk *disk, struct pnfs_block_volume *b)
{
int ret, i;
for (i = 0; i < ARRAY_SIZE(designator_types); i++) {
u8 type = designator_types[i];
ret = disk->fops->get_unique_id(disk, b->scsi.designator, type);
if (ret > 0) {
b->scsi.code_set = PS_CODE_SET_BINARY;
b->scsi.designator_type = type;
b->scsi.designator_len = ret;
return 0;
}
}
return -EINVAL;
}
static int
nfsd4_block_get_device_info_scsi(struct super_block *sb,
struct nfs4_client *clp,
struct nfsd4_getdeviceinfo *gdp)
{
struct pnfs_block_deviceaddr *dev;
struct pnfs_block_volume *b;
const struct pr_ops *ops;
int ret;
dev = kzalloc_flex(*dev, volumes, 1);
if (!dev)
return -ENOMEM;
gdp->gd_device = dev;
dev->nr_volumes = 1;
b = &dev->volumes[0];
b->type = PNFS_BLOCK_VOLUME_SCSI;
b->scsi.pr_key = nfsd4_scsi_pr_key(clp);
ret = nfsd4_block_get_unique_id(sb->s_bdev->bd_disk, b);
if (ret < 0)
goto out_free_dev;
ret = -EINVAL;
ops = sb->s_bdev->bd_disk->fops->pr_ops;
if (!ops) {
pr_err("pNFS: device %s does not support PRs.\n",
sb->s_id);
goto out_free_dev;
}
ret = nfsd4_scsi_fence_insert(clp, sb->s_bdev->bd_dev);
if (ret < 0)
goto out_free_dev;
ret = ops->pr_register(sb->s_bdev, 0, NFSD_MDS_PR_KEY, true);
if (ret) {
pr_err("pNFS: failed to register key for device %s.\n",
sb->s_id);
goto out_free_dev;
}
ret = ops->pr_reserve(sb->s_bdev, NFSD_MDS_PR_KEY,
PR_EXCLUSIVE_ACCESS_REG_ONLY, 0);
if (ret) {
pr_err("pNFS: failed to reserve device %s.\n",
sb->s_id);
goto out_free_dev;
}
return 0;
out_free_dev:
kfree(dev);
gdp->gd_device = NULL;
return ret;
}
static __be32
nfsd4_scsi_proc_getdeviceinfo(struct super_block *sb,
struct svc_rqst *rqstp,
struct nfs4_client *clp,
struct nfsd4_getdeviceinfo *gdp)
{
if (bdev_is_partition(sb->s_bdev))
return nfserr_inval;
return nfserrno(nfsd4_block_get_device_info_scsi(sb, clp, gdp));
}
static __be32
nfsd4_scsi_proc_layoutcommit(struct inode *inode, struct svc_rqst *rqstp,
struct nfsd4_layoutcommit *lcp)
{
struct iomap *iomaps;
int nr_iomaps;
__be32 nfserr;
rqstp->rq_arg = lcp->lc_up_layout;
svcxdr_init_decode(rqstp);
nfserr = nfsd4_scsi_decode_layoutupdate(&rqstp->rq_arg_stream,
&iomaps, &nr_iomaps, i_blocksize(inode));
if (nfserr != nfs_ok)
return nfserr;
return nfsd4_block_commit_blocks(inode, lcp, iomaps, nr_iomaps);
}
/*
* Perform the fence operation to prevent the client from accessing the
* block device. If a fence operation is already in progress, wait for
* it to complete before checking the NFSD_MDS_PR_FENCED flag. Once the
* operation is complete, check the flag. If NFSD_MDS_PR_FENCED is set,
* update the layout stateid by setting the ls_fenced flag to indicate
* that the client has been fenced.
*
* The cl_fence_mutex ensures that the fence operation has been fully
* completed, rather than just in progress, when returning from this
* function.
*
* Return true if client was fenced otherwise return false.
*/
static bool
nfsd4_scsi_fence_client(struct nfs4_layout_stateid *ls, struct nfsd_file *file)
{
struct nfs4_client *clp = ls->ls_stid.sc_client;
struct block_device *bdev = file->nf_file->f_path.mnt->mnt_sb->s_bdev;
int status;
bool ret;
mutex_lock(&clp->cl_fence_mutex);
if (nfsd4_scsi_fence_set(clp, bdev->bd_dev)) {
mutex_unlock(&clp->cl_fence_mutex);
return true;
}
status = bdev->bd_disk->fops->pr_ops->pr_preempt(bdev, NFSD_MDS_PR_KEY,
nfsd4_scsi_pr_key(clp),
PR_EXCLUSIVE_ACCESS_REG_ONLY, true);
/*
* Reset to allow retry only when the command could not have
* reached the device. Negative status means a local error
* (e.g., -ENOMEM) prevented the command from being sent.
* PR_STS_PATH_FAILED, PR_STS_PATH_FAST_FAILED, and
* PR_STS_RETRY_PATH_FAILURE indicate transport path failures
* before device delivery.
*
* For all other errors, the command may have reached the device
* and the preempt may have succeeded. Avoid resetting, since
* retrying a successful preempt returns PR_STS_IOERR or
* PR_STS_RESERVATION_CONFLICT, which would cause an infinite
* retry loop.
*/
switch (status) {
case 0:
case PR_STS_IOERR:
case PR_STS_RESERVATION_CONFLICT:
ret = true;
break;
default:
/* retry-able and other errors */
ret = false;
nfsd4_scsi_fence_clear(clp, bdev->bd_dev);
break;
}
mutex_unlock(&clp->cl_fence_mutex);
trace_nfsd_pnfs_fence(clp, bdev->bd_disk->disk_name, status);
return ret;
}
const struct nfsd4_layout_ops scsi_layout_ops = {
/*
* Pretend that we send notification to the client. This is a blatant
* lie to force recent Linux clients to cache our device IDs.
* We rarely ever change the device ID, so the harm of leaking deviceids
* for a while isn't too bad. Unfortunately RFC5661 is a complete mess
* in this regard, but I filed errata 4119 for this a while ago, and
* hopefully the Linux client will eventually start caching deviceids
* without this again.
*/
.notify_types =
NOTIFY_DEVICEID4_DELETE | NOTIFY_DEVICEID4_CHANGE,
.proc_getdeviceinfo = nfsd4_scsi_proc_getdeviceinfo,
.encode_getdeviceinfo = nfsd4_block_encode_getdeviceinfo,
.proc_layoutget = nfsd4_block_proc_layoutget,
.encode_layoutget = nfsd4_block_encode_layoutget,
.proc_layoutcommit = nfsd4_scsi_proc_layoutcommit,
.fence_client = nfsd4_scsi_fence_client,
};
#endif /* CONFIG_NFSD_SCSILAYOUT */
+295
View File
@@ -0,0 +1,295 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2014-2016 Christoph Hellwig.
*/
#include <linux/sunrpc/svc.h>
#include <linux/exportfs.h>
#include <linux/iomap.h>
#include <linux/nfs4.h>
#include "nfsd.h"
#include "blocklayoutxdr.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_PNFS
/**
* nfsd4_block_encode_layoutget - encode block/scsi layout extent array
* @xdr: stream for data encoding
* @lgp: layoutget content, actually an array of extents to encode
*
* Encode the opaque loc_body field in the layoutget response. Since the
* pnfs_block_layout4 and pnfs_scsi_layout4 structures on the wire are
* the same, this function is used by both layout drivers.
*
* Return values:
* %nfs_ok: Success, all extents encoded into @xdr
* %nfserr_toosmall: Not enough space in @xdr to encode all the data
*/
__be32
nfsd4_block_encode_layoutget(struct xdr_stream *xdr,
const struct nfsd4_layoutget *lgp)
{
const struct pnfs_block_layout *bl = lgp->lg_content;
u32 i, len = sizeof(__be32) + bl->nr_extents * PNFS_BLOCK_EXTENT_SIZE;
__be32 *p;
p = xdr_reserve_space(xdr, sizeof(__be32) + len);
if (!p)
return nfserr_toosmall;
*p++ = cpu_to_be32(len);
*p++ = cpu_to_be32(bl->nr_extents);
for (i = 0; i < bl->nr_extents; i++) {
const struct pnfs_block_extent *bex = bl->extents + i;
p = svcxdr_encode_deviceid4(p, &bex->vol_id);
p = xdr_encode_hyper(p, bex->foff);
p = xdr_encode_hyper(p, bex->len);
p = xdr_encode_hyper(p, bex->soff);
*p++ = cpu_to_be32(bex->es);
}
return nfs_ok;
}
static int
nfsd4_block_encode_volume(struct xdr_stream *xdr, struct pnfs_block_volume *b)
{
__be32 *p;
int len;
switch (b->type) {
case PNFS_BLOCK_VOLUME_SIMPLE:
len = 4 + 4 + 8 + 4 + (XDR_QUADLEN(b->simple.sig_len) << 2);
p = xdr_reserve_space(xdr, len);
if (!p)
return -ETOOSMALL;
*p++ = cpu_to_be32(b->type);
*p++ = cpu_to_be32(1); /* single signature */
p = xdr_encode_hyper(p, b->simple.offset);
p = xdr_encode_opaque(p, b->simple.sig, b->simple.sig_len);
break;
case PNFS_BLOCK_VOLUME_SCSI:
len = 4 + 4 + 4 + 4 + (XDR_QUADLEN(b->scsi.designator_len) << 2) + 8;
p = xdr_reserve_space(xdr, len);
if (!p)
return -ETOOSMALL;
*p++ = cpu_to_be32(b->type);
*p++ = cpu_to_be32(b->scsi.code_set);
*p++ = cpu_to_be32(b->scsi.designator_type);
p = xdr_encode_opaque(p, b->scsi.designator, b->scsi.designator_len);
p = xdr_encode_hyper(p, b->scsi.pr_key);
break;
default:
return -ENOTSUPP;
}
return len;
}
__be32
nfsd4_block_encode_getdeviceinfo(struct xdr_stream *xdr,
const struct nfsd4_getdeviceinfo *gdp)
{
struct pnfs_block_deviceaddr *dev = gdp->gd_device;
int len = sizeof(__be32), ret, i;
__be32 *p;
/*
* See paragraph 5 of RFC 8881 S18.40.3.
*/
if (!gdp->gd_maxcount) {
if (xdr_stream_encode_u32(xdr, 0) != XDR_UNIT)
return nfserr_resource;
return nfs_ok;
}
p = xdr_reserve_space(xdr, len + sizeof(__be32));
if (!p)
return nfserr_resource;
for (i = 0; i < dev->nr_volumes; i++) {
ret = nfsd4_block_encode_volume(xdr, &dev->volumes[i]);
if (ret < 0)
return nfserrno(ret);
len += ret;
}
/*
* Fill in the overall length and number of volumes at the beginning
* of the layout.
*/
*p++ = cpu_to_be32(len);
*p++ = cpu_to_be32(dev->nr_volumes);
return 0;
}
/**
* nfsd4_block_decode_layoutupdate - decode the block layout extent array
* @xdr: subbuf set to the encoded array
* @iomapp: pointer to store the decoded extent array
* @nr_iomapsp: pointer to store the number of extents
* @block_size: alignment of extent offset and length
*
* This function decodes the opaque field of the layoutupdate4 structure
* in a layoutcommit request for the block layout driver. The field is
* actually an array of extents sent by the client. It also checks that
* the file offset, storage offset and length of each extent are aligned
* by @block_size.
*
* Return values:
* %nfs_ok: Successful decoding, @iomapp and @nr_iomapsp are valid
* %nfserr_bad_xdr: The encoded array in @xdr is invalid
* %nfserr_inval: An unaligned extent found
* %nfserr_delay: Failed to allocate memory for @iomapp
*/
__be32
nfsd4_block_decode_layoutupdate(struct xdr_stream *xdr, struct iomap **iomapp,
int *nr_iomapsp, u32 block_size)
{
struct iomap *iomaps;
u32 nr_iomaps, expected, len, i;
__be32 nfserr;
if (xdr_stream_decode_u32(xdr, &nr_iomaps))
return nfserr_bad_xdr;
len = sizeof(__be32) + xdr_stream_remaining(xdr);
expected = sizeof(__be32) + nr_iomaps * PNFS_BLOCK_EXTENT_SIZE;
if (len != expected)
return nfserr_bad_xdr;
iomaps = kzalloc_objs(*iomaps, nr_iomaps);
if (!iomaps)
return nfserr_delay;
for (i = 0; i < nr_iomaps; i++) {
struct pnfs_block_extent bex;
if (nfsd4_decode_deviceid4(xdr, &bex.vol_id)) {
nfserr = nfserr_bad_xdr;
goto fail;
}
if (xdr_stream_decode_u64(xdr, &bex.foff)) {
nfserr = nfserr_bad_xdr;
goto fail;
}
if (bex.foff & (block_size - 1)) {
nfserr = nfserr_inval;
goto fail;
}
if (xdr_stream_decode_u64(xdr, &bex.len)) {
nfserr = nfserr_bad_xdr;
goto fail;
}
if (bex.len & (block_size - 1)) {
nfserr = nfserr_inval;
goto fail;
}
if (xdr_stream_decode_u64(xdr, &bex.soff)) {
nfserr = nfserr_bad_xdr;
goto fail;
}
if (bex.soff & (block_size - 1)) {
nfserr = nfserr_inval;
goto fail;
}
if (xdr_stream_decode_u32(xdr, &bex.es)) {
nfserr = nfserr_bad_xdr;
goto fail;
}
if (bex.es != PNFS_BLOCK_READWRITE_DATA) {
nfserr = nfserr_inval;
goto fail;
}
iomaps[i].offset = bex.foff;
iomaps[i].length = bex.len;
}
*iomapp = iomaps;
*nr_iomapsp = nr_iomaps;
return nfs_ok;
fail:
kfree(iomaps);
return nfserr;
}
/**
* nfsd4_scsi_decode_layoutupdate - decode the scsi layout extent array
* @xdr: subbuf set to the encoded array
* @iomapp: pointer to store the decoded extent array
* @nr_iomapsp: pointer to store the number of extents
* @block_size: alignment of extent offset and length
*
* This function decodes the opaque field of the layoutupdate4 structure
* in a layoutcommit request for the scsi layout driver. The field is
* actually an array of extents sent by the client. It also checks that
* the offset and length of each extent are aligned by @block_size.
*
* Return values:
* %nfs_ok: Successful decoding, @iomapp and @nr_iomapsp are valid
* %nfserr_bad_xdr: The encoded array in @xdr is invalid
* %nfserr_inval: An unaligned extent found
* %nfserr_delay: Failed to allocate memory for @iomapp
*/
__be32
nfsd4_scsi_decode_layoutupdate(struct xdr_stream *xdr, struct iomap **iomapp,
int *nr_iomapsp, u32 block_size)
{
struct iomap *iomaps;
u32 nr_iomaps, expected, len, i;
__be32 nfserr;
if (xdr_stream_decode_u32(xdr, &nr_iomaps))
return nfserr_bad_xdr;
len = sizeof(__be32) + xdr_stream_remaining(xdr);
expected = sizeof(__be32) + nr_iomaps * PNFS_SCSI_RANGE_SIZE;
if (len != expected)
return nfserr_bad_xdr;
iomaps = kzalloc_objs(*iomaps, nr_iomaps);
if (!iomaps)
return nfserr_delay;
for (i = 0; i < nr_iomaps; i++) {
u64 val;
if (xdr_stream_decode_u64(xdr, &val)) {
nfserr = nfserr_bad_xdr;
goto fail;
}
if (val & (block_size - 1)) {
nfserr = nfserr_inval;
goto fail;
}
iomaps[i].offset = val;
if (xdr_stream_decode_u64(xdr, &val)) {
nfserr = nfserr_bad_xdr;
goto fail;
}
if (val & (block_size - 1)) {
nfserr = nfserr_inval;
goto fail;
}
iomaps[i].length = val;
}
*iomapp = iomaps;
*nr_iomapsp = nr_iomaps;
return nfs_ok;
fail:
kfree(iomaps);
return nfserr;
}
+76
View File
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _NFSD_BLOCKLAYOUTXDR_H
#define _NFSD_BLOCKLAYOUTXDR_H 1
#include <linux/blkdev.h>
#include "xdr4.h"
struct iomap;
struct xdr_stream;
/* On the wire size of the layout4 struct with zero number of extents */
#define PNFS_BLOCK_LAYOUT4_SIZE \
(sizeof(__be32) * 2 + /* offset4 */ \
sizeof(__be32) * 2 + /* length4 */ \
sizeof(__be32) + /* layoutiomode4 */ \
sizeof(__be32) + /* layouttype4 */ \
sizeof(__be32) + /* number of bytes */ \
sizeof(__be32)) /* number of extents */
struct pnfs_block_extent {
struct nfsd4_deviceid vol_id;
u64 foff;
u64 len;
u64 soff;
enum pnfs_block_extent_state es;
};
struct pnfs_block_range {
u64 foff;
u64 len;
};
struct pnfs_block_layout {
u32 nr_extents;
struct pnfs_block_extent extents[] __counted_by(nr_extents);
};
/*
* Random upper cap for the uuid length to avoid unbounded allocation.
* Not actually limited by the protocol.
*/
#define PNFS_BLOCK_UUID_LEN 128
struct pnfs_block_volume {
enum pnfs_block_volume_type type;
union {
struct {
u64 offset;
u32 sig_len;
u8 sig[PNFS_BLOCK_UUID_LEN];
} simple;
struct {
enum scsi_code_set code_set;
enum scsi_designator_type designator_type;
int designator_len;
u8 designator[256];
u64 pr_key;
} scsi;
};
};
struct pnfs_block_deviceaddr {
u32 nr_volumes;
struct pnfs_block_volume volumes[] __counted_by(nr_volumes);
};
__be32 nfsd4_block_encode_getdeviceinfo(struct xdr_stream *xdr,
const struct nfsd4_getdeviceinfo *gdp);
__be32 nfsd4_block_encode_layoutget(struct xdr_stream *xdr,
const struct nfsd4_layoutget *lgp);
__be32 nfsd4_block_decode_layoutupdate(struct xdr_stream *xdr,
struct iomap **iomapp, int *nr_iomapsp, u32 block_size);
__be32 nfsd4_scsi_decode_layoutupdate(struct xdr_stream *xdr,
struct iomap **iomapp, int *nr_iomapsp, u32 block_size);
#endif /* _NFSD_BLOCKLAYOUTXDR_H */
+91
View File
@@ -0,0 +1,91 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Request reply cache. This was heavily inspired by the
* implementation in 4.3BSD/4.4BSD.
*
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
*/
#ifndef NFSCACHE_H
#define NFSCACHE_H
#include <linux/sunrpc/svc.h>
#include "nfsd.h"
/*
* Representation of a reply cache entry.
*
* Note that we use a sockaddr_in6 to hold the address instead of the more
* typical sockaddr_storage. This is for space reasons, since sockaddr_storage
* is much larger than a sockaddr_in6.
*/
struct nfsd_cacherep {
struct {
/* Keep often-read xid, csum in the same cache line: */
__be32 k_xid;
__wsum k_csum;
u32 k_proc;
u32 k_prot;
u32 k_vers;
unsigned int k_len;
struct sockaddr_in6 k_addr;
} c_key;
struct rb_node c_node;
struct list_head c_lru;
unsigned char c_state, /* unused, inprog, done */
c_type, /* status, buffer */
c_secure : 1; /* req came from port < 1024 */
unsigned long c_timestamp;
union {
struct kvec u_vec;
__be32 u_status;
} c_u;
};
#define c_replvec c_u.u_vec
#define c_replstat c_u.u_status
/* cache entry states */
enum {
RC_UNUSED,
RC_INPROG,
RC_DONE
};
/* return values */
enum {
RC_DROPIT,
RC_REPLY,
RC_DOIT
};
/*
* Cache types.
* We may want to add more types one day, e.g. for diropres and
* attrstat replies. Using cache entries with fixed length instead
* of buffer pointers may be more efficient.
*/
enum {
RC_NOCACHE,
RC_REPLSTAT,
RC_REPLBUFF,
};
/* Cache entries expire after this time period */
#define RC_EXPIRE (120 * HZ)
/* Checksum this amount of the request */
#define RC_CSUMLEN (256U)
int nfsd_drc_slab_create(void);
void nfsd_drc_slab_free(void);
int nfsd_reply_cache_init(struct nfsd_net *);
void nfsd_reply_cache_shutdown(struct nfsd_net *);
int nfsd_cache_lookup(struct svc_rqst *rqstp, unsigned int start,
unsigned int len, struct nfsd_cacherep **cacherep);
void nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp,
int cachetype, __be32 *statp);
int nfsd_reply_cache_stats_show(struct seq_file *m, void *v);
#endif /* NFSCACHE_H */
+41
View File
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _NFSD4_CURRENT_STATE_H
#define _NFSD4_CURRENT_STATE_H
#include "state.h"
#include "xdr4.h"
extern void clear_current_stateid(struct nfsd4_compound_state *cstate);
/*
* functions to set current state id
*/
extern void nfsd4_set_opendowngradestateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_set_openstateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_set_lockstateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_set_closestateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
/*
* functions to consume current state id
*/
extern void nfsd4_get_opendowngradestateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_get_delegreturnstateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_get_freestateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_get_setattrstateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_get_closestateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_get_lockustateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_get_readstateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
extern void nfsd4_get_writestateid(struct nfsd4_compound_state *,
union nfsd4_op_u *);
#endif /* _NFSD4_CURRENT_STATE_H */
+147
View File
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/debugfs.h>
#include "nfsd.h"
static struct dentry *nfsd_top_dir __read_mostly;
/*
* /sys/kernel/debug/nfsd/disable-splice-read
*
* Contents:
* %0: NFS READ is allowed to use page splicing
* %1: NFS READ uses only iov iter read
*
* The default value of this setting is zero (page splicing is
* allowed). This setting takes immediate effect for all NFS
* versions, all exports, and in all NFSD net namespaces.
*/
static int nfsd_dsr_get(void *data, u64 *val)
{
*val = nfsd_disable_splice_read ? 1 : 0;
return 0;
}
static int nfsd_dsr_set(void *data, u64 val)
{
nfsd_disable_splice_read = (val > 0);
if (!nfsd_disable_splice_read) {
/*
* Must use buffered I/O if splice_read is enabled.
*/
nfsd_io_cache_read = NFSD_IO_BUFFERED;
}
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(nfsd_dsr_fops, nfsd_dsr_get, nfsd_dsr_set, "%llu\n");
/*
* /sys/kernel/debug/nfsd/io_cache_read
*
* Contents:
* %0: NFS READ will use buffered IO
* %1: NFS READ will use dontcache (buffered IO w/ dropbehind)
* %2: NFS READ will use direct IO
*
* This setting takes immediate effect for all NFS versions,
* all exports, and in all NFSD net namespaces.
*/
static int nfsd_io_cache_read_get(void *data, u64 *val)
{
*val = nfsd_io_cache_read;
return 0;
}
static int nfsd_io_cache_read_set(void *data, u64 val)
{
int ret = 0;
switch (val) {
case NFSD_IO_BUFFERED:
nfsd_io_cache_read = NFSD_IO_BUFFERED;
break;
case NFSD_IO_DONTCACHE:
case NFSD_IO_DIRECT:
/*
* Must disable splice_read when enabling
* NFSD_IO_DONTCACHE.
*/
nfsd_disable_splice_read = true;
nfsd_io_cache_read = val;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(nfsd_io_cache_read_fops, nfsd_io_cache_read_get,
nfsd_io_cache_read_set, "%llu\n");
/*
* /sys/kernel/debug/nfsd/io_cache_write
*
* Contents:
* %0: NFS WRITE will use buffered IO
* %1: NFS WRITE will use dontcache (buffered IO w/ dropbehind)
*
* This setting takes immediate effect for all NFS versions,
* all exports, and in all NFSD net namespaces.
*/
static int nfsd_io_cache_write_get(void *data, u64 *val)
{
*val = nfsd_io_cache_write;
return 0;
}
static int nfsd_io_cache_write_set(void *data, u64 val)
{
int ret = 0;
switch (val) {
case NFSD_IO_BUFFERED:
case NFSD_IO_DONTCACHE:
case NFSD_IO_DIRECT:
nfsd_io_cache_write = val;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
DEFINE_DEBUGFS_ATTRIBUTE(nfsd_io_cache_write_fops, nfsd_io_cache_write_get,
nfsd_io_cache_write_set, "%llu\n");
void nfsd_debugfs_exit(void)
{
debugfs_remove_recursive(nfsd_top_dir);
nfsd_top_dir = NULL;
}
void nfsd_debugfs_init(void)
{
nfsd_top_dir = debugfs_create_dir("nfsd", NULL);
debugfs_create_file("disable-splice-read", S_IWUSR | S_IRUGO,
nfsd_top_dir, NULL, &nfsd_dsr_fops);
debugfs_create_file("io_cache_read", 0644, nfsd_top_dir, NULL,
&nfsd_io_cache_read_fops);
debugfs_create_file("io_cache_write", 0644, nfsd_top_dir, NULL,
&nfsd_io_cache_write_fops);
#ifdef CONFIG_NFSD_V4
debugfs_create_bool("delegated_timestamps", 0644, nfsd_top_dir,
&nfsd_delegts_enabled);
#endif
}
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 1995-1997 Olaf Kirch <okir@monad.swb.de>
*/
#ifndef NFSD_EXPORT_H
#define NFSD_EXPORT_H
#include <linux/sunrpc/cache.h>
#include <linux/percpu_counter.h>
#include <uapi/linux/nfsd/export.h>
#include <linux/nfs4.h>
struct knfsd_fh;
struct svc_fh;
struct svc_rqst;
/*
* FS Locations
*/
#define MAX_FS_LOCATIONS 128
struct nfsd4_fs_location {
char *hosts; /* colon separated list of hosts */
char *path; /* slash separated list of path components */
};
struct nfsd4_fs_locations {
uint32_t locations_count;
struct nfsd4_fs_location *locations;
/* If we're not actually serving this data ourselves (only providing a
* list of replicas that do serve it) then we set "migrated": */
int migrated;
};
/*
* We keep an array of pseudoflavors with the export, in order from most
* to least preferred. For the foreseeable future, we don't expect more
* than the eight pseudoflavors null, unix, krb5, krb5i, krb5p, skpm3,
* spkm3i, and spkm3p (and using all 8 at once should be rare).
*/
#define MAX_SECINFO_LIST 8
#define EX_UUID_LEN 16
struct exp_flavor_info {
u32 pseudoflavor;
u32 flags;
};
/* Per-export stats */
enum {
EXP_STATS_FH_STALE,
EXP_STATS_IO_READ,
EXP_STATS_IO_WRITE,
EXP_STATS_COUNTERS_NUM
};
struct export_stats {
time64_t start_time;
struct percpu_counter counter[EXP_STATS_COUNTERS_NUM];
};
struct svc_export {
struct cache_head h;
struct auth_domain * ex_client;
int ex_flags;
int ex_fsid;
struct path ex_path;
kuid_t ex_anon_uid;
kgid_t ex_anon_gid;
unsigned char * ex_uuid; /* 16 byte fsid */
struct nfsd4_fs_locations ex_fslocs;
uint32_t ex_nflavors;
struct exp_flavor_info ex_flavors[MAX_SECINFO_LIST];
u32 ex_layout_types;
struct nfsd4_deviceid_map *ex_devid_map;
struct cache_detail *cd;
struct rcu_head ex_rcu;
unsigned long ex_xprtsec_modes;
struct export_stats *ex_stats;
};
/* an "export key" (expkey) maps a filehandlefragement to an
* svc_export for a given client. There can be several per export,
* for the different fsid types.
*/
struct svc_expkey {
struct cache_head h;
struct auth_domain * ek_client;
u8 ek_fsidtype;
u32 ek_fsid[6];
struct path ek_path;
struct rcu_head ek_rcu;
};
#define EX_ISSYNC(exp) (!((exp)->ex_flags & NFSEXP_ASYNC))
#define EX_NOHIDE(exp) ((exp)->ex_flags & NFSEXP_NOHIDE)
#define EX_WGATHER(exp) ((exp)->ex_flags & NFSEXP_GATHERED_WRITES)
struct svc_cred;
int nfsexp_flags(struct svc_cred *cred, struct svc_export *exp);
__be32 check_xprtsec_policy(struct svc_export *exp, struct svc_rqst *rqstp);
__be32 check_security_flavor(struct svc_export *exp, struct svc_rqst *rqstp,
bool may_bypass_gss);
__be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp,
bool may_bypass_gss);
/*
* Function declarations
*/
int nfsd_export_init(struct net *);
void nfsd_export_shutdown(struct net *);
void nfsd_export_flush(struct net *);
struct svc_export * rqst_exp_get_by_name(struct svc_rqst *,
const struct path *);
struct svc_export * rqst_exp_parent(struct svc_rqst *,
struct path *);
struct svc_export * rqst_find_fsidzero_export(struct svc_rqst *);
int exp_rootfh(struct net *, struct auth_domain *,
char *path, struct knfsd_fh *, int maxsize);
__be32 exp_pseudoroot(struct svc_rqst *, struct svc_fh *);
static inline void exp_put(struct svc_export *exp)
{
cache_put(&exp->h, exp->cd);
}
static inline struct svc_export *exp_get(struct svc_export *exp)
{
cache_get(&exp->h);
return exp;
}
struct svc_export *rqst_exp_find(struct cache_req *reqp, struct net *net,
struct auth_domain *cl, struct auth_domain *gsscl,
int fsid_type, u32 *fsidv);
#endif /* NFSD_EXPORT_H */
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
#ifndef _FS_NFSD_FILECACHE_H
#define _FS_NFSD_FILECACHE_H
#include <linux/fsnotify_backend.h>
/*
* Limit the time that the list_lru_one lock is held during
* an LRU scan.
*/
#define NFSD_FILE_GC_BATCH (16UL)
/*
* This is the fsnotify_mark container that nfsd attaches to the files that it
* is holding open. Note that we have a separate refcount here aside from the
* one in the fsnotify_mark. We only want a single fsnotify_mark attached to
* the inode, and for each nfsd_file to hold a reference to it.
*
* The fsnotify_mark is itself refcounted, but that's not sufficient to tell us
* how to put that reference. If there are still outstanding nfsd_files that
* reference the mark, then we would want to call fsnotify_put_mark on it.
* If there were not, then we'd need to call fsnotify_destroy_mark. Since we
* can't really tell the difference, we use the nfm_mark to keep track of how
* many nfsd_files hold references to the mark. When that counter goes to zero
* then we know to call fsnotify_destroy_mark on it.
*/
struct nfsd_file_mark {
struct fsnotify_mark nfm_mark;
refcount_t nfm_ref;
};
/*
* A representation of a file that has been opened by knfsd. These are hashed
* in the hashtable by inode pointer value. Note that this object doesn't
* hold a reference to the inode by itself, so the nf_inode pointer should
* never be dereferenced, only used for comparison.
*/
struct nfsd_file {
struct rhlist_head nf_rlist;
void *nf_inode;
struct file *nf_file;
const struct cred *nf_cred;
struct net *nf_net;
#define NFSD_FILE_HASHED (0)
#define NFSD_FILE_PENDING (1)
#define NFSD_FILE_REFERENCED (2)
#define NFSD_FILE_GC (3)
#define NFSD_FILE_RECENT (4)
unsigned long nf_flags;
refcount_t nf_ref;
unsigned char nf_may;
struct nfsd_file_mark *nf_mark;
struct list_head nf_lru;
struct list_head nf_gc;
struct rcu_head nf_rcu;
ktime_t nf_birthtime;
u32 nf_dio_mem_align;
u32 nf_dio_offset_align;
u32 nf_dio_read_offset_align;
};
int nfsd_file_cache_init(void);
void nfsd_file_cache_purge(struct net *);
void nfsd_file_cache_shutdown(void);
int nfsd_file_cache_start_net(struct net *net);
void nfsd_file_cache_shutdown_net(struct net *net);
void nfsd_file_put(struct nfsd_file *nf);
struct net *nfsd_file_put_local(struct nfsd_file __rcu **nf);
struct nfsd_file *nfsd_file_get(struct nfsd_file *nf);
struct file *nfsd_file_file(struct nfsd_file *nf);
void nfsd_file_close_inode_sync(struct inode *inode);
void nfsd_file_close_export(struct net *net, const struct path *path);
void nfsd_file_net_dispose(struct nfsd_net *nn);
bool nfsd_file_is_cached(struct inode *inode);
__be32 nfsd_file_acquire_gc(struct svc_rqst *rqstp, struct svc_fh *fhp,
unsigned int may_flags, struct nfsd_file **nfp);
__be32 nfsd_file_acquire(struct svc_rqst *rqstp, struct svc_fh *fhp,
unsigned int may_flags, struct nfsd_file **nfp);
__be32 nfsd_file_acquire_opened(struct svc_rqst *rqstp, struct svc_fh *fhp,
unsigned int may_flags, struct file *file,
struct nfsd_file **nfp);
__be32 nfsd_file_acquire_local(struct net *net, struct svc_cred *cred,
struct auth_domain *client, struct svc_fh *fhp,
unsigned int may_flags, struct nfsd_file **pnf);
__be32 nfsd_file_acquire_dir(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_file **pnf);
int nfsd_file_cache_stats_show(struct seq_file *m, void *v);
#endif /* _FS_NFSD_FILECACHE_H */
+144
View File
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016 Tom Haynes <loghyr@primarydata.com>
*
* The following implements a super-simple flex-file server
* where the NFSv4.1 mds is also the ds. And the storage is
* the same. I.e., writing to the mds via a NFSv4.1 WRITE
* goes to the same location as the NFSv3 WRITE.
*/
#include <linux/slab.h>
#include <linux/nfsd/debug.h>
#include <linux/sunrpc/addr.h>
#include "flexfilelayoutxdr.h"
#include "pnfs.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_PNFS
static __be32
nfsd4_ff_proc_layoutget(struct svc_rqst *rqstp, struct inode *inode,
const struct svc_fh *fhp, struct nfsd4_layoutget *args)
{
struct nfsd4_layout_seg *seg = &args->lg_seg;
u32 device_generation = 0;
int error;
uid_t u;
struct pnfs_ff_layout *fl;
/*
* The super simple flex file server has 1 mirror, 1 data server,
* and 1 file handle. So instead of 4 allocs, do 1 for now.
* Zero it out for the stateid - don't want junk in there!
*/
error = -ENOMEM;
fl = kzalloc_obj(*fl);
if (!fl)
goto out_error;
args->lg_content = fl;
/*
* Avoid layout commit, try to force the I/O to the DS,
* and for fun, cause all IOMODE_RW layout segments to
* effectively be WRITE only.
*/
fl->flags = FF_FLAGS_NO_LAYOUTCOMMIT | FF_FLAGS_NO_IO_THRU_MDS |
FF_FLAGS_NO_READ_IO;
/* Do not allow a IOMODE_READ segment to have write pemissions */
if (seg->iomode == IOMODE_READ) {
u = from_kuid(&init_user_ns, inode->i_uid) + 1;
fl->uid = make_kuid(&init_user_ns, u);
} else
fl->uid = inode->i_uid;
fl->gid = inode->i_gid;
error = nfsd4_set_deviceid(&fl->deviceid, fhp, device_generation);
if (error)
goto out_error;
fl->fh.size = fhp->fh_handle.fh_size;
memcpy(fl->fh.data, &fhp->fh_handle.fh_raw, fl->fh.size);
/* Give whole file layout segments */
seg->offset = 0;
seg->length = NFS4_MAX_UINT64;
dprintk("GET: 0x%llx:0x%llx %d\n", seg->offset, seg->length,
seg->iomode);
return 0;
out_error:
seg->length = 0;
return nfserrno(error);
}
static __be32
nfsd4_ff_proc_getdeviceinfo(struct super_block *sb, struct svc_rqst *rqstp,
struct nfs4_client *clp, struct nfsd4_getdeviceinfo *gdp)
{
struct pnfs_ff_device_addr *da;
u16 port;
char addr[INET6_ADDRSTRLEN];
da = kzalloc_obj(struct pnfs_ff_device_addr);
if (!da)
return nfserrno(-ENOMEM);
gdp->gd_device = da;
da->version = 3;
da->minor_version = 0;
da->rsize = svc_max_payload(rqstp);
da->wsize = da->rsize;
rpc_ntop((struct sockaddr *)&rqstp->rq_daddr,
addr, INET6_ADDRSTRLEN);
if (rqstp->rq_daddr.ss_family == AF_INET) {
struct sockaddr_in *sin;
sin = (struct sockaddr_in *)&rqstp->rq_daddr;
port = ntohs(sin->sin_port);
snprintf(da->netaddr.netid, FF_NETID_LEN + 1, "tcp");
da->netaddr.netid_len = 3;
} else {
struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)&rqstp->rq_daddr;
port = ntohs(sin6->sin6_port);
snprintf(da->netaddr.netid, FF_NETID_LEN + 1, "tcp6");
da->netaddr.netid_len = 4;
}
da->netaddr.addr_len =
snprintf(da->netaddr.addr, FF_ADDR_LEN + 1,
"%s.%d.%d", addr, port >> 8, port & 0xff);
da->tightly_coupled = false;
return 0;
}
static __be32
nfsd4_ff_proc_layoutcommit(struct inode *inode, struct svc_rqst *rqstp,
struct nfsd4_layoutcommit *lcp)
{
return nfs_ok;
}
const struct nfsd4_layout_ops ff_layout_ops = {
.notify_types =
NOTIFY_DEVICEID4_DELETE | NOTIFY_DEVICEID4_CHANGE,
.disable_recalls = true,
.proc_getdeviceinfo = nfsd4_ff_proc_getdeviceinfo,
.encode_getdeviceinfo = nfsd4_ff_encode_getdeviceinfo,
.proc_layoutget = nfsd4_ff_proc_layoutget,
.encode_layoutget = nfsd4_ff_encode_layoutget,
.proc_layoutcommit = nfsd4_ff_proc_layoutcommit,
};
+124
View File
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2016 Tom Haynes <loghyr@primarydata.com>
*/
#include <linux/sunrpc/svc.h>
#include <linux/nfs4.h>
#include "nfsd.h"
#include "flexfilelayoutxdr.h"
#define NFSDDBG_FACILITY NFSDDBG_PNFS
struct ff_idmap {
char buf[11];
int len;
};
__be32
nfsd4_ff_encode_layoutget(struct xdr_stream *xdr,
const struct nfsd4_layoutget *lgp)
{
const struct pnfs_ff_layout *fl = lgp->lg_content;
int len, mirror_len, ds_len, fh_len;
__be32 *p;
/*
* Unlike nfsd4_encode_user, we know these will
* always be stringified.
*/
struct ff_idmap uid;
struct ff_idmap gid;
fh_len = 4 + fl->fh.size;
uid.len = sprintf(uid.buf, "%u", from_kuid(&init_user_ns, fl->uid));
gid.len = sprintf(gid.buf, "%u", from_kgid(&init_user_ns, fl->gid));
/* 8 + len for recording the length, name, and padding */
ds_len = 20 + sizeof(stateid_opaque_t) + 4 + fh_len +
8 + uid.len + 8 + gid.len;
mirror_len = 4 + ds_len;
/* The layout segment */
len = 20 + mirror_len;
p = xdr_reserve_space(xdr, sizeof(__be32) + len);
if (!p)
return nfserr_toosmall;
*p++ = cpu_to_be32(len);
p = xdr_encode_hyper(p, 0); /* stripe unit of 1 */
*p++ = cpu_to_be32(1); /* single mirror */
*p++ = cpu_to_be32(1); /* single data server */
p = svcxdr_encode_deviceid4(p, &fl->deviceid);
*p++ = cpu_to_be32(1); /* efficiency */
*p++ = cpu_to_be32(fl->stateid.si_generation);
p = xdr_encode_opaque_fixed(p, &fl->stateid.si_opaque,
sizeof(stateid_opaque_t));
*p++ = cpu_to_be32(1); /* single file handle */
p = xdr_encode_opaque(p, fl->fh.data, fl->fh.size);
p = xdr_encode_opaque(p, uid.buf, uid.len);
p = xdr_encode_opaque(p, gid.buf, gid.len);
*p++ = cpu_to_be32(fl->flags);
*p++ = cpu_to_be32(0); /* No stats collect hint */
return 0;
}
__be32
nfsd4_ff_encode_getdeviceinfo(struct xdr_stream *xdr,
const struct nfsd4_getdeviceinfo *gdp)
{
struct pnfs_ff_device_addr *da = gdp->gd_device;
int len;
int ver_len;
int addr_len;
__be32 *p;
/*
* See paragraph 5 of RFC 8881 S18.40.3.
*/
if (!gdp->gd_maxcount) {
if (xdr_stream_encode_u32(xdr, 0) != XDR_UNIT)
return nfserr_resource;
return nfs_ok;
}
/* len + padding for two strings */
addr_len = 16 + da->netaddr.netid_len + da->netaddr.addr_len;
ver_len = 20;
len = 4 + ver_len + 4 + addr_len;
p = xdr_reserve_space(xdr, len + sizeof(__be32));
if (!p)
return nfserr_resource;
/*
* Fill in the overall length and number of volumes at the beginning
* of the layout.
*/
*p++ = cpu_to_be32(len);
*p++ = cpu_to_be32(1); /* 1 netaddr */
p = xdr_encode_opaque(p, da->netaddr.netid, da->netaddr.netid_len);
p = xdr_encode_opaque(p, da->netaddr.addr, da->netaddr.addr_len);
*p++ = cpu_to_be32(1); /* 1 versions */
*p++ = cpu_to_be32(da->version);
*p++ = cpu_to_be32(da->minor_version);
*p++ = cpu_to_be32(da->rsize);
*p++ = cpu_to_be32(da->wsize);
*p++ = cpu_to_be32(da->tightly_coupled);
return 0;
}
+50
View File
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2016 Tom Haynes <loghyr@primarydata.com>
*/
#ifndef _NFSD_FLEXFILELAYOUTXDR_H
#define _NFSD_FLEXFILELAYOUTXDR_H 1
#include <linux/inet.h>
#include "xdr4.h"
#define FF_FLAGS_NO_LAYOUTCOMMIT 1
#define FF_FLAGS_NO_IO_THRU_MDS 2
#define FF_FLAGS_NO_READ_IO 4
struct xdr_stream;
#define FF_NETID_LEN (4)
#define FF_ADDR_LEN (INET6_ADDRSTRLEN + 8)
struct pnfs_ff_netaddr {
char netid[FF_NETID_LEN + 1];
char addr[FF_ADDR_LEN + 1];
u32 netid_len;
u32 addr_len;
};
struct pnfs_ff_device_addr {
struct pnfs_ff_netaddr netaddr;
u32 version;
u32 minor_version;
u32 rsize;
u32 wsize;
bool tightly_coupled;
};
struct pnfs_ff_layout {
u32 flags;
u32 stats_collect_hint;
kuid_t uid;
kgid_t gid;
struct nfsd4_deviceid deviceid;
stateid_t stateid;
struct nfs_fh fh;
};
__be32 nfsd4_ff_encode_getdeviceinfo(struct xdr_stream *xdr,
const struct nfsd4_getdeviceinfo *gdp);
__be32 nfsd4_ff_encode_layoutget(struct xdr_stream *xdr,
const struct nfsd4_layoutget *lgp);
#endif /* _NFSD_FLEXFILELAYOUTXDR_H */
+60
View File
@@ -0,0 +1,60 @@
/*
* Mapping of UID to name and vice versa.
*
* Copyright (c) 2002, 2003 The Regents of the University of
* Michigan. All rights reserved.
> *
* Marius Aamodt Eriksen <marius@umich.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef LINUX_NFSD_IDMAP_H
#define LINUX_NFSD_IDMAP_H
#include <linux/in.h>
#include <linux/sunrpc/svc.h>
#include <linux/nfs_idmap.h>
#ifdef CONFIG_NFSD_V4
int nfsd_idmap_init(struct net *);
void nfsd_idmap_shutdown(struct net *);
#else
static inline int nfsd_idmap_init(struct net *net)
{
return 0;
}
static inline void nfsd_idmap_shutdown(struct net *net)
{
}
#endif
__be32 nfsd_map_name_to_uid(struct svc_rqst *, const char *, size_t, kuid_t *);
__be32 nfsd_map_name_to_gid(struct svc_rqst *, const char *, size_t, kgid_t *);
__be32 nfsd4_encode_user(struct xdr_stream *, struct svc_rqst *, kuid_t);
__be32 nfsd4_encode_group(struct xdr_stream *, struct svc_rqst *, kgid_t);
#endif /* LINUX_NFSD_IDMAP_H */
+217
View File
@@ -0,0 +1,217 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NFS server support for local clients to bypass network stack
*
* Copyright (C) 2014 Weston Andros Adamson <dros@primarydata.com>
* Copyright (C) 2019 Trond Myklebust <trond.myklebust@hammerspace.com>
* Copyright (C) 2024 Mike Snitzer <snitzer@hammerspace.com>
* Copyright (C) 2024 NeilBrown <neilb@suse.de>
*/
#include <linux/exportfs.h>
#include <linux/sunrpc/svcauth.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs.h>
#include <linux/nfs_common.h>
#include <linux/nfslocalio.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_xdr.h>
#include <linux/string.h>
#include "nfsd.h"
#include "vfs.h"
#include "netns.h"
#include "filecache.h"
#include "cache.h"
/**
* nfsd_open_local_fh - lookup a local filehandle @nfs_fh and map to nfsd_file
*
* @net: 'struct net' to get the proper nfsd_net required for LOCALIO access
* @dom: 'struct auth_domain' required for LOCALIO access
* @rpc_clnt: rpc_clnt that the client established
* @cred: cred that the client established
* @nfs_fh: filehandle to lookup
* @pnf: place to find the nfsd_file, or store it if it was non-NULL
* @fmode: fmode_t to use for open
*
* This function maps a local fh to a path on a local filesystem.
* This is useful when the nfs client has the local server mounted - it can
* avoid all the NFS overhead with reads, writes and commits.
*
* On successful return, returned nfsd_file will have its nf_net member
* set. Caller (NFS client) is responsible for calling nfsd_net_put and
* nfsd_file_put (via nfs_to_nfsd_file_put_local).
*/
static struct nfsd_file *
nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
struct rpc_clnt *rpc_clnt, const struct cred *cred,
const struct nfs_fh *nfs_fh, struct nfsd_file __rcu **pnf,
const fmode_t fmode)
{
int mayflags = NFSD_MAY_LOCALIO;
struct svc_cred rq_cred;
struct svc_fh fh;
struct nfsd_file *localio;
__be32 beres;
if (nfs_fh->size > NFS4_FHSIZE)
return ERR_PTR(-EINVAL);
if (!nfsd_net_try_get(net))
return ERR_PTR(-ENXIO);
rcu_read_lock();
localio = nfsd_file_get(rcu_dereference(*pnf));
rcu_read_unlock();
if (localio)
return localio;
/* nfs_fh -> svc_fh */
fh_init(&fh, NFS4_FHSIZE);
fh.fh_handle.fh_size = nfs_fh->size;
memcpy(fh.fh_handle.fh_raw, nfs_fh->data, nfs_fh->size);
if (fmode & FMODE_READ)
mayflags |= NFSD_MAY_READ;
if (fmode & FMODE_WRITE)
mayflags |= NFSD_MAY_WRITE;
svcauth_map_clnt_to_svc_cred_local(rpc_clnt, cred, &rq_cred);
beres = nfsd_file_acquire_local(net, &rq_cred, dom,
&fh, mayflags, &localio);
if (beres)
localio = ERR_PTR(nfs_stat_to_errno(be32_to_cpu(beres)));
fh_put(&fh);
if (rq_cred.cr_group_info)
put_group_info(rq_cred.cr_group_info);
if (!IS_ERR(localio)) {
struct nfsd_file *new;
if (!nfsd_net_try_get(net)) {
nfsd_file_put(localio);
nfsd_net_put(net);
return ERR_PTR(-ENXIO);
}
nfsd_file_get(localio);
again:
new = unrcu_pointer(cmpxchg(pnf, NULL, RCU_INITIALIZER(localio)));
if (new) {
/* Some other thread installed an nfsd_file */
if (nfsd_file_get(new) == NULL)
goto again;
/*
* Drop the ref we were going to install (both file and
* net) and the one we were going to return (only file).
*/
nfsd_file_put(localio);
nfsd_net_put(net);
nfsd_file_put(localio);
localio = new;
}
} else
nfsd_net_put(net);
return localio;
}
static void nfsd_file_dio_alignment(struct nfsd_file *nf,
u32 *nf_dio_mem_align,
u32 *nf_dio_offset_align,
u32 *nf_dio_read_offset_align)
{
*nf_dio_mem_align = nf->nf_dio_mem_align;
*nf_dio_offset_align = nf->nf_dio_offset_align;
*nf_dio_read_offset_align = nf->nf_dio_read_offset_align;
}
static const struct nfsd_localio_operations nfsd_localio_ops = {
.nfsd_net_try_get = nfsd_net_try_get,
.nfsd_net_put = nfsd_net_put,
.nfsd_open_local_fh = nfsd_open_local_fh,
.nfsd_file_put_local = nfsd_file_put_local,
.nfsd_file_file = nfsd_file_file,
.nfsd_file_dio_alignment = nfsd_file_dio_alignment,
};
void nfsd_localio_ops_init(void)
{
nfs_to = &nfsd_localio_ops;
}
/*
* UUID_IS_LOCAL XDR functions
*/
static __be32 localio_proc_null(struct svc_rqst *rqstp)
{
return rpc_success;
}
struct localio_uuidarg {
uuid_t uuid;
};
static __be32 localio_proc_uuid_is_local(struct svc_rqst *rqstp)
{
struct localio_uuidarg *argp = rqstp->rq_argp;
struct net *net = SVC_NET(rqstp);
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
nfs_uuid_is_local(&argp->uuid, &nn->local_clients,
&nn->local_clients_lock,
net, rqstp->rq_client, THIS_MODULE);
return rpc_success;
}
static bool localio_decode_uuidarg(struct svc_rqst *rqstp,
struct xdr_stream *xdr)
{
struct localio_uuidarg *argp = rqstp->rq_argp;
u8 uuid[UUID_SIZE];
if (decode_opaque_fixed(xdr, uuid, UUID_SIZE))
return false;
import_uuid(&argp->uuid, uuid);
return true;
}
static const struct svc_procedure localio_procedures1[] = {
[LOCALIOPROC_NULL] = {
.pc_func = localio_proc_null,
.pc_decode = nfssvc_decode_voidarg,
.pc_encode = nfssvc_encode_voidres,
.pc_argsize = sizeof(struct nfsd_voidargs),
.pc_ressize = sizeof(struct nfsd_voidres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = 0,
.pc_name = "NULL",
},
[LOCALIOPROC_UUID_IS_LOCAL] = {
.pc_func = localio_proc_uuid_is_local,
.pc_decode = localio_decode_uuidarg,
.pc_encode = nfssvc_encode_voidres,
.pc_argsize = sizeof(struct localio_uuidarg),
.pc_argzero = sizeof(struct localio_uuidarg),
.pc_ressize = sizeof(struct nfsd_voidres),
.pc_cachetype = RC_NOCACHE,
.pc_name = "UUID_IS_LOCAL",
},
};
#define LOCALIO_NR_PROCEDURES ARRAY_SIZE(localio_procedures1)
static DEFINE_PER_CPU_ALIGNED(unsigned long,
localio_count[LOCALIO_NR_PROCEDURES]);
const struct svc_version localio_version1 = {
.vs_vers = 1,
.vs_nproc = LOCALIO_NR_PROCEDURES,
.vs_proc = localio_procedures1,
.vs_dispatch = nfsd_dispatch,
.vs_count = localio_count,
.vs_xdrsize = XDR_QUADLEN(UUID_SIZE),
.vs_hidden = true,
};
+110
View File
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file contains all the stubs needed when communicating with lockd.
* This level of indirection is necessary so we can run nfsd+lockd without
* requiring the nfs client to be compiled in/loaded, and vice versa.
*
* Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
*/
#include <linux/file.h>
#include <linux/lockd/bind.h>
#include "nfsd.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_LOCKD
/**
* nlm_fopen - Open an NFSD file
* @rqstp: NLM RPC procedure execution context
* @f: NFS file handle to be opened
* @filp: OUT: an opened struct file
* @flags: the POSIX open flags to use
*
* nlm_fopen() holds the dentry reference until nlm_fclose() releases it.
*
* Returns zero on success or a negative errno value if the file
* cannot be opened.
*/
static int nlm_fopen(struct svc_rqst *rqstp, struct nfs_fh *f,
struct file **filp, int flags)
{
__be32 nfserr;
int access;
struct svc_fh fh;
/* must initialize before using! but maxsize doesn't matter */
fh_init(&fh,0);
fh.fh_handle.fh_size = f->size;
memcpy(&fh.fh_handle.fh_raw, f->data, f->size);
fh.fh_export = NULL;
/*
* Allow BYPASS_GSS as some client implementations use AUTH_SYS
* for NLM even when GSS is used for NFS.
* Allow OWNER_OVERRIDE as permission might have been changed
* after the file was opened.
* Pass MAY_NLM so that authentication can be completely bypassed
* if NFSEXP_NOAUTHNLM is set. Some older clients use AUTH_NULL
* for NLM requests.
*/
access = (flags == O_WRONLY) ? NFSD_MAY_WRITE : NFSD_MAY_READ;
access |= NFSD_MAY_NLM | NFSD_MAY_OWNER_OVERRIDE | NFSD_MAY_BYPASS_GSS;
nfserr = nfsd_open(rqstp, &fh, S_IFREG, access, filp);
fh_put(&fh);
switch (nfserr) {
case nfs_ok:
break;
case nfserr_jukebox:
/*
* This error can indicate a presence of a conflicting
* delegation to an NLM lock request. Options are:
* (1) For now, drop this request and make the client
* retry. When delegation is returned, client's lock retry
* will complete.
* (2) NLM4_DENIED as per "spec" signals to the client
* that the lock is unavailable now but client can retry.
* Linux client implementation does not. It treats
* NLM4_DENIED same as NLM4_FAILED and fails the request.
* (3) For the future, treat this as blocked lock and try
* to callback when the delegation is returned but might
* not have a proper lock request to block on.
*/
return -EWOULDBLOCK;
case nfserr_stale:
return -ESTALE;
default:
return -ENOLCK;
}
return 0;
}
/**
* nlm_fclose - Close an NFSD file
* @filp: a struct file that was opened by nlm_fopen()
*/
static void
nlm_fclose(struct file *filp)
{
fput(filp);
}
static const struct nlmsvc_binding nfsd_nlm_ops = {
.fopen = nlm_fopen, /* open file for locking */
.fclose = nlm_fclose, /* close file */
};
void
nfsd_lockd_init(void)
{
dprintk("nfsd: initializing lockd\n");
nlmsvc_ops = &nfsd_nlm_ops;
}
void
nfsd_lockd_shutdown(void)
{
nlmsvc_ops = NULL;
}
+245
View File
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/nfsd.yaml */
/* YNL-GEN kernel source */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#include <net/netlink.h>
#include <net/genetlink.h>
#include "netlink.h"
#include <uapi/linux/nfsd_netlink.h>
/* Common nested types */
const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1] = {
[NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR] = { .type = NLA_U32, },
[NFSD_A_AUTH_FLAVOR_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff),
};
const struct nla_policy nfsd_expkey_nl_policy[NFSD_A_EXPKEY_PATH + 1] = {
[NFSD_A_EXPKEY_SEQNO] = { .type = NLA_U64, },
[NFSD_A_EXPKEY_CLIENT] = { .type = NLA_NUL_STRING, },
[NFSD_A_EXPKEY_FSIDTYPE] = { .type = NLA_U8, },
[NFSD_A_EXPKEY_FSID] = { .type = NLA_BINARY, },
[NFSD_A_EXPKEY_NEGATIVE] = { .type = NLA_FLAG, },
[NFSD_A_EXPKEY_EXPIRY] = { .type = NLA_U64, },
[NFSD_A_EXPKEY_PATH] = { .type = NLA_NUL_STRING, },
};
const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1] = {
[NFSD_A_FSLOCATION_HOST] = { .type = NLA_NUL_STRING, },
[NFSD_A_FSLOCATION_PATH] = { .type = NLA_NUL_STRING, },
};
const struct nla_policy nfsd_fslocations_nl_policy[NFSD_A_FSLOCATIONS_LOCATION + 1] = {
[NFSD_A_FSLOCATIONS_LOCATION] = NLA_POLICY_NESTED(nfsd_fslocation_nl_policy),
};
const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1] = {
[NFSD_A_SOCK_ADDR] = { .type = NLA_BINARY, },
[NFSD_A_SOCK_TRANSPORT_NAME] = { .type = NLA_NUL_STRING, },
};
const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1] = {
[NFSD_A_SVC_EXPORT_SEQNO] = { .type = NLA_U64, },
[NFSD_A_SVC_EXPORT_CLIENT] = { .type = NLA_NUL_STRING, },
[NFSD_A_SVC_EXPORT_PATH] = { .type = NLA_NUL_STRING, },
[NFSD_A_SVC_EXPORT_NEGATIVE] = { .type = NLA_FLAG, },
[NFSD_A_SVC_EXPORT_EXPIRY] = { .type = NLA_U64, },
[NFSD_A_SVC_EXPORT_ANON_UID] = { .type = NLA_U32, },
[NFSD_A_SVC_EXPORT_ANON_GID] = { .type = NLA_U32, },
[NFSD_A_SVC_EXPORT_FSLOCATIONS] = NLA_POLICY_NESTED(nfsd_fslocations_nl_policy),
[NFSD_A_SVC_EXPORT_UUID] = { .type = NLA_BINARY, },
[NFSD_A_SVC_EXPORT_SECINFO] = NLA_POLICY_NESTED(nfsd_auth_flavor_nl_policy),
[NFSD_A_SVC_EXPORT_XPRTSEC] = NLA_POLICY_MASK(NLA_U32, 0x7),
[NFSD_A_SVC_EXPORT_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff),
[NFSD_A_SVC_EXPORT_FSID] = { .type = NLA_S32, },
};
const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1] = {
[NFSD_A_VERSION_MAJOR] = { .type = NLA_U32, },
[NFSD_A_VERSION_MINOR] = { .type = NLA_U32, },
[NFSD_A_VERSION_ENABLED] = { .type = NLA_FLAG, },
};
/* NFSD_CMD_THREADS_SET - do */
static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_FH_KEY + 1] = {
[NFSD_A_SERVER_THREADS] = { .type = NLA_U32, },
[NFSD_A_SERVER_GRACETIME] = { .type = NLA_U32, },
[NFSD_A_SERVER_LEASETIME] = { .type = NLA_U32, },
[NFSD_A_SERVER_SCOPE] = { .type = NLA_NUL_STRING, },
[NFSD_A_SERVER_MIN_THREADS] = { .type = NLA_U32, },
[NFSD_A_SERVER_FH_KEY] = NLA_POLICY_EXACT_LEN(16),
};
/* NFSD_CMD_VERSION_SET - do */
static const struct nla_policy nfsd_version_set_nl_policy[NFSD_A_SERVER_PROTO_VERSION + 1] = {
[NFSD_A_SERVER_PROTO_VERSION] = NLA_POLICY_NESTED(nfsd_version_nl_policy),
};
/* NFSD_CMD_LISTENER_SET - do */
static const struct nla_policy nfsd_listener_set_nl_policy[NFSD_A_SERVER_SOCK_ADDR + 1] = {
[NFSD_A_SERVER_SOCK_ADDR] = NLA_POLICY_NESTED(nfsd_sock_nl_policy),
};
/* NFSD_CMD_POOL_MODE_SET - do */
static const struct nla_policy nfsd_pool_mode_set_nl_policy[NFSD_A_POOL_MODE_MODE + 1] = {
[NFSD_A_POOL_MODE_MODE] = { .type = NLA_NUL_STRING, },
};
/* NFSD_CMD_SVC_EXPORT_SET_REQS - do */
static const struct nla_policy nfsd_svc_export_set_reqs_nl_policy[NFSD_A_SVC_EXPORT_REQS_REQUESTS + 1] = {
[NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy),
};
/* NFSD_CMD_EXPKEY_SET_REQS - do */
static const struct nla_policy nfsd_expkey_set_reqs_nl_policy[NFSD_A_EXPKEY_REQS_REQUESTS + 1] = {
[NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy),
};
/* NFSD_CMD_CACHE_FLUSH - do */
static const struct nla_policy nfsd_cache_flush_nl_policy[NFSD_A_CACHE_FLUSH_MASK + 1] = {
[NFSD_A_CACHE_FLUSH_MASK] = NLA_POLICY_MASK(NLA_U32, 0x3),
};
/* NFSD_CMD_UNLOCK_IP - do */
static const struct nla_policy nfsd_unlock_ip_nl_policy[NFSD_A_UNLOCK_IP_ADDRESS + 1] = {
[NFSD_A_UNLOCK_IP_ADDRESS] = NLA_POLICY_MIN_LEN(16),
};
/* NFSD_CMD_UNLOCK_FILESYSTEM - do */
static const struct nla_policy nfsd_unlock_filesystem_nl_policy[NFSD_A_UNLOCK_FILESYSTEM_PATH + 1] = {
[NFSD_A_UNLOCK_FILESYSTEM_PATH] = { .type = NLA_NUL_STRING, },
};
/* NFSD_CMD_UNLOCK_EXPORT - do */
static const struct nla_policy nfsd_unlock_export_nl_policy[NFSD_A_UNLOCK_EXPORT_PATH + 1] = {
[NFSD_A_UNLOCK_EXPORT_PATH] = { .type = NLA_NUL_STRING, },
};
/* Ops table for nfsd */
static const struct genl_split_ops nfsd_nl_ops[] = {
{
.cmd = NFSD_CMD_RPC_STATUS_GET,
.dumpit = nfsd_nl_rpc_status_get_dumpit,
.flags = GENL_CMD_CAP_DUMP,
},
{
.cmd = NFSD_CMD_THREADS_SET,
.doit = nfsd_nl_threads_set_doit,
.policy = nfsd_threads_set_nl_policy,
.maxattr = NFSD_A_SERVER_FH_KEY,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_THREADS_GET,
.doit = nfsd_nl_threads_get_doit,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_VERSION_SET,
.doit = nfsd_nl_version_set_doit,
.policy = nfsd_version_set_nl_policy,
.maxattr = NFSD_A_SERVER_PROTO_VERSION,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_VERSION_GET,
.doit = nfsd_nl_version_get_doit,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_LISTENER_SET,
.doit = nfsd_nl_listener_set_doit,
.policy = nfsd_listener_set_nl_policy,
.maxattr = NFSD_A_SERVER_SOCK_ADDR,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_LISTENER_GET,
.doit = nfsd_nl_listener_get_doit,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_POOL_MODE_SET,
.doit = nfsd_nl_pool_mode_set_doit,
.policy = nfsd_pool_mode_set_nl_policy,
.maxattr = NFSD_A_POOL_MODE_MODE,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_POOL_MODE_GET,
.doit = nfsd_nl_pool_mode_get_doit,
.flags = GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_SVC_EXPORT_GET_REQS,
.dumpit = nfsd_nl_svc_export_get_reqs_dumpit,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
},
{
.cmd = NFSD_CMD_SVC_EXPORT_SET_REQS,
.doit = nfsd_nl_svc_export_set_reqs_doit,
.policy = nfsd_svc_export_set_reqs_nl_policy,
.maxattr = NFSD_A_SVC_EXPORT_REQS_REQUESTS,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_EXPKEY_GET_REQS,
.dumpit = nfsd_nl_expkey_get_reqs_dumpit,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
},
{
.cmd = NFSD_CMD_EXPKEY_SET_REQS,
.doit = nfsd_nl_expkey_set_reqs_doit,
.policy = nfsd_expkey_set_reqs_nl_policy,
.maxattr = NFSD_A_EXPKEY_REQS_REQUESTS,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_CACHE_FLUSH,
.doit = nfsd_nl_cache_flush_doit,
.policy = nfsd_cache_flush_nl_policy,
.maxattr = NFSD_A_CACHE_FLUSH_MASK,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_UNLOCK_IP,
.doit = nfsd_nl_unlock_ip_doit,
.policy = nfsd_unlock_ip_nl_policy,
.maxattr = NFSD_A_UNLOCK_IP_ADDRESS,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_UNLOCK_FILESYSTEM,
.doit = nfsd_nl_unlock_filesystem_doit,
.policy = nfsd_unlock_filesystem_nl_policy,
.maxattr = NFSD_A_UNLOCK_FILESYSTEM_PATH,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
.cmd = NFSD_CMD_UNLOCK_EXPORT,
.doit = nfsd_nl_unlock_export_doit,
.policy = nfsd_unlock_export_nl_policy,
.maxattr = NFSD_A_UNLOCK_EXPORT_PATH,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
};
static const struct genl_multicast_group nfsd_nl_mcgrps[] = {
[NFSD_NLGRP_NONE] = { "none", },
[NFSD_NLGRP_EXPORTD] = { "exportd", },
};
struct genl_family nfsd_nl_family __ro_after_init = {
.name = NFSD_FAMILY_NAME,
.version = NFSD_FAMILY_VERSION,
.netnsok = true,
.parallel_ops = true,
.module = THIS_MODULE,
.split_ops = nfsd_nl_ops,
.n_split_ops = ARRAY_SIZE(nfsd_nl_ops),
.mcgrps = nfsd_nl_mcgrps,
.n_mcgrps = ARRAY_SIZE(nfsd_nl_mcgrps),
};
+53
View File
@@ -0,0 +1,53 @@
/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
/* Do not edit directly, auto-generated from: */
/* Documentation/netlink/specs/nfsd.yaml */
/* YNL-GEN kernel header */
/* To regenerate run: tools/net/ynl/ynl-regen.sh */
#ifndef _LINUX_NFSD_GEN_H
#define _LINUX_NFSD_GEN_H
#include <net/netlink.h>
#include <net/genetlink.h>
#include <uapi/linux/nfsd_netlink.h>
/* Common nested types */
extern const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1];
extern const struct nla_policy nfsd_expkey_nl_policy[NFSD_A_EXPKEY_PATH + 1];
extern const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1];
extern const struct nla_policy nfsd_fslocations_nl_policy[NFSD_A_FSLOCATIONS_LOCATION + 1];
extern const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1];
extern const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1];
extern const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1];
int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_threads_get_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_version_set_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_version_get_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_pool_mode_set_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb,
struct genl_info *info);
int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info);
int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info);
enum {
NFSD_NLGRP_NONE,
NFSD_NLGRP_EXPORTD,
};
extern struct genl_family nfsd_nl_family;
#endif /* _LINUX_NFSD_GEN_H */
+247
View File
@@ -0,0 +1,247 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* per net namespace data structures for nfsd
*
* Copyright (C) 2012, Jeff Layton <jlayton@redhat.com>
*/
#ifndef __NFSD_NETNS_H__
#define __NFSD_NETNS_H__
#include <net/net_namespace.h>
#include <net/netns/generic.h>
#include <linux/filelock.h>
#include <linux/nfs4.h>
#include <linux/percpu_counter.h>
#include <linux/percpu-refcount.h>
#include <linux/siphash.h>
#include <linux/sunrpc/stats.h>
/* Hash tables for nfs4_clientid state */
#define CLIENT_HASH_BITS 4
#define CLIENT_HASH_SIZE (1 << CLIENT_HASH_BITS)
#define CLIENT_HASH_MASK (CLIENT_HASH_SIZE - 1)
#define SESSION_HASH_SIZE 512
struct cld_net;
struct nfsd_net_cb;
struct nfsd4_client_tracking_ops;
enum {
/* cache misses due only to checksum comparison failures */
NFSD_STATS_PAYLOAD_MISSES,
/* amount of memory (in bytes) currently consumed by the DRC */
NFSD_STATS_DRC_MEM_USAGE,
NFSD_STATS_RC_HITS, /* repcache hits */
NFSD_STATS_RC_MISSES, /* repcache misses */
NFSD_STATS_RC_NOCACHE, /* uncached reqs */
NFSD_STATS_FH_STALE, /* FH stale error */
NFSD_STATS_IO_READ, /* bytes returned to read requests */
NFSD_STATS_IO_WRITE, /* bytes passed in write requests */
#ifdef CONFIG_NFSD_V4
NFSD_STATS_FIRST_NFS4_OP, /* count of individual nfsv4 operations */
NFSD_STATS_LAST_NFS4_OP = NFSD_STATS_FIRST_NFS4_OP + LAST_NFS4_OP,
#define NFSD_STATS_NFS4_OP(op) (NFSD_STATS_FIRST_NFS4_OP + (op))
NFSD_STATS_WDELEG_GETATTR, /* count of getattr conflict with wdeleg */
#endif
NFSD_STATS_COUNTERS_NUM
};
/*
* Represents a nfsd "container". With respect to nfsv4 state tracking, the
* fields of interest are the *_id_hashtbls and the *_name_tree. These track
* the nfs4_client objects by either short or long form clientid.
*
* Each nfsd_net runs a nfs4_laundromat workqueue job when necessary to clean
* up expired clients and delegations within the container.
*/
struct nfsd_net {
struct cld_net *cld_net;
struct cache_detail *svc_expkey_cache;
struct cache_detail *svc_export_cache;
struct cache_detail *idtoname_cache;
struct cache_detail *nametoid_cache;
struct lock_manager nfsd4_manager;
bool grace_ended;
bool grace_end_forced;
time64_t boot_time;
struct dentry *nfsd_client_dir;
/*
* reclaim_str_hashtbl[] holds known client info from previous reset/reboot
* used in reboot/reset lease grace period processing
*
* conf_id_hashtbl[], and conf_name_tree hold confirmed
* setclientid_confirmed info.
*
* unconf_str_hastbl[] and unconf_name_tree hold unconfirmed
* setclientid info.
*/
struct list_head *reclaim_str_hashtbl;
int reclaim_str_hashtbl_size;
struct list_head *conf_id_hashtbl;
struct rb_root conf_name_tree;
struct list_head *unconf_id_hashtbl;
struct rb_root unconf_name_tree;
struct list_head *sessionid_hashtbl;
/*
* client_lru holds client queue ordered by nfs4_client.cl_time
* for lease renewal.
*
* close_lru holds (open) stateowner queue ordered by nfs4_stateowner.so_time
* for last close replay.
*
* All of the above fields are protected by the client_mutex.
*/
struct list_head client_lru;
struct list_head close_lru;
/* protects del_recall_lru and delegation hash/unhash */
spinlock_t deleg_lock ____cacheline_aligned;
struct list_head del_recall_lru;
/* protected by blocked_locks_lock */
struct list_head blocked_locks_lru;
struct delayed_work laundromat_work;
/* client_lock protects the client lru list and session hash table */
spinlock_t client_lock;
/* protects blocked_locks_lru */
spinlock_t blocked_locks_lock;
struct file *rec_file;
bool in_grace;
const struct nfsd4_client_tracking_ops *client_tracking_ops;
time64_t nfsd4_lease;
time64_t nfsd4_grace;
bool somebody_reclaimed;
bool track_reclaim_completes;
atomic_t nr_reclaim_complete;
bool nfsd_net_up;
bool lockd_up;
seqlock_t writeverf_lock;
unsigned char writeverf[8];
/*
* Minimum number of threads to run per pool. If 0 then the
* min == max requested number of threads.
*/
unsigned int min_threads;
u32 clientid_base;
u32 clientid_counter;
u32 clverifier_counter;
struct svc_info nfsd_info;
#define nfsd_serv nfsd_info.serv
struct percpu_ref nfsd_net_ref;
struct completion nfsd_net_confirm_done;
struct completion nfsd_net_free_done;
/*
* clientid and stateid data for construction of net unique COPY
* stateids.
*/
u32 s2s_cp_cl_id;
struct idr s2s_cp_stateids;
spinlock_t s2s_cp_lock;
atomic_t pending_async_copies;
/*
* Version information
*/
bool nfsd_versions[NFSD_MAXVERS + 1];
bool nfsd4_minorversions[NFSD_SUPPORTED_MINOR_VERSION + 1];
/*
* Duplicate reply cache
*/
struct nfsd_drc_bucket *drc_hashtbl;
/* max number of entries allowed in the cache */
unsigned int max_drc_entries;
/* number of significant bits in the hash value */
unsigned int maskbits;
unsigned int drc_hashsize;
/*
* Stats and other tracking of on the duplicate reply cache.
* The longest_chain* fields are modified with only the per-bucket
* cache lock, which isn't really safe and should be fixed if we want
* these statistics to be completely accurate.
*/
/* total number of entries */
atomic_t num_drc_entries;
/* Per-netns stats counters */
struct percpu_counter counter[NFSD_STATS_COUNTERS_NUM];
/* sunrpc svc stats */
struct svc_stat nfsd_svcstats;
/* longest hash chain seen */
unsigned int longest_chain;
/* size of cache when we saw the longest hash chain */
unsigned int longest_chain_cachesize;
struct shrinker *nfsd_reply_cache_shrinker;
/* tracking server-to-server copy mounts */
spinlock_t nfsd_ssc_lock;
struct list_head nfsd_ssc_mount_list;
wait_queue_head_t nfsd_ssc_waitq;
/* utsname taken from the process that starts the server */
char nfsd_name[UNX_MAXNODENAME+1];
struct nfsd_fcache_disposal *fcache_disposal;
siphash_key_t siphash_key;
atomic_t nfs4_client_count;
int nfs4_max_clients;
atomic_t nfsd_courtesy_clients;
struct shrinker *nfsd_client_shrinker;
struct work_struct nfsd_shrinker_work;
/* last time an admin-revoke happened for NFSv4.0 */
time64_t nfs40_last_revoke;
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
/* Local clients to be invalidated when net is shut down */
spinlock_t local_clients_lock;
struct list_head local_clients;
#endif
siphash_key_t *fh_key;
struct nfsd_net_cb *nfsd_cb;
};
/* Simple check to find out if a given net was properly initialized */
#define nfsd_netns_ready(nn) ((nn)->sessionid_hashtbl)
extern bool nfsd_support_version(int vers);
extern unsigned int nfsd_net_id;
bool nfsd_net_try_get(struct net *net);
void nfsd_net_put(struct net *net);
void nfsd_copy_write_verifier(__be32 verf[2], struct nfsd_net *nn);
void nfsd_reset_write_verifier(struct nfsd_net *nn);
#endif /* __NFSD_NETNS_H__ */
+396
View File
@@ -0,0 +1,396 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Process version 2 NFSACL requests.
*
* Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de>
*/
#include "nfsd.h"
/* FIXME: nfsacl.h is a broken header */
#include <linux/nfsacl.h>
#include <linux/gfp.h>
#include "cache.h"
#include "xdr3.h"
#include "vfs.h"
#define NFSDDBG_FACILITY NFSDDBG_PROC
/*
* NULL call.
*/
static __be32
nfsacld_proc_null(struct svc_rqst *rqstp)
{
return rpc_success;
}
/*
* Get the Access and/or Default ACL of a file.
*/
static __be32 nfsacld_proc_getacl(struct svc_rqst *rqstp)
{
struct nfsd3_getaclargs *argp = rqstp->rq_argp;
struct nfsd3_getaclres *resp = rqstp->rq_resp;
struct posix_acl *acl;
struct inode *inode;
svc_fh *fh;
dprintk("nfsd: GETACL(2acl) %s\n", SVCFH_fmt(&argp->fh));
fh = fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
if (resp->status != nfs_ok)
goto out;
inode = d_inode(fh->fh_dentry);
if (argp->mask & ~NFS_ACL_MASK) {
resp->status = nfserr_io;
goto out;
}
resp->mask = argp->mask;
resp->status = fh_getattr(fh, &resp->stat);
if (resp->status != nfs_ok)
goto out;
if (resp->mask & (NFS_ACL|NFS_ACLCNT)) {
acl = get_inode_acl(inode, ACL_TYPE_ACCESS);
if (acl == NULL) {
/* Solaris returns the inode's minimum ACL. */
acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
}
if (IS_ERR(acl)) {
resp->status = nfserrno(PTR_ERR(acl));
goto fail;
}
resp->acl_access = acl;
}
if (resp->mask & (NFS_DFACL|NFS_DFACLCNT)) {
/* Check how Solaris handles requests for the Default ACL
of a non-directory! */
acl = get_inode_acl(inode, ACL_TYPE_DEFAULT);
if (IS_ERR(acl)) {
resp->status = nfserrno(PTR_ERR(acl));
goto fail;
}
resp->acl_default = acl;
}
/* resp->acl_{access,default} are released in nfssvc_release_getacl. */
out:
return rpc_success;
fail:
posix_acl_release(resp->acl_access);
posix_acl_release(resp->acl_default);
resp->acl_access = NULL;
resp->acl_default = NULL;
goto out;
}
/*
* Set the Access and/or Default ACL of a file.
*/
static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp)
{
struct nfsd3_setaclargs *argp = rqstp->rq_argp;
struct nfsd_attrstat *resp = rqstp->rq_resp;
struct inode *inode;
svc_fh *fh;
int error;
dprintk("nfsd: SETACL(2acl) %s\n", SVCFH_fmt(&argp->fh));
fh = fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_SATTR);
if (resp->status != nfs_ok)
goto out;
inode = d_inode(fh->fh_dentry);
error = fh_want_write(fh);
if (error)
goto out_errno;
inode_lock(inode);
error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_ACCESS,
argp->acl_access);
if (error)
goto out_drop_lock;
error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_DEFAULT,
argp->acl_default);
if (error)
goto out_drop_lock;
inode_unlock(inode);
fh_drop_write(fh);
resp->status = fh_getattr(fh, &resp->stat);
out:
/* argp->acl_{access,default} are released in nfsaclsvc_release_setacl. */
return rpc_success;
out_drop_lock:
inode_unlock(inode);
fh_drop_write(fh);
out_errno:
resp->status = nfserrno(error);
goto out;
}
/*
* Check file attributes
*/
static __be32 nfsacld_proc_getattr(struct svc_rqst *rqstp)
{
struct nfsd_fhandle *argp = rqstp->rq_argp;
struct nfsd_attrstat *resp = rqstp->rq_resp;
dprintk("nfsd: GETATTR %s\n", SVCFH_fmt(&argp->fh));
fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
if (resp->status != nfs_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
return rpc_success;
}
/*
* Check file access
*/
static __be32 nfsacld_proc_access(struct svc_rqst *rqstp)
{
struct nfsd3_accessargs *argp = rqstp->rq_argp;
struct nfsd3_accessres *resp = rqstp->rq_resp;
dprintk("nfsd: ACCESS(2acl) %s 0x%x\n",
SVCFH_fmt(&argp->fh),
argp->access);
fh_copy(&resp->fh, &argp->fh);
resp->access = argp->access;
resp->status = nfsd_access(rqstp, &resp->fh, &resp->access, NULL);
if (resp->status != nfs_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
return rpc_success;
}
/*
* XDR decode functions
*/
static bool
nfsaclsvc_decode_getaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_getaclargs *argp = rqstp->rq_argp;
if (!svcxdr_decode_fhandle(xdr, &argp->fh))
return false;
if (xdr_stream_decode_u32(xdr, &argp->mask) < 0)
return false;
return true;
}
static bool
nfsaclsvc_decode_setaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_setaclargs *argp = rqstp->rq_argp;
if (!svcxdr_decode_fhandle(xdr, &argp->fh))
return false;
if (xdr_stream_decode_u32(xdr, &argp->mask) < 0)
return false;
if (argp->mask & ~NFS_ACL_MASK)
return false;
if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NFS_ACL) ?
&argp->acl_access : NULL))
return false;
if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NFS_DFACL) ?
&argp->acl_default : NULL))
return false;
return true;
}
static bool
nfsaclsvc_decode_accessargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_accessargs *args = rqstp->rq_argp;
if (!svcxdr_decode_fhandle(xdr, &args->fh))
return false;
if (xdr_stream_decode_u32(xdr, &args->access) < 0)
return false;
return true;
}
/*
* XDR encode functions
*/
/* GETACL */
static bool
nfsaclsvc_encode_getaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_getaclres *resp = rqstp->rq_resp;
struct dentry *dentry = resp->fh.fh_dentry;
struct inode *inode;
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
if (dentry == NULL || d_really_is_negative(dentry))
return true;
inode = d_inode(dentry);
if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
return false;
if (xdr_stream_encode_u32(xdr, resp->mask) < 0)
return false;
if (!nfs_stream_encode_acl(xdr, inode, resp->acl_access,
resp->mask & NFS_ACL, 0))
return false;
if (!nfs_stream_encode_acl(xdr, inode, resp->acl_default,
resp->mask & NFS_DFACL, NFS_ACL_DEFAULT))
return false;
return true;
}
/* ACCESS */
static bool
nfsaclsvc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_accessres *resp = rqstp->rq_resp;
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
case nfs_ok:
if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
return false;
if (xdr_stream_encode_u32(xdr, resp->access) < 0)
return false;
break;
}
return true;
}
/*
* XDR release functions
*/
static void nfsaclsvc_release_getacl(struct svc_rqst *rqstp)
{
struct nfsd3_getaclres *resp = rqstp->rq_resp;
fh_put(&resp->fh);
posix_acl_release(resp->acl_access);
posix_acl_release(resp->acl_default);
}
static void nfsaclsvc_release_access(struct svc_rqst *rqstp)
{
struct nfsd3_accessres *resp = rqstp->rq_resp;
fh_put(&resp->fh);
}
static void nfsaclsvc_release_setacl(struct svc_rqst *rqstp)
{
struct nfsd3_setaclargs *argp = rqstp->rq_argp;
struct nfsd_attrstat *resp = rqstp->rq_resp;
fh_put(&resp->fh);
posix_acl_release(argp->acl_access);
posix_acl_release(argp->acl_default);
}
#define ST 1 /* status*/
#define AT 21 /* attributes */
#define pAT (1+AT) /* post attributes - conditional */
#define ACL (1+NFS_ACL_MAX_ENTRIES*3) /* Access Control List */
static const struct svc_procedure nfsd_acl_procedures2[5] = {
[ACLPROC2_NULL] = {
.pc_func = nfsacld_proc_null,
.pc_decode = nfssvc_decode_voidarg,
.pc_encode = nfssvc_encode_voidres,
.pc_argsize = sizeof(struct nfsd_voidargs),
.pc_argzero = sizeof(struct nfsd_voidargs),
.pc_ressize = sizeof(struct nfsd_voidres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST,
.pc_name = "NULL",
},
[ACLPROC2_GETACL] = {
.pc_func = nfsacld_proc_getacl,
.pc_decode = nfsaclsvc_decode_getaclargs,
.pc_encode = nfsaclsvc_encode_getaclres,
.pc_release = nfsaclsvc_release_getacl,
.pc_argsize = sizeof(struct nfsd3_getaclargs),
.pc_argzero = sizeof(struct nfsd3_getaclargs),
.pc_ressize = sizeof(struct nfsd3_getaclres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+1+2*(1+ACL),
.pc_name = "GETACL",
},
[ACLPROC2_SETACL] = {
.pc_func = nfsacld_proc_setacl,
.pc_decode = nfsaclsvc_decode_setaclargs,
.pc_encode = nfssvc_encode_attrstatres,
.pc_release = nfsaclsvc_release_setacl,
.pc_argsize = sizeof(struct nfsd3_setaclargs),
.pc_argzero = sizeof(struct nfsd3_setaclargs),
.pc_ressize = sizeof(struct nfsd_attrstat),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+AT,
.pc_name = "SETACL",
},
[ACLPROC2_GETATTR] = {
.pc_func = nfsacld_proc_getattr,
.pc_decode = nfssvc_decode_fhandleargs,
.pc_encode = nfssvc_encode_attrstatres,
.pc_release = nfssvc_release_attrstat,
.pc_argsize = sizeof(struct nfsd_fhandle),
.pc_argzero = sizeof(struct nfsd_fhandle),
.pc_ressize = sizeof(struct nfsd_attrstat),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+AT,
.pc_name = "GETATTR",
},
[ACLPROC2_ACCESS] = {
.pc_func = nfsacld_proc_access,
.pc_decode = nfsaclsvc_decode_accessargs,
.pc_encode = nfsaclsvc_encode_accessres,
.pc_release = nfsaclsvc_release_access,
.pc_argsize = sizeof(struct nfsd3_accessargs),
.pc_argzero = sizeof(struct nfsd3_accessargs),
.pc_ressize = sizeof(struct nfsd3_accessres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+AT+1,
.pc_name = "SETATTR",
},
};
static DEFINE_PER_CPU_ALIGNED(unsigned long,
nfsd_acl_count2[ARRAY_SIZE(nfsd_acl_procedures2)]);
const struct svc_version nfsd_acl_version2 = {
.vs_vers = 2,
.vs_nproc = ARRAY_SIZE(nfsd_acl_procedures2),
.vs_proc = nfsd_acl_procedures2,
.vs_count = nfsd_acl_count2,
.vs_dispatch = nfsd_dispatch,
.vs_xdrsize = NFS3_SVC_XDRSIZE,
};
+286
View File
@@ -0,0 +1,286 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Process version 3 NFSACL requests.
*
* Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de>
*/
#include "nfsd.h"
/* FIXME: nfsacl.h is a broken header */
#include <linux/nfsacl.h>
#include <linux/gfp.h>
#include "cache.h"
#include "xdr3.h"
#include "vfs.h"
/*
* NULL call.
*/
static __be32
nfsd3_proc_null(struct svc_rqst *rqstp)
{
return rpc_success;
}
/*
* Get the Access and/or Default ACL of a file.
*/
static __be32 nfsd3_proc_getacl(struct svc_rqst *rqstp)
{
struct nfsd3_getaclargs *argp = rqstp->rq_argp;
struct nfsd3_getaclres *resp = rqstp->rq_resp;
struct posix_acl *acl;
struct inode *inode;
svc_fh *fh;
fh = fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_NOP);
if (resp->status != nfs_ok)
goto out;
inode = d_inode(fh->fh_dentry);
if (argp->mask & ~NFS_ACL_MASK) {
resp->status = nfserr_inval;
goto out;
}
resp->mask = argp->mask;
if (resp->mask & (NFS_ACL|NFS_ACLCNT)) {
acl = get_inode_acl(inode, ACL_TYPE_ACCESS);
if (acl == NULL) {
/* Solaris returns the inode's minimum ACL. */
acl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
}
if (IS_ERR(acl)) {
resp->status = nfserrno(PTR_ERR(acl));
goto fail;
}
resp->acl_access = acl;
}
if (resp->mask & (NFS_DFACL|NFS_DFACLCNT)) {
/* Check how Solaris handles requests for the Default ACL
of a non-directory! */
acl = get_inode_acl(inode, ACL_TYPE_DEFAULT);
if (IS_ERR(acl)) {
resp->status = nfserrno(PTR_ERR(acl));
goto fail;
}
resp->acl_default = acl;
}
/* resp->acl_{access,default} are released in nfs3svc_release_getacl. */
out:
return rpc_success;
fail:
posix_acl_release(resp->acl_access);
posix_acl_release(resp->acl_default);
resp->acl_access = NULL;
resp->acl_default = NULL;
goto out;
}
/*
* Set the Access and/or Default ACL of a file.
*/
static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp)
{
struct nfsd3_setaclargs *argp = rqstp->rq_argp;
struct nfsd3_attrstat *resp = rqstp->rq_resp;
struct inode *inode;
svc_fh *fh;
int error;
fh = fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0, NFSD_MAY_SATTR);
if (resp->status != nfs_ok)
goto out;
inode = d_inode(fh->fh_dentry);
error = fh_want_write(fh);
if (error)
goto out_errno;
inode_lock(inode);
error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_ACCESS,
argp->acl_access);
if (error)
goto out_drop_lock;
error = set_posix_acl(&nop_mnt_idmap, fh->fh_dentry, ACL_TYPE_DEFAULT,
argp->acl_default);
out_drop_lock:
inode_unlock(inode);
fh_drop_write(fh);
out_errno:
resp->status = nfserrno(error);
out:
/* argp->acl_{access,default} are released in nfs3svc_release_setacl. */
return rpc_success;
}
/*
* XDR decode functions
*/
static bool
nfs3svc_decode_getaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_getaclargs *args = rqstp->rq_argp;
if (!svcxdr_decode_nfs_fh3(xdr, &args->fh))
return false;
if (xdr_stream_decode_u32(xdr, &args->mask) < 0)
return false;
return true;
}
static bool
nfs3svc_decode_setaclargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_setaclargs *argp = rqstp->rq_argp;
if (!svcxdr_decode_nfs_fh3(xdr, &argp->fh))
return false;
if (xdr_stream_decode_u32(xdr, &argp->mask) < 0)
return false;
if (argp->mask & ~NFS_ACL_MASK)
return false;
if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NFS_ACL) ?
&argp->acl_access : NULL))
return false;
if (!nfs_stream_decode_acl(xdr, NULL, (argp->mask & NFS_DFACL) ?
&argp->acl_default : NULL))
return false;
return true;
}
/*
* XDR encode functions
*/
/* GETACL */
static bool
nfs3svc_encode_getaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_getaclres *resp = rqstp->rq_resp;
struct dentry *dentry = resp->fh.fh_dentry;
struct inode *inode;
if (!svcxdr_encode_nfsstat3(xdr, resp->status))
return false;
switch (resp->status) {
case nfs_ok:
inode = d_inode(dentry);
if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
return false;
if (xdr_stream_encode_u32(xdr, resp->mask) < 0)
return false;
if (!nfs_stream_encode_acl(xdr, inode, resp->acl_access,
resp->mask & NFS_ACL, 0))
return false;
if (!nfs_stream_encode_acl(xdr, inode, resp->acl_default,
resp->mask & NFS_DFACL,
NFS_ACL_DEFAULT))
return false;
break;
default:
if (!svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh))
return false;
}
return true;
}
/* SETACL */
static bool
nfs3svc_encode_setaclres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd3_attrstat *resp = rqstp->rq_resp;
return svcxdr_encode_nfsstat3(xdr, resp->status) &&
svcxdr_encode_post_op_attr(rqstp, xdr, &resp->fh);
}
/*
* XDR release functions
*/
static void nfs3svc_release_getacl(struct svc_rqst *rqstp)
{
struct nfsd3_getaclres *resp = rqstp->rq_resp;
fh_put(&resp->fh);
posix_acl_release(resp->acl_access);
posix_acl_release(resp->acl_default);
}
static void nfs3svc_release_setacl(struct svc_rqst *rqstp)
{
struct nfsd3_setaclargs *argp = rqstp->rq_argp;
struct nfsd3_attrstat *resp = rqstp->rq_resp;
fh_put(&resp->fh);
posix_acl_release(argp->acl_access);
posix_acl_release(argp->acl_default);
}
#define ST 1 /* status*/
#define AT 21 /* attributes */
#define pAT (1+AT) /* post attributes - conditional */
#define ACL (1+NFS_ACL_MAX_ENTRIES*3) /* Access Control List */
static const struct svc_procedure nfsd_acl_procedures3[3] = {
[ACLPROC3_NULL] = {
.pc_func = nfsd3_proc_null,
.pc_decode = nfssvc_decode_voidarg,
.pc_encode = nfssvc_encode_voidres,
.pc_argsize = sizeof(struct nfsd_voidargs),
.pc_argzero = sizeof(struct nfsd_voidargs),
.pc_ressize = sizeof(struct nfsd_voidres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST,
.pc_name = "NULL",
},
[ACLPROC3_GETACL] = {
.pc_func = nfsd3_proc_getacl,
.pc_decode = nfs3svc_decode_getaclargs,
.pc_encode = nfs3svc_encode_getaclres,
.pc_release = nfs3svc_release_getacl,
.pc_argsize = sizeof(struct nfsd3_getaclargs),
.pc_argzero = sizeof(struct nfsd3_getaclargs),
.pc_ressize = sizeof(struct nfsd3_getaclres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+1+2*(1+ACL),
.pc_name = "GETACL",
},
[ACLPROC3_SETACL] = {
.pc_func = nfsd3_proc_setacl,
.pc_decode = nfs3svc_decode_setaclargs,
.pc_encode = nfs3svc_encode_setaclres,
.pc_release = nfs3svc_release_setacl,
.pc_argsize = sizeof(struct nfsd3_setaclargs),
.pc_argzero = sizeof(struct nfsd3_setaclargs),
.pc_ressize = sizeof(struct nfsd3_attrstat),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+pAT,
.pc_name = "SETACL",
},
};
static DEFINE_PER_CPU_ALIGNED(unsigned long,
nfsd_acl_count3[ARRAY_SIZE(nfsd_acl_procedures3)]);
const struct svc_version nfsd_acl_version3 = {
.vs_vers = 3,
.vs_nproc = ARRAY_SIZE(nfsd_acl_procedures3),
.vs_proc = nfsd_acl_procedures3,
.vs_count = nfsd_acl_count3,
.vs_dispatch = nfsd_dispatch,
.vs_xdrsize = NFS3_SVC_XDRSIZE,
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+884
View File
@@ -0,0 +1,884 @@
/*
* Common NFSv4 ACL handling code.
*
* Copyright (c) 2002, 2003 The Regents of the University of Michigan.
* All rights reserved.
*
* Marius Aamodt Eriksen <marius@umich.edu>
* Jeff Sedlak <jsedlak@umich.edu>
* J. Bruce Fields <bfields@umich.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/posix_acl.h>
#include "nfsfh.h"
#include "nfsd.h"
#include "acl.h"
#include "vfs.h"
#define NFS4_ACL_TYPE_DEFAULT 0x01
#define NFS4_ACL_DIR 0x02
#define NFS4_ACL_OWNER 0x04
/* mode bit translations: */
#define NFS4_READ_MODE (NFS4_ACE_READ_DATA)
#define NFS4_WRITE_MODE (NFS4_ACE_WRITE_DATA | NFS4_ACE_APPEND_DATA)
#define NFS4_EXECUTE_MODE NFS4_ACE_EXECUTE
#define NFS4_ANYONE_MODE (NFS4_ACE_READ_ATTRIBUTES | NFS4_ACE_READ_ACL | NFS4_ACE_SYNCHRONIZE)
#define NFS4_OWNER_MODE (NFS4_ACE_WRITE_ATTRIBUTES | NFS4_ACE_WRITE_ACL)
/* flags used to simulate posix default ACLs */
#define NFS4_INHERITANCE_FLAGS (NFS4_ACE_FILE_INHERIT_ACE \
| NFS4_ACE_DIRECTORY_INHERIT_ACE)
#define NFS4_SUPPORTED_FLAGS (NFS4_INHERITANCE_FLAGS \
| NFS4_ACE_INHERIT_ONLY_ACE \
| NFS4_ACE_IDENTIFIER_GROUP)
static u32
mask_from_posix(unsigned short perm, unsigned int flags)
{
int mask = NFS4_ANYONE_MODE;
if (flags & NFS4_ACL_OWNER)
mask |= NFS4_OWNER_MODE;
if (perm & ACL_READ)
mask |= NFS4_READ_MODE;
if (perm & ACL_WRITE)
mask |= NFS4_WRITE_MODE;
if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
mask |= NFS4_ACE_DELETE_CHILD;
if (perm & ACL_EXECUTE)
mask |= NFS4_EXECUTE_MODE;
return mask;
}
static u32
deny_mask_from_posix(unsigned short perm, u32 flags)
{
u32 mask = 0;
if (perm & ACL_READ)
mask |= NFS4_READ_MODE;
if (perm & ACL_WRITE)
mask |= NFS4_WRITE_MODE;
if ((perm & ACL_WRITE) && (flags & NFS4_ACL_DIR))
mask |= NFS4_ACE_DELETE_CHILD;
if (perm & ACL_EXECUTE)
mask |= NFS4_EXECUTE_MODE;
return mask;
}
/* XXX: modify functions to return NFS errors; they're only ever
* used by nfs code, after all.... */
/* We only map from NFSv4 to POSIX ACLs when setting ACLs, when we err on the
* side of being more restrictive, so the mode bit mapping below is
* pessimistic. An optimistic version would be needed to handle DENY's,
* but we expect to coalesce all ALLOWs and DENYs before mapping to mode
* bits. */
static void
low_mode_from_nfs4(u32 perm, unsigned short *mode, unsigned int flags)
{
u32 write_mode = NFS4_WRITE_MODE;
if (flags & NFS4_ACL_DIR)
write_mode |= NFS4_ACE_DELETE_CHILD;
*mode = 0;
if ((perm & NFS4_READ_MODE) == NFS4_READ_MODE)
*mode |= ACL_READ;
if ((perm & write_mode) == write_mode)
*mode |= ACL_WRITE;
if ((perm & NFS4_EXECUTE_MODE) == NFS4_EXECUTE_MODE)
*mode |= ACL_EXECUTE;
}
static short ace2type(struct nfs4_ace *);
static void _posix_to_nfsv4_one(struct posix_acl *, struct nfs4_acl *,
unsigned int);
int
nfsd4_get_nfs4_acl(struct svc_rqst *rqstp, struct dentry *dentry,
struct nfs4_acl **acl)
{
struct inode *inode = d_inode(dentry);
int error = 0;
struct posix_acl *pacl = NULL, *dpacl = NULL;
unsigned int flags = 0;
int size = 0;
pacl = get_inode_acl(inode, ACL_TYPE_ACCESS);
if (!pacl)
pacl = posix_acl_from_mode(inode->i_mode, GFP_KERNEL);
if (IS_ERR(pacl))
return PTR_ERR(pacl);
/* allocate for worst case: one (deny, allow) pair each: */
size += 2 * pacl->a_count;
if (S_ISDIR(inode->i_mode)) {
flags = NFS4_ACL_DIR;
dpacl = get_inode_acl(inode, ACL_TYPE_DEFAULT);
if (IS_ERR(dpacl)) {
error = PTR_ERR(dpacl);
goto rel_pacl;
}
if (dpacl)
size += 2 * dpacl->a_count;
}
*acl = kmalloc(nfs4_acl_bytes(size), GFP_KERNEL);
if (*acl == NULL) {
error = -ENOMEM;
goto out;
}
(*acl)->naces = 0;
_posix_to_nfsv4_one(pacl, *acl, flags & ~NFS4_ACL_TYPE_DEFAULT);
if (dpacl)
_posix_to_nfsv4_one(dpacl, *acl, flags | NFS4_ACL_TYPE_DEFAULT);
out:
posix_acl_release(dpacl);
rel_pacl:
posix_acl_release(pacl);
return error;
}
struct posix_acl_summary {
unsigned short owner;
unsigned short users;
unsigned short group;
unsigned short groups;
unsigned short other;
unsigned short mask;
};
static void
summarize_posix_acl(struct posix_acl *acl, struct posix_acl_summary *pas)
{
struct posix_acl_entry *pa, *pe;
/*
* Only pas.users and pas.groups need initialization; previous
* posix_acl_valid() calls ensure that the other fields will be
* initialized in the following loop. But, just to placate gcc:
*/
memset(pas, 0, sizeof(*pas));
pas->mask = 07;
FOREACH_ACL_ENTRY(pa, acl, pe) {
switch (pa->e_tag) {
case ACL_USER_OBJ:
pas->owner = pa->e_perm;
break;
case ACL_GROUP_OBJ:
pas->group = pa->e_perm;
break;
case ACL_USER:
pas->users |= pa->e_perm;
break;
case ACL_GROUP:
pas->groups |= pa->e_perm;
break;
case ACL_OTHER:
pas->other = pa->e_perm;
break;
case ACL_MASK:
pas->mask = pa->e_perm;
break;
}
}
/* We'll only care about effective permissions: */
pas->users &= pas->mask;
pas->group &= pas->mask;
pas->groups &= pas->mask;
}
/* We assume the acl has been verified with posix_acl_valid. */
static void
_posix_to_nfsv4_one(struct posix_acl *pacl, struct nfs4_acl *acl,
unsigned int flags)
{
struct posix_acl_entry *pa, *group_owner_entry;
struct nfs4_ace *ace;
struct posix_acl_summary pas;
unsigned short deny;
int eflag = ((flags & NFS4_ACL_TYPE_DEFAULT) ?
NFS4_INHERITANCE_FLAGS | NFS4_ACE_INHERIT_ONLY_ACE : 0);
BUG_ON(pacl->a_count < 3);
summarize_posix_acl(pacl, &pas);
pa = pacl->a_entries;
ace = acl->aces + acl->naces;
/* We could deny everything not granted by the owner: */
deny = ~pas.owner;
/*
* but it is equivalent (and simpler) to deny only what is not
* granted by later entries:
*/
deny &= pas.users | pas.group | pas.groups | pas.other;
if (deny) {
ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
ace->flag = eflag;
ace->access_mask = deny_mask_from_posix(deny, flags);
ace->whotype = NFS4_ACL_WHO_OWNER;
ace++;
acl->naces++;
}
ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
ace->flag = eflag;
ace->access_mask = mask_from_posix(pa->e_perm, flags | NFS4_ACL_OWNER);
ace->whotype = NFS4_ACL_WHO_OWNER;
ace++;
acl->naces++;
pa++;
while (pa->e_tag == ACL_USER) {
deny = ~(pa->e_perm & pas.mask);
deny &= pas.groups | pas.group | pas.other;
if (deny) {
ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
ace->flag = eflag;
ace->access_mask = deny_mask_from_posix(deny, flags);
ace->whotype = NFS4_ACL_WHO_NAMED;
ace->who_uid = pa->e_uid;
ace++;
acl->naces++;
}
ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
ace->flag = eflag;
ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
flags);
ace->whotype = NFS4_ACL_WHO_NAMED;
ace->who_uid = pa->e_uid;
ace++;
acl->naces++;
pa++;
}
/* In the case of groups, we apply allow ACEs first, then deny ACEs,
* since a user can be in more than one group. */
/* allow ACEs */
group_owner_entry = pa;
ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
ace->flag = eflag;
ace->access_mask = mask_from_posix(pas.group, flags);
ace->whotype = NFS4_ACL_WHO_GROUP;
ace++;
acl->naces++;
pa++;
while (pa->e_tag == ACL_GROUP) {
ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
ace->access_mask = mask_from_posix(pa->e_perm & pas.mask,
flags);
ace->whotype = NFS4_ACL_WHO_NAMED;
ace->who_gid = pa->e_gid;
ace++;
acl->naces++;
pa++;
}
/* deny ACEs */
pa = group_owner_entry;
deny = ~pas.group & pas.other;
if (deny) {
ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
ace->flag = eflag;
ace->access_mask = deny_mask_from_posix(deny, flags);
ace->whotype = NFS4_ACL_WHO_GROUP;
ace++;
acl->naces++;
}
pa++;
while (pa->e_tag == ACL_GROUP) {
deny = ~(pa->e_perm & pas.mask);
deny &= pas.other;
if (deny) {
ace->type = NFS4_ACE_ACCESS_DENIED_ACE_TYPE;
ace->flag = eflag | NFS4_ACE_IDENTIFIER_GROUP;
ace->access_mask = deny_mask_from_posix(deny, flags);
ace->whotype = NFS4_ACL_WHO_NAMED;
ace->who_gid = pa->e_gid;
ace++;
acl->naces++;
}
pa++;
}
if (pa->e_tag == ACL_MASK)
pa++;
ace->type = NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE;
ace->flag = eflag;
ace->access_mask = mask_from_posix(pa->e_perm, flags);
ace->whotype = NFS4_ACL_WHO_EVERYONE;
acl->naces++;
}
static bool
pace_gt(struct posix_acl_entry *pace1, struct posix_acl_entry *pace2)
{
if (pace1->e_tag != pace2->e_tag)
return pace1->e_tag > pace2->e_tag;
if (pace1->e_tag == ACL_USER)
return uid_gt(pace1->e_uid, pace2->e_uid);
if (pace1->e_tag == ACL_GROUP)
return gid_gt(pace1->e_gid, pace2->e_gid);
return false;
}
/**
* sort_pacl_range - sort a range of POSIX ACL entries by tag and id
* @pacl: POSIX ACL containing entries to sort
* @start: starting index of range to sort
* @end: ending index of range to sort (inclusive)
*
* Sorts ACL entries in place so that USER entries are ordered by UID
* and GROUP entries are ordered by GID. Required before calling
* posix_acl_valid().
*/
void sort_pacl_range(struct posix_acl *pacl, int start, int end)
{
int sorted = 0, i;
/* Bubble sort: acceptable here because ACLs are typically short. */
while (!sorted) {
sorted = 1;
for (i = start; i < end; i++) {
if (pace_gt(&pacl->a_entries[i],
&pacl->a_entries[i+1])) {
sorted = 0;
swap(pacl->a_entries[i],
pacl->a_entries[i + 1]);
}
}
}
}
static void
sort_pacl(struct posix_acl *pacl)
{
/* posix_acl_valid requires that users and groups be in order
* by uid/gid. */
int i, j;
/* no users or groups */
if (!pacl || pacl->a_count <= 4)
return;
i = 1;
while (pacl->a_entries[i].e_tag == ACL_USER)
i++;
sort_pacl_range(pacl, 1, i-1);
BUG_ON(pacl->a_entries[i].e_tag != ACL_GROUP_OBJ);
j = ++i;
while (pacl->a_entries[j].e_tag == ACL_GROUP)
j++;
sort_pacl_range(pacl, i, j-1);
return;
}
/*
* While processing the NFSv4 ACE, this maintains bitmasks representing
* which permission bits have been allowed and which denied to a given
* entity: */
struct posix_ace_state {
u32 allow;
u32 deny;
};
struct posix_user_ace_state {
union {
kuid_t uid;
kgid_t gid;
};
struct posix_ace_state perms;
};
struct posix_ace_state_array {
int n;
struct posix_user_ace_state aces[];
};
/*
* While processing the NFSv4 ACE, this maintains the partial permissions
* calculated so far: */
struct posix_acl_state {
unsigned char valid;
struct posix_ace_state owner;
struct posix_ace_state group;
struct posix_ace_state other;
struct posix_ace_state everyone;
struct posix_ace_state mask; /* Deny unused in this case */
struct posix_ace_state_array *users;
struct posix_ace_state_array *groups;
};
static int
init_state(struct posix_acl_state *state, int cnt)
{
int alloc;
memset(state, 0, sizeof(struct posix_acl_state));
/*
* In the worst case, each individual acl could be for a distinct
* named user or group, but we don't know which, so we allocate
* enough space for either:
*/
alloc = sizeof(struct posix_ace_state_array)
+ cnt*sizeof(struct posix_user_ace_state);
state->users = kzalloc(alloc, GFP_KERNEL);
if (!state->users)
return -ENOMEM;
state->groups = kzalloc(alloc, GFP_KERNEL);
if (!state->groups) {
kfree(state->users);
return -ENOMEM;
}
return 0;
}
static void
free_state(struct posix_acl_state *state) {
kfree(state->users);
kfree(state->groups);
}
static inline void add_to_mask(struct posix_acl_state *state, struct posix_ace_state *astate)
{
state->mask.allow |= astate->allow;
}
static struct posix_acl *
posix_state_to_acl(struct posix_acl_state *state, unsigned int flags)
{
struct posix_acl_entry *pace;
struct posix_acl *pacl;
int nace;
int i;
/*
* ACLs with no ACEs are treated differently in the inheritable
* and effective cases: when there are no inheritable ACEs,
* calls ->set_acl with a NULL ACL structure.
*/
if (!state->valid && (flags & NFS4_ACL_TYPE_DEFAULT))
return NULL;
/*
* When there are no effective ACEs, the following will end
* up setting a 3-element effective posix ACL with all
* permissions zero.
*/
if (!state->users->n && !state->groups->n)
nace = 3;
else /* Note we also include a MASK ACE in this case: */
nace = 4 + state->users->n + state->groups->n;
pacl = posix_acl_alloc(nace, GFP_KERNEL);
if (!pacl)
return ERR_PTR(-ENOMEM);
pace = pacl->a_entries;
pace->e_tag = ACL_USER_OBJ;
low_mode_from_nfs4(state->owner.allow, &pace->e_perm, flags);
for (i=0; i < state->users->n; i++) {
pace++;
pace->e_tag = ACL_USER;
low_mode_from_nfs4(state->users->aces[i].perms.allow,
&pace->e_perm, flags);
pace->e_uid = state->users->aces[i].uid;
add_to_mask(state, &state->users->aces[i].perms);
}
pace++;
pace->e_tag = ACL_GROUP_OBJ;
low_mode_from_nfs4(state->group.allow, &pace->e_perm, flags);
add_to_mask(state, &state->group);
for (i=0; i < state->groups->n; i++) {
pace++;
pace->e_tag = ACL_GROUP;
low_mode_from_nfs4(state->groups->aces[i].perms.allow,
&pace->e_perm, flags);
pace->e_gid = state->groups->aces[i].gid;
add_to_mask(state, &state->groups->aces[i].perms);
}
if (state->users->n || state->groups->n) {
pace++;
pace->e_tag = ACL_MASK;
low_mode_from_nfs4(state->mask.allow, &pace->e_perm, flags);
}
pace++;
pace->e_tag = ACL_OTHER;
low_mode_from_nfs4(state->other.allow, &pace->e_perm, flags);
return pacl;
}
static inline void allow_bits(struct posix_ace_state *astate, u32 mask)
{
/* Allow all bits in the mask not already denied: */
astate->allow |= mask & ~astate->deny;
}
static inline void deny_bits(struct posix_ace_state *astate, u32 mask)
{
/* Deny all bits in the mask not already allowed: */
astate->deny |= mask & ~astate->allow;
}
static int find_uid(struct posix_acl_state *state, kuid_t uid)
{
struct posix_ace_state_array *a = state->users;
int i;
for (i = 0; i < a->n; i++)
if (uid_eq(a->aces[i].uid, uid))
return i;
/* Not found: */
a->n++;
a->aces[i].uid = uid;
a->aces[i].perms.allow = state->everyone.allow;
a->aces[i].perms.deny = state->everyone.deny;
return i;
}
static int find_gid(struct posix_acl_state *state, kgid_t gid)
{
struct posix_ace_state_array *a = state->groups;
int i;
for (i = 0; i < a->n; i++)
if (gid_eq(a->aces[i].gid, gid))
return i;
/* Not found: */
a->n++;
a->aces[i].gid = gid;
a->aces[i].perms.allow = state->everyone.allow;
a->aces[i].perms.deny = state->everyone.deny;
return i;
}
static void deny_bits_array(struct posix_ace_state_array *a, u32 mask)
{
int i;
for (i=0; i < a->n; i++)
deny_bits(&a->aces[i].perms, mask);
}
static void allow_bits_array(struct posix_ace_state_array *a, u32 mask)
{
int i;
for (i=0; i < a->n; i++)
allow_bits(&a->aces[i].perms, mask);
}
static void process_one_v4_ace(struct posix_acl_state *state,
struct nfs4_ace *ace)
{
u32 mask = ace->access_mask;
short type = ace2type(ace);
int i;
state->valid |= type;
switch (type) {
case ACL_USER_OBJ:
if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
allow_bits(&state->owner, mask);
} else {
deny_bits(&state->owner, mask);
}
break;
case ACL_USER:
i = find_uid(state, ace->who_uid);
if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
allow_bits(&state->users->aces[i].perms, mask);
} else {
deny_bits(&state->users->aces[i].perms, mask);
mask = state->users->aces[i].perms.deny;
deny_bits(&state->owner, mask);
}
break;
case ACL_GROUP_OBJ:
if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
allow_bits(&state->group, mask);
} else {
deny_bits(&state->group, mask);
mask = state->group.deny;
deny_bits(&state->owner, mask);
deny_bits(&state->everyone, mask);
deny_bits_array(state->users, mask);
deny_bits_array(state->groups, mask);
}
break;
case ACL_GROUP:
i = find_gid(state, ace->who_gid);
if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
allow_bits(&state->groups->aces[i].perms, mask);
} else {
deny_bits(&state->groups->aces[i].perms, mask);
mask = state->groups->aces[i].perms.deny;
deny_bits(&state->owner, mask);
deny_bits(&state->group, mask);
deny_bits(&state->everyone, mask);
deny_bits_array(state->users, mask);
deny_bits_array(state->groups, mask);
}
break;
case ACL_OTHER:
if (ace->type == NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE) {
allow_bits(&state->owner, mask);
allow_bits(&state->group, mask);
allow_bits(&state->other, mask);
allow_bits(&state->everyone, mask);
allow_bits_array(state->users, mask);
allow_bits_array(state->groups, mask);
} else {
deny_bits(&state->owner, mask);
deny_bits(&state->group, mask);
deny_bits(&state->other, mask);
deny_bits(&state->everyone, mask);
deny_bits_array(state->users, mask);
deny_bits_array(state->groups, mask);
}
}
}
static int nfs4_acl_nfsv4_to_posix(struct nfs4_acl *acl,
struct posix_acl **pacl, struct posix_acl **dpacl,
unsigned int flags)
{
struct posix_acl_state effective_acl_state, default_acl_state;
struct nfs4_ace *ace;
int ret;
ret = init_state(&effective_acl_state, acl->naces);
if (ret)
return ret;
ret = init_state(&default_acl_state, acl->naces);
if (ret)
goto out_estate;
ret = -EINVAL;
for (ace = acl->aces; ace < acl->aces + acl->naces; ace++) {
if (ace->type != NFS4_ACE_ACCESS_ALLOWED_ACE_TYPE &&
ace->type != NFS4_ACE_ACCESS_DENIED_ACE_TYPE)
goto out_dstate;
if (ace->flag & ~NFS4_SUPPORTED_FLAGS)
goto out_dstate;
if ((ace->flag & NFS4_INHERITANCE_FLAGS) == 0) {
process_one_v4_ace(&effective_acl_state, ace);
continue;
}
if (!(flags & NFS4_ACL_DIR))
goto out_dstate;
/*
* Note that when only one of FILE_INHERIT or DIRECTORY_INHERIT
* is set, we're effectively turning on the other. That's OK,
* according to rfc 3530.
*/
process_one_v4_ace(&default_acl_state, ace);
if (!(ace->flag & NFS4_ACE_INHERIT_ONLY_ACE))
process_one_v4_ace(&effective_acl_state, ace);
}
/*
* At this point, the default ACL may have zeroed-out entries for owner,
* group and other. That usually results in a non-sensical resulting ACL
* that denies all access except to any ACE that was explicitly added.
*
* The setfacl command solves a similar problem with this logic:
*
* "If a Default ACL entry is created, and the Default ACL contains
* no owner, owning group, or others entry, a copy of the ACL
* owner, owning group, or others entry is added to the Default ACL."
*
* Copy any missing ACEs from the effective set, if any ACEs were
* explicitly set.
*/
if (default_acl_state.valid) {
if (!(default_acl_state.valid & ACL_USER_OBJ))
default_acl_state.owner = effective_acl_state.owner;
if (!(default_acl_state.valid & ACL_GROUP_OBJ))
default_acl_state.group = effective_acl_state.group;
if (!(default_acl_state.valid & ACL_OTHER))
default_acl_state.other = effective_acl_state.other;
}
*pacl = posix_state_to_acl(&effective_acl_state, flags);
if (IS_ERR(*pacl)) {
ret = PTR_ERR(*pacl);
*pacl = NULL;
goto out_dstate;
}
*dpacl = posix_state_to_acl(&default_acl_state,
flags | NFS4_ACL_TYPE_DEFAULT);
if (IS_ERR(*dpacl)) {
ret = PTR_ERR(*dpacl);
*dpacl = NULL;
posix_acl_release(*pacl);
*pacl = NULL;
goto out_dstate;
}
sort_pacl(*pacl);
sort_pacl(*dpacl);
ret = 0;
out_dstate:
free_state(&default_acl_state);
out_estate:
free_state(&effective_acl_state);
return ret;
}
__be32 nfsd4_acl_to_attr(enum nfs_ftype4 type, struct nfs4_acl *acl,
struct nfsd_attrs *attr)
{
int host_error;
unsigned int flags = 0;
if (!acl)
return nfs_ok;
if (type == NF4DIR)
flags = NFS4_ACL_DIR;
host_error = nfs4_acl_nfsv4_to_posix(acl, &attr->na_pacl,
&attr->na_dpacl, flags);
if (host_error == -EINVAL)
return nfserr_attrnotsupp;
else
return nfserrno(host_error);
}
static short
ace2type(struct nfs4_ace *ace)
{
switch (ace->whotype) {
case NFS4_ACL_WHO_NAMED:
return (ace->flag & NFS4_ACE_IDENTIFIER_GROUP ?
ACL_GROUP : ACL_USER);
case NFS4_ACL_WHO_OWNER:
return ACL_USER_OBJ;
case NFS4_ACL_WHO_GROUP:
return ACL_GROUP_OBJ;
case NFS4_ACL_WHO_EVERYONE:
return ACL_OTHER;
}
BUG();
return -1;
}
/*
* return the size of the struct nfs4_acl required to represent an acl
* with @entries entries.
*/
int nfs4_acl_bytes(int entries)
{
return sizeof(struct nfs4_acl) + entries * sizeof(struct nfs4_ace);
}
static struct {
char *string;
int stringlen;
int type;
} s2t_map[] = {
{
.string = "OWNER@",
.stringlen = sizeof("OWNER@") - 1,
.type = NFS4_ACL_WHO_OWNER,
},
{
.string = "GROUP@",
.stringlen = sizeof("GROUP@") - 1,
.type = NFS4_ACL_WHO_GROUP,
},
{
.string = "EVERYONE@",
.stringlen = sizeof("EVERYONE@") - 1,
.type = NFS4_ACL_WHO_EVERYONE,
},
};
int
nfs4_acl_get_whotype(char *p, u32 len)
{
int i;
for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
if (s2t_map[i].stringlen == len &&
0 == memcmp(s2t_map[i].string, p, len))
return s2t_map[i].type;
}
return NFS4_ACL_WHO_NAMED;
}
__be32 nfs4_acl_write_who(struct xdr_stream *xdr, int who)
{
__be32 *p;
int i;
for (i = 0; i < ARRAY_SIZE(s2t_map); i++) {
if (s2t_map[i].type != who)
continue;
p = xdr_reserve_space(xdr, s2t_map[i].stringlen + 4);
if (!p)
return nfserr_resource;
p = xdr_encode_opaque(p, s2t_map[i].string,
s2t_map[i].stringlen);
return 0;
}
WARN_ON_ONCE(1);
return nfserr_serverfault;
}
File diff suppressed because it is too large Load Diff
+732
View File
@@ -0,0 +1,732 @@
/*
* Mapping of UID/GIDs to name and vice versa.
*
* Copyright (c) 2002, 2003 The Regents of the University of
* Michigan. All rights reserved.
*
* Marius Aamodt Eriksen <marius@umich.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/sunrpc/svc_xprt.h>
#include <net/net_namespace.h>
#include "idmap.h"
#include "nfsd.h"
#include "netns.h"
#include "vfs.h"
/*
* Turn off idmapping when using AUTH_SYS.
*/
static bool nfs4_disable_idmapping = true;
module_param(nfs4_disable_idmapping, bool, 0644);
MODULE_PARM_DESC(nfs4_disable_idmapping,
"Turn off server's NFSv4 idmapping when using 'sec=sys'");
/*
* Cache entry
*/
/*
* XXX we know that IDMAP_NAMESZ < PAGE_SIZE, but it's ugly to rely on
* that.
*/
struct ent {
struct cache_head h;
int type; /* User / Group */
u32 id;
char name[IDMAP_NAMESZ];
char authname[IDMAP_NAMESZ];
struct rcu_head rcu_head;
};
/* Common entry handling */
#define ENT_HASHBITS 8
#define ENT_HASHMAX (1 << ENT_HASHBITS)
static void
ent_init(struct cache_head *cnew, struct cache_head *citm)
{
struct ent *new = container_of(cnew, struct ent, h);
struct ent *itm = container_of(citm, struct ent, h);
new->id = itm->id;
new->type = itm->type;
strscpy(new->name, itm->name, sizeof(new->name));
strscpy(new->authname, itm->authname, sizeof(new->authname));
}
static void
ent_put(struct kref *ref)
{
struct ent *map = container_of(ref, struct ent, h.ref);
kfree_rcu(map, rcu_head);
}
static struct cache_head *
ent_alloc(void)
{
struct ent *e = kmalloc_obj(*e);
if (e)
return &e->h;
else
return NULL;
}
/*
* ID -> Name cache
*/
static uint32_t
idtoname_hash(struct ent *ent)
{
uint32_t hash;
hash = hash_str(ent->authname, ENT_HASHBITS);
hash = hash_long(hash ^ ent->id, ENT_HASHBITS);
/* Flip LSB for user/group */
if (ent->type == IDMAP_TYPE_GROUP)
hash ^= 1;
return hash;
}
static int
idtoname_upcall(struct cache_detail *cd, struct cache_head *h)
{
return sunrpc_cache_upcall_warn(cd, h);
}
static void
idtoname_request(struct cache_detail *cd, struct cache_head *ch, char **bpp,
int *blen)
{
struct ent *ent = container_of(ch, struct ent, h);
char idstr[11];
qword_add(bpp, blen, ent->authname);
snprintf(idstr, sizeof(idstr), "%u", ent->id);
qword_add(bpp, blen, ent->type == IDMAP_TYPE_GROUP ? "group" : "user");
qword_add(bpp, blen, idstr);
(*bpp)[-1] = '\n';
}
static int
idtoname_match(struct cache_head *ca, struct cache_head *cb)
{
struct ent *a = container_of(ca, struct ent, h);
struct ent *b = container_of(cb, struct ent, h);
return (a->id == b->id && a->type == b->type &&
strcmp(a->authname, b->authname) == 0);
}
static int
idtoname_show(struct seq_file *m, struct cache_detail *cd, struct cache_head *h)
{
struct ent *ent;
if (h == NULL) {
seq_puts(m, "#domain type id [name]\n");
return 0;
}
ent = container_of(h, struct ent, h);
seq_printf(m, "%s %s %u", ent->authname,
ent->type == IDMAP_TYPE_GROUP ? "group" : "user",
ent->id);
if (test_bit(CACHE_VALID, &h->flags))
seq_printf(m, " %s", ent->name);
seq_putc(m, '\n');
return 0;
}
static void
warn_no_idmapd(struct cache_detail *detail, int has_died)
{
printk("nfsd: nfsv4 idmapping failing: has idmapd %s?\n",
has_died ? "died" : "not been started");
}
static int idtoname_parse(struct cache_detail *, char *, int);
static struct ent *idtoname_lookup(struct cache_detail *, struct ent *);
static struct ent *idtoname_update(struct cache_detail *, struct ent *,
struct ent *);
static const struct cache_detail idtoname_cache_template = {
.owner = THIS_MODULE,
.hash_size = ENT_HASHMAX,
.name = "nfs4.idtoname",
.cache_put = ent_put,
.cache_upcall = idtoname_upcall,
.cache_request = idtoname_request,
.cache_parse = idtoname_parse,
.cache_show = idtoname_show,
.warn_no_listener = warn_no_idmapd,
.match = idtoname_match,
.init = ent_init,
.update = ent_init,
.alloc = ent_alloc,
};
static int
idtoname_parse(struct cache_detail *cd, char *buf, int buflen)
{
struct ent ent, *res;
char *buf1, *bp;
int len;
int error = -EINVAL;
if (buf[buflen - 1] != '\n')
return (-EINVAL);
buf[buflen - 1]= '\0';
buf1 = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (buf1 == NULL)
return (-ENOMEM);
memset(&ent, 0, sizeof(ent));
/* Authentication name */
len = qword_get(&buf, buf1, PAGE_SIZE);
if (len <= 0 || len >= IDMAP_NAMESZ)
goto out;
memcpy(ent.authname, buf1, sizeof(ent.authname));
/* Type */
if (qword_get(&buf, buf1, PAGE_SIZE) <= 0)
goto out;
ent.type = strcmp(buf1, "user") == 0 ?
IDMAP_TYPE_USER : IDMAP_TYPE_GROUP;
/* ID */
if (qword_get(&buf, buf1, PAGE_SIZE) <= 0)
goto out;
ent.id = simple_strtoul(buf1, &bp, 10);
if (bp == buf1)
goto out;
/* expiry */
error = get_expiry(&buf, &ent.h.expiry_time);
if (error)
goto out;
error = -ENOMEM;
res = idtoname_lookup(cd, &ent);
if (!res)
goto out;
/* Name */
error = -EINVAL;
len = qword_get(&buf, buf1, PAGE_SIZE);
if (len < 0 || len >= IDMAP_NAMESZ)
goto out;
if (len == 0)
set_bit(CACHE_NEGATIVE, &ent.h.flags);
else
memcpy(ent.name, buf1, sizeof(ent.name));
error = -ENOMEM;
res = idtoname_update(cd, &ent, res);
if (res == NULL)
goto out;
cache_put(&res->h, cd);
error = 0;
out:
kfree(buf1);
return error;
}
static struct ent *
idtoname_lookup(struct cache_detail *cd, struct ent *item)
{
struct cache_head *ch = sunrpc_cache_lookup_rcu(cd, &item->h,
idtoname_hash(item));
if (ch)
return container_of(ch, struct ent, h);
else
return NULL;
}
static struct ent *
idtoname_update(struct cache_detail *cd, struct ent *new, struct ent *old)
{
struct cache_head *ch = sunrpc_cache_update(cd, &new->h, &old->h,
idtoname_hash(new));
if (ch)
return container_of(ch, struct ent, h);
else
return NULL;
}
/*
* Name -> ID cache
*/
static inline int
nametoid_hash(struct ent *ent)
{
return hash_str(ent->name, ENT_HASHBITS);
}
static int
nametoid_upcall(struct cache_detail *cd, struct cache_head *h)
{
return sunrpc_cache_upcall_warn(cd, h);
}
static void
nametoid_request(struct cache_detail *cd, struct cache_head *ch, char **bpp,
int *blen)
{
struct ent *ent = container_of(ch, struct ent, h);
qword_add(bpp, blen, ent->authname);
qword_add(bpp, blen, ent->type == IDMAP_TYPE_GROUP ? "group" : "user");
qword_add(bpp, blen, ent->name);
(*bpp)[-1] = '\n';
}
static int
nametoid_match(struct cache_head *ca, struct cache_head *cb)
{
struct ent *a = container_of(ca, struct ent, h);
struct ent *b = container_of(cb, struct ent, h);
return (a->type == b->type && strcmp(a->name, b->name) == 0 &&
strcmp(a->authname, b->authname) == 0);
}
static int
nametoid_show(struct seq_file *m, struct cache_detail *cd, struct cache_head *h)
{
struct ent *ent;
if (h == NULL) {
seq_puts(m, "#domain type name [id]\n");
return 0;
}
ent = container_of(h, struct ent, h);
seq_printf(m, "%s %s %s", ent->authname,
ent->type == IDMAP_TYPE_GROUP ? "group" : "user",
ent->name);
if (test_bit(CACHE_VALID, &h->flags))
seq_printf(m, " %u", ent->id);
seq_putc(m, '\n');
return 0;
}
static struct ent *nametoid_lookup(struct cache_detail *, struct ent *);
static struct ent *nametoid_update(struct cache_detail *, struct ent *,
struct ent *);
static int nametoid_parse(struct cache_detail *, char *, int);
static const struct cache_detail nametoid_cache_template = {
.owner = THIS_MODULE,
.hash_size = ENT_HASHMAX,
.name = "nfs4.nametoid",
.cache_put = ent_put,
.cache_upcall = nametoid_upcall,
.cache_request = nametoid_request,
.cache_parse = nametoid_parse,
.cache_show = nametoid_show,
.warn_no_listener = warn_no_idmapd,
.match = nametoid_match,
.init = ent_init,
.update = ent_init,
.alloc = ent_alloc,
};
static int
nametoid_parse(struct cache_detail *cd, char *buf, int buflen)
{
struct ent ent, *res;
char *buf1;
int len, error = -EINVAL;
if (buf[buflen - 1] != '\n')
return (-EINVAL);
buf[buflen - 1]= '\0';
buf1 = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (buf1 == NULL)
return (-ENOMEM);
memset(&ent, 0, sizeof(ent));
/* Authentication name */
len = qword_get(&buf, buf1, PAGE_SIZE);
if (len <= 0 || len >= IDMAP_NAMESZ)
goto out;
memcpy(ent.authname, buf1, sizeof(ent.authname));
/* Type */
if (qword_get(&buf, buf1, PAGE_SIZE) <= 0)
goto out;
ent.type = strcmp(buf1, "user") == 0 ?
IDMAP_TYPE_USER : IDMAP_TYPE_GROUP;
/* Name */
len = qword_get(&buf, buf1, PAGE_SIZE);
if (len <= 0 || len >= IDMAP_NAMESZ)
goto out;
memcpy(ent.name, buf1, sizeof(ent.name));
/* expiry */
error = get_expiry(&buf, &ent.h.expiry_time);
if (error)
goto out;
/* ID */
error = get_int(&buf, &ent.id);
if (error == -EINVAL)
goto out;
if (error == -ENOENT)
set_bit(CACHE_NEGATIVE, &ent.h.flags);
error = -ENOMEM;
res = nametoid_lookup(cd, &ent);
if (res == NULL)
goto out;
res = nametoid_update(cd, &ent, res);
if (res == NULL)
goto out;
cache_put(&res->h, cd);
error = 0;
out:
kfree(buf1);
return (error);
}
static struct ent *
nametoid_lookup(struct cache_detail *cd, struct ent *item)
{
struct cache_head *ch = sunrpc_cache_lookup_rcu(cd, &item->h,
nametoid_hash(item));
if (ch)
return container_of(ch, struct ent, h);
else
return NULL;
}
static struct ent *
nametoid_update(struct cache_detail *cd, struct ent *new, struct ent *old)
{
struct cache_head *ch = sunrpc_cache_update(cd, &new->h, &old->h,
nametoid_hash(new));
if (ch)
return container_of(ch, struct ent, h);
else
return NULL;
}
/*
* Exported API
*/
int
nfsd_idmap_init(struct net *net)
{
int rv;
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
nn->idtoname_cache = cache_create_net(&idtoname_cache_template, net);
if (IS_ERR(nn->idtoname_cache))
return PTR_ERR(nn->idtoname_cache);
rv = cache_register_net(nn->idtoname_cache, net);
if (rv)
goto destroy_idtoname_cache;
nn->nametoid_cache = cache_create_net(&nametoid_cache_template, net);
if (IS_ERR(nn->nametoid_cache)) {
rv = PTR_ERR(nn->nametoid_cache);
goto unregister_idtoname_cache;
}
rv = cache_register_net(nn->nametoid_cache, net);
if (rv)
goto destroy_nametoid_cache;
return 0;
destroy_nametoid_cache:
cache_destroy_net(nn->nametoid_cache, net);
unregister_idtoname_cache:
cache_unregister_net(nn->idtoname_cache, net);
destroy_idtoname_cache:
cache_destroy_net(nn->idtoname_cache, net);
return rv;
}
void
nfsd_idmap_shutdown(struct net *net)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
cache_unregister_net(nn->idtoname_cache, net);
cache_unregister_net(nn->nametoid_cache, net);
cache_destroy_net(nn->idtoname_cache, net);
cache_destroy_net(nn->nametoid_cache, net);
}
static int
idmap_lookup(struct svc_rqst *rqstp,
struct ent *(*lookup_fn)(struct cache_detail *, struct ent *),
struct ent *key, struct cache_detail *detail, struct ent **item)
{
int ret;
*item = lookup_fn(detail, key);
if (!*item)
return -ENOMEM;
retry:
ret = cache_check(detail, &(*item)->h, &rqstp->rq_chandle);
if (ret == -ETIMEDOUT) {
struct ent *prev_item = *item;
*item = lookup_fn(detail, key);
if (*item != prev_item)
goto retry;
cache_put(&(*item)->h, detail);
}
return ret;
}
static char *
rqst_authname(struct svc_rqst *rqstp)
{
struct auth_domain *clp;
clp = rqstp->rq_gssclient ? rqstp->rq_gssclient : rqstp->rq_client;
return clp->name;
}
static __be32
idmap_name_to_id(struct svc_rqst *rqstp, int type, const char *name, u32 namelen,
u32 *id)
{
struct ent *item, key = {
.type = type,
};
int ret;
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
if (namelen + 1 > sizeof(key.name))
return nfserr_badowner;
memcpy(key.name, name, namelen);
key.name[namelen] = '\0';
strscpy(key.authname, rqst_authname(rqstp), sizeof(key.authname));
ret = idmap_lookup(rqstp, nametoid_lookup, &key, nn->nametoid_cache, &item);
if (ret == -ENOENT)
return nfserr_badowner;
if (ret)
return nfserrno(ret);
*id = item->id;
cache_put(&item->h, nn->nametoid_cache);
return 0;
}
static __be32 encode_ascii_id(struct xdr_stream *xdr, u32 id)
{
char buf[11];
int len;
__be32 *p;
len = sprintf(buf, "%u", id);
p = xdr_reserve_space(xdr, len + 4);
if (!p)
return nfserr_resource;
p = xdr_encode_opaque(p, buf, len);
return 0;
}
static __be32 idmap_id_to_name(struct xdr_stream *xdr,
struct svc_rqst *rqstp, int type, u32 id)
{
struct ent *item, key = {
.id = id,
.type = type,
};
__be32 status = nfs_ok;
__be32 *p;
int ret;
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
strscpy(key.authname, rqst_authname(rqstp), sizeof(key.authname));
ret = idmap_lookup(rqstp, idtoname_lookup, &key, nn->idtoname_cache, &item);
if (ret == -ENOENT)
return encode_ascii_id(xdr, id);
if (ret)
return nfserrno(ret);
ret = strlen(item->name);
WARN_ON_ONCE(ret > IDMAP_NAMESZ);
p = xdr_reserve_space(xdr, ret + 4);
if (unlikely(!p)) {
status = nfserr_resource;
goto out_put;
}
xdr_encode_opaque(p, item->name, ret);
out_put:
cache_put(&item->h, nn->idtoname_cache);
return status;
}
static bool
numeric_name_to_id(struct svc_rqst *rqstp, int type, const char *name, u32 namelen, u32 *id)
{
int ret;
char buf[11];
if (namelen + 1 > sizeof(buf))
/* too long to represent a 32-bit id: */
return false;
/* Just to make sure it's null-terminated: */
memcpy(buf, name, namelen);
buf[namelen] = '\0';
ret = kstrtouint(buf, 10, id);
return ret == 0;
}
static __be32
do_name_to_id(struct svc_rqst *rqstp, int type, const char *name, u32 namelen, u32 *id)
{
if (nfs4_disable_idmapping && rqstp->rq_cred.cr_flavor < RPC_AUTH_GSS)
if (numeric_name_to_id(rqstp, type, name, namelen, id))
return 0;
/*
* otherwise, fall through and try idmapping, for
* backwards compatibility with clients sending names:
*/
return idmap_name_to_id(rqstp, type, name, namelen, id);
}
static __be32 encode_name_from_id(struct xdr_stream *xdr,
struct svc_rqst *rqstp, int type, u32 id)
{
if (nfs4_disable_idmapping && rqstp->rq_cred.cr_flavor < RPC_AUTH_GSS)
return encode_ascii_id(xdr, id);
return idmap_id_to_name(xdr, rqstp, type, id);
}
/**
* nfsd_map_name_to_uid - Map user@domain to local UID
* @rqstp: RPC execution context
* @name: user@domain name to be mapped
* @namelen: length of name, in bytes
* @uid: OUT: mapped local UID value
*
* Returns nfs_ok on success or an NFSv4 status code on failure.
*/
__be32 nfsd_map_name_to_uid(struct svc_rqst *rqstp, const char *name,
size_t namelen, kuid_t *uid)
{
__be32 status;
u32 id = -1;
/*
* The idmap lookup below triggers an upcall that invokes
* cache_check(). RQ_USEDEFERRAL must be clear to prevent
* cache_check() from setting RQ_DROPME via svc_defer().
* NFSv4 servers are not permitted to drop requests. Also
* RQ_DROPME will force NFSv4.1 session slot processing to
* be skipped.
*/
WARN_ON_ONCE(test_bit(RQ_USEDEFERRAL, &rqstp->rq_flags));
if (name == NULL || namelen == 0)
return nfserr_inval;
status = do_name_to_id(rqstp, IDMAP_TYPE_USER, name, namelen, &id);
if (status)
return status;
*uid = make_kuid(nfsd_user_namespace(rqstp), id);
if (!uid_valid(*uid))
status = nfserr_badowner;
return status;
}
/**
* nfsd_map_name_to_gid - Map user@domain to local GID
* @rqstp: RPC execution context
* @name: user@domain name to be mapped
* @namelen: length of name, in bytes
* @gid: OUT: mapped local GID value
*
* Returns nfs_ok on success or an NFSv4 status code on failure.
*/
__be32 nfsd_map_name_to_gid(struct svc_rqst *rqstp, const char *name,
size_t namelen, kgid_t *gid)
{
__be32 status;
u32 id = -1;
/*
* The idmap lookup below triggers an upcall that invokes
* cache_check(). RQ_USEDEFERRAL must be clear to prevent
* cache_check() from setting RQ_DROPME via svc_defer().
* NFSv4 servers are not permitted to drop requests. Also
* RQ_DROPME will force NFSv4.1 session slot processing to
* be skipped.
*/
WARN_ON_ONCE(test_bit(RQ_USEDEFERRAL, &rqstp->rq_flags));
if (name == NULL || namelen == 0)
return nfserr_inval;
status = do_name_to_id(rqstp, IDMAP_TYPE_GROUP, name, namelen, &id);
if (status)
return status;
*gid = make_kgid(nfsd_user_namespace(rqstp), id);
if (!gid_valid(*gid))
status = nfserr_badowner;
return status;
}
__be32 nfsd4_encode_user(struct xdr_stream *xdr, struct svc_rqst *rqstp,
kuid_t uid)
{
u32 id = from_kuid_munged(nfsd_user_namespace(rqstp), uid);
return encode_name_from_id(xdr, rqstp, IDMAP_TYPE_USER, id);
}
__be32 nfsd4_encode_group(struct xdr_stream *xdr, struct svc_rqst *rqstp,
kgid_t gid)
{
u32 id = from_kgid_munged(nfsd_user_namespace(rqstp), gid);
return encode_name_from_id(xdr, rqstp, IDMAP_TYPE_GROUP, id);
}
+956
View File
@@ -0,0 +1,956 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2014 Christoph Hellwig.
*/
#include <linux/exportfs_block.h>
#include <linux/kmod.h>
#include <linux/file.h>
#include <linux/jhash.h>
#include <linux/sched.h>
#include <linux/sunrpc/addr.h>
#include "pnfs.h"
#include "netns.h"
#include "trace.h"
#define NFSDDBG_FACILITY NFSDDBG_PNFS
struct nfs4_layout {
struct list_head lo_perstate;
struct nfs4_layout_stateid *lo_state;
struct nfsd4_layout_seg lo_seg;
};
static struct kmem_cache *nfs4_layout_cache;
static struct kmem_cache *nfs4_layout_stateid_cache;
static const struct nfsd4_callback_ops nfsd4_cb_layout_ops;
static const struct lease_manager_operations nfsd4_layouts_lm_ops;
static void nfsd4_layout_fence_worker(struct work_struct *work);
const struct nfsd4_layout_ops *nfsd4_layout_ops[LAYOUT_TYPE_MAX] = {
#ifdef CONFIG_NFSD_FLEXFILELAYOUT
[LAYOUT_FLEX_FILES] = &ff_layout_ops,
#endif
#ifdef CONFIG_NFSD_BLOCKLAYOUT
[LAYOUT_BLOCK_VOLUME] = &bl_layout_ops,
#endif
#ifdef CONFIG_NFSD_SCSILAYOUT
[LAYOUT_SCSI] = &scsi_layout_ops,
#endif
};
/* pNFS device ID to export fsid mapping */
#define DEVID_HASH_BITS 8
#define DEVID_HASH_SIZE (1 << DEVID_HASH_BITS)
#define DEVID_HASH_MASK (DEVID_HASH_SIZE - 1)
static u64 nfsd_devid_seq = 1;
static struct list_head nfsd_devid_hash[DEVID_HASH_SIZE];
static DEFINE_SPINLOCK(nfsd_devid_lock);
static inline u32 devid_hashfn(u64 idx)
{
return jhash_2words(idx, idx >> 32, 0) & DEVID_HASH_MASK;
}
static void
nfsd4_alloc_devid_map(const struct svc_fh *fhp)
{
const struct knfsd_fh *fh = &fhp->fh_handle;
size_t fsid_len = key_len(fh->fh_fsid_type);
struct nfsd4_deviceid_map *map, *old;
int i;
map = kzalloc(sizeof(*map) + fsid_len, GFP_KERNEL);
if (!map)
return;
map->fsid_type = fh->fh_fsid_type;
memcpy(&map->fsid, fh_fsid(fh), fsid_len);
spin_lock(&nfsd_devid_lock);
if (fhp->fh_export->ex_devid_map)
goto out_unlock;
for (i = 0; i < DEVID_HASH_SIZE; i++) {
list_for_each_entry(old, &nfsd_devid_hash[i], hash) {
if (old->fsid_type != fh->fh_fsid_type)
continue;
if (memcmp(old->fsid, fh_fsid(fh),
key_len(old->fsid_type)))
continue;
fhp->fh_export->ex_devid_map = old;
goto out_unlock;
}
}
map->idx = nfsd_devid_seq++;
list_add_tail_rcu(&map->hash, &nfsd_devid_hash[devid_hashfn(map->idx)]);
fhp->fh_export->ex_devid_map = map;
map = NULL;
out_unlock:
spin_unlock(&nfsd_devid_lock);
kfree(map);
}
struct nfsd4_deviceid_map *
nfsd4_find_devid_map(int idx)
{
struct nfsd4_deviceid_map *map, *ret = NULL;
rcu_read_lock();
list_for_each_entry_rcu(map, &nfsd_devid_hash[devid_hashfn(idx)], hash)
if (map->idx == idx)
ret = map;
rcu_read_unlock();
return ret;
}
int
nfsd4_set_deviceid(struct nfsd4_deviceid *id, const struct svc_fh *fhp,
u32 device_generation)
{
if (!fhp->fh_export->ex_devid_map) {
nfsd4_alloc_devid_map(fhp);
if (!fhp->fh_export->ex_devid_map)
return -ENOMEM;
}
id->fsid_idx = fhp->fh_export->ex_devid_map->idx;
id->generation = device_generation;
return 0;
}
void nfsd4_setup_layout_type(struct svc_export *exp)
{
struct super_block *sb = exp->ex_path.mnt->mnt_sb;
expfs_block_layouts_t block_supported = exportfs_layouts_supported(sb);
if (IS_ENABLED(CONFIG_NFSD_FLEXFILELAYOUT))
exp->ex_layout_types |= 1 << LAYOUT_FLEX_FILES;
if (IS_ENABLED(CONFIG_NFSD_BLOCKLAYOUT) &&
(block_supported & EXPFS_BLOCK_IN_BAND_ID))
exp->ex_layout_types |= 1 << LAYOUT_BLOCK_VOLUME;
if (IS_ENABLED(CONFIG_NFSD_SCSILAYOUT) &&
(block_supported & EXPFS_BLOCK_OUT_OF_BAND_ID))
exp->ex_layout_types |= 1 << LAYOUT_SCSI;
}
void nfsd4_close_layout(struct nfs4_layout_stateid *ls)
{
struct nfsd_file *fl;
spin_lock(&ls->ls_stid.sc_file->fi_lock);
fl = ls->ls_file;
ls->ls_file = NULL;
spin_unlock(&ls->ls_stid.sc_file->fi_lock);
if (fl) {
if (!nfsd4_layout_ops[ls->ls_layout_type]->disable_recalls)
kernel_setlease(fl->nf_file, F_UNLCK, NULL,
(void **)&ls);
nfsd_file_put(fl);
}
}
static void
nfsd4_free_layout_stateid(struct nfs4_stid *stid)
{
struct nfs4_layout_stateid *ls = layoutstateid(stid);
struct nfs4_client *clp = ls->ls_stid.sc_client;
struct nfs4_file *fp = ls->ls_stid.sc_file;
trace_nfsd_layoutstate_free(&ls->ls_stid.sc_stateid);
spin_lock(&ls->ls_lock);
if (delayed_work_pending(&ls->ls_fence_work)) {
spin_unlock(&ls->ls_lock);
cancel_delayed_work_sync(&ls->ls_fence_work);
} else
spin_unlock(&ls->ls_lock);
spin_lock(&clp->cl_lock);
list_del_init(&ls->ls_perclnt);
spin_unlock(&clp->cl_lock);
spin_lock(&fp->fi_lock);
list_del_init(&ls->ls_perfile);
spin_unlock(&fp->fi_lock);
nfsd4_close_layout(ls);
if (ls->ls_recalled)
atomic_dec(&ls->ls_stid.sc_file->fi_lo_recalls);
kmem_cache_free(nfs4_layout_stateid_cache, ls);
}
static int
nfsd4_layout_setlease(struct nfs4_layout_stateid *ls)
{
struct file_lease *fl;
int status;
if (nfsd4_layout_ops[ls->ls_layout_type]->disable_recalls)
return 0;
fl = locks_alloc_lease();
if (!fl)
return -ENOMEM;
locks_init_lease(fl);
fl->fl_lmops = &nfsd4_layouts_lm_ops;
fl->c.flc_flags = FL_LAYOUT;
fl->c.flc_type = F_RDLCK;
fl->c.flc_owner = ls;
fl->c.flc_pid = current->tgid;
fl->c.flc_file = ls->ls_file->nf_file;
status = kernel_setlease(fl->c.flc_file, fl->c.flc_type, &fl, NULL);
if (status) {
locks_free_lease(fl);
return status;
}
BUG_ON(fl != NULL);
return 0;
}
static struct nfs4_layout_stateid *
nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate,
struct nfs4_stid *parent, u32 layout_type)
{
struct nfs4_client *clp = cstate->clp;
struct nfs4_file *fp = parent->sc_file;
struct nfs4_layout_stateid *ls;
struct nfs4_stid *stp;
stp = nfs4_alloc_stid(cstate->clp, nfs4_layout_stateid_cache,
nfsd4_free_layout_stateid);
if (!stp)
return NULL;
get_nfs4_file(fp);
stp->sc_file = fp;
if (parent->sc_export)
stp->sc_export = exp_get(parent->sc_export);
ls = layoutstateid(stp);
INIT_LIST_HEAD(&ls->ls_perclnt);
INIT_LIST_HEAD(&ls->ls_perfile);
spin_lock_init(&ls->ls_lock);
INIT_LIST_HEAD(&ls->ls_layouts);
mutex_init(&ls->ls_mutex);
ls->ls_layout_type = layout_type;
nfsd4_init_cb(&ls->ls_recall, clp, &nfsd4_cb_layout_ops,
NFSPROC4_CLNT_CB_LAYOUT);
if (parent->sc_type == SC_TYPE_DELEG)
ls->ls_file = nfsd_file_get(fp->fi_deleg_file);
else
ls->ls_file = find_any_file(fp);
BUG_ON(!ls->ls_file);
ls->ls_fenced = false;
ls->ls_fence_delay = 0;
INIT_DELAYED_WORK(&ls->ls_fence_work, nfsd4_layout_fence_worker);
if (nfsd4_layout_setlease(ls)) {
nfs4_put_stid(stp);
return NULL;
}
spin_lock(&clp->cl_lock);
stp->sc_type = SC_TYPE_LAYOUT;
list_add(&ls->ls_perclnt, &clp->cl_lo_states);
spin_unlock(&clp->cl_lock);
spin_lock(&fp->fi_lock);
list_add(&ls->ls_perfile, &fp->fi_lo_states);
spin_unlock(&fp->fi_lock);
trace_nfsd_layoutstate_alloc(&ls->ls_stid.sc_stateid);
return ls;
}
__be32
nfsd4_preprocess_layout_stateid(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate, stateid_t *stateid,
bool create, u32 layout_type, struct nfs4_layout_stateid **lsp)
{
struct nfs4_layout_stateid *ls;
struct nfs4_stid *stid;
unsigned short typemask = SC_TYPE_LAYOUT;
__be32 status;
if (create)
typemask |= (SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG);
status = nfsd4_lookup_stateid(cstate, stateid, typemask, 0, &stid,
net_generic(SVC_NET(rqstp), nfsd_net_id));
if (status)
goto out;
if (!fh_match(&cstate->current_fh.fh_handle,
&stid->sc_file->fi_fhandle)) {
status = nfserr_bad_stateid;
goto out_put_stid;
}
if (stid->sc_type != SC_TYPE_LAYOUT) {
ls = nfsd4_alloc_layout_stateid(cstate, stid, layout_type);
nfs4_put_stid(stid);
status = nfserr_jukebox;
if (!ls)
goto out;
mutex_lock(&ls->ls_mutex);
} else {
ls = container_of(stid, struct nfs4_layout_stateid, ls_stid);
status = nfserr_bad_stateid;
mutex_lock(&ls->ls_mutex);
if (nfsd4_stateid_generation_after(stateid, &stid->sc_stateid))
goto out_unlock_stid;
if (layout_type != ls->ls_layout_type)
goto out_unlock_stid;
}
*lsp = ls;
return 0;
out_unlock_stid:
mutex_unlock(&ls->ls_mutex);
out_put_stid:
nfs4_put_stid(stid);
out:
return status;
}
static void
nfsd4_recall_file_layout(struct nfs4_layout_stateid *ls)
{
spin_lock(&ls->ls_lock);
if (ls->ls_recalled)
goto out_unlock;
if (list_empty(&ls->ls_layouts))
goto out_unlock;
ls->ls_recalled = true;
atomic_inc(&ls->ls_stid.sc_file->fi_lo_recalls);
trace_nfsd_layout_recall(&ls->ls_stid.sc_stateid);
if (!test_and_set_bit(NFSD4_CALLBACK_RUNNING, &ls->ls_recall.cb_flags)) {
refcount_inc(&ls->ls_stid.sc_count);
nfsd4_run_cb(&ls->ls_recall);
}
out_unlock:
spin_unlock(&ls->ls_lock);
}
static inline u64
layout_end(struct nfsd4_layout_seg *seg)
{
u64 end = seg->offset + seg->length;
return end >= seg->offset ? end : NFS4_MAX_UINT64;
}
static void
layout_update_len(struct nfsd4_layout_seg *lo, u64 end)
{
if (end == NFS4_MAX_UINT64)
lo->length = NFS4_MAX_UINT64;
else
lo->length = end - lo->offset;
}
static bool
layouts_overlapping(struct nfs4_layout *lo, struct nfsd4_layout_seg *s)
{
if (s->iomode != IOMODE_ANY && s->iomode != lo->lo_seg.iomode)
return false;
if (layout_end(&lo->lo_seg) <= s->offset)
return false;
if (layout_end(s) <= lo->lo_seg.offset)
return false;
return true;
}
static bool
layouts_try_merge(struct nfsd4_layout_seg *lo, struct nfsd4_layout_seg *new)
{
if (lo->iomode != new->iomode)
return false;
if (layout_end(new) < lo->offset)
return false;
if (layout_end(lo) < new->offset)
return false;
lo->offset = min(lo->offset, new->offset);
layout_update_len(lo, max(layout_end(lo), layout_end(new)));
return true;
}
static __be32
nfsd4_recall_conflict(struct nfs4_layout_stateid *ls)
{
struct nfs4_file *fp = ls->ls_stid.sc_file;
struct nfs4_layout_stateid *l, *n;
__be32 nfserr = nfs_ok;
assert_spin_locked(&fp->fi_lock);
list_for_each_entry_safe(l, n, &fp->fi_lo_states, ls_perfile) {
if (l != ls) {
nfsd4_recall_file_layout(l);
nfserr = nfserr_recallconflict;
}
}
return nfserr;
}
__be32
nfsd4_insert_layout(struct nfsd4_layoutget *lgp, struct nfs4_layout_stateid *ls)
{
struct nfsd4_layout_seg *seg = &lgp->lg_seg;
struct nfs4_file *fp = ls->ls_stid.sc_file;
struct nfs4_layout *lp, *new = NULL;
__be32 nfserr;
spin_lock(&fp->fi_lock);
nfserr = nfsd4_recall_conflict(ls);
if (nfserr)
goto out;
spin_lock(&ls->ls_lock);
list_for_each_entry(lp, &ls->ls_layouts, lo_perstate) {
if (layouts_try_merge(&lp->lo_seg, seg))
goto done;
}
spin_unlock(&ls->ls_lock);
spin_unlock(&fp->fi_lock);
new = kmem_cache_alloc(nfs4_layout_cache, GFP_KERNEL);
if (!new)
return nfserr_jukebox;
memcpy(&new->lo_seg, seg, sizeof(new->lo_seg));
new->lo_state = ls;
spin_lock(&fp->fi_lock);
nfserr = nfsd4_recall_conflict(ls);
if (nfserr)
goto out;
spin_lock(&ls->ls_lock);
list_for_each_entry(lp, &ls->ls_layouts, lo_perstate) {
if (layouts_try_merge(&lp->lo_seg, seg))
goto done;
}
refcount_inc(&ls->ls_stid.sc_count);
list_add_tail(&new->lo_perstate, &ls->ls_layouts);
new = NULL;
done:
nfs4_inc_and_copy_stateid(&lgp->lg_sid, &ls->ls_stid);
spin_unlock(&ls->ls_lock);
out:
spin_unlock(&fp->fi_lock);
if (new)
kmem_cache_free(nfs4_layout_cache, new);
return nfserr;
}
static void
nfsd4_free_layouts(struct list_head *reaplist)
{
while (!list_empty(reaplist)) {
struct nfs4_layout *lp = list_first_entry(reaplist,
struct nfs4_layout, lo_perstate);
list_del(&lp->lo_perstate);
nfs4_put_stid(&lp->lo_state->ls_stid);
kmem_cache_free(nfs4_layout_cache, lp);
}
}
static void
nfsd4_return_file_layout(struct nfs4_layout *lp, struct nfsd4_layout_seg *seg,
struct list_head *reaplist)
{
struct nfsd4_layout_seg *lo = &lp->lo_seg;
u64 end = layout_end(lo);
if (seg->offset <= lo->offset) {
if (layout_end(seg) >= end) {
list_move_tail(&lp->lo_perstate, reaplist);
return;
}
lo->offset = layout_end(seg);
} else {
/* retain the whole layout segment on a split. */
if (layout_end(seg) < end) {
dprintk("%s: split not supported\n", __func__);
return;
}
end = seg->offset;
}
layout_update_len(lo, end);
}
__be32
nfsd4_return_file_layouts(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate,
struct nfsd4_layoutreturn *lrp)
{
struct nfs4_layout_stateid *ls;
struct nfs4_layout *lp, *n;
LIST_HEAD(reaplist);
__be32 nfserr;
int found = 0;
nfserr = nfsd4_preprocess_layout_stateid(rqstp, cstate, &lrp->lr_sid,
false, lrp->lr_layout_type,
&ls);
if (nfserr) {
trace_nfsd_layout_return_lookup_fail(&lrp->lr_sid);
return nfserr;
}
spin_lock(&ls->ls_lock);
list_for_each_entry_safe(lp, n, &ls->ls_layouts, lo_perstate) {
if (layouts_overlapping(lp, &lrp->lr_seg)) {
nfsd4_return_file_layout(lp, &lrp->lr_seg, &reaplist);
found++;
}
}
if (!list_empty(&ls->ls_layouts)) {
if (found)
nfs4_inc_and_copy_stateid(&lrp->lr_sid, &ls->ls_stid);
lrp->lrs_present = true;
} else {
trace_nfsd_layoutstate_unhash(&ls->ls_stid.sc_stateid);
ls->ls_stid.sc_status |= SC_STATUS_CLOSED;
lrp->lrs_present = false;
}
spin_unlock(&ls->ls_lock);
mutex_unlock(&ls->ls_mutex);
nfs4_put_stid(&ls->ls_stid);
nfsd4_free_layouts(&reaplist);
return nfs_ok;
}
__be32
nfsd4_return_client_layouts(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate,
struct nfsd4_layoutreturn *lrp)
{
struct nfs4_layout_stateid *ls, *n;
struct nfs4_client *clp = cstate->clp;
struct nfs4_layout *lp, *t;
LIST_HEAD(reaplist);
lrp->lrs_present = false;
spin_lock(&clp->cl_lock);
list_for_each_entry_safe(ls, n, &clp->cl_lo_states, ls_perclnt) {
if (ls->ls_layout_type != lrp->lr_layout_type)
continue;
if (lrp->lr_return_type == RETURN_FSID &&
!fh_fsid_match(&ls->ls_stid.sc_file->fi_fhandle,
&cstate->current_fh.fh_handle))
continue;
spin_lock(&ls->ls_lock);
list_for_each_entry_safe(lp, t, &ls->ls_layouts, lo_perstate) {
if (lrp->lr_seg.iomode == IOMODE_ANY ||
lrp->lr_seg.iomode == lp->lo_seg.iomode)
list_move_tail(&lp->lo_perstate, &reaplist);
}
spin_unlock(&ls->ls_lock);
}
spin_unlock(&clp->cl_lock);
nfsd4_free_layouts(&reaplist);
return 0;
}
static void
nfsd4_return_all_layouts(struct nfs4_layout_stateid *ls,
struct list_head *reaplist)
{
spin_lock(&ls->ls_lock);
list_splice_init(&ls->ls_layouts, reaplist);
spin_unlock(&ls->ls_lock);
}
void
nfsd4_return_all_client_layouts(struct nfs4_client *clp)
{
struct nfs4_layout_stateid *ls, *n;
LIST_HEAD(reaplist);
spin_lock(&clp->cl_lock);
list_for_each_entry_safe(ls, n, &clp->cl_lo_states, ls_perclnt)
nfsd4_return_all_layouts(ls, &reaplist);
spin_unlock(&clp->cl_lock);
nfsd4_free_layouts(&reaplist);
}
void
nfsd4_return_all_file_layouts(struct nfs4_client *clp, struct nfs4_file *fp)
{
struct nfs4_layout_stateid *ls, *n;
LIST_HEAD(reaplist);
spin_lock(&fp->fi_lock);
list_for_each_entry_safe(ls, n, &fp->fi_lo_states, ls_perfile) {
if (ls->ls_stid.sc_client == clp)
nfsd4_return_all_layouts(ls, &reaplist);
}
spin_unlock(&fp->fi_lock);
nfsd4_free_layouts(&reaplist);
}
static void
nfsd4_cb_layout_fail(struct nfs4_layout_stateid *ls, struct nfsd_file *file)
{
struct nfs4_client *clp = ls->ls_stid.sc_client;
char addr_str[INET6_ADDRSTRLEN];
static char const nfsd_recall_failed[] = "/sbin/nfsd-recall-failed";
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
NULL
};
char *argv[8];
int error;
rpc_ntop((struct sockaddr *)&clp->cl_addr, addr_str, sizeof(addr_str));
printk(KERN_WARNING
"nfsd: client %s failed to respond to layout recall. "
" Fencing..\n", addr_str);
argv[0] = (char *)nfsd_recall_failed;
argv[1] = addr_str;
argv[2] = file->nf_file->f_path.mnt->mnt_sb->s_id;
argv[3] = NULL;
error = call_usermodehelper(nfsd_recall_failed, argv, envp,
UMH_WAIT_PROC);
if (error) {
printk(KERN_ERR "nfsd: fence failed for client %s: %d!\n",
addr_str, error);
}
}
static void
nfsd4_cb_layout_prepare(struct nfsd4_callback *cb)
{
struct nfs4_layout_stateid *ls =
container_of(cb, struct nfs4_layout_stateid, ls_recall);
mutex_lock(&ls->ls_mutex);
nfs4_inc_and_copy_stateid(&ls->ls_recall_sid, &ls->ls_stid);
mutex_unlock(&ls->ls_mutex);
}
static int
nfsd4_cb_layout_done(struct nfsd4_callback *cb, struct rpc_task *task)
{
struct nfs4_layout_stateid *ls =
container_of(cb, struct nfs4_layout_stateid, ls_recall);
struct nfsd_net *nn;
ktime_t now, cutoff;
const struct nfsd4_layout_ops *ops;
struct nfsd_file *fl;
trace_nfsd_cb_layout_done(&ls->ls_stid.sc_stateid, task);
switch (task->tk_status) {
case 0:
case -NFS4ERR_DELAY:
/*
* Anything left? If not, then call it done. Note that we don't
* take the spinlock since this is an optimization and nothing
* should get added until the cb counter goes to zero.
*/
if (list_empty(&ls->ls_layouts))
return 1;
/* Poll the client until it's done with the layout */
now = ktime_get();
nn = net_generic(ls->ls_stid.sc_client->net, nfsd_net_id);
/* Client gets 2 lease periods to return it */
cutoff = ktime_add_ns(task->tk_start,
(u64)nn->nfsd4_lease * NSEC_PER_SEC * 2);
if (ktime_before(now, cutoff)) {
rpc_delay(task, HZ/100); /* 10 mili-seconds */
return 0;
}
fallthrough;
default:
/*
* Unknown error or non-responding client, we'll need to fence.
*/
trace_nfsd_layout_recall_fail(&ls->ls_stid.sc_stateid);
rcu_read_lock();
fl = nfsd_file_get(ls->ls_file);
rcu_read_unlock();
if (fl) {
ops = nfsd4_layout_ops[ls->ls_layout_type];
if (ops->fence_client)
ops->fence_client(ls, fl);
else
nfsd4_cb_layout_fail(ls, fl);
nfsd_file_put(fl);
}
return 1;
case -NFS4ERR_NOMATCHING_LAYOUT:
trace_nfsd_layout_recall_done(&ls->ls_stid.sc_stateid);
task->tk_status = 0;
return 1;
}
}
static void
nfsd4_cb_layout_release(struct nfsd4_callback *cb)
{
struct nfs4_layout_stateid *ls =
container_of(cb, struct nfs4_layout_stateid, ls_recall);
LIST_HEAD(reaplist);
trace_nfsd_layout_recall_release(&ls->ls_stid.sc_stateid);
nfsd4_return_all_layouts(ls, &reaplist);
nfsd4_free_layouts(&reaplist);
nfs4_put_stid(&ls->ls_stid);
}
static const struct nfsd4_callback_ops nfsd4_cb_layout_ops = {
.prepare = nfsd4_cb_layout_prepare,
.done = nfsd4_cb_layout_done,
.release = nfsd4_cb_layout_release,
.opcode = OP_CB_LAYOUTRECALL,
};
static bool
nfsd4_layout_lm_break(struct file_lease *fl)
{
/*
* Enforce break lease timeout to prevent NFSD
* thread from hanging in __break_lease.
*/
nfsd4_recall_file_layout(fl->c.flc_owner);
return false;
}
static int
nfsd4_layout_lm_change(struct file_lease *onlist, int arg,
struct list_head *dispose)
{
BUG_ON(!(arg & F_UNLCK));
return lease_modify(onlist, arg, dispose);
}
/**
* nfsd4_layout_lm_open_conflict - see if the given file points to an inode that has
* an existing open that would conflict with the
* desired lease.
* @filp: file to check
* @arg: type of lease that we're trying to acquire
*
* The kernel will call into this operation to determine whether there
* are conflicting opens that may prevent the layout from being granted.
* For nfsd, that check is done at a higher level, so this trivially
* returns 0.
*/
static int
nfsd4_layout_lm_open_conflict(struct file *filp, int arg)
{
return 0;
}
static void
nfsd4_layout_fence_worker(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct nfs4_layout_stateid *ls = container_of(dwork,
struct nfs4_layout_stateid, ls_fence_work);
struct nfsd_file *nf;
struct block_device *bdev;
struct nfs4_client *clp;
struct nfsd_net *nn;
/*
* The workqueue clears WORK_STRUCT_PENDING before invoking
* this callback. Re-arm immediately so that
* delayed_work_pending() returns true while the fence
* operation is in progress, preventing
* lm_breaker_timedout() from taking a duplicate reference.
*/
mod_delayed_work(system_dfl_wq, &ls->ls_fence_work, 0);
spin_lock(&ls->ls_lock);
if (list_empty(&ls->ls_layouts)) {
spin_unlock(&ls->ls_lock);
dispose:
cancel_delayed_work(&ls->ls_fence_work);
/* unlock the lease so that tasks waiting on it can proceed */
nfsd4_close_layout(ls);
ls->ls_fenced = true;
nfs4_put_stid(&ls->ls_stid);
return;
}
spin_unlock(&ls->ls_lock);
rcu_read_lock();
nf = nfsd_file_get(ls->ls_file);
rcu_read_unlock();
if (!nf)
goto dispose;
clp = ls->ls_stid.sc_client;
nn = net_generic(clp->net, nfsd_net_id);
bdev = nf->nf_file->f_path.mnt->mnt_sb->s_bdev;
if (nfsd4_layout_ops[ls->ls_layout_type]->fence_client(ls, nf)) {
/* fenced ok */
nfsd_file_put(nf);
pr_warn("%s: FENCED client[%pISpc] clid[%d] to device[%s]\n",
__func__, (struct sockaddr *)&clp->cl_addr,
clp->cl_clientid.cl_id - nn->clientid_base,
bdev->bd_disk->disk_name);
goto dispose;
}
/* fence failed */
nfsd_file_put(nf);
if (!clp->cl_fence_retry_warn) {
pr_warn("%s: FENCE failed client[%pISpc] clid[%d] device[%s]\n",
__func__, (struct sockaddr *)&clp->cl_addr,
clp->cl_clientid.cl_id - nn->clientid_base,
bdev->bd_disk->disk_name);
clp->cl_fence_retry_warn = true;
}
/*
* The fence worker retries the fencing operation indefinitely to
* prevent data corruption. The admin needs to take the following
* actions to restore access to the file for other clients:
*
* . shutdown or power off the client being fenced.
* . manually expire the client to release all its state on the server;
* echo 'expire' > /proc/fs/nfsd/clients/clid/ctl'.
*
* Where:
*
* clid: is the unique client identifier displayed in
* the warning message above.
*/
if (!ls->ls_fence_delay)
ls->ls_fence_delay = HZ;
else
ls->ls_fence_delay = min(ls->ls_fence_delay << 1,
MAX_FENCE_DELAY);
mod_delayed_work(system_dfl_wq, &ls->ls_fence_work, ls->ls_fence_delay);
}
/**
* nfsd4_layout_lm_breaker_timedout - The layout recall has timed out.
* @fl: file to check
*
* If the layout type supports a fence operation, schedule a worker to
* fence the client from accessing the block device.
*
* This function runs under the protection of the spin_lock flc_lock.
* At this time, the file_lease associated with the layout stateid is
* on the flc_list. A reference count is incremented on the layout
* stateid to prevent it from being freed while the fence worker is
* executing. Once the fence worker finishes its operation, it releases
* this reference.
*
* The fence worker continues to run until either the client has been
* fenced or the layout becomes invalid. The layout can become invalid
* as a result of a LAYOUTRETURN or when the CB_LAYOUT recall callback
* has completed.
*
* Return true if the file_lease should be disposed of by the caller;
* otherwise, return false.
*/
static bool
nfsd4_layout_lm_breaker_timedout(struct file_lease *fl)
{
struct nfs4_layout_stateid *ls = fl->c.flc_owner;
if ((!nfsd4_layout_ops[ls->ls_layout_type]->fence_client) ||
ls->ls_fenced)
return true;
if (delayed_work_pending(&ls->ls_fence_work))
return false;
/*
* Make sure layout has not been returned yet before
* taking a reference count on the layout stateid.
*/
spin_lock(&ls->ls_lock);
if (list_empty(&ls->ls_layouts) ||
!refcount_inc_not_zero(&ls->ls_stid.sc_count)) {
spin_unlock(&ls->ls_lock);
return true;
}
spin_unlock(&ls->ls_lock);
mod_delayed_work(system_dfl_wq, &ls->ls_fence_work, 0);
return false;
}
static const struct lease_manager_operations nfsd4_layouts_lm_ops = {
.lm_break = nfsd4_layout_lm_break,
.lm_change = nfsd4_layout_lm_change,
.lm_open_conflict = nfsd4_layout_lm_open_conflict,
.lm_breaker_timedout = nfsd4_layout_lm_breaker_timedout,
};
int
nfsd4_init_pnfs(void)
{
int i;
for (i = 0; i < DEVID_HASH_SIZE; i++)
INIT_LIST_HEAD(&nfsd_devid_hash[i]);
nfs4_layout_cache = KMEM_CACHE(nfs4_layout, 0);
if (!nfs4_layout_cache)
return -ENOMEM;
nfs4_layout_stateid_cache = KMEM_CACHE(nfs4_layout_stateid, 0);
if (!nfs4_layout_stateid_cache) {
kmem_cache_destroy(nfs4_layout_cache);
return -ENOMEM;
}
return 0;
}
void
nfsd4_exit_pnfs(void)
{
int i;
kmem_cache_destroy(nfs4_layout_cache);
kmem_cache_destroy(nfs4_layout_stateid_cache);
for (i = 0; i < DEVID_HASH_SIZE; i++) {
struct nfsd4_deviceid_map *map, *n;
list_for_each_entry_safe(map, n, &nfsd_devid_hash[i], hash)
kfree(map);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+569
View File
@@ -0,0 +1,569 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x
// XDR specification modification time: Thu Jan 8 23:12:07 2026
#include <linux/sunrpc/svc.h>
#include "nfs4xdr_gen.h"
static bool __maybe_unused
xdrgen_decode_int64_t(struct xdr_stream *xdr, int64_t *ptr)
{
return xdrgen_decode_hyper(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_uint32_t(struct xdr_stream *xdr, uint32_t *ptr)
{
return xdrgen_decode_unsigned_int(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_bitmap4(struct xdr_stream *xdr, bitmap4 *ptr)
{
if (xdr_stream_decode_u32(xdr, &ptr->count) < 0)
return false;
for (u32 i = 0; i < ptr->count; i++)
if (!xdrgen_decode_uint32_t(xdr, &ptr->element[i]))
return false;
return true;
}
static bool __maybe_unused
xdrgen_decode_utf8string(struct xdr_stream *xdr, utf8string *ptr)
{
return xdrgen_decode_opaque(xdr, ptr, 0);
}
static bool __maybe_unused
xdrgen_decode_utf8str_cis(struct xdr_stream *xdr, utf8str_cis *ptr)
{
return xdrgen_decode_utf8string(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_utf8str_cs(struct xdr_stream *xdr, utf8str_cs *ptr)
{
return xdrgen_decode_utf8string(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_utf8str_mixed(struct xdr_stream *xdr, utf8str_mixed *ptr)
{
return xdrgen_decode_utf8string(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_nfstime4(struct xdr_stream *xdr, struct nfstime4 *ptr)
{
if (!xdrgen_decode_int64_t(xdr, &ptr->seconds))
return false;
if (!xdrgen_decode_uint32_t(xdr, &ptr->nseconds))
return false;
return true;
}
static bool __maybe_unused
xdrgen_decode_fattr4_offline(struct xdr_stream *xdr, fattr4_offline *ptr)
{
return xdrgen_decode_bool(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_open_arguments4(struct xdr_stream *xdr, struct open_arguments4 *ptr)
{
if (!xdrgen_decode_bitmap4(xdr, &ptr->oa_share_access))
return false;
if (!xdrgen_decode_bitmap4(xdr, &ptr->oa_share_deny))
return false;
if (!xdrgen_decode_bitmap4(xdr, &ptr->oa_share_access_want))
return false;
if (!xdrgen_decode_bitmap4(xdr, &ptr->oa_open_claim))
return false;
if (!xdrgen_decode_bitmap4(xdr, &ptr->oa_create_mode))
return false;
return true;
}
static bool __maybe_unused
xdrgen_decode_open_args_share_access4(struct xdr_stream *xdr, open_args_share_access4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case OPEN_ARGS_SHARE_ACCESS_READ:
case OPEN_ARGS_SHARE_ACCESS_WRITE:
case OPEN_ARGS_SHARE_ACCESS_BOTH:
break;
default:
return false;
}
*ptr = val;
return true;
}
static bool __maybe_unused
xdrgen_decode_open_args_share_deny4(struct xdr_stream *xdr, open_args_share_deny4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case OPEN_ARGS_SHARE_DENY_NONE:
case OPEN_ARGS_SHARE_DENY_READ:
case OPEN_ARGS_SHARE_DENY_WRITE:
case OPEN_ARGS_SHARE_DENY_BOTH:
break;
default:
return false;
}
*ptr = val;
return true;
}
static bool __maybe_unused
xdrgen_decode_open_args_share_access_want4(struct xdr_stream *xdr, open_args_share_access_want4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case OPEN_ARGS_SHARE_ACCESS_WANT_ANY_DELEG:
case OPEN_ARGS_SHARE_ACCESS_WANT_NO_DELEG:
case OPEN_ARGS_SHARE_ACCESS_WANT_CANCEL:
case OPEN_ARGS_SHARE_ACCESS_WANT_SIGNAL_DELEG_WHEN_RESRC_AVAIL:
case OPEN_ARGS_SHARE_ACCESS_WANT_PUSH_DELEG_WHEN_UNCONTENDED:
case OPEN_ARGS_SHARE_ACCESS_WANT_DELEG_TIMESTAMPS:
case OPEN_ARGS_SHARE_ACCESS_WANT_OPEN_XOR_DELEGATION:
break;
default:
return false;
}
*ptr = val;
return true;
}
static bool __maybe_unused
xdrgen_decode_open_args_open_claim4(struct xdr_stream *xdr, open_args_open_claim4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case OPEN_ARGS_OPEN_CLAIM_NULL:
case OPEN_ARGS_OPEN_CLAIM_PREVIOUS:
case OPEN_ARGS_OPEN_CLAIM_DELEGATE_CUR:
case OPEN_ARGS_OPEN_CLAIM_DELEGATE_PREV:
case OPEN_ARGS_OPEN_CLAIM_FH:
case OPEN_ARGS_OPEN_CLAIM_DELEG_CUR_FH:
case OPEN_ARGS_OPEN_CLAIM_DELEG_PREV_FH:
break;
default:
return false;
}
*ptr = val;
return true;
}
static bool __maybe_unused
xdrgen_decode_open_args_createmode4(struct xdr_stream *xdr, open_args_createmode4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case OPEN_ARGS_CREATEMODE_UNCHECKED4:
case OPEN_ARGS_CREATE_MODE_GUARDED:
case OPEN_ARGS_CREATEMODE_EXCLUSIVE4:
case OPEN_ARGS_CREATE_MODE_EXCLUSIVE4_1:
break;
default:
return false;
}
*ptr = val;
return true;
}
bool
xdrgen_decode_fattr4_open_arguments(struct xdr_stream *xdr, fattr4_open_arguments *ptr)
{
return xdrgen_decode_open_arguments4(xdr, ptr);
}
/*
* Determine what OPEN supports.
*/
bool
xdrgen_decode_fattr4_time_deleg_access(struct xdr_stream *xdr, fattr4_time_deleg_access *ptr)
{
return xdrgen_decode_nfstime4(xdr, ptr);
}
bool
xdrgen_decode_fattr4_time_deleg_modify(struct xdr_stream *xdr, fattr4_time_deleg_modify *ptr)
{
return xdrgen_decode_nfstime4(xdr, ptr);
}
/*
* New RECOMMENDED Attribute for
* delegation caching of times
*/
static bool __maybe_unused
xdrgen_decode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case OPEN_DELEGATE_NONE:
case OPEN_DELEGATE_READ:
case OPEN_DELEGATE_WRITE:
case OPEN_DELEGATE_NONE_EXT:
case OPEN_DELEGATE_READ_ATTRS_DELEG:
case OPEN_DELEGATE_WRITE_ATTRS_DELEG:
break;
default:
return false;
}
*ptr = val;
return true;
}
bool
xdrgen_decode_aclmodel4(struct xdr_stream *xdr, aclmodel4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case ACL_MODEL_NFS4:
case ACL_MODEL_POSIX_DRAFT:
case ACL_MODEL_NONE:
break;
default:
return false;
}
*ptr = val;
return true;
}
bool
xdrgen_decode_aclscope4(struct xdr_stream *xdr, aclscope4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case ACL_SCOPE_FILE_OBJECT:
case ACL_SCOPE_FILE_SYSTEM:
case ACL_SCOPE_SERVER:
break;
default:
return false;
}
*ptr = val;
return true;
}
bool
xdrgen_decode_posixacetag4(struct xdr_stream *xdr, posixacetag4 *ptr)
{
u32 val;
if (xdr_stream_decode_u32(xdr, &val) < 0)
return false;
/* Compiler may optimize to a range check for dense enums */
switch (val) {
case POSIXACE4_TAG_USER_OBJ:
case POSIXACE4_TAG_USER:
case POSIXACE4_TAG_GROUP_OBJ:
case POSIXACE4_TAG_GROUP:
case POSIXACE4_TAG_MASK:
case POSIXACE4_TAG_OTHER:
break;
default:
return false;
}
*ptr = val;
return true;
}
bool
xdrgen_decode_posixaceperm4(struct xdr_stream *xdr, posixaceperm4 *ptr)
{
return xdrgen_decode_uint32_t(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_posixace4(struct xdr_stream *xdr, struct posixace4 *ptr)
{
if (!xdrgen_decode_posixacetag4(xdr, &ptr->tag))
return false;
if (!xdrgen_decode_posixaceperm4(xdr, &ptr->perm))
return false;
if (!xdrgen_decode_utf8str_mixed(xdr, &ptr->who))
return false;
return true;
}
static bool __maybe_unused
xdrgen_decode_fattr4_acl_trueform(struct xdr_stream *xdr, fattr4_acl_trueform *ptr)
{
return xdrgen_decode_aclmodel4(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_fattr4_acl_trueform_scope(struct xdr_stream *xdr, fattr4_acl_trueform_scope *ptr)
{
return xdrgen_decode_aclscope4(xdr, ptr);
}
static bool __maybe_unused
xdrgen_decode_fattr4_posix_default_acl(struct xdr_stream *xdr, fattr4_posix_default_acl *ptr)
{
if (xdr_stream_decode_u32(xdr, &ptr->count) < 0)
return false;
for (u32 i = 0; i < ptr->count; i++)
if (!xdrgen_decode_posixace4(xdr, &ptr->element[i]))
return false;
return true;
}
static bool __maybe_unused
xdrgen_decode_fattr4_posix_access_acl(struct xdr_stream *xdr, fattr4_posix_access_acl *ptr)
{
if (xdr_stream_decode_u32(xdr, &ptr->count) < 0)
return false;
for (u32 i = 0; i < ptr->count; i++)
if (!xdrgen_decode_posixace4(xdr, &ptr->element[i]))
return false;
return true;
}
/*
* New for POSIX ACL extension
*/
static bool __maybe_unused
xdrgen_encode_int64_t(struct xdr_stream *xdr, const int64_t value)
{
return xdrgen_encode_hyper(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_uint32_t(struct xdr_stream *xdr, const uint32_t value)
{
return xdrgen_encode_unsigned_int(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_bitmap4(struct xdr_stream *xdr, const bitmap4 value)
{
if (xdr_stream_encode_u32(xdr, value.count) != XDR_UNIT)
return false;
for (u32 i = 0; i < value.count; i++)
if (!xdrgen_encode_uint32_t(xdr, value.element[i]))
return false;
return true;
}
static bool __maybe_unused
xdrgen_encode_utf8string(struct xdr_stream *xdr, const utf8string value)
{
return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0;
}
static bool __maybe_unused
xdrgen_encode_utf8str_cis(struct xdr_stream *xdr, const utf8str_cis value)
{
return xdrgen_encode_utf8string(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_utf8str_cs(struct xdr_stream *xdr, const utf8str_cs value)
{
return xdrgen_encode_utf8string(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_utf8str_mixed(struct xdr_stream *xdr, const utf8str_mixed value)
{
return xdrgen_encode_utf8string(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_nfstime4(struct xdr_stream *xdr, const struct nfstime4 *value)
{
if (!xdrgen_encode_int64_t(xdr, value->seconds))
return false;
if (!xdrgen_encode_uint32_t(xdr, value->nseconds))
return false;
return true;
}
static bool __maybe_unused
xdrgen_encode_fattr4_offline(struct xdr_stream *xdr, const fattr4_offline value)
{
return xdrgen_encode_bool(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_open_arguments4(struct xdr_stream *xdr, const struct open_arguments4 *value)
{
if (!xdrgen_encode_bitmap4(xdr, value->oa_share_access))
return false;
if (!xdrgen_encode_bitmap4(xdr, value->oa_share_deny))
return false;
if (!xdrgen_encode_bitmap4(xdr, value->oa_share_access_want))
return false;
if (!xdrgen_encode_bitmap4(xdr, value->oa_open_claim))
return false;
if (!xdrgen_encode_bitmap4(xdr, value->oa_create_mode))
return false;
return true;
}
static bool __maybe_unused
xdrgen_encode_open_args_share_access4(struct xdr_stream *xdr, open_args_share_access4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
static bool __maybe_unused
xdrgen_encode_open_args_share_deny4(struct xdr_stream *xdr, open_args_share_deny4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
static bool __maybe_unused
xdrgen_encode_open_args_share_access_want4(struct xdr_stream *xdr, open_args_share_access_want4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
static bool __maybe_unused
xdrgen_encode_open_args_open_claim4(struct xdr_stream *xdr, open_args_open_claim4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
static bool __maybe_unused
xdrgen_encode_open_args_createmode4(struct xdr_stream *xdr, open_args_createmode4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
bool
xdrgen_encode_fattr4_open_arguments(struct xdr_stream *xdr, const fattr4_open_arguments *value)
{
return xdrgen_encode_open_arguments4(xdr, value);
}
bool
xdrgen_encode_fattr4_time_deleg_access(struct xdr_stream *xdr, const fattr4_time_deleg_access *value)
{
return xdrgen_encode_nfstime4(xdr, value);
}
bool
xdrgen_encode_fattr4_time_deleg_modify(struct xdr_stream *xdr, const fattr4_time_deleg_modify *value)
{
return xdrgen_encode_nfstime4(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
bool
xdrgen_encode_aclmodel4(struct xdr_stream *xdr, aclmodel4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
bool
xdrgen_encode_aclscope4(struct xdr_stream *xdr, aclscope4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
bool
xdrgen_encode_posixacetag4(struct xdr_stream *xdr, posixacetag4 value)
{
return xdr_stream_encode_u32(xdr, value) == XDR_UNIT;
}
bool
xdrgen_encode_posixaceperm4(struct xdr_stream *xdr, const posixaceperm4 value)
{
return xdrgen_encode_uint32_t(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_posixace4(struct xdr_stream *xdr, const struct posixace4 *value)
{
if (!xdrgen_encode_posixacetag4(xdr, value->tag))
return false;
if (!xdrgen_encode_posixaceperm4(xdr, value->perm))
return false;
if (!xdrgen_encode_utf8str_mixed(xdr, value->who))
return false;
return true;
}
static bool __maybe_unused
xdrgen_encode_fattr4_acl_trueform(struct xdr_stream *xdr, const fattr4_acl_trueform value)
{
return xdrgen_encode_aclmodel4(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_fattr4_acl_trueform_scope(struct xdr_stream *xdr, const fattr4_acl_trueform_scope value)
{
return xdrgen_encode_aclscope4(xdr, value);
}
static bool __maybe_unused
xdrgen_encode_fattr4_posix_default_acl(struct xdr_stream *xdr, const fattr4_posix_default_acl value)
{
if (xdr_stream_encode_u32(xdr, value.count) != XDR_UNIT)
return false;
for (u32 i = 0; i < value.count; i++)
if (!xdrgen_encode_posixace4(xdr, &value.element[i]))
return false;
return true;
}
static bool __maybe_unused
xdrgen_encode_fattr4_posix_access_acl(struct xdr_stream *xdr, const fattr4_posix_access_acl value)
{
if (xdr_stream_encode_u32(xdr, value.count) != XDR_UNIT)
return false;
for (u32 i = 0; i < value.count; i++)
if (!xdrgen_encode_posixace4(xdr, &value.element[i]))
return false;
return true;
}
+35
View File
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */
/* XDR specification modification time: Thu Jan 8 23:12:07 2026 */
#ifndef _LINUX_XDRGEN_NFS4_1_DECL_H
#define _LINUX_XDRGEN_NFS4_1_DECL_H
#include <linux/types.h>
#include <linux/sunrpc/xdr.h>
#include <linux/sunrpc/xdrgen/_defs.h>
#include <linux/sunrpc/xdrgen/_builtins.h>
#include <linux/sunrpc/xdrgen/nfs4_1.h>
bool xdrgen_decode_fattr4_open_arguments(struct xdr_stream *xdr, fattr4_open_arguments *ptr);
bool xdrgen_encode_fattr4_open_arguments(struct xdr_stream *xdr, const fattr4_open_arguments *value);
bool xdrgen_decode_fattr4_time_deleg_access(struct xdr_stream *xdr, fattr4_time_deleg_access *ptr);
bool xdrgen_encode_fattr4_time_deleg_access(struct xdr_stream *xdr, const fattr4_time_deleg_access *value);
bool xdrgen_decode_fattr4_time_deleg_modify(struct xdr_stream *xdr, fattr4_time_deleg_modify *ptr);
bool xdrgen_encode_fattr4_time_deleg_modify(struct xdr_stream *xdr, const fattr4_time_deleg_modify *value);
bool xdrgen_decode_aclmodel4(struct xdr_stream *xdr, aclmodel4 *ptr);
bool xdrgen_encode_aclmodel4(struct xdr_stream *xdr, aclmodel4 value);
bool xdrgen_decode_aclscope4(struct xdr_stream *xdr, aclscope4 *ptr);
bool xdrgen_encode_aclscope4(struct xdr_stream *xdr, aclscope4 value);
bool xdrgen_decode_posixacetag4(struct xdr_stream *xdr, posixacetag4 *ptr);
bool xdrgen_encode_posixacetag4(struct xdr_stream *xdr, posixacetag4 value);
bool xdrgen_decode_posixaceperm4(struct xdr_stream *xdr, posixaceperm4 *ptr);
bool xdrgen_encode_posixaceperm4(struct xdr_stream *xdr, const posixaceperm4 value);
#endif /* _LINUX_XDRGEN_NFS4_1_DECL_H */
+667
View File
@@ -0,0 +1,667 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Request reply cache. This is currently a global cache, but this may
* change in the future and be a per-client cache.
*
* This code is heavily inspired by the 44BSD implementation, although
* it does things a bit differently.
*
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
*/
#include <linux/sunrpc/svc_xprt.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/sunrpc/addr.h>
#include <linux/highmem.h>
#include <linux/log2.h>
#include <linux/hash.h>
#include <net/checksum.h>
#include "nfsd.h"
#include "cache.h"
#include "trace.h"
/*
* We use this value to determine the number of hash buckets from the max
* cache size, the idea being that when the cache is at its maximum number
* of entries, then this should be the average number of entries per bucket.
*/
#define TARGET_BUCKET_SIZE 8
struct nfsd_drc_bucket {
struct rb_root rb_head;
struct list_head lru_head;
spinlock_t cache_lock;
};
static struct kmem_cache *drc_slab;
static int nfsd_cache_append(struct svc_rqst *rqstp, struct kvec *vec);
static unsigned long nfsd_reply_cache_count(struct shrinker *shrink,
struct shrink_control *sc);
static unsigned long nfsd_reply_cache_scan(struct shrinker *shrink,
struct shrink_control *sc);
/*
* Put a cap on the size of the DRC based on the amount of available
* low memory in the machine.
*
* 64MB: 8192
* 128MB: 11585
* 256MB: 16384
* 512MB: 23170
* 1GB: 32768
* 2GB: 46340
* 4GB: 65536
* 8GB: 92681
* 16GB: 131072
*
* ...with a hard cap of 256k entries. In the worst case, each entry will be
* ~1k, so the above numbers should give a rough max of the amount of memory
* used in k.
*
* XXX: these limits are per-container, so memory used will increase
* linearly with number of containers. Maybe that's OK.
*/
static unsigned int
nfsd_cache_size_limit(void)
{
unsigned int limit;
unsigned long low_pages = totalram_pages() - totalhigh_pages();
limit = (16 * int_sqrt(low_pages)) << (PAGE_SHIFT-10);
return min_t(unsigned int, limit, 256*1024);
}
/*
* Compute the number of hash buckets we need. Divide the max cachesize by
* the "target" max bucket size, and round up to next power of two.
*/
static unsigned int
nfsd_hashsize(unsigned int limit)
{
return roundup_pow_of_two(limit / TARGET_BUCKET_SIZE);
}
static struct nfsd_cacherep *
nfsd_cacherep_alloc(struct svc_rqst *rqstp, __wsum csum,
struct nfsd_net *nn)
{
struct nfsd_cacherep *rp;
rp = kmem_cache_alloc(drc_slab, GFP_KERNEL);
if (rp) {
rp->c_state = RC_UNUSED;
rp->c_type = RC_NOCACHE;
RB_CLEAR_NODE(&rp->c_node);
INIT_LIST_HEAD(&rp->c_lru);
memset(&rp->c_key, 0, sizeof(rp->c_key));
rp->c_key.k_xid = rqstp->rq_xid;
rp->c_key.k_proc = rqstp->rq_proc;
rpc_copy_addr((struct sockaddr *)&rp->c_key.k_addr, svc_addr(rqstp));
rpc_set_port((struct sockaddr *)&rp->c_key.k_addr, rpc_get_port(svc_addr(rqstp)));
rp->c_key.k_prot = rqstp->rq_prot;
rp->c_key.k_vers = rqstp->rq_vers;
rp->c_key.k_len = rqstp->rq_arg.len;
rp->c_key.k_csum = csum;
}
return rp;
}
static void nfsd_cacherep_free(struct nfsd_cacherep *rp)
{
if (rp->c_type == RC_REPLBUFF)
kfree(rp->c_replvec.iov_base);
kmem_cache_free(drc_slab, rp);
}
static unsigned long
nfsd_cacherep_dispose(struct list_head *dispose)
{
struct nfsd_cacherep *rp;
unsigned long freed = 0;
while (!list_empty(dispose)) {
rp = list_first_entry(dispose, struct nfsd_cacherep, c_lru);
list_del(&rp->c_lru);
nfsd_cacherep_free(rp);
freed++;
}
return freed;
}
static void
nfsd_cacherep_unlink_locked(struct nfsd_net *nn, struct nfsd_drc_bucket *b,
struct nfsd_cacherep *rp)
{
if (rp->c_type == RC_REPLBUFF && rp->c_replvec.iov_base)
nfsd_stats_drc_mem_usage_sub(nn, rp->c_replvec.iov_len);
if (rp->c_state != RC_UNUSED) {
rb_erase(&rp->c_node, &b->rb_head);
list_del(&rp->c_lru);
atomic_dec(&nn->num_drc_entries);
nfsd_stats_drc_mem_usage_sub(nn, sizeof(*rp));
}
}
static void
nfsd_reply_cache_free_locked(struct nfsd_drc_bucket *b, struct nfsd_cacherep *rp,
struct nfsd_net *nn)
{
nfsd_cacherep_unlink_locked(nn, b, rp);
nfsd_cacherep_free(rp);
}
static void
nfsd_reply_cache_free(struct nfsd_drc_bucket *b, struct nfsd_cacherep *rp,
struct nfsd_net *nn)
{
spin_lock(&b->cache_lock);
nfsd_cacherep_unlink_locked(nn, b, rp);
spin_unlock(&b->cache_lock);
nfsd_cacherep_free(rp);
}
int nfsd_drc_slab_create(void)
{
drc_slab = KMEM_CACHE(nfsd_cacherep, 0);
return drc_slab ? 0: -ENOMEM;
}
void nfsd_drc_slab_free(void)
{
kmem_cache_destroy(drc_slab);
}
int nfsd_reply_cache_init(struct nfsd_net *nn)
{
unsigned int hashsize;
unsigned int i;
nn->max_drc_entries = nfsd_cache_size_limit();
atomic_set(&nn->num_drc_entries, 0);
hashsize = nfsd_hashsize(nn->max_drc_entries);
nn->maskbits = ilog2(hashsize);
nn->drc_hashtbl = kvzalloc(array_size(hashsize,
sizeof(*nn->drc_hashtbl)), GFP_KERNEL);
if (!nn->drc_hashtbl)
return -ENOMEM;
nn->nfsd_reply_cache_shrinker = shrinker_alloc(0, "nfsd-reply:%s",
nn->nfsd_name);
if (!nn->nfsd_reply_cache_shrinker)
goto out_shrinker;
nn->nfsd_reply_cache_shrinker->scan_objects = nfsd_reply_cache_scan;
nn->nfsd_reply_cache_shrinker->count_objects = nfsd_reply_cache_count;
nn->nfsd_reply_cache_shrinker->seeks = 1;
nn->nfsd_reply_cache_shrinker->private_data = nn;
shrinker_register(nn->nfsd_reply_cache_shrinker);
for (i = 0; i < hashsize; i++) {
INIT_LIST_HEAD(&nn->drc_hashtbl[i].lru_head);
spin_lock_init(&nn->drc_hashtbl[i].cache_lock);
}
nn->drc_hashsize = hashsize;
return 0;
out_shrinker:
kvfree(nn->drc_hashtbl);
printk(KERN_ERR "nfsd: failed to allocate reply cache\n");
return -ENOMEM;
}
void nfsd_reply_cache_shutdown(struct nfsd_net *nn)
{
struct nfsd_cacherep *rp;
unsigned int i;
shrinker_free(nn->nfsd_reply_cache_shrinker);
for (i = 0; i < nn->drc_hashsize; i++) {
struct list_head *head = &nn->drc_hashtbl[i].lru_head;
while (!list_empty(head)) {
rp = list_first_entry(head, struct nfsd_cacherep, c_lru);
nfsd_reply_cache_free_locked(&nn->drc_hashtbl[i],
rp, nn);
}
}
kvfree(nn->drc_hashtbl);
nn->drc_hashtbl = NULL;
nn->drc_hashsize = 0;
}
static void
lru_put_end(struct nfsd_drc_bucket *b, struct nfsd_cacherep *rp)
{
rp->c_timestamp = jiffies;
list_move_tail(&rp->c_lru, &b->lru_head);
}
static noinline struct nfsd_drc_bucket *
nfsd_cache_bucket_find(__be32 xid, struct nfsd_net *nn)
{
unsigned int hash = hash_32((__force u32)xid, nn->maskbits);
return &nn->drc_hashtbl[hash];
}
/*
* Remove and return no more than @max expired entries in bucket @b.
* If @max is zero, do not limit the number of removed entries.
*/
static void
nfsd_prune_bucket_locked(struct nfsd_net *nn, struct nfsd_drc_bucket *b,
unsigned int max, struct list_head *dispose)
{
unsigned long expiry = jiffies - RC_EXPIRE;
struct nfsd_cacherep *rp, *tmp;
unsigned int freed = 0;
lockdep_assert_held(&b->cache_lock);
/* The bucket LRU is ordered oldest-first. */
list_for_each_entry_safe(rp, tmp, &b->lru_head, c_lru) {
if (atomic_read(&nn->num_drc_entries) <= nn->max_drc_entries &&
time_before(expiry, rp->c_timestamp))
break;
nfsd_cacherep_unlink_locked(nn, b, rp);
list_add(&rp->c_lru, dispose);
if (max && ++freed > max)
break;
}
}
/**
* nfsd_reply_cache_count - count_objects method for the DRC shrinker
* @shrink: our registered shrinker context
* @sc: garbage collection parameters
*
* Returns the total number of entries in the duplicate reply cache. To
* keep things simple and quick, this is not the number of expired entries
* in the cache (ie, the number that would be removed by a call to
* nfsd_reply_cache_scan).
*/
static unsigned long
nfsd_reply_cache_count(struct shrinker *shrink, struct shrink_control *sc)
{
struct nfsd_net *nn = shrink->private_data;
return atomic_read(&nn->num_drc_entries);
}
/**
* nfsd_reply_cache_scan - scan_objects method for the DRC shrinker
* @shrink: our registered shrinker context
* @sc: garbage collection parameters
*
* Free expired entries on each bucket's LRU list until we've released
* nr_to_scan freed objects. Nothing will be released if the cache
* has not exceeded it's max_drc_entries limit.
*
* Returns the number of entries released by this call.
*/
static unsigned long
nfsd_reply_cache_scan(struct shrinker *shrink, struct shrink_control *sc)
{
struct nfsd_net *nn = shrink->private_data;
unsigned long freed = 0;
LIST_HEAD(dispose);
unsigned int i;
for (i = 0; i < nn->drc_hashsize; i++) {
struct nfsd_drc_bucket *b = &nn->drc_hashtbl[i];
if (list_empty(&b->lru_head))
continue;
spin_lock(&b->cache_lock);
nfsd_prune_bucket_locked(nn, b, 0, &dispose);
spin_unlock(&b->cache_lock);
freed += nfsd_cacherep_dispose(&dispose);
if (freed > sc->nr_to_scan)
break;
}
return freed;
}
/**
* nfsd_cache_csum - Checksum incoming NFS Call arguments
* @buf: buffer containing a whole RPC Call message
* @start: starting byte of the NFS Call header
* @remaining: size of the NFS Call header, in bytes
*
* Compute a weak checksum of the leading bytes of an NFS procedure
* call header to help verify that a retransmitted Call matches an
* entry in the duplicate reply cache.
*
* To avoid assumptions about how the RPC message is laid out in
* @buf and what else it might contain (eg, a GSS MIC suffix), the
* caller passes us the exact location and length of the NFS Call
* header.
*
* Returns a 32-bit checksum value, as defined in RFC 793.
*/
static __wsum nfsd_cache_csum(struct xdr_buf *buf, unsigned int start,
unsigned int remaining)
{
unsigned int base, len;
struct xdr_buf subbuf;
__wsum csum = 0;
void *p;
int idx;
if (remaining > RC_CSUMLEN)
remaining = RC_CSUMLEN;
if (xdr_buf_subsegment(buf, &subbuf, start, remaining))
return csum;
/* rq_arg.head first */
if (subbuf.head[0].iov_len) {
len = min_t(unsigned int, subbuf.head[0].iov_len, remaining);
csum = csum_partial(subbuf.head[0].iov_base, len, csum);
remaining -= len;
}
/* Continue into page array */
idx = subbuf.page_base / PAGE_SIZE;
base = subbuf.page_base & ~PAGE_MASK;
while (remaining) {
p = page_address(subbuf.pages[idx]) + base;
len = min_t(unsigned int, PAGE_SIZE - base, remaining);
csum = csum_partial(p, len, csum);
remaining -= len;
base = 0;
++idx;
}
return csum;
}
static int
nfsd_cache_key_cmp(const struct nfsd_cacherep *key,
const struct nfsd_cacherep *rp, struct nfsd_net *nn)
{
if (key->c_key.k_xid == rp->c_key.k_xid &&
key->c_key.k_csum != rp->c_key.k_csum) {
nfsd_stats_payload_misses_inc(nn);
trace_nfsd_drc_mismatch(nn, key, rp);
}
return memcmp(&key->c_key, &rp->c_key, sizeof(key->c_key));
}
/*
* Search the request hash for an entry that matches the given rqstp.
* Must be called with cache_lock held. Returns the found entry or
* inserts an empty key on failure.
*/
static struct nfsd_cacherep *
nfsd_cache_insert(struct nfsd_drc_bucket *b, struct nfsd_cacherep *key,
struct nfsd_net *nn)
{
struct nfsd_cacherep *rp, *ret = key;
struct rb_node **p = &b->rb_head.rb_node,
*parent = NULL;
unsigned int entries = 0;
int cmp;
while (*p != NULL) {
++entries;
parent = *p;
rp = rb_entry(parent, struct nfsd_cacherep, c_node);
cmp = nfsd_cache_key_cmp(key, rp, nn);
if (cmp < 0)
p = &parent->rb_left;
else if (cmp > 0)
p = &parent->rb_right;
else {
ret = rp;
goto out;
}
}
rb_link_node(&key->c_node, parent, p);
rb_insert_color(&key->c_node, &b->rb_head);
out:
/* tally hash chain length stats */
if (entries > nn->longest_chain) {
nn->longest_chain = entries;
nn->longest_chain_cachesize = atomic_read(&nn->num_drc_entries);
} else if (entries == nn->longest_chain) {
/* prefer to keep the smallest cachesize possible here */
nn->longest_chain_cachesize = min_t(unsigned int,
nn->longest_chain_cachesize,
atomic_read(&nn->num_drc_entries));
}
return ret;
}
/**
* nfsd_cache_lookup - Find an entry in the duplicate reply cache
* @rqstp: Incoming Call to find
* @start: starting byte in @rqstp->rq_arg of the NFS Call header
* @len: size of the NFS Call header, in bytes
* @cacherep: OUT: DRC entry for this request
*
* Try to find an entry matching the current call in the cache. When none
* is found, we try to grab the oldest expired entry off the LRU list. If
* a suitable one isn't there, then drop the cache_lock and allocate a
* new one, then search again in case one got inserted while this thread
* didn't hold the lock.
*
* Return values:
* %RC_DOIT: Process the request normally
* %RC_REPLY: Reply from cache
* %RC_DROPIT: Do not process the request further
*/
int nfsd_cache_lookup(struct svc_rqst *rqstp, unsigned int start,
unsigned int len, struct nfsd_cacherep **cacherep)
{
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
struct nfsd_thread_local_info *ntli = rqstp->rq_private;
struct nfsd_cacherep *rp, *found;
__wsum csum;
struct nfsd_drc_bucket *b;
int type = ntli->ntli_cachetype;
LIST_HEAD(dispose);
int rtn = RC_DOIT;
if (type == RC_NOCACHE) {
nfsd_stats_rc_nocache_inc(nn);
goto out;
}
csum = nfsd_cache_csum(&rqstp->rq_arg, start, len);
/*
* Since the common case is a cache miss followed by an insert,
* preallocate an entry.
*/
rp = nfsd_cacherep_alloc(rqstp, csum, nn);
if (!rp)
goto out;
b = nfsd_cache_bucket_find(rqstp->rq_xid, nn);
spin_lock(&b->cache_lock);
found = nfsd_cache_insert(b, rp, nn);
if (found != rp)
goto found_entry;
*cacherep = rp;
rp->c_state = RC_INPROG;
nfsd_prune_bucket_locked(nn, b, 3, &dispose);
spin_unlock(&b->cache_lock);
nfsd_cacherep_dispose(&dispose);
nfsd_stats_rc_misses_inc(nn);
atomic_inc(&nn->num_drc_entries);
nfsd_stats_drc_mem_usage_add(nn, sizeof(*rp));
goto out;
found_entry:
/* We found a matching entry which is either in progress or done. */
nfsd_reply_cache_free_locked(NULL, rp, nn);
nfsd_stats_rc_hits_inc(nn);
rtn = RC_DROPIT;
rp = found;
/* Request being processed */
if (rp->c_state == RC_INPROG)
goto out_trace;
/* From the hall of fame of impractical attacks:
* Is this a user who tries to snoop on the cache? */
rtn = RC_DOIT;
if (!test_bit(RQ_SECURE, &rqstp->rq_flags) && rp->c_secure)
goto out_trace;
/* Compose RPC reply header */
switch (rp->c_type) {
case RC_NOCACHE:
break;
case RC_REPLSTAT:
xdr_stream_encode_be32(&rqstp->rq_res_stream, rp->c_replstat);
rtn = RC_REPLY;
break;
case RC_REPLBUFF:
if (!nfsd_cache_append(rqstp, &rp->c_replvec))
goto out_unlock; /* should not happen */
rtn = RC_REPLY;
break;
default:
WARN_ONCE(1, "nfsd: bad repcache type %d\n", rp->c_type);
}
out_trace:
trace_nfsd_drc_found(nn, rqstp, rtn);
out_unlock:
spin_unlock(&b->cache_lock);
out:
return rtn;
}
/**
* nfsd_cache_update - Update an entry in the duplicate reply cache.
* @rqstp: svc_rqst with a finished Reply
* @rp: IN: DRC entry for this request
* @cachetype: which cache to update
* @statp: pointer to Reply's NFS status code, or NULL
*
* This is called from nfsd_dispatch when the procedure has been
* executed and the complete reply is in rqstp->rq_res.
*
* We're copying around data here rather than swapping buffers because
* the toplevel loop requires max-sized buffers, which would be a waste
* of memory for a cache with a max reply size of 100 bytes (diropokres).
*
* If we should start to use different types of cache entries tailored
* specifically for attrstat and fh's, we may save even more space.
*
* Also note that a cachetype of RC_NOCACHE can legally be passed when
* nfsd failed to encode a reply that otherwise would have been cached.
* In this case, nfsd_cache_update is called with statp == NULL.
*/
void nfsd_cache_update(struct svc_rqst *rqstp, struct nfsd_cacherep *rp,
int cachetype, __be32 *statp)
{
struct nfsd_net *nn = net_generic(SVC_NET(rqstp), nfsd_net_id);
struct kvec *resv = &rqstp->rq_res.head[0], *cachv;
struct nfsd_drc_bucket *b;
int len;
size_t bufsize = 0;
if (!rp)
return;
b = nfsd_cache_bucket_find(rp->c_key.k_xid, nn);
len = resv->iov_len - ((char*)statp - (char*)resv->iov_base);
len >>= 2;
/* Don't cache excessive amounts of data and XDR failures */
if (!statp || len > (256 >> 2)) {
nfsd_reply_cache_free(b, rp, nn);
return;
}
switch (cachetype) {
case RC_REPLSTAT:
if (len != 1)
printk("nfsd: RC_REPLSTAT/reply len %d!\n",len);
rp->c_replstat = *statp;
break;
case RC_REPLBUFF:
cachv = &rp->c_replvec;
bufsize = len << 2;
cachv->iov_base = kmalloc(bufsize, GFP_KERNEL);
if (!cachv->iov_base) {
nfsd_reply_cache_free(b, rp, nn);
return;
}
cachv->iov_len = bufsize;
memcpy(cachv->iov_base, statp, bufsize);
break;
case RC_NOCACHE:
nfsd_reply_cache_free(b, rp, nn);
return;
}
spin_lock(&b->cache_lock);
nfsd_stats_drc_mem_usage_add(nn, bufsize);
lru_put_end(b, rp);
rp->c_secure = test_bit(RQ_SECURE, &rqstp->rq_flags);
rp->c_type = cachetype;
rp->c_state = RC_DONE;
spin_unlock(&b->cache_lock);
return;
}
static int
nfsd_cache_append(struct svc_rqst *rqstp, struct kvec *data)
{
__be32 *p;
p = xdr_reserve_space(&rqstp->rq_res_stream, data->iov_len);
if (unlikely(!p))
return false;
memcpy(p, data->iov_base, data->iov_len);
xdr_commit_encode(&rqstp->rq_res_stream);
return true;
}
/*
* Note that fields may be added, removed or reordered in the future. Programs
* scraping this file for info should test the labels to ensure they're
* getting the correct field.
*/
int nfsd_reply_cache_stats_show(struct seq_file *m, void *v)
{
struct nfsd_net *nn = net_generic(file_inode(m->file)->i_sb->s_fs_info,
nfsd_net_id);
seq_printf(m, "max entries: %u\n", nn->max_drc_entries);
seq_printf(m, "num entries: %u\n",
atomic_read(&nn->num_drc_entries));
seq_printf(m, "hash buckets: %u\n", 1 << nn->maskbits);
seq_printf(m, "mem usage: %lld\n",
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_DRC_MEM_USAGE]));
seq_printf(m, "cache hits: %lld\n",
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_HITS]));
seq_printf(m, "cache misses: %lld\n",
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_MISSES]));
seq_printf(m, "not cached: %lld\n",
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_NOCACHE]));
seq_printf(m, "payload misses: %lld\n",
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_PAYLOAD_MISSES]));
seq_printf(m, "longest chain len: %u\n", nn->longest_chain);
seq_printf(m, "cachesize at longest: %u\n", nn->longest_chain_cachesize);
return 0;
}
File diff suppressed because it is too large Load Diff
+592
View File
@@ -0,0 +1,592 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Hodge-podge collection of knfsd-related stuff.
* I will sort this out later.
*
* Copyright (C) 1995-1997 Olaf Kirch <okir@monad.swb.de>
*/
#ifndef LINUX_NFSD_NFSD_H
#define LINUX_NFSD_NFSD_H
#include <linux/types.h>
#include <linux/mount.h>
#include <linux/nfs.h>
#include <linux/nfs2.h>
#include <linux/nfs3.h>
#include <linux/nfs4.h>
#include <linux/sunrpc/svc.h>
#include <linux/sunrpc/svc_xprt.h>
#include <linux/sunrpc/msg_prot.h>
#include <linux/sunrpc/addr.h>
#include <uapi/linux/nfsd/debug.h>
#include "export.h"
#undef ifdebug
#ifdef CONFIG_SUNRPC_DEBUG
# define ifdebug(flag) if (nfsd_debug & NFSDDBG_##flag)
#else
# define ifdebug(flag) if (0)
#endif
/*
* nfsd version
*/
#define NFSD_MINVERS 2
#define NFSD_MAXVERS 4
#define NFSD_SUPPORTED_MINOR_VERSION 2
bool nfsd_support_version(int vers);
#include "netns.h"
#include "stats.h"
/*
* Default and maximum payload size (NFS READ or WRITE), in bytes.
* The maximum is an implementation limit.
*/
enum {
NFSSVC_DEFBLKSIZE = 4 * 1024 * 1024,
NFSSVC_MAXBLKSIZE = RPCSVC_MAXPAYLOAD,
};
struct readdir_cd {
__be32 err; /* 0, nfserr, or nfserr_eof */
};
/* Maximum number of operations per session compound */
#define NFSD_MAX_OPS_PER_COMPOUND 200
extern struct svc_program nfsd_programs[];
extern const struct svc_version nfsd_version2, nfsd_version3, nfsd_version4;
extern struct mutex nfsd_mutex;
extern atomic_t nfsd_th_cnt; /* number of available threads */
extern const struct seq_operations nfs_exports_op;
struct nfsd_thread_local_info {
struct nfs4_client **ntli_lease_breaker;
int ntli_cachetype;
};
/*
* Common void argument and result helpers
*/
struct nfsd_voidargs { };
struct nfsd_voidres { };
bool nfssvc_decode_voidarg(struct svc_rqst *rqstp,
struct xdr_stream *xdr);
bool nfssvc_encode_voidres(struct svc_rqst *rqstp,
struct xdr_stream *xdr);
/*
* Function prototypes.
*/
int nfsd_svc(int n, int *nservers, struct net *net,
const struct cred *cred, const char *scope);
int nfsd_dispatch(struct svc_rqst *rqstp);
int nfsd_nrthreads(struct net *);
int nfsd_nrpools(struct net *);
int nfsd_get_nrthreads(int n, int *, struct net *);
int nfsd_set_nrthreads(int n, int *, struct net *);
void nfsd_shutdown_threads(struct net *net);
struct svc_rqst *nfsd_current_rqst(void);
struct nfsdfs_client {
struct kref cl_ref;
void (*cl_release)(struct kref *kref);
};
struct nfsdfs_client *get_nfsdfs_client(struct inode *);
struct dentry *nfsd_client_mkdir(struct nfsd_net *nn,
struct nfsdfs_client *ncl, u32 id,
const struct tree_descr *,
struct dentry **fdentries);
void nfsd_client_rmdir(struct dentry *dentry);
int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type);
#if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL)
#ifdef CONFIG_NFSD_V2_ACL
extern const struct svc_version nfsd_acl_version2;
#else
#define nfsd_acl_version2 NULL
#endif
#ifdef CONFIG_NFSD_V3_ACL
extern const struct svc_version nfsd_acl_version3;
#else
#define nfsd_acl_version3 NULL
#endif
#endif
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
extern const struct svc_version localio_version1;
#endif
struct nfsd_net;
enum vers_op {NFSD_SET, NFSD_CLEAR, NFSD_TEST, NFSD_AVAIL };
int nfsd_vers(struct nfsd_net *nn, int vers, enum vers_op change);
int nfsd_minorversion(struct nfsd_net *nn, u32 minorversion, enum vers_op change);
void nfsd_reset_versions(struct nfsd_net *nn);
int nfsd_create_serv(struct net *net);
void nfsd_destroy_serv(struct net *net);
#ifdef CONFIG_DEBUG_FS
void nfsd_debugfs_init(void);
void nfsd_debugfs_exit(void);
#else
static inline void nfsd_debugfs_init(void) {}
static inline void nfsd_debugfs_exit(void) {}
#endif
extern bool nfsd_disable_splice_read __read_mostly;
extern bool nfsd_delegts_enabled __read_mostly;
enum {
/* Any new NFSD_IO enum value must be added at the end */
NFSD_IO_BUFFERED,
NFSD_IO_DONTCACHE,
NFSD_IO_DIRECT,
};
extern u64 nfsd_io_cache_read __read_mostly;
extern u64 nfsd_io_cache_write __read_mostly;
extern int nfsd_max_blksize;
static inline int nfsd_v4client(struct svc_rqst *rq)
{
return rq && rq->rq_prog == NFS_PROGRAM && rq->rq_vers == 4;
}
static inline struct user_namespace *
nfsd_user_namespace(const struct svc_rqst *rqstp)
{
const struct cred *cred = rqstp->rq_xprt->xpt_cred;
return cred ? cred->user_ns : &init_user_ns;
}
/*
* NFSv4 State
*/
#ifdef CONFIG_NFSD_V4
extern unsigned long max_delegations;
int nfsd4_init_slabs(void);
void nfsd4_free_slabs(void);
int nfs4_state_start(void);
int nfs4_state_start_net(struct net *net);
void nfs4_state_shutdown(void);
void nfs4_state_shutdown_net(struct net *net);
int nfs4_reset_recoverydir(char *recdir);
char * nfs4_recoverydir(void);
bool nfsd4_spo_must_allow(struct svc_rqst *rqstp);
int nfsd4_create_laundry_wq(void);
void nfsd4_destroy_laundry_wq(void);
bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp, struct inode *inode);
#else
static inline int nfsd4_init_slabs(void) { return 0; }
static inline void nfsd4_free_slabs(void) { }
static inline int nfs4_state_start(void) { return 0; }
static inline int nfs4_state_start_net(struct net *net) { return 0; }
static inline void nfs4_state_shutdown(void) { }
static inline void nfs4_state_shutdown_net(struct net *net) { }
static inline int nfs4_reset_recoverydir(char *recdir) { return 0; }
static inline char * nfs4_recoverydir(void) {return NULL; }
static inline bool nfsd4_spo_must_allow(struct svc_rqst *rqstp)
{
return false;
}
static inline int nfsd4_create_laundry_wq(void) { return 0; };
static inline void nfsd4_destroy_laundry_wq(void) {};
static inline bool nfsd_wait_for_delegreturn(struct svc_rqst *rqstp,
struct inode *inode)
{
return false;
}
#endif
/*
* lockd binding
*/
void nfsd_lockd_init(void);
void nfsd_lockd_shutdown(void);
/*
* These macros provide pre-xdr'ed values for faster operation.
*/
#define nfs_ok cpu_to_be32(NFS_OK)
#define nfserr_perm cpu_to_be32(NFSERR_PERM)
#define nfserr_noent cpu_to_be32(NFSERR_NOENT)
#define nfserr_io cpu_to_be32(NFSERR_IO)
#define nfserr_nxio cpu_to_be32(NFSERR_NXIO)
#define nfserr_acces cpu_to_be32(NFSERR_ACCES)
#define nfserr_exist cpu_to_be32(NFSERR_EXIST)
#define nfserr_xdev cpu_to_be32(NFSERR_XDEV)
#define nfserr_nodev cpu_to_be32(NFSERR_NODEV)
#define nfserr_notdir cpu_to_be32(NFSERR_NOTDIR)
#define nfserr_isdir cpu_to_be32(NFSERR_ISDIR)
#define nfserr_inval cpu_to_be32(NFSERR_INVAL)
#define nfserr_fbig cpu_to_be32(NFSERR_FBIG)
#define nfserr_nospc cpu_to_be32(NFSERR_NOSPC)
#define nfserr_rofs cpu_to_be32(NFSERR_ROFS)
#define nfserr_mlink cpu_to_be32(NFSERR_MLINK)
#define nfserr_nametoolong cpu_to_be32(NFSERR_NAMETOOLONG)
#define nfserr_notempty cpu_to_be32(NFSERR_NOTEMPTY)
#define nfserr_dquot cpu_to_be32(NFSERR_DQUOT)
#define nfserr_stale cpu_to_be32(NFSERR_STALE)
#define nfserr_remote cpu_to_be32(NFSERR_REMOTE)
#define nfserr_wflush cpu_to_be32(NFSERR_WFLUSH)
#define nfserr_badhandle cpu_to_be32(NFSERR_BADHANDLE)
#define nfserr_notsync cpu_to_be32(NFSERR_NOT_SYNC)
#define nfserr_badcookie cpu_to_be32(NFSERR_BAD_COOKIE)
#define nfserr_notsupp cpu_to_be32(NFSERR_NOTSUPP)
#define nfserr_toosmall cpu_to_be32(NFSERR_TOOSMALL)
#define nfserr_serverfault cpu_to_be32(NFSERR_SERVERFAULT)
#define nfserr_badtype cpu_to_be32(NFSERR_BADTYPE)
#define nfserr_jukebox cpu_to_be32(NFSERR_JUKEBOX)
#define nfserr_denied cpu_to_be32(NFSERR_DENIED)
#define nfserr_deadlock cpu_to_be32(NFSERR_DEADLOCK)
#define nfserr_expired cpu_to_be32(NFSERR_EXPIRED)
#define nfserr_bad_cookie cpu_to_be32(NFSERR_BAD_COOKIE)
#define nfserr_same cpu_to_be32(NFSERR_SAME)
#define nfserr_clid_inuse cpu_to_be32(NFSERR_CLID_INUSE)
#define nfserr_stale_clientid cpu_to_be32(NFSERR_STALE_CLIENTID)
#define nfserr_resource cpu_to_be32(NFSERR_RESOURCE)
#define nfserr_moved cpu_to_be32(NFSERR_MOVED)
#define nfserr_nofilehandle cpu_to_be32(NFSERR_NOFILEHANDLE)
#define nfserr_minor_vers_mismatch cpu_to_be32(NFSERR_MINOR_VERS_MISMATCH)
#define nfserr_share_denied cpu_to_be32(NFSERR_SHARE_DENIED)
#define nfserr_stale_stateid cpu_to_be32(NFSERR_STALE_STATEID)
#define nfserr_old_stateid cpu_to_be32(NFSERR_OLD_STATEID)
#define nfserr_bad_stateid cpu_to_be32(NFSERR_BAD_STATEID)
#define nfserr_bad_seqid cpu_to_be32(NFSERR_BAD_SEQID)
#define nfserr_symlink cpu_to_be32(NFSERR_SYMLINK)
#define nfserr_not_same cpu_to_be32(NFSERR_NOT_SAME)
#define nfserr_lock_range cpu_to_be32(NFSERR_LOCK_RANGE)
#define nfserr_restorefh cpu_to_be32(NFSERR_RESTOREFH)
#define nfserr_attrnotsupp cpu_to_be32(NFSERR_ATTRNOTSUPP)
#define nfserr_bad_xdr cpu_to_be32(NFSERR_BAD_XDR)
#define nfserr_openmode cpu_to_be32(NFSERR_OPENMODE)
#define nfserr_badowner cpu_to_be32(NFSERR_BADOWNER)
#define nfserr_locks_held cpu_to_be32(NFSERR_LOCKS_HELD)
#define nfserr_op_illegal cpu_to_be32(NFSERR_OP_ILLEGAL)
#define nfserr_grace cpu_to_be32(NFSERR_GRACE)
#define nfserr_no_grace cpu_to_be32(NFSERR_NO_GRACE)
#define nfserr_reclaim_bad cpu_to_be32(NFSERR_RECLAIM_BAD)
#define nfserr_badname cpu_to_be32(NFSERR_BADNAME)
#define nfserr_admin_revoked cpu_to_be32(NFS4ERR_ADMIN_REVOKED)
#define nfserr_cb_path_down cpu_to_be32(NFSERR_CB_PATH_DOWN)
#define nfserr_locked cpu_to_be32(NFSERR_LOCKED)
#define nfserr_wrongsec cpu_to_be32(NFSERR_WRONGSEC)
#define nfserr_delay cpu_to_be32(NFS4ERR_DELAY)
#define nfserr_badiomode cpu_to_be32(NFS4ERR_BADIOMODE)
#define nfserr_badlayout cpu_to_be32(NFS4ERR_BADLAYOUT)
#define nfserr_bad_session_digest cpu_to_be32(NFS4ERR_BAD_SESSION_DIGEST)
#define nfserr_badsession cpu_to_be32(NFS4ERR_BADSESSION)
#define nfserr_badslot cpu_to_be32(NFS4ERR_BADSLOT)
#define nfserr_complete_already cpu_to_be32(NFS4ERR_COMPLETE_ALREADY)
#define nfserr_conn_not_bound_to_session cpu_to_be32(NFS4ERR_CONN_NOT_BOUND_TO_SESSION)
#define nfserr_deleg_already_wanted cpu_to_be32(NFS4ERR_DELEG_ALREADY_WANTED)
#define nfserr_back_chan_busy cpu_to_be32(NFS4ERR_BACK_CHAN_BUSY)
#define nfserr_layouttrylater cpu_to_be32(NFS4ERR_LAYOUTTRYLATER)
#define nfserr_layoutunavailable cpu_to_be32(NFS4ERR_LAYOUTUNAVAILABLE)
#define nfserr_nomatching_layout cpu_to_be32(NFS4ERR_NOMATCHING_LAYOUT)
#define nfserr_recallconflict cpu_to_be32(NFS4ERR_RECALLCONFLICT)
#define nfserr_unknown_layouttype cpu_to_be32(NFS4ERR_UNKNOWN_LAYOUTTYPE)
#define nfserr_seq_misordered cpu_to_be32(NFS4ERR_SEQ_MISORDERED)
#define nfserr_sequence_pos cpu_to_be32(NFS4ERR_SEQUENCE_POS)
#define nfserr_req_too_big cpu_to_be32(NFS4ERR_REQ_TOO_BIG)
#define nfserr_rep_too_big cpu_to_be32(NFS4ERR_REP_TOO_BIG)
#define nfserr_rep_too_big_to_cache cpu_to_be32(NFS4ERR_REP_TOO_BIG_TO_CACHE)
#define nfserr_retry_uncached_rep cpu_to_be32(NFS4ERR_RETRY_UNCACHED_REP)
#define nfserr_unsafe_compound cpu_to_be32(NFS4ERR_UNSAFE_COMPOUND)
#define nfserr_too_many_ops cpu_to_be32(NFS4ERR_TOO_MANY_OPS)
#define nfserr_op_not_in_session cpu_to_be32(NFS4ERR_OP_NOT_IN_SESSION)
#define nfserr_hash_alg_unsupp cpu_to_be32(NFS4ERR_HASH_ALG_UNSUPP)
#define nfserr_clientid_busy cpu_to_be32(NFS4ERR_CLIENTID_BUSY)
#define nfserr_pnfs_io_hole cpu_to_be32(NFS4ERR_PNFS_IO_HOLE)
#define nfserr_seq_false_retry cpu_to_be32(NFS4ERR_SEQ_FALSE_RETRY)
#define nfserr_bad_high_slot cpu_to_be32(NFS4ERR_BAD_HIGH_SLOT)
#define nfserr_deadsession cpu_to_be32(NFS4ERR_DEADSESSION)
#define nfserr_encr_alg_unsupp cpu_to_be32(NFS4ERR_ENCR_ALG_UNSUPP)
#define nfserr_pnfs_no_layout cpu_to_be32(NFS4ERR_PNFS_NO_LAYOUT)
#define nfserr_not_only_op cpu_to_be32(NFS4ERR_NOT_ONLY_OP)
#define nfserr_wrong_cred cpu_to_be32(NFS4ERR_WRONG_CRED)
#define nfserr_wrong_type cpu_to_be32(NFS4ERR_WRONG_TYPE)
#define nfserr_dirdeleg_unavail cpu_to_be32(NFS4ERR_DIRDELEG_UNAVAIL)
#define nfserr_reject_deleg cpu_to_be32(NFS4ERR_REJECT_DELEG)
#define nfserr_returnconflict cpu_to_be32(NFS4ERR_RETURNCONFLICT)
#define nfserr_deleg_revoked cpu_to_be32(NFS4ERR_DELEG_REVOKED)
#define nfserr_partner_notsupp cpu_to_be32(NFS4ERR_PARTNER_NOTSUPP)
#define nfserr_partner_no_auth cpu_to_be32(NFS4ERR_PARTNER_NO_AUTH)
#define nfserr_union_notsupp cpu_to_be32(NFS4ERR_UNION_NOTSUPP)
#define nfserr_offload_denied cpu_to_be32(NFS4ERR_OFFLOAD_DENIED)
#define nfserr_wrong_lfs cpu_to_be32(NFS4ERR_WRONG_LFS)
#define nfserr_badlabel cpu_to_be32(NFS4ERR_BADLABEL)
#define nfserr_file_open cpu_to_be32(NFS4ERR_FILE_OPEN)
#define nfserr_xattr2big cpu_to_be32(NFS4ERR_XATTR2BIG)
#define nfserr_noxattr cpu_to_be32(NFS4ERR_NOXATTR)
/*
* Error codes for internal use. We use enum to choose numbers that are
* not already assigned, then covert to be32 resulting in a number that
* cannot conflict with any existing be32 nfserr value.
*/
enum {
/* end-of-file indicator in readdir */
NFSERR_EOF = NFS4ERR_FIRST_FREE,
#define nfserr_eof cpu_to_be32(NFSERR_EOF)
/* replay detected */
NFSERR_REPLAY_ME,
#define nfserr_replay_me cpu_to_be32(NFSERR_REPLAY_ME)
/* nfs41 replay detected */
NFSERR_REPLAY_CACHE,
#define nfserr_replay_cache cpu_to_be32(NFSERR_REPLAY_CACHE)
/* symlink found where dir expected - handled differently to
* other symlink found errors by NFSv3.
*/
NFSERR_SYMLINK_NOT_DIR,
#define nfserr_symlink_not_dir cpu_to_be32(NFSERR_SYMLINK_NOT_DIR)
};
/* Check for dir entries '.' and '..' */
#define isdotent(n, l) (l < 3 && n[0] == '.' && (l == 1 || n[1] == '.'))
#ifdef CONFIG_NFSD_V4
/* before processing a COMPOUND operation, we have to check that there
* is enough space in the buffer for XDR encode to succeed. otherwise,
* we might process an operation with side effects, and be unable to
* tell the client that the operation succeeded.
*
* COMPOUND_SLACK_SPACE - this is the minimum bytes of buffer space
* needed to encode an "ordinary" _successful_ operation. (GETATTR,
* READ, READDIR, and READLINK have their own buffer checks.) if we
* fall below this level, we fail the next operation with NFS4ERR_RESOURCE.
*
* COMPOUND_ERR_SLACK_SPACE - this is the minimum bytes of buffer space
* needed to encode an operation which has failed with NFS4ERR_RESOURCE.
* care is taken to ensure that we never fall below this level for any
* reason.
*/
#define COMPOUND_SLACK_SPACE 140 /* OP_GETFH */
#define COMPOUND_ERR_SLACK_SPACE 16 /* OP_SETATTR */
#define NFSD_LAUNDROMAT_MINTIMEOUT 1 /* seconds */
#define NFSD_COURTESY_CLIENT_TIMEOUT (24 * 60 * 60) /* seconds */
#define NFSD_CLIENT_MAX_TRIM_PER_RUN 128
#define NFS4_CLIENTS_PER_GB 1024
#define NFSD_DELEGRETURN_TIMEOUT (HZ / 34) /* 30ms */
#define NFSD_CB_GETATTR_TIMEOUT NFSD_DELEGRETURN_TIMEOUT
/*
* The following attributes are not implemented by NFSD:
* ARCHIVE (deprecated anyway)
* HIDDEN (unlikely to be supported any time soon)
* MIMETYPE (unlikely to be supported any time soon)
* QUOTA_* (will be supported in a forthcoming patch)
* SYSTEM (unlikely to be supported any time soon)
* TIME_BACKUP (unlikely to be supported any time soon)
*/
#define NFSD4_SUPPORTED_ATTRS_WORD0 \
(FATTR4_WORD0_SUPPORTED_ATTRS | FATTR4_WORD0_TYPE | FATTR4_WORD0_FH_EXPIRE_TYPE \
| FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE | FATTR4_WORD0_LINK_SUPPORT \
| FATTR4_WORD0_SYMLINK_SUPPORT | FATTR4_WORD0_NAMED_ATTR | FATTR4_WORD0_FSID \
| FATTR4_WORD0_UNIQUE_HANDLES | FATTR4_WORD0_LEASE_TIME | FATTR4_WORD0_RDATTR_ERROR \
| FATTR4_WORD0_ACLSUPPORT | FATTR4_WORD0_CANSETTIME | FATTR4_WORD0_CASE_INSENSITIVE \
| FATTR4_WORD0_CASE_PRESERVING | FATTR4_WORD0_CHOWN_RESTRICTED \
| FATTR4_WORD0_FILEHANDLE | FATTR4_WORD0_FILEID | FATTR4_WORD0_FILES_AVAIL \
| FATTR4_WORD0_FILES_FREE | FATTR4_WORD0_FILES_TOTAL | FATTR4_WORD0_FS_LOCATIONS | FATTR4_WORD0_HOMOGENEOUS \
| FATTR4_WORD0_MAXFILESIZE | FATTR4_WORD0_MAXLINK | FATTR4_WORD0_MAXNAME \
| FATTR4_WORD0_MAXREAD | FATTR4_WORD0_MAXWRITE | FATTR4_WORD0_ACL)
#define NFSD4_SUPPORTED_ATTRS_WORD1 \
(FATTR4_WORD1_MODE | FATTR4_WORD1_NO_TRUNC | FATTR4_WORD1_NUMLINKS \
| FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP | FATTR4_WORD1_RAWDEV \
| FATTR4_WORD1_SPACE_AVAIL | FATTR4_WORD1_SPACE_FREE | FATTR4_WORD1_SPACE_TOTAL \
| FATTR4_WORD1_SPACE_USED | FATTR4_WORD1_TIME_ACCESS | FATTR4_WORD1_TIME_ACCESS_SET \
| FATTR4_WORD1_TIME_DELTA | FATTR4_WORD1_TIME_METADATA | FATTR4_WORD1_TIME_CREATE \
| FATTR4_WORD1_TIME_MODIFY | FATTR4_WORD1_TIME_MODIFY_SET | FATTR4_WORD1_MOUNTED_ON_FILEID)
#define NFSD4_SUPPORTED_ATTRS_WORD2 0
/* 4.1 */
#ifdef CONFIG_NFSD_PNFS
#define PNFSD_SUPPORTED_ATTRS_WORD1 FATTR4_WORD1_FS_LAYOUT_TYPES
#define PNFSD_SUPPORTED_ATTRS_WORD2 \
(FATTR4_WORD2_LAYOUT_BLKSIZE | FATTR4_WORD2_LAYOUT_TYPES)
#else
#define PNFSD_SUPPORTED_ATTRS_WORD1 0
#define PNFSD_SUPPORTED_ATTRS_WORD2 0
#endif /* CONFIG_NFSD_PNFS */
#define NFSD4_1_SUPPORTED_ATTRS_WORD0 \
NFSD4_SUPPORTED_ATTRS_WORD0
#define NFSD4_1_SUPPORTED_ATTRS_WORD1 \
(NFSD4_SUPPORTED_ATTRS_WORD1 | PNFSD_SUPPORTED_ATTRS_WORD1)
#define NFSD4_1_SUPPORTED_ATTRS_WORD2 \
(NFSD4_SUPPORTED_ATTRS_WORD2 | PNFSD_SUPPORTED_ATTRS_WORD2 | \
FATTR4_WORD2_SUPPATTR_EXCLCREAT)
/* 4.2 */
#ifdef CONFIG_NFSD_V4_SECURITY_LABEL
#define NFSD4_2_SECURITY_ATTRS FATTR4_WORD2_SECURITY_LABEL
#else
#define NFSD4_2_SECURITY_ATTRS 0
#endif
#ifdef CONFIG_NFSD_V4_POSIX_ACLS
#define NFSD4_2_POSIX_ACL_ATTRS \
(FATTR4_WORD2_ACL_TRUEFORM | \
FATTR4_WORD2_ACL_TRUEFORM_SCOPE | \
FATTR4_WORD2_POSIX_DEFAULT_ACL | \
FATTR4_WORD2_POSIX_ACCESS_ACL)
#else
#define NFSD4_2_POSIX_ACL_ATTRS 0
#endif
#define NFSD4_2_SUPPORTED_ATTRS_WORD2 \
(NFSD4_1_SUPPORTED_ATTRS_WORD2 | \
FATTR4_WORD2_MODE_UMASK | \
FATTR4_WORD2_CLONE_BLKSIZE | \
NFSD4_2_SECURITY_ATTRS | \
FATTR4_WORD2_XATTR_SUPPORT | \
FATTR4_WORD2_TIME_DELEG_ACCESS | \
FATTR4_WORD2_TIME_DELEG_MODIFY | \
FATTR4_WORD2_OPEN_ARGUMENTS | \
NFSD4_2_POSIX_ACL_ATTRS)
extern const u32 nfsd_suppattrs[3][3];
static inline __be32 nfsd4_set_netaddr(struct sockaddr *addr,
struct nfs42_netaddr *netaddr)
{
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
unsigned int port;
size_t ret_addr, ret_port;
switch (addr->sa_family) {
case AF_INET:
port = ntohs(sin->sin_port);
sprintf(netaddr->netid, "tcp");
netaddr->netid_len = 3;
break;
case AF_INET6:
port = ntohs(sin6->sin6_port);
sprintf(netaddr->netid, "tcp6");
netaddr->netid_len = 4;
break;
default:
return nfserr_inval;
}
ret_addr = rpc_ntop(addr, netaddr->addr, sizeof(netaddr->addr));
ret_port = snprintf(netaddr->addr + ret_addr,
RPCBIND_MAXUADDRLEN + 1 - ret_addr,
".%u.%u", port >> 8, port & 0xff);
WARN_ON(ret_port >= RPCBIND_MAXUADDRLEN + 1 - ret_addr);
netaddr->addr_len = ret_addr + ret_port;
return 0;
}
static inline bool bmval_is_subset(const u32 *bm1, const u32 *bm2)
{
return !((bm1[0] & ~bm2[0]) ||
(bm1[1] & ~bm2[1]) ||
(bm1[2] & ~bm2[2]));
}
static inline bool nfsd_attrs_supported(u32 minorversion, const u32 *bmval)
{
return bmval_is_subset(bmval, nfsd_suppattrs[minorversion]);
}
/* These will return ERR_INVAL if specified in GETATTR or READDIR. */
#define NFSD_WRITEONLY_ATTRS_WORD1 \
(FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET)
/*
* These are the only attrs allowed in CREATE/OPEN/SETATTR. Don't add
* a writeable attribute here without also adding code to parse it to
* nfsd4_decode_fattr().
*/
#define NFSD_WRITEABLE_ATTRS_WORD0 \
(FATTR4_WORD0_SIZE | FATTR4_WORD0_ACL)
#define NFSD_WRITEABLE_ATTRS_WORD1 \
(FATTR4_WORD1_MODE | FATTR4_WORD1_OWNER | FATTR4_WORD1_OWNER_GROUP \
| FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_CREATE \
| FATTR4_WORD1_TIME_MODIFY_SET)
#ifdef CONFIG_NFSD_V4_SECURITY_LABEL
#define MAYBE_FATTR4_WORD2_SECURITY_LABEL \
FATTR4_WORD2_SECURITY_LABEL
#else
#define MAYBE_FATTR4_WORD2_SECURITY_LABEL 0
#endif
#ifdef CONFIG_NFSD_V4_POSIX_ACLS
#define MAYBE_FATTR4_WORD2_POSIX_ACL_ATTRS \
FATTR4_WORD2_POSIX_DEFAULT_ACL | FATTR4_WORD2_POSIX_ACCESS_ACL
#else
#define MAYBE_FATTR4_WORD2_POSIX_ACL_ATTRS 0
#endif
#define NFSD_WRITEABLE_ATTRS_WORD2 \
(FATTR4_WORD2_MODE_UMASK \
| MAYBE_FATTR4_WORD2_SECURITY_LABEL \
| FATTR4_WORD2_TIME_DELEG_ACCESS \
| FATTR4_WORD2_TIME_DELEG_MODIFY \
| MAYBE_FATTR4_WORD2_POSIX_ACL_ATTRS \
)
#define NFSD_SUPPATTR_EXCLCREAT_WORD0 \
NFSD_WRITEABLE_ATTRS_WORD0
/*
* we currently store the exclusive create verifier in the v_{a,m}time
* attributes so the client can't set these at create time using EXCLUSIVE4_1
*/
#define NFSD_SUPPATTR_EXCLCREAT_WORD1 \
(NFSD_WRITEABLE_ATTRS_WORD1 & \
~(FATTR4_WORD1_TIME_ACCESS_SET | FATTR4_WORD1_TIME_MODIFY_SET))
/*
* The FATTR4_WORD2_TIME_DELEG attributes are not to be allowed for
* OPEN(create) with EXCLUSIVE4_1. It doesn't make sense to set a
* delegated timestamp on a new file.
*
* This mask includes NFSv4.2-only attributes (e.g., POSIX ACLs).
* Version filtering occurs via nfsd_suppattrs[] before this mask
* is applied, so pre-4.2 clients never see unsupported attributes.
*/
#define NFSD_SUPPATTR_EXCLCREAT_WORD2 \
(NFSD_WRITEABLE_ATTRS_WORD2 & \
~(FATTR4_WORD2_TIME_DELEG_ACCESS | FATTR4_WORD2_TIME_DELEG_MODIFY))
extern int nfsd4_is_junction(struct dentry *dentry);
extern int register_cld_notifier(void);
extern void unregister_cld_notifier(void);
#ifdef CONFIG_NFSD_V4_2_INTER_SSC
extern void nfsd4_ssc_init_umount_work(struct nfsd_net *nn);
#endif
extern void nfsd4_init_leases_net(struct nfsd_net *nn);
#else /* CONFIG_NFSD_V4 */
static inline int nfsd4_is_junction(struct dentry *dentry)
{
return 0;
}
static inline void nfsd4_init_leases_net(struct nfsd_net *nn) { };
#define register_cld_notifier() 0
#define unregister_cld_notifier() do { } while(0)
#endif /* CONFIG_NFSD_V4 */
#endif /* LINUX_NFSD_NFSD_H */
+962
View File
@@ -0,0 +1,962 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NFS server file handle treatment.
*
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
* Portions Copyright (C) 1999 G. Allen Morris III <gam3@acm.org>
* Extensive rewrite by Neil Brown <neilb@cse.unsw.edu.au> Southern-Spring 1999
* ... and again Southern-Winter 2001 to support export_operations
*/
#include <linux/exportfs.h>
#include <linux/sunrpc/svcauth_gss.h>
#include <crypto/utils.h>
#include "nfsd.h"
#include "vfs.h"
#include "auth.h"
#include "trace.h"
#define NFSDDBG_FACILITY NFSDDBG_FH
/*
* our acceptability function.
* if NOSUBTREECHECK, accept anything
* if not, require that we can walk up to exp->ex_dentry
* doing some checks on the 'x' bits
*/
static int nfsd_acceptable(void *expv, struct dentry *dentry)
{
struct svc_export *exp = expv;
int rv;
struct dentry *tdentry;
struct dentry *parent;
if (exp->ex_flags & NFSEXP_NOSUBTREECHECK)
return 1;
tdentry = dget(dentry);
while (tdentry != exp->ex_path.dentry && !IS_ROOT(tdentry)) {
/* make sure parents give x permission to user */
int err;
parent = dget_parent(tdentry);
err = inode_permission(&nop_mnt_idmap,
d_inode(parent), MAY_EXEC);
if (err < 0) {
dput(parent);
break;
}
dput(tdentry);
tdentry = parent;
}
if (tdentry != exp->ex_path.dentry)
dprintk("nfsd_acceptable failed at %p %pd\n", tdentry, tdentry);
rv = (tdentry == exp->ex_path.dentry);
dput(tdentry);
return rv;
}
/* Type check. The correct error return for type mismatches does not seem to be
* generally agreed upon. SunOS seems to use EISDIR if file isn't S_IFREG; a
* comment in the NFSv3 spec says this is incorrect (implementation notes for
* the write call).
*/
static inline __be32
nfsd_mode_check(struct dentry *dentry, umode_t requested)
{
umode_t mode = d_inode(dentry)->i_mode & S_IFMT;
if (requested == 0) /* the caller doesn't care */
return nfs_ok;
if (mode == requested) {
if (mode == S_IFDIR && !d_can_lookup(dentry)) {
WARN_ON_ONCE(1);
return nfserr_notdir;
}
return nfs_ok;
}
if (mode == S_IFLNK) {
if (requested == S_IFDIR)
return nfserr_symlink_not_dir;
return nfserr_symlink;
}
if (requested == S_IFDIR)
return nfserr_notdir;
if (mode == S_IFDIR)
return nfserr_isdir;
return nfserr_wrong_type;
}
static bool nfsd_originating_port_ok(struct svc_rqst *rqstp,
struct svc_cred *cred,
struct svc_export *exp)
{
if (nfsexp_flags(cred, exp) & NFSEXP_INSECURE_PORT)
return true;
/* We don't require gss requests to use low ports: */
if (cred->cr_flavor >= RPC_AUTH_GSS)
return true;
return test_bit(RQ_SECURE, &rqstp->rq_flags);
}
static __be32 nfsd_setuser_and_check_port(struct svc_rqst *rqstp,
struct svc_cred *cred,
struct svc_export *exp)
{
/* Check if the request originated from a secure port. */
if (rqstp && !nfsd_originating_port_ok(rqstp, cred, exp)) {
if (IS_ENABLED(CONFIG_SUNRPC_DEBUG)) {
char buf[RPC_MAX_ADDRBUFLEN];
dprintk("nfsd: request from insecure port %s!\n",
svc_print_addr(rqstp, buf, sizeof(buf)));
}
return nfserr_perm;
}
/* Set user creds for this exportpoint */
return nfserrno(nfsd_setuser(cred, exp));
}
static inline __be32 check_pseudo_root(struct dentry *dentry,
struct svc_export *exp)
{
if (!(exp->ex_flags & NFSEXP_V4ROOT))
return nfs_ok;
/*
* We're exposing only the directories and symlinks that have to be
* traversed on the way to real exports:
*/
if (unlikely(!d_is_dir(dentry) &&
!d_is_symlink(dentry)))
return nfserr_stale;
/*
* A pseudoroot export gives permission to access only one
* single directory; the kernel has to make another upcall
* before granting access to anything else under it:
*/
if (unlikely(dentry != exp->ex_path.dentry))
return nfserr_stale;
return nfs_ok;
}
/* Size of a file handle MAC, in 4-octet words */
#define FH_MAC_WORDS (sizeof(__le64) / 4)
static bool fh_append_mac(struct svc_fh *fhp, struct net *net)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
struct knfsd_fh *fh = &fhp->fh_handle;
siphash_key_t *fh_key = nn->fh_key;
__le64 hash;
if (!fh_key)
goto out_no_key;
if (fh->fh_size + sizeof(hash) > fhp->fh_maxsize)
goto out_no_space;
hash = cpu_to_le64(siphash(&fh->fh_raw, fh->fh_size, fh_key));
memcpy(&fh->fh_raw[fh->fh_size], &hash, sizeof(hash));
fh->fh_size += sizeof(hash);
return true;
out_no_key:
pr_warn_ratelimited("NFSD: unable to sign filehandles, fh_key not set.\n");
return false;
out_no_space:
pr_warn_ratelimited("NFSD: unable to sign filehandles, fh_size %zu would be greater than fh_maxsize %d.\n",
fh->fh_size + sizeof(hash), fhp->fh_maxsize);
return false;
}
/*
* Verify that the filehandle's MAC was hashed from this filehandle
* given the server's fh_key:
*/
static bool fh_verify_mac(struct svc_fh *fhp, struct net *net)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
struct knfsd_fh *fh = &fhp->fh_handle;
siphash_key_t *fh_key = nn->fh_key;
__le64 hash;
if (!fh_key) {
pr_warn_ratelimited("NFSD: unable to verify signed filehandles, fh_key not set.\n");
return false;
}
hash = cpu_to_le64(siphash(&fh->fh_raw, fh->fh_size - sizeof(hash), fh_key));
return crypto_memneq(&fh->fh_raw[fh->fh_size - sizeof(hash)],
&hash, sizeof(hash)) == 0;
}
/*
* Use the given filehandle to look up the corresponding export and
* dentry. On success, the results are used to set fh_export and
* fh_dentry.
*/
static __be32 nfsd_set_fh_dentry(struct svc_rqst *rqstp, struct net *net,
struct svc_cred *cred,
struct auth_domain *client,
struct auth_domain *gssclient,
struct svc_fh *fhp)
{
struct knfsd_fh *fh = &fhp->fh_handle;
struct fid *fid = NULL;
struct svc_export *exp;
struct dentry *dentry;
int fileid_type;
int data_left = fh->fh_size/4;
int len;
__be32 error;
error = nfserr_badhandle;
if (fh->fh_size == 0)
return nfserr_nofilehandle;
if (fh->fh_version != 1)
return error;
if (--data_left < 0)
return error;
if (fh->fh_auth_type != 0)
return error;
len = key_len(fh->fh_fsid_type) / 4;
if (len == 0)
return error;
if (fh->fh_fsid_type == FSID_MAJOR_MINOR) {
u32 *fsid = fh_fsid(fh);
/* deprecated, convert to type 3 */
len = key_len(FSID_ENCODE_DEV)/4;
fh->fh_fsid_type = FSID_ENCODE_DEV;
/*
* struct knfsd_fh uses host-endian fields, which are
* sometimes used to hold net-endian values. This
* confuses sparse, so we must use __force here to
* keep it from complaining.
*/
fsid[0] = new_encode_dev(MKDEV(ntohl((__force __be32)fsid[0]),
ntohl((__force __be32)fsid[1])));
fsid[1] = fsid[2];
}
data_left -= len;
if (data_left < 0)
return error;
exp = rqst_exp_find(rqstp ? &rqstp->rq_chandle : NULL,
net, client, gssclient,
fh->fh_fsid_type, fh_fsid(fh));
fid = (struct fid *)(fh_fsid(fh) + len);
error = nfserr_stale;
if (IS_ERR(exp)) {
trace_nfsd_set_fh_dentry_badexport(rqstp, fhp, PTR_ERR(exp));
if (PTR_ERR(exp) == -ENOENT)
return error;
return nfserrno(PTR_ERR(exp));
}
if (exp->ex_flags & NFSEXP_NOSUBTREECHECK) {
/* Elevate privileges so that the lack of 'r' or 'x'
* permission on some parent directory will
* not stop exportfs_decode_fh from being able
* to reconnect a directory into the dentry cache.
* The same problem can affect "SUBTREECHECK" exports,
* but as nfsd_acceptable depends on correct
* access control settings being in effect, we cannot
* fix that case easily.
*/
struct cred *new = prepare_creds();
if (!new) {
error = nfserrno(-ENOMEM);
goto out;
}
new->cap_effective =
cap_raise_nfsd_set(new->cap_effective,
new->cap_permitted);
put_cred(override_creds(new));
} else {
error = nfsd_setuser_and_check_port(rqstp, cred, exp);
if (error)
goto out;
}
/*
* Look up the dentry using the NFS file handle.
*/
fileid_type = fh->fh_fileid_type;
error = nfserr_stale;
if (fileid_type == FILEID_ROOT) {
/* We don't sign or verify the root, no per-file identity */
dentry = dget(exp->ex_path.dentry);
} else {
if (exp->ex_flags & NFSEXP_SIGN_FH) {
if (!fh_verify_mac(fhp, net)) {
trace_nfsd_set_fh_dentry_badmac(rqstp, fhp, -ESTALE);
goto out;
}
data_left -= FH_MAC_WORDS;
}
dentry = exportfs_decode_fh_raw(exp->ex_path.mnt, fid,
data_left, fileid_type, 0,
nfsd_acceptable, exp);
if (IS_ERR_OR_NULL(dentry)) {
trace_nfsd_set_fh_dentry_badhandle(rqstp, fhp,
dentry ? PTR_ERR(dentry) : -ESTALE);
switch (PTR_ERR(dentry)) {
case -ENOMEM:
case -ETIMEDOUT:
break;
default:
dentry = ERR_PTR(-ESTALE);
}
}
}
error = nfserr_badhandle;
if (dentry == NULL)
goto out;
if (IS_ERR(dentry)) {
if (PTR_ERR(dentry) != -EINVAL)
error = nfserrno(PTR_ERR(dentry));
goto out;
}
if (d_is_dir(dentry) &&
(dentry->d_flags & DCACHE_DISCONNECTED)) {
printk("nfsd: find_fh_dentry returned a DISCONNECTED directory: %pd2\n",
dentry);
}
switch (fhp->fh_maxsize) {
case NFS4_FHSIZE:
if (dentry->d_sb->s_export_op->flags & EXPORT_OP_NOATOMIC_ATTR)
fhp->fh_no_atomic_attr = true;
fhp->fh_64bit_cookies = true;
break;
case NFS3_FHSIZE:
if (dentry->d_sb->s_export_op->flags & EXPORT_OP_NOWCC)
fhp->fh_no_wcc = true;
fhp->fh_64bit_cookies = true;
if (exp->ex_flags & NFSEXP_V4ROOT)
goto out;
break;
case NFS_FHSIZE:
fhp->fh_no_wcc = true;
if (EX_WGATHER(exp))
fhp->fh_use_wgather = true;
if (exp->ex_flags & NFSEXP_V4ROOT)
goto out;
}
fhp->fh_dentry = dentry;
fhp->fh_export = exp;
return 0;
out:
exp_put(exp);
return error;
}
/**
* __fh_verify - filehandle lookup and access checking
* @rqstp: RPC transaction context, or NULL
* @net: net namespace in which to perform the export lookup
* @cred: RPC user credential
* @client: RPC auth domain
* @gssclient: RPC GSS auth domain, or NULL
* @fhp: filehandle to be verified
* @type: expected type of object pointed to by filehandle
* @access: type of access needed to object
*
* See fh_verify() for further descriptions of @fhp, @type, and @access.
*/
static __be32
__fh_verify(struct svc_rqst *rqstp,
struct net *net, struct svc_cred *cred,
struct auth_domain *client,
struct auth_domain *gssclient,
struct svc_fh *fhp, umode_t type, int access)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
struct svc_export *exp = NULL;
bool may_bypass_gss = false;
struct dentry *dentry;
__be32 error;
if (!fhp->fh_dentry) {
error = nfsd_set_fh_dentry(rqstp, net, cred, client,
gssclient, fhp);
if (error)
goto out;
}
dentry = fhp->fh_dentry;
exp = fhp->fh_export;
trace_nfsd_fh_verify(rqstp, fhp, type, access);
/*
* We still have to do all these permission checks, even when
* fh_dentry is already set:
* - fh_verify may be called multiple times with different
* "access" arguments (e.g. nfsd_proc_create calls
* fh_verify(...,NFSD_MAY_EXEC) first, then later (in
* nfsd_create) calls fh_verify(...,NFSD_MAY_CREATE).
* - in the NFSv4 case, the filehandle may have been filled
* in by fh_compose, and given a dentry, but further
* compound operations performed with that filehandle
* still need permissions checks. In the worst case, a
* mountpoint crossing may have changed the export
* options, and we may now need to use a different uid
* (for example, if different id-squashing options are in
* effect on the new filesystem).
*/
error = check_pseudo_root(dentry, exp);
if (error)
goto out;
error = nfsd_setuser_and_check_port(rqstp, cred, exp);
if (error)
goto out;
error = nfsd_mode_check(dentry, type);
if (error)
goto out;
/*
* If rqstp is NULL, this is a LOCALIO request which will only
* ever use a filehandle/credential pair for which access has
* been affirmed (by ACCESS or OPEN NFS requests) over the
* wire. Skip both the xprtsec policy and the security flavor
* checks.
*/
if (!rqstp)
goto check_permissions;
if ((access & NFSD_MAY_NLM) && (exp->ex_flags & NFSEXP_NOAUTHNLM))
/* NLM is allowed to fully bypass authentication */
goto out;
/*
* NLM is allowed to bypass the xprtsec policy check because lockd
* doesn't support xprtsec.
*/
if (!(access & NFSD_MAY_NLM)) {
error = check_xprtsec_policy(exp, rqstp);
if (error)
goto out;
}
if (access & NFSD_MAY_BYPASS_GSS)
may_bypass_gss = true;
/*
* Clients may expect to be able to use auth_sys during mount,
* even if they use gss for everything else; see section 2.3.2
* of rfc 2623.
*/
if (access & NFSD_MAY_BYPASS_GSS_ON_ROOT
&& exp->ex_path.dentry == dentry)
may_bypass_gss = true;
error = check_security_flavor(exp, rqstp, may_bypass_gss);
if (error)
goto out;
svc_xprt_set_valid(rqstp->rq_xprt);
check_permissions:
/* Finally, check access permissions. */
error = nfsd_permission(cred, exp, dentry, access);
out:
trace_nfsd_fh_verify_err(rqstp, fhp, type, access, error);
if (error == nfserr_stale)
nfsd_stats_fh_stale_inc(nn, exp);
return error;
}
/**
* fh_verify_local - filehandle lookup and access checking
* @net: net namespace in which to perform the export lookup
* @cred: RPC user credential
* @client: RPC auth domain
* @fhp: filehandle to be verified
* @type: expected type of object pointed to by filehandle
* @access: type of access needed to object
*
* This API can be used by callers who do not have an RPC
* transaction context (ie are not running in an nfsd thread).
*
* See fh_verify() for further descriptions of @fhp, @type, and @access.
*/
__be32
fh_verify_local(struct net *net, struct svc_cred *cred,
struct auth_domain *client, struct svc_fh *fhp,
umode_t type, int access)
{
return __fh_verify(NULL, net, cred, client, NULL,
fhp, type, access);
}
/**
* fh_verify - filehandle lookup and access checking
* @rqstp: pointer to current rpc request
* @fhp: filehandle to be verified
* @type: expected type of object pointed to by filehandle
* @access: type of access needed to object
*
* Look up a dentry from the on-the-wire filehandle, check the client's
* access to the export, and set the current task's credentials.
*
* Regardless of success or failure of fh_verify(), fh_put() should be
* called on @fhp when the caller is finished with the filehandle.
*
* fh_verify() may be called multiple times on a given filehandle, for
* example, when processing an NFSv4 compound. The first call will look
* up a dentry using the on-the-wire filehandle. Subsequent calls will
* skip the lookup and just perform the other checks and possibly change
* the current task's credentials.
*
* @type specifies the type of object expected using one of the S_IF*
* constants defined in include/linux/stat.h. The caller may use zero
* to indicate that it doesn't care, or a negative integer to indicate
* that it expects something not of the given type.
*
* @access is formed from the NFSD_MAY_* constants defined in
* fs/nfsd/vfs.h.
*/
__be32
fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, umode_t type, int access)
{
return __fh_verify(rqstp, SVC_NET(rqstp), &rqstp->rq_cred,
rqstp->rq_client, rqstp->rq_gssclient,
fhp, type, access);
}
/*
* Compose a file handle for an NFS reply.
*
* Note that when first composed, the dentry may not yet have
* an inode. In this case a call to fh_update should be made
* before the fh goes out on the wire ...
*/
static void _fh_update(struct svc_fh *fhp, struct svc_export *exp,
struct dentry *dentry)
{
if (dentry != exp->ex_path.dentry) {
struct fid *fid = (struct fid *)
(fh_fsid(&fhp->fh_handle) + fhp->fh_handle.fh_size/4 - 1);
int maxsize = (fhp->fh_maxsize - fhp->fh_handle.fh_size)/4;
int fh_flags = (exp->ex_flags & NFSEXP_NOSUBTREECHECK) ? 0 :
EXPORT_FH_CONNECTABLE;
int fileid_type =
exportfs_encode_fh(dentry, fid, &maxsize, fh_flags);
fhp->fh_handle.fh_fileid_type =
fileid_type > 0 ? fileid_type : FILEID_INVALID;
fhp->fh_handle.fh_size += maxsize * 4;
if (exp->ex_flags & NFSEXP_SIGN_FH)
if (!fh_append_mac(fhp, exp->cd->net))
fhp->fh_handle.fh_fileid_type = FILEID_INVALID;
} else {
fhp->fh_handle.fh_fileid_type = FILEID_ROOT;
}
}
static bool is_root_export(struct svc_export *exp)
{
return exp->ex_path.dentry == exp->ex_path.dentry->d_sb->s_root;
}
static struct super_block *exp_sb(struct svc_export *exp)
{
return exp->ex_path.dentry->d_sb;
}
static bool fsid_type_ok_for_exp(u8 fsid_type, struct svc_export *exp)
{
switch (fsid_type) {
case FSID_DEV:
if (!old_valid_dev(exp_sb(exp)->s_dev))
return false;
fallthrough;
case FSID_MAJOR_MINOR:
case FSID_ENCODE_DEV:
return exp_sb(exp)->s_type->fs_flags & FS_REQUIRES_DEV;
case FSID_NUM:
return exp->ex_flags & NFSEXP_FSID;
case FSID_UUID8:
case FSID_UUID16:
if (!is_root_export(exp))
return false;
fallthrough;
case FSID_UUID4_INUM:
case FSID_UUID16_INUM:
return exp->ex_uuid != NULL;
}
return true;
}
static void set_version_and_fsid_type(struct svc_fh *fhp, struct svc_export *exp, struct svc_fh *ref_fh)
{
u8 version;
u8 fsid_type;
retry:
version = 1;
if (ref_fh && ref_fh->fh_export == exp) {
version = ref_fh->fh_handle.fh_version;
fsid_type = ref_fh->fh_handle.fh_fsid_type;
ref_fh = NULL;
switch (version) {
case 0xca:
fsid_type = FSID_DEV;
break;
case 1:
break;
default:
goto retry;
}
/*
* As the fsid -> filesystem mapping was guided by
* user-space, there is no guarantee that the filesystem
* actually supports that fsid type. If it doesn't we
* loop around again without ref_fh set.
*/
if (!fsid_type_ok_for_exp(fsid_type, exp))
goto retry;
} else if (exp->ex_flags & NFSEXP_FSID) {
fsid_type = FSID_NUM;
} else if (exp->ex_uuid) {
if (fhp->fh_maxsize >= 64) {
if (is_root_export(exp))
fsid_type = FSID_UUID16;
else
fsid_type = FSID_UUID16_INUM;
} else {
if (is_root_export(exp))
fsid_type = FSID_UUID8;
else
fsid_type = FSID_UUID4_INUM;
}
} else if (!old_valid_dev(exp_sb(exp)->s_dev))
/* for newer device numbers, we must use a newer fsid format */
fsid_type = FSID_ENCODE_DEV;
else
fsid_type = FSID_DEV;
fhp->fh_handle.fh_version = version;
if (version)
fhp->fh_handle.fh_fsid_type = fsid_type;
}
__be32
fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry,
struct svc_fh *ref_fh)
{
/* ref_fh is a reference file handle.
* if it is non-null and for the same filesystem, then we should compose
* a filehandle which is of the same version, where possible.
*/
struct inode * inode = d_inode(dentry);
dev_t ex_dev = exp_sb(exp)->s_dev;
dprintk("nfsd: fh_compose(exp %02x:%02x/%llu %pd2, ino=%llu)\n",
MAJOR(ex_dev), MINOR(ex_dev),
d_inode(exp->ex_path.dentry)->i_ino,
dentry,
(inode ? inode->i_ino : 0));
/* Choose filehandle version and fsid type based on
* the reference filehandle (if it is in the same export)
* or the export options.
*/
set_version_and_fsid_type(fhp, exp, ref_fh);
/* If we have a ref_fh, then copy the fh_no_wcc setting from it. */
fhp->fh_no_wcc = ref_fh ? ref_fh->fh_no_wcc : false;
if (ref_fh == fhp)
fh_put(ref_fh);
if (fhp->fh_dentry) {
printk(KERN_ERR "fh_compose: fh %pd2 not initialized!\n",
dentry);
}
if (fhp->fh_maxsize < NFS_FHSIZE)
printk(KERN_ERR "fh_compose: called with maxsize %d! %pd2\n",
fhp->fh_maxsize,
dentry);
fhp->fh_dentry = dget(dentry); /* our internal copy */
fhp->fh_export = exp_get(exp);
fhp->fh_handle.fh_size =
key_len(fhp->fh_handle.fh_fsid_type) + 4;
fhp->fh_handle.fh_auth_type = 0;
mk_fsid(fhp->fh_handle.fh_fsid_type,
fh_fsid(&fhp->fh_handle),
ex_dev,
d_inode(exp->ex_path.dentry)->i_ino,
exp->ex_fsid, exp->ex_uuid);
if (inode)
_fh_update(fhp, exp, dentry);
if (fhp->fh_handle.fh_fileid_type == FILEID_INVALID) {
fh_put(fhp);
return nfserr_stale;
}
return 0;
}
/*
* Update file handle information after changing a dentry.
* This is only called by nfsd_create, nfsd_create_v3 and nfsd_proc_create
*/
__be32
fh_update(struct svc_fh *fhp)
{
struct dentry *dentry;
if (!fhp->fh_dentry)
goto out_bad;
dentry = fhp->fh_dentry;
if (d_really_is_negative(dentry))
goto out_negative;
if (fhp->fh_handle.fh_fileid_type != FILEID_ROOT)
return 0;
_fh_update(fhp, fhp->fh_export, dentry);
if (fhp->fh_handle.fh_fileid_type == FILEID_INVALID)
return nfserr_stale;
return 0;
out_bad:
printk(KERN_ERR "fh_update: fh not verified!\n");
return nfserr_serverfault;
out_negative:
printk(KERN_ERR "fh_update: %pd2 still negative!\n",
dentry);
return nfserr_serverfault;
}
/**
* fh_getattr - Retrieve attributes on a local file
* @fhp: File handle of target file
* @stat: Caller-supplied kstat buffer to be filled in
*
* Returns nfs_ok on success, otherwise an NFS status code is
* returned.
*/
__be32 fh_getattr(const struct svc_fh *fhp, struct kstat *stat)
{
struct path p = {
.mnt = fhp->fh_export->ex_path.mnt,
.dentry = fhp->fh_dentry,
};
struct inode *inode = d_inode(p.dentry);
u32 request_mask = STATX_BASIC_STATS;
if (S_ISREG(inode->i_mode))
request_mask |= (STATX_DIOALIGN | STATX_DIO_READ_ALIGN);
if (fhp->fh_maxsize == NFS4_FHSIZE)
request_mask |= (STATX_BTIME | STATX_CHANGE_COOKIE);
return nfserrno(vfs_getattr(&p, stat, request_mask,
AT_STATX_SYNC_AS_STAT));
}
/**
* fh_fill_pre_attrs - Fill in pre-op attributes
* @fhp: file handle to be updated
*
*/
__be32 __must_check fh_fill_pre_attrs(struct svc_fh *fhp)
{
bool v4 = (fhp->fh_maxsize == NFS4_FHSIZE);
struct kstat stat;
__be32 err;
if (fhp->fh_no_wcc || fhp->fh_pre_saved)
return nfs_ok;
err = fh_getattr(fhp, &stat);
if (err)
return err;
if (v4)
fhp->fh_pre_change = nfsd4_change_attribute(&stat);
fhp->fh_pre_mtime = stat.mtime;
fhp->fh_pre_ctime = stat.ctime;
fhp->fh_pre_size = stat.size;
fhp->fh_pre_saved = true;
return nfs_ok;
}
/**
* fh_fill_post_attrs - Fill in post-op attributes
* @fhp: file handle to be updated
*
*/
__be32 fh_fill_post_attrs(struct svc_fh *fhp)
{
bool v4 = (fhp->fh_maxsize == NFS4_FHSIZE);
__be32 err;
if (fhp->fh_no_wcc)
return nfs_ok;
if (fhp->fh_post_saved)
printk("nfsd: inode locked twice during operation.\n");
err = fh_getattr(fhp, &fhp->fh_post_attr);
if (err)
return err;
fhp->fh_post_saved = true;
if (v4)
fhp->fh_post_change =
nfsd4_change_attribute(&fhp->fh_post_attr);
return nfs_ok;
}
/**
* fh_fill_both_attrs - Fill pre-op and post-op attributes
* @fhp: file handle to be updated
*
* This is used when the directory wasn't changed, but wcc attributes
* are needed anyway.
*/
__be32 __must_check fh_fill_both_attrs(struct svc_fh *fhp)
{
__be32 err;
err = fh_fill_post_attrs(fhp);
if (err)
return err;
fhp->fh_pre_change = fhp->fh_post_change;
fhp->fh_pre_mtime = fhp->fh_post_attr.mtime;
fhp->fh_pre_ctime = fhp->fh_post_attr.ctime;
fhp->fh_pre_size = fhp->fh_post_attr.size;
fhp->fh_pre_saved = true;
return nfs_ok;
}
/*
* Release a file handle.
*/
void
fh_put(struct svc_fh *fhp)
{
struct dentry * dentry = fhp->fh_dentry;
struct svc_export * exp = fhp->fh_export;
if (dentry) {
fhp->fh_dentry = NULL;
dput(dentry);
fh_clear_pre_post_attrs(fhp);
}
fh_drop_write(fhp);
if (exp) {
exp_put(exp);
fhp->fh_export = NULL;
}
fhp->fh_no_wcc = false;
return;
}
/*
* Shorthand for dprintk()'s
*/
char * SVCFH_fmt(struct svc_fh *fhp)
{
struct knfsd_fh *fh = &fhp->fh_handle;
static char buf[2+1+1+64*3+1];
if (fh->fh_size > 64)
return "bad-fh";
sprintf(buf, "%d: %*ph", fh->fh_size, fh->fh_size, fh->fh_raw);
return buf;
}
enum fsid_source fsid_source(const struct svc_fh *fhp)
{
if (fhp->fh_handle.fh_version != 1)
return FSIDSOURCE_DEV;
switch(fhp->fh_handle.fh_fsid_type) {
case FSID_DEV:
case FSID_ENCODE_DEV:
case FSID_MAJOR_MINOR:
if (exp_sb(fhp->fh_export)->s_type->fs_flags & FS_REQUIRES_DEV)
return FSIDSOURCE_DEV;
break;
case FSID_NUM:
if (fhp->fh_export->ex_flags & NFSEXP_FSID)
return FSIDSOURCE_FSID;
break;
default:
break;
}
/* either a UUID type filehandle, or the filehandle doesn't
* match the export.
*/
if (fhp->fh_export->ex_flags & NFSEXP_FSID)
return FSIDSOURCE_FSID;
if (fhp->fh_export->ex_uuid)
return FSIDSOURCE_UUID;
return FSIDSOURCE_DEV;
}
/**
* nfsd4_change_attribute - Generate an NFSv4 change_attribute value
* @stat: inode attributes
*
* Caller must fill in @stat before calling, typically by invoking
* vfs_getattr() with STATX_MODE, STATX_CTIME, and STATX_CHANGE_COOKIE.
* Returns an unsigned 64-bit changeid4 value (RFC 8881 Section 3.2).
*
* We could use i_version alone as the change attribute. However, i_version
* can go backwards on a regular file after an unclean shutdown. On its own
* that doesn't necessarily cause a problem, but if i_version goes backwards
* and then is incremented again it could reuse a value that was previously
* used before boot, and a client who queried the two values might incorrectly
* assume nothing changed.
*
* By using both ctime and the i_version counter we guarantee that as long as
* time doesn't go backwards we never reuse an old value. If the filesystem
* advertises STATX_ATTR_CHANGE_MONOTONIC, then this mitigation is not
* needed.
*
* We only need to do this for regular files as well. For directories, we
* assume that the new change attr is always logged to stable storage in some
* fashion before the results can be seen.
*/
u64 nfsd4_change_attribute(const struct kstat *stat)
{
u64 chattr;
if (stat->result_mask & STATX_CHANGE_COOKIE) {
chattr = stat->change_cookie;
if (S_ISREG(stat->mode) &&
!(stat->attributes & STATX_ATTR_CHANGE_MONOTONIC)) {
chattr += (u64)stat->ctime.tv_sec << 30;
chattr += stat->ctime.tv_nsec;
}
} else {
chattr = time_to_chattr(&stat->ctime);
}
return chattr;
}
+339
View File
@@ -0,0 +1,339 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 1995, 1996, 1997 Olaf Kirch <okir@monad.swb.de>
*
* This file describes the layout of the file handles as passed
* over the wire.
*/
#ifndef _LINUX_NFSD_NFSFH_H
#define _LINUX_NFSD_NFSFH_H
#include <linux/crc32.h>
#include <linux/sunrpc/svc.h>
#include <linux/iversion.h>
#include <linux/exportfs.h>
#include <linux/nfs4.h>
#include "export.h"
/*
* The file handle starts with a sequence of four-byte words.
* The first word contains a version number (1) and three descriptor bytes
* that tell how the remaining 3 variable length fields should be handled.
* These three bytes are auth_type, fsid_type and fileid_type.
*
* All four-byte values are in host-byte-order.
*
* The auth_type field is deprecated and must be set to 0.
*
* The fsid_type identifies how the filesystem (or export point) is
* encoded.
* Current values:
* 0 - 4 byte device id (ms-2-bytes major, ls-2-bytes minor), 4byte inode number
* NOTE: we cannot use the kdev_t device id value, because kdev_t.h
* says we mustn't. We must break it up and reassemble.
* 1 - 4 byte user specified identifier
* 2 - 4 byte major, 4 byte minor, 4 byte inode number - DEPRECATED
* 3 - 4 byte device id, encoded for user-space, 4 byte inode number
* 4 - 4 byte inode number and 4 byte uuid
* 5 - 8 byte uuid
* 6 - 16 byte uuid
* 7 - 8 byte inode number and 16 byte uuid
*
* The fileid_type identifies how the file within the filesystem is encoded.
* The values for this field are filesystem specific, exccept that
* filesystems must not use the values '0' or '0xff'. 'See enum fid_type'
* in include/linux/exportfs.h for currently registered values.
*/
struct knfsd_fh {
unsigned int fh_size; /*
* Points to the current size while
* building a new file handle.
*/
u8 fh_raw[NFS4_FHSIZE];
};
#define fh_version fh_raw[0]
#define fh_auth_type fh_raw[1]
#define fh_fsid_type fh_raw[2]
#define fh_fileid_type fh_raw[3]
static inline u32 *fh_fsid(const struct knfsd_fh *fh)
{
return (u32 *)&fh->fh_raw[4];
}
static inline __u32 ino_t_to_u32(ino_t ino)
{
return (__u32) ino;
}
static inline ino_t u32_to_ino_t(__u32 uino)
{
return (ino_t) uino;
}
/*
* This is the internal representation of an NFS handle used in knfsd.
* pre_mtime/post_version will be used to support wcc_attr's in NFSv3.
*/
typedef struct svc_fh {
struct knfsd_fh fh_handle; /* FH data */
int fh_maxsize; /* max size for fh_handle */
struct dentry * fh_dentry; /* validated dentry */
struct svc_export * fh_export; /* export pointer */
bool fh_want_write; /* remount protection taken */
bool fh_no_wcc; /* no wcc data needed */
bool fh_no_atomic_attr;
/*
* wcc data is not atomic with
* operation
*/
bool fh_use_wgather; /* NFSv2 wgather option */
bool fh_64bit_cookies;/* readdir cookie size */
int fh_flags; /* FH flags */
bool fh_post_saved; /* post-op attrs saved */
bool fh_pre_saved; /* pre-op attrs saved */
/* Pre-op attributes saved when inode is locked */
__u64 fh_pre_size; /* size before operation */
struct timespec64 fh_pre_mtime; /* mtime before oper */
struct timespec64 fh_pre_ctime; /* ctime before oper */
/*
* pre-op nfsv4 change attr: note must check IS_I_VERSION(inode)
* to find out if it is valid.
*/
u64 fh_pre_change;
/* Post-op attributes saved in fh_fill_post_attrs() */
struct kstat fh_post_attr; /* full attrs after operation */
u64 fh_post_change; /* nfsv4 change; see above */
} svc_fh;
#define NFSD4_FH_FOREIGN (1<<0)
#define SET_FH_FLAG(c, f) ((c)->fh_flags |= (f))
#define HAS_FH_FLAG(c, f) ((c)->fh_flags & (f))
enum nfsd_fsid {
FSID_DEV = 0,
FSID_NUM,
FSID_MAJOR_MINOR,
FSID_ENCODE_DEV,
FSID_UUID4_INUM,
FSID_UUID8,
FSID_UUID16,
FSID_UUID16_INUM,
};
enum fsid_source {
FSIDSOURCE_DEV,
FSIDSOURCE_FSID,
FSIDSOURCE_UUID,
};
extern enum fsid_source fsid_source(const struct svc_fh *fhp);
/*
* This might look a little large to "inline" but in all calls except
* one, 'vers' is constant so moste of the function disappears.
*
* In some cases the values are considered to be host endian and in
* others, net endian. fsidv is always considered to be u32 as the
* callers don't know which it will be. So we must use __force to keep
* sparse from complaining. Since these values are opaque to the
* client, that shouldn't be a problem.
*/
static inline void mk_fsid(int vers, u32 *fsidv, dev_t dev, ino_t ino,
u32 fsid, unsigned char *uuid)
{
u32 *up;
switch(vers) {
case FSID_DEV:
fsidv[0] = (__force __u32)htonl((MAJOR(dev)<<16) |
MINOR(dev));
fsidv[1] = ino_t_to_u32(ino);
break;
case FSID_NUM:
fsidv[0] = fsid;
break;
case FSID_MAJOR_MINOR:
fsidv[0] = (__force __u32)htonl(MAJOR(dev));
fsidv[1] = (__force __u32)htonl(MINOR(dev));
fsidv[2] = ino_t_to_u32(ino);
break;
case FSID_ENCODE_DEV:
fsidv[0] = new_encode_dev(dev);
fsidv[1] = ino_t_to_u32(ino);
break;
case FSID_UUID4_INUM:
/* 4 byte fsid and inode number */
up = (u32*)uuid;
fsidv[0] = ino_t_to_u32(ino);
fsidv[1] = up[0] ^ up[1] ^ up[2] ^ up[3];
break;
case FSID_UUID8:
/* 8 byte fsid */
up = (u32*)uuid;
fsidv[0] = up[0] ^ up[2];
fsidv[1] = up[1] ^ up[3];
break;
case FSID_UUID16:
/* 16 byte fsid - NFSv3+ only */
memcpy(fsidv, uuid, 16);
break;
case FSID_UUID16_INUM:
/* 8 byte inode and 16 byte fsid */
*(u64*)fsidv = (u64)ino;
memcpy(fsidv+2, uuid, 16);
break;
default: BUG();
}
}
static inline int key_len(int type)
{
switch(type) {
case FSID_DEV: return 8;
case FSID_NUM: return 4;
case FSID_MAJOR_MINOR: return 12;
case FSID_ENCODE_DEV: return 8;
case FSID_UUID4_INUM: return 8;
case FSID_UUID8: return 8;
case FSID_UUID16: return 16;
case FSID_UUID16_INUM: return 24;
default: return 0;
}
}
/*
* Shorthand for dprintk()'s
*/
extern char * SVCFH_fmt(struct svc_fh *fhp);
/*
* Function prototypes
*/
__be32 fh_verify(struct svc_rqst *, struct svc_fh *, umode_t, int);
__be32 fh_verify_local(struct net *, struct svc_cred *, struct auth_domain *,
struct svc_fh *, umode_t, int);
__be32 fh_getattr(const struct svc_fh *fhp, struct kstat *stat);
__be32 fh_compose(struct svc_fh *, struct svc_export *, struct dentry *, struct svc_fh *);
__be32 fh_update(struct svc_fh *);
void fh_put(struct svc_fh *);
static __inline__ struct svc_fh *
fh_copy(struct svc_fh *dst, const struct svc_fh *src)
{
WARN_ON(src->fh_dentry);
*dst = *src;
return dst;
}
static inline void
fh_copy_shallow(struct knfsd_fh *dst, const struct knfsd_fh *src)
{
dst->fh_size = src->fh_size;
memcpy(&dst->fh_raw, &src->fh_raw, src->fh_size);
}
static __inline__ struct svc_fh *
fh_init(struct svc_fh *fhp, int maxsize)
{
memset(fhp, 0, sizeof(*fhp));
fhp->fh_maxsize = maxsize;
return fhp;
}
static inline bool fh_match(const struct knfsd_fh *fh1,
const struct knfsd_fh *fh2)
{
if (fh1->fh_size != fh2->fh_size)
return false;
if (memcmp(fh1->fh_raw, fh2->fh_raw, fh1->fh_size) != 0)
return false;
return true;
}
static inline bool fh_fsid_match(const struct knfsd_fh *fh1,
const struct knfsd_fh *fh2)
{
u32 *fsid1 = fh_fsid(fh1);
u32 *fsid2 = fh_fsid(fh2);
if (fh1->fh_fsid_type != fh2->fh_fsid_type)
return false;
if (memcmp(fsid1, fsid2, key_len(fh1->fh_fsid_type)) != 0)
return false;
return true;
}
/**
* fh_want_write - Get write access to an export
* @fhp: File handle of file to be written
*
* Caller must invoke fh_drop_write() when its write operation
* is complete.
*
* Returns 0 if the file handle's export can be written to. Otherwise
* the export is not prepared for updates, and the returned negative
* errno value reflects the reason for the failure.
*/
static inline int fh_want_write(struct svc_fh *fhp)
{
int ret;
if (fhp->fh_want_write)
return 0;
ret = mnt_want_write(fhp->fh_export->ex_path.mnt);
if (!ret)
fhp->fh_want_write = true;
return ret;
}
/**
* fh_drop_write - Release write access on an export
* @fhp: File handle of file on which fh_want_write() was previously called
*/
static inline void fh_drop_write(struct svc_fh *fhp)
{
if (fhp->fh_want_write) {
fhp->fh_want_write = false;
mnt_drop_write(fhp->fh_export->ex_path.mnt);
}
}
/**
* knfsd_fh_hash - calculate the crc32 hash for the filehandle
* @fh - pointer to filehandle
*
* returns a crc32 hash for the filehandle that is compatible with
* the one displayed by "wireshark".
*/
static inline u32 knfsd_fh_hash(const struct knfsd_fh *fh)
{
return ~crc32_le(0xFFFFFFFF, fh->fh_raw, fh->fh_size);
}
/**
* fh_clear_pre_post_attrs - Reset pre/post attributes
* @fhp: file handle to be updated
*
*/
static inline void fh_clear_pre_post_attrs(struct svc_fh *fhp)
{
fhp->fh_post_saved = false;
fhp->fh_pre_saved = false;
}
u64 nfsd4_change_attribute(const struct kstat *stat);
__be32 __must_check fh_fill_pre_attrs(struct svc_fh *fhp);
__be32 fh_fill_post_attrs(struct svc_fh *fhp);
__be32 __must_check fh_fill_both_attrs(struct svc_fh *fhp);
#endif /* _LINUX_NFSD_NFSFH_H */
+850
View File
@@ -0,0 +1,850 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Process version 2 NFS requests.
*
* Copyright (C) 1995-1997 Olaf Kirch <okir@monad.swb.de>
*/
#include <linux/namei.h>
#include "cache.h"
#include "xdr.h"
#include "vfs.h"
#include "trace.h"
#define NFSDDBG_FACILITY NFSDDBG_PROC
static __be32 nfsd_map_status(__be32 status)
{
switch (status) {
case nfs_ok:
break;
case nfserr_nofilehandle:
case nfserr_badhandle:
status = nfserr_stale;
break;
case nfserr_wrongsec:
case nfserr_xdev:
case nfserr_file_open:
status = nfserr_acces;
break;
case nfserr_symlink_not_dir:
status = nfserr_notdir;
break;
case nfserr_symlink:
case nfserr_wrong_type:
status = nfserr_io;
break;
}
return status;
}
static __be32
nfsd_proc_null(struct svc_rqst *rqstp)
{
return rpc_success;
}
/*
* Get a file's attributes
* N.B. After this call resp->fh needs an fh_put
*/
static __be32
nfsd_proc_getattr(struct svc_rqst *rqstp)
{
struct nfsd_fhandle *argp = rqstp->rq_argp;
struct nfsd_attrstat *resp = rqstp->rq_resp;
trace_nfsd_vfs_getattr(rqstp, &argp->fh);
fh_copy(&resp->fh, &argp->fh);
resp->status = fh_verify(rqstp, &resp->fh, 0,
NFSD_MAY_NOP | NFSD_MAY_BYPASS_GSS_ON_ROOT);
if (resp->status != nfs_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/*
* Set a file's attributes
* N.B. After this call resp->fh needs an fh_put
*/
static __be32
nfsd_proc_setattr(struct svc_rqst *rqstp)
{
struct nfsd_sattrargs *argp = rqstp->rq_argp;
struct nfsd_attrstat *resp = rqstp->rq_resp;
struct iattr *iap = &argp->attrs;
struct nfsd_attrs attrs = {
.na_iattr = iap,
};
struct svc_fh *fhp;
dprintk("nfsd: SETATTR %s, valid=%x, size=%ld\n",
SVCFH_fmt(&argp->fh),
argp->attrs.ia_valid, (long) argp->attrs.ia_size);
fhp = fh_copy(&resp->fh, &argp->fh);
/*
* NFSv2 does not differentiate between "set-[ac]time-to-now"
* which only requires access, and "set-[ac]time-to-X" which
* requires ownership.
* So if it looks like it might be "set both to the same time which
* is close to now", and if setattr_prepare fails, then we
* convert to "set to now" instead of "set to explicit time"
*
* We only call setattr_prepare as the last test as technically
* it is not an interface that we should be using.
*/
#define BOTH_TIME_SET (ATTR_ATIME_SET | ATTR_MTIME_SET)
#define MAX_TOUCH_TIME_ERROR (30*60)
if ((iap->ia_valid & BOTH_TIME_SET) == BOTH_TIME_SET &&
iap->ia_mtime.tv_sec == iap->ia_atime.tv_sec) {
/*
* Looks probable.
*
* Now just make sure time is in the right ballpark.
* Solaris, at least, doesn't seem to care what the time
* request is. We require it be within 30 minutes of now.
*/
time64_t delta = iap->ia_atime.tv_sec - ktime_get_real_seconds();
resp->status = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP);
if (resp->status != nfs_ok)
goto out;
if (delta < 0)
delta = -delta;
if (delta < MAX_TOUCH_TIME_ERROR &&
setattr_prepare(&nop_mnt_idmap, fhp->fh_dentry, iap) != 0) {
/*
* Turn off ATTR_[AM]TIME_SET but leave ATTR_[AM]TIME.
* This will cause notify_change to set these times
* to "now"
*/
iap->ia_valid &= ~BOTH_TIME_SET;
}
}
resp->status = nfsd_setattr(rqstp, fhp, &attrs, NULL);
if (resp->status != nfs_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/* Obsolete, replaced by MNTPROC_MNT. */
static __be32
nfsd_proc_root(struct svc_rqst *rqstp)
{
return rpc_success;
}
/*
* Look up a path name component
* Note: the dentry in the resp->fh may be negative if the file
* doesn't exist yet.
* N.B. After this call resp->fh needs an fh_put
*/
static __be32
nfsd_proc_lookup(struct svc_rqst *rqstp)
{
struct nfsd_diropargs *argp = rqstp->rq_argp;
struct nfsd_diropres *resp = rqstp->rq_resp;
dprintk("nfsd: LOOKUP %s %.*s\n",
SVCFH_fmt(&argp->fh), argp->len, argp->name);
fh_init(&resp->fh, NFS_FHSIZE);
resp->status = nfsd_lookup(rqstp, &argp->fh, argp->name, argp->len,
&resp->fh);
fh_put(&argp->fh);
if (resp->status != nfs_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/*
* Read a symlink.
*/
static __be32
nfsd_proc_readlink(struct svc_rqst *rqstp)
{
struct nfsd_fhandle *argp = rqstp->rq_argp;
struct nfsd_readlinkres *resp = rqstp->rq_resp;
dprintk("nfsd: READLINK %s\n", SVCFH_fmt(&argp->fh));
/* Read the symlink. */
resp->len = NFS_MAXPATHLEN;
resp->page = *(rqstp->rq_next_page++);
resp->status = nfsd_readlink(rqstp, &argp->fh,
page_address(resp->page), &resp->len);
fh_put(&argp->fh);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/*
* Read a portion of a file.
* N.B. After this call resp->fh needs an fh_put
*/
static __be32
nfsd_proc_read(struct svc_rqst *rqstp)
{
struct nfsd_readargs *argp = rqstp->rq_argp;
struct nfsd_readres *resp = rqstp->rq_resp;
u32 eof;
dprintk("nfsd: READ %s %d bytes at %d\n",
SVCFH_fmt(&argp->fh),
argp->count, argp->offset);
argp->count = min_t(u32, argp->count, NFS_MAXDATA);
argp->count = min_t(u32, argp->count, rqstp->rq_res.buflen);
resp->pages = rqstp->rq_next_page;
/* Obtain buffer pointer for payload. 19 is 1 word for
* status, 17 words for fattr, and 1 word for the byte count.
*/
svc_reserve_auth(rqstp, (19<<2) + argp->count + 4);
resp->count = argp->count;
fh_copy(&resp->fh, &argp->fh);
resp->status = nfsd_read(rqstp, &resp->fh, argp->offset,
&resp->count, &eof);
if (resp->status == nfs_ok)
resp->status = fh_getattr(&resp->fh, &resp->stat);
else if (resp->status == nfserr_jukebox)
set_bit(RQ_DROPME, &rqstp->rq_flags);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/* Reserved */
static __be32
nfsd_proc_writecache(struct svc_rqst *rqstp)
{
return rpc_success;
}
/*
* Write data to a file
* N.B. After this call resp->fh needs an fh_put
*/
static __be32
nfsd_proc_write(struct svc_rqst *rqstp)
{
struct nfsd_writeargs *argp = rqstp->rq_argp;
struct nfsd_attrstat *resp = rqstp->rq_resp;
unsigned long cnt = argp->len;
dprintk("nfsd: WRITE %s %u bytes at %d\n",
SVCFH_fmt(&argp->fh),
argp->len, argp->offset);
fh_copy(&resp->fh, &argp->fh);
resp->status = nfsd_write(rqstp, &resp->fh, argp->offset,
&argp->payload, &cnt, NFS_DATA_SYNC, NULL);
if (resp->status == nfs_ok)
resp->status = fh_getattr(&resp->fh, &resp->stat);
else if (resp->status == nfserr_jukebox)
set_bit(RQ_DROPME, &rqstp->rq_flags);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/*
* CREATE processing is complicated. The keyword here is `overloaded.'
* The parent directory is kept locked between the check for existence
* and the actual create() call in compliance with VFS protocols.
* N.B. After this call _both_ argp->fh and resp->fh need an fh_put
*/
static __be32
nfsd_proc_create(struct svc_rqst *rqstp)
{
struct nfsd_createargs *argp = rqstp->rq_argp;
struct nfsd_diropres *resp = rqstp->rq_resp;
svc_fh *dirfhp = &argp->fh;
svc_fh *newfhp = &resp->fh;
struct iattr *attr = &argp->attrs;
struct nfsd_attrs attrs = {
.na_iattr = attr,
};
struct inode *inode;
struct dentry *dchild;
int type, mode;
int hosterr;
dev_t rdev = 0, wanted = new_decode_dev(attr->ia_size);
/* First verify the parent file handle */
resp->status = fh_verify(rqstp, dirfhp, S_IFDIR, NFSD_MAY_EXEC);
if (resp->status != nfs_ok)
goto done; /* must fh_put dirfhp even on error */
/* Check for NFSD_MAY_WRITE in nfsd_create if necessary */
resp->status = nfserr_exist;
if (isdotent(argp->name, argp->len))
goto done;
hosterr = fh_want_write(dirfhp);
if (hosterr) {
resp->status = nfserrno(hosterr);
goto done;
}
dchild = start_creating(&nop_mnt_idmap, dirfhp->fh_dentry,
&QSTR_LEN(argp->name, argp->len));
if (IS_ERR(dchild)) {
resp->status = nfserrno(PTR_ERR(dchild));
goto out_write;
}
fh_init(newfhp, NFS_FHSIZE);
resp->status = fh_compose(newfhp, dirfhp->fh_export, dchild, dirfhp);
if (!resp->status && d_really_is_negative(dchild))
resp->status = nfserr_noent;
if (resp->status) {
if (resp->status != nfserr_noent)
goto out_unlock;
/*
* If the new file handle wasn't verified, we can't tell
* whether the file exists or not. Time to bail ...
*/
resp->status = nfserr_acces;
if (!newfhp->fh_dentry) {
printk(KERN_WARNING
"nfsd_proc_create: file handle not verified\n");
goto out_unlock;
}
}
inode = d_inode(newfhp->fh_dentry);
/* Unfudge the mode bits */
if (attr->ia_valid & ATTR_MODE) {
type = attr->ia_mode & S_IFMT;
mode = attr->ia_mode & ~S_IFMT;
if (!type) {
/* no type, so if target exists, assume same as that,
* else assume a file */
if (inode) {
type = inode->i_mode & S_IFMT;
switch(type) {
case S_IFCHR:
case S_IFBLK:
/* reserve rdev for later checking */
rdev = inode->i_rdev;
attr->ia_valid |= ATTR_SIZE;
fallthrough;
case S_IFIFO:
/* this is probably a permission check..
* at least IRIX implements perm checking on
* echo thing > device-special-file-or-pipe
* by doing a CREATE with type==0
*/
resp->status = nfsd_permission(
&rqstp->rq_cred,
newfhp->fh_export,
newfhp->fh_dentry,
NFSD_MAY_WRITE|NFSD_MAY_LOCAL_ACCESS);
if (resp->status && resp->status != nfserr_rofs)
goto out_unlock;
}
} else
type = S_IFREG;
}
} else if (inode) {
type = inode->i_mode & S_IFMT;
mode = inode->i_mode & ~S_IFMT;
} else {
type = S_IFREG;
mode = 0; /* ??? */
}
attr->ia_valid |= ATTR_MODE;
attr->ia_mode = mode;
/* Special treatment for non-regular files according to the
* gospel of sun micro
*/
if (type != S_IFREG) {
if (type != S_IFBLK && type != S_IFCHR) {
rdev = 0;
} else if (type == S_IFCHR && !(attr->ia_valid & ATTR_SIZE)) {
/* If you think you've seen the worst, grok this. */
type = S_IFIFO;
} else {
/* Okay, char or block special */
if (!rdev)
rdev = wanted;
}
/* we've used the SIZE information, so discard it */
attr->ia_valid &= ~ATTR_SIZE;
/* Make sure the type and device matches */
resp->status = nfserr_exist;
if (inode && inode_wrong_type(inode, type))
goto out_unlock;
}
resp->status = nfs_ok;
if (!inode) {
/* File doesn't exist. Create it and set attrs */
resp->status = nfsd_create_locked(rqstp, dirfhp, &attrs, type,
rdev, newfhp);
/* nfsd_create_locked() unlocked the parent */
dput(dchild);
goto out_write;
} else if (type == S_IFREG) {
dprintk("nfsd: existing %s, valid=%x, size=%ld\n",
argp->name, attr->ia_valid, (long) attr->ia_size);
/* File already exists. We ignore all attributes except
* size, so that creat() behaves exactly like
* open(..., O_CREAT|O_TRUNC|O_WRONLY).
*/
attr->ia_valid &= ATTR_SIZE;
if (attr->ia_valid)
resp->status = nfsd_setattr(rqstp, newfhp, &attrs,
NULL);
}
out_unlock:
end_creating(dchild);
out_write:
fh_drop_write(dirfhp);
done:
fh_put(dirfhp);
if (resp->status != nfs_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
static __be32
nfsd_proc_remove(struct svc_rqst *rqstp)
{
struct nfsd_diropargs *argp = rqstp->rq_argp;
struct nfsd_stat *resp = rqstp->rq_resp;
/* Unlink. -SIFDIR means file must not be a directory */
resp->status = nfsd_unlink(rqstp, &argp->fh, -S_IFDIR,
argp->name, argp->len);
fh_put(&argp->fh);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
static __be32
nfsd_proc_rename(struct svc_rqst *rqstp)
{
struct nfsd_renameargs *argp = rqstp->rq_argp;
struct nfsd_stat *resp = rqstp->rq_resp;
resp->status = nfsd_rename(rqstp, &argp->ffh, argp->fname, argp->flen,
&argp->tfh, argp->tname, argp->tlen);
fh_put(&argp->ffh);
fh_put(&argp->tfh);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
static __be32
nfsd_proc_link(struct svc_rqst *rqstp)
{
struct nfsd_linkargs *argp = rqstp->rq_argp;
struct nfsd_stat *resp = rqstp->rq_resp;
resp->status = nfsd_link(rqstp, &argp->tfh, argp->tname, argp->tlen,
&argp->ffh);
fh_put(&argp->ffh);
fh_put(&argp->tfh);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
static __be32
nfsd_proc_symlink(struct svc_rqst *rqstp)
{
struct nfsd_symlinkargs *argp = rqstp->rq_argp;
struct nfsd_stat *resp = rqstp->rq_resp;
struct nfsd_attrs attrs = {
.na_iattr = &argp->attrs,
};
struct svc_fh newfh;
if (argp->tlen > NFS_MAXPATHLEN) {
resp->status = nfserr_nametoolong;
goto out;
}
argp->tname = svc_fill_symlink_pathname(rqstp, &argp->first,
page_address(rqstp->rq_arg.pages[0]),
argp->tlen);
if (IS_ERR(argp->tname)) {
resp->status = nfserrno(PTR_ERR(argp->tname));
goto out;
}
fh_init(&newfh, NFS_FHSIZE);
resp->status = nfsd_symlink(rqstp, &argp->ffh, argp->fname, argp->flen,
argp->tname, &attrs, &newfh);
kfree(argp->tname);
fh_put(&argp->ffh);
fh_put(&newfh);
out:
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/*
* Make directory. This operation is not idempotent.
* N.B. After this call resp->fh needs an fh_put
*/
static __be32
nfsd_proc_mkdir(struct svc_rqst *rqstp)
{
struct nfsd_createargs *argp = rqstp->rq_argp;
struct nfsd_diropres *resp = rqstp->rq_resp;
struct nfsd_attrs attrs = {
.na_iattr = &argp->attrs,
};
if (resp->fh.fh_dentry) {
printk(KERN_WARNING
"nfsd_proc_mkdir: response already verified??\n");
}
argp->attrs.ia_valid &= ~ATTR_SIZE;
fh_init(&resp->fh, NFS_FHSIZE);
resp->status = nfsd_create(rqstp, &argp->fh, argp->name, argp->len,
&attrs, S_IFDIR, 0, &resp->fh);
fh_put(&argp->fh);
if (resp->status != nfs_ok)
goto out;
resp->status = fh_getattr(&resp->fh, &resp->stat);
out:
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/*
* Remove a directory
*/
static __be32
nfsd_proc_rmdir(struct svc_rqst *rqstp)
{
struct nfsd_diropargs *argp = rqstp->rq_argp;
struct nfsd_stat *resp = rqstp->rq_resp;
resp->status = nfsd_unlink(rqstp, &argp->fh, S_IFDIR,
argp->name, argp->len);
fh_put(&argp->fh);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
static void nfsd_init_dirlist_pages(struct svc_rqst *rqstp,
struct nfsd_readdirres *resp,
u32 count)
{
struct xdr_buf *buf = &resp->dirlist;
struct xdr_stream *xdr = &resp->xdr;
memset(buf, 0, sizeof(*buf));
/* Reserve room for the NULL ptr & eof flag (-2 words) */
buf->buflen = clamp(count, (u32)(XDR_UNIT * 2), (u32)PAGE_SIZE);
buf->buflen -= XDR_UNIT * 2;
buf->pages = rqstp->rq_next_page;
rqstp->rq_next_page++;
xdr_init_encode_pages(xdr, buf);
}
/*
* Read a portion of a directory.
*/
static __be32
nfsd_proc_readdir(struct svc_rqst *rqstp)
{
struct nfsd_readdirargs *argp = rqstp->rq_argp;
struct nfsd_readdirres *resp = rqstp->rq_resp;
loff_t offset;
trace_nfsd_vfs_readdir(rqstp, &argp->fh, argp->count, argp->cookie);
nfsd_init_dirlist_pages(rqstp, resp, argp->count);
resp->common.err = nfs_ok;
resp->cookie_offset = 0;
offset = argp->cookie;
resp->status = nfsd_readdir(rqstp, &argp->fh, &offset,
&resp->common, nfssvc_encode_entry);
nfssvc_encode_nfscookie(resp, offset);
fh_put(&argp->fh);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/*
* Get file system info
*/
static __be32
nfsd_proc_statfs(struct svc_rqst *rqstp)
{
struct nfsd_fhandle *argp = rqstp->rq_argp;
struct nfsd_statfsres *resp = rqstp->rq_resp;
resp->status = nfsd_statfs(rqstp, &argp->fh, &resp->stats,
NFSD_MAY_BYPASS_GSS_ON_ROOT);
fh_put(&argp->fh);
resp->status = nfsd_map_status(resp->status);
return rpc_success;
}
/*
* NFSv2 Server procedures.
* Only the results of non-idempotent operations are cached.
*/
#define ST 1 /* status */
#define FH 8 /* filehandle */
#define AT 18 /* attributes */
static const struct svc_procedure nfsd_procedures2[18] = {
[NFSPROC_NULL] = {
.pc_func = nfsd_proc_null,
.pc_decode = nfssvc_decode_voidarg,
.pc_encode = nfssvc_encode_voidres,
.pc_argsize = sizeof(struct nfsd_voidargs),
.pc_argzero = sizeof(struct nfsd_voidargs),
.pc_ressize = sizeof(struct nfsd_voidres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = 0,
.pc_name = "NULL",
},
[NFSPROC_GETATTR] = {
.pc_func = nfsd_proc_getattr,
.pc_decode = nfssvc_decode_fhandleargs,
.pc_encode = nfssvc_encode_attrstatres,
.pc_release = nfssvc_release_attrstat,
.pc_argsize = sizeof(struct nfsd_fhandle),
.pc_argzero = sizeof(struct nfsd_fhandle),
.pc_ressize = sizeof(struct nfsd_attrstat),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+AT,
.pc_name = "GETATTR",
},
[NFSPROC_SETATTR] = {
.pc_func = nfsd_proc_setattr,
.pc_decode = nfssvc_decode_sattrargs,
.pc_encode = nfssvc_encode_attrstatres,
.pc_release = nfssvc_release_attrstat,
.pc_argsize = sizeof(struct nfsd_sattrargs),
.pc_argzero = sizeof(struct nfsd_sattrargs),
.pc_ressize = sizeof(struct nfsd_attrstat),
.pc_cachetype = RC_REPLBUFF,
.pc_xdrressize = ST+AT,
.pc_name = "SETATTR",
},
[NFSPROC_ROOT] = {
.pc_func = nfsd_proc_root,
.pc_decode = nfssvc_decode_voidarg,
.pc_encode = nfssvc_encode_voidres,
.pc_argsize = sizeof(struct nfsd_voidargs),
.pc_argzero = sizeof(struct nfsd_voidargs),
.pc_ressize = sizeof(struct nfsd_voidres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = 0,
.pc_name = "ROOT",
},
[NFSPROC_LOOKUP] = {
.pc_func = nfsd_proc_lookup,
.pc_decode = nfssvc_decode_diropargs,
.pc_encode = nfssvc_encode_diropres,
.pc_release = nfssvc_release_diropres,
.pc_argsize = sizeof(struct nfsd_diropargs),
.pc_argzero = sizeof(struct nfsd_diropargs),
.pc_ressize = sizeof(struct nfsd_diropres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+FH+AT,
.pc_name = "LOOKUP",
},
[NFSPROC_READLINK] = {
.pc_func = nfsd_proc_readlink,
.pc_decode = nfssvc_decode_fhandleargs,
.pc_encode = nfssvc_encode_readlinkres,
.pc_argsize = sizeof(struct nfsd_fhandle),
.pc_argzero = sizeof(struct nfsd_fhandle),
.pc_ressize = sizeof(struct nfsd_readlinkres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+1+NFS_MAXPATHLEN/4,
.pc_name = "READLINK",
},
[NFSPROC_READ] = {
.pc_func = nfsd_proc_read,
.pc_decode = nfssvc_decode_readargs,
.pc_encode = nfssvc_encode_readres,
.pc_release = nfssvc_release_readres,
.pc_argsize = sizeof(struct nfsd_readargs),
.pc_argzero = sizeof(struct nfsd_readargs),
.pc_ressize = sizeof(struct nfsd_readres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+AT+1+NFS_MAXDATA/4,
.pc_name = "READ",
},
[NFSPROC_WRITECACHE] = {
.pc_func = nfsd_proc_writecache,
.pc_decode = nfssvc_decode_voidarg,
.pc_encode = nfssvc_encode_voidres,
.pc_argsize = sizeof(struct nfsd_voidargs),
.pc_argzero = sizeof(struct nfsd_voidargs),
.pc_ressize = sizeof(struct nfsd_voidres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = 0,
.pc_name = "WRITECACHE",
},
[NFSPROC_WRITE] = {
.pc_func = nfsd_proc_write,
.pc_decode = nfssvc_decode_writeargs,
.pc_encode = nfssvc_encode_attrstatres,
.pc_release = nfssvc_release_attrstat,
.pc_argsize = sizeof(struct nfsd_writeargs),
.pc_argzero = sizeof(struct nfsd_writeargs),
.pc_ressize = sizeof(struct nfsd_attrstat),
.pc_cachetype = RC_REPLBUFF,
.pc_xdrressize = ST+AT,
.pc_name = "WRITE",
},
[NFSPROC_CREATE] = {
.pc_func = nfsd_proc_create,
.pc_decode = nfssvc_decode_createargs,
.pc_encode = nfssvc_encode_diropres,
.pc_release = nfssvc_release_diropres,
.pc_argsize = sizeof(struct nfsd_createargs),
.pc_argzero = sizeof(struct nfsd_createargs),
.pc_ressize = sizeof(struct nfsd_diropres),
.pc_cachetype = RC_REPLBUFF,
.pc_xdrressize = ST+FH+AT,
.pc_name = "CREATE",
},
[NFSPROC_REMOVE] = {
.pc_func = nfsd_proc_remove,
.pc_decode = nfssvc_decode_diropargs,
.pc_encode = nfssvc_encode_statres,
.pc_argsize = sizeof(struct nfsd_diropargs),
.pc_argzero = sizeof(struct nfsd_diropargs),
.pc_ressize = sizeof(struct nfsd_stat),
.pc_cachetype = RC_REPLSTAT,
.pc_xdrressize = ST,
.pc_name = "REMOVE",
},
[NFSPROC_RENAME] = {
.pc_func = nfsd_proc_rename,
.pc_decode = nfssvc_decode_renameargs,
.pc_encode = nfssvc_encode_statres,
.pc_argsize = sizeof(struct nfsd_renameargs),
.pc_argzero = sizeof(struct nfsd_renameargs),
.pc_ressize = sizeof(struct nfsd_stat),
.pc_cachetype = RC_REPLSTAT,
.pc_xdrressize = ST,
.pc_name = "RENAME",
},
[NFSPROC_LINK] = {
.pc_func = nfsd_proc_link,
.pc_decode = nfssvc_decode_linkargs,
.pc_encode = nfssvc_encode_statres,
.pc_argsize = sizeof(struct nfsd_linkargs),
.pc_argzero = sizeof(struct nfsd_linkargs),
.pc_ressize = sizeof(struct nfsd_stat),
.pc_cachetype = RC_REPLSTAT,
.pc_xdrressize = ST,
.pc_name = "LINK",
},
[NFSPROC_SYMLINK] = {
.pc_func = nfsd_proc_symlink,
.pc_decode = nfssvc_decode_symlinkargs,
.pc_encode = nfssvc_encode_statres,
.pc_argsize = sizeof(struct nfsd_symlinkargs),
.pc_argzero = sizeof(struct nfsd_symlinkargs),
.pc_ressize = sizeof(struct nfsd_stat),
.pc_cachetype = RC_REPLSTAT,
.pc_xdrressize = ST,
.pc_name = "SYMLINK",
},
[NFSPROC_MKDIR] = {
.pc_func = nfsd_proc_mkdir,
.pc_decode = nfssvc_decode_createargs,
.pc_encode = nfssvc_encode_diropres,
.pc_release = nfssvc_release_diropres,
.pc_argsize = sizeof(struct nfsd_createargs),
.pc_argzero = sizeof(struct nfsd_createargs),
.pc_ressize = sizeof(struct nfsd_diropres),
.pc_cachetype = RC_REPLBUFF,
.pc_xdrressize = ST+FH+AT,
.pc_name = "MKDIR",
},
[NFSPROC_RMDIR] = {
.pc_func = nfsd_proc_rmdir,
.pc_decode = nfssvc_decode_diropargs,
.pc_encode = nfssvc_encode_statres,
.pc_argsize = sizeof(struct nfsd_diropargs),
.pc_argzero = sizeof(struct nfsd_diropargs),
.pc_ressize = sizeof(struct nfsd_stat),
.pc_cachetype = RC_REPLSTAT,
.pc_xdrressize = ST,
.pc_name = "RMDIR",
},
[NFSPROC_READDIR] = {
.pc_func = nfsd_proc_readdir,
.pc_decode = nfssvc_decode_readdirargs,
.pc_encode = nfssvc_encode_readdirres,
.pc_argsize = sizeof(struct nfsd_readdirargs),
.pc_argzero = sizeof(struct nfsd_readdirargs),
.pc_ressize = sizeof(struct nfsd_readdirres),
.pc_cachetype = RC_NOCACHE,
.pc_name = "READDIR",
},
[NFSPROC_STATFS] = {
.pc_func = nfsd_proc_statfs,
.pc_decode = nfssvc_decode_fhandleargs,
.pc_encode = nfssvc_encode_statfsres,
.pc_argsize = sizeof(struct nfsd_fhandle),
.pc_argzero = sizeof(struct nfsd_fhandle),
.pc_ressize = sizeof(struct nfsd_statfsres),
.pc_cachetype = RC_NOCACHE,
.pc_xdrressize = ST+5,
.pc_name = "STATFS",
},
};
static DEFINE_PER_CPU_ALIGNED(unsigned long,
nfsd_count2[ARRAY_SIZE(nfsd_procedures2)]);
const struct svc_version nfsd_version2 = {
.vs_vers = 2,
.vs_nproc = ARRAY_SIZE(nfsd_procedures2),
.vs_proc = nfsd_procedures2,
.vs_count = nfsd_count2,
.vs_dispatch = nfsd_dispatch,
.vs_xdrsize = NFS2_SVC_XDRSIZE,
};
File diff suppressed because it is too large Load Diff
+663
View File
@@ -0,0 +1,663 @@
// SPDX-License-Identifier: GPL-2.0
/*
* XDR support for nfsd
*
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
*/
#include "vfs.h"
#include "xdr.h"
#include "auth.h"
/*
* Mapping of S_IF* types to NFS file types
*/
static const u32 nfs_ftypes[] = {
NFNON, NFCHR, NFCHR, NFBAD,
NFDIR, NFBAD, NFBLK, NFBAD,
NFREG, NFBAD, NFLNK, NFBAD,
NFSOCK, NFBAD, NFLNK, NFBAD,
};
/*
* Basic NFSv2 data types (RFC 1094 Section 2.3)
*/
/**
* svcxdr_encode_stat - Encode an NFSv2 status code
* @xdr: XDR stream
* @status: status value to encode
*
* Return values:
* %false: Send buffer space was exhausted
* %true: Success
*/
bool
svcxdr_encode_stat(struct xdr_stream *xdr, __be32 status)
{
__be32 *p;
p = xdr_reserve_space(xdr, sizeof(status));
if (!p)
return false;
*p = status;
return true;
}
/**
* svcxdr_decode_fhandle - Decode an NFSv2 file handle
* @xdr: XDR stream positioned at an encoded NFSv2 FH
* @fhp: OUT: filled-in server file handle
*
* Return values:
* %false: The encoded file handle was not valid
* %true: @fhp has been initialized
*/
bool
svcxdr_decode_fhandle(struct xdr_stream *xdr, struct svc_fh *fhp)
{
__be32 *p;
p = xdr_inline_decode(xdr, NFS_FHSIZE);
if (!p)
return false;
fh_init(fhp, NFS_FHSIZE);
memcpy(&fhp->fh_handle.fh_raw, p, NFS_FHSIZE);
fhp->fh_handle.fh_size = NFS_FHSIZE;
return true;
}
static bool
svcxdr_encode_fhandle(struct xdr_stream *xdr, const struct svc_fh *fhp)
{
__be32 *p;
p = xdr_reserve_space(xdr, NFS_FHSIZE);
if (!p)
return false;
memcpy(p, &fhp->fh_handle.fh_raw, NFS_FHSIZE);
return true;
}
static __be32 *
encode_timeval(__be32 *p, const struct timespec64 *time)
{
*p++ = cpu_to_be32((u32)time->tv_sec);
if (time->tv_nsec)
*p++ = cpu_to_be32(time->tv_nsec / NSEC_PER_USEC);
else
*p++ = xdr_zero;
return p;
}
static bool
svcxdr_decode_filename(struct xdr_stream *xdr, char **name, unsigned int *len)
{
u32 size, i;
__be32 *p;
char *c;
if (xdr_stream_decode_u32(xdr, &size) < 0)
return false;
if (size == 0 || size > NFS_MAXNAMLEN)
return false;
p = xdr_inline_decode(xdr, size);
if (!p)
return false;
*len = size;
*name = (char *)p;
for (i = 0, c = *name; i < size; i++, c++)
if (*c == '\0' || *c == '/')
return false;
return true;
}
static bool
svcxdr_decode_diropargs(struct xdr_stream *xdr, struct svc_fh *fhp,
char **name, unsigned int *len)
{
return svcxdr_decode_fhandle(xdr, fhp) &&
svcxdr_decode_filename(xdr, name, len);
}
static bool
svcxdr_decode_sattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
struct iattr *iap)
{
u32 tmp1, tmp2;
__be32 *p;
p = xdr_inline_decode(xdr, XDR_UNIT * 8);
if (!p)
return false;
iap->ia_valid = 0;
/*
* Some Sun clients put 0xffff in the mode field when they
* mean 0xffffffff.
*/
tmp1 = be32_to_cpup(p++);
if (tmp1 != (u32)-1 && tmp1 != 0xffff) {
iap->ia_valid |= ATTR_MODE;
iap->ia_mode = tmp1;
}
tmp1 = be32_to_cpup(p++);
if (tmp1 != (u32)-1) {
iap->ia_uid = make_kuid(nfsd_user_namespace(rqstp), tmp1);
if (uid_valid(iap->ia_uid))
iap->ia_valid |= ATTR_UID;
}
tmp1 = be32_to_cpup(p++);
if (tmp1 != (u32)-1) {
iap->ia_gid = make_kgid(nfsd_user_namespace(rqstp), tmp1);
if (gid_valid(iap->ia_gid))
iap->ia_valid |= ATTR_GID;
}
tmp1 = be32_to_cpup(p++);
if (tmp1 != (u32)-1) {
iap->ia_valid |= ATTR_SIZE;
iap->ia_size = tmp1;
}
tmp1 = be32_to_cpup(p++);
tmp2 = be32_to_cpup(p++);
if (tmp1 != (u32)-1 && tmp2 != (u32)-1) {
iap->ia_valid |= ATTR_ATIME | ATTR_ATIME_SET;
iap->ia_atime.tv_sec = tmp1;
iap->ia_atime.tv_nsec = tmp2 * NSEC_PER_USEC;
}
tmp1 = be32_to_cpup(p++);
tmp2 = be32_to_cpup(p++);
if (tmp1 != (u32)-1 && tmp2 != (u32)-1) {
iap->ia_valid |= ATTR_MTIME | ATTR_MTIME_SET;
iap->ia_mtime.tv_sec = tmp1;
iap->ia_mtime.tv_nsec = tmp2 * NSEC_PER_USEC;
/*
* Passing the invalid value useconds=1000000 for mtime
* is a Sun convention for "set both mtime and atime to
* current server time". It's needed to make permissions
* checks for the "touch" program across v2 mounts to
* Solaris and Irix boxes work correctly. See description of
* sattr in section 6.1 of "NFS Illustrated" by
* Brent Callaghan, Addison-Wesley, ISBN 0-201-32750-5
*/
if (tmp2 == 1000000)
iap->ia_valid &= ~(ATTR_ATIME_SET|ATTR_MTIME_SET);
}
return true;
}
/**
* svcxdr_encode_fattr - Encode NFSv2 file attributes
* @rqstp: Context of a completed RPC transaction
* @xdr: XDR stream
* @fhp: File handle to encode
* @stat: Attributes to encode
*
* Return values:
* %false: Send buffer space was exhausted
* %true: Success
*/
bool
svcxdr_encode_fattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
const struct svc_fh *fhp, const struct kstat *stat)
{
struct user_namespace *userns = nfsd_user_namespace(rqstp);
struct dentry *dentry = fhp->fh_dentry;
int type = stat->mode & S_IFMT;
struct timespec64 time;
__be32 *p;
u32 fsid;
p = xdr_reserve_space(xdr, XDR_UNIT * 17);
if (!p)
return false;
*p++ = cpu_to_be32(nfs_ftypes[type >> 12]);
*p++ = cpu_to_be32((u32)stat->mode);
*p++ = cpu_to_be32((u32)stat->nlink);
*p++ = cpu_to_be32((u32)from_kuid_munged(userns, stat->uid));
*p++ = cpu_to_be32((u32)from_kgid_munged(userns, stat->gid));
if (S_ISLNK(type) && stat->size > NFS_MAXPATHLEN)
*p++ = cpu_to_be32(NFS_MAXPATHLEN);
else
*p++ = cpu_to_be32((u32) stat->size);
*p++ = cpu_to_be32((u32) stat->blksize);
if (S_ISCHR(type) || S_ISBLK(type))
*p++ = cpu_to_be32(new_encode_dev(stat->rdev));
else
*p++ = cpu_to_be32(0xffffffff);
*p++ = cpu_to_be32((u32)stat->blocks);
switch (fsid_source(fhp)) {
case FSIDSOURCE_FSID:
fsid = (u32)fhp->fh_export->ex_fsid;
break;
case FSIDSOURCE_UUID:
fsid = ((u32 *)fhp->fh_export->ex_uuid)[0];
fsid ^= ((u32 *)fhp->fh_export->ex_uuid)[1];
fsid ^= ((u32 *)fhp->fh_export->ex_uuid)[2];
fsid ^= ((u32 *)fhp->fh_export->ex_uuid)[3];
break;
default:
fsid = new_encode_dev(stat->dev);
break;
}
*p++ = cpu_to_be32(fsid);
*p++ = cpu_to_be32((u32)stat->ino);
p = encode_timeval(p, &stat->atime);
time = stat->mtime;
lease_get_mtime(d_inode(dentry), &time);
p = encode_timeval(p, &time);
encode_timeval(p, &stat->ctime);
return true;
}
/*
* XDR decode functions
*/
bool
nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_fhandle *args = rqstp->rq_argp;
return svcxdr_decode_fhandle(xdr, &args->fh);
}
bool
nfssvc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_sattrargs *args = rqstp->rq_argp;
return svcxdr_decode_fhandle(xdr, &args->fh) &&
svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
}
bool
nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_diropargs *args = rqstp->rq_argp;
return svcxdr_decode_diropargs(xdr, &args->fh, &args->name, &args->len);
}
bool
nfssvc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_readargs *args = rqstp->rq_argp;
u32 totalcount;
if (!svcxdr_decode_fhandle(xdr, &args->fh))
return false;
if (xdr_stream_decode_u32(xdr, &args->offset) < 0)
return false;
if (xdr_stream_decode_u32(xdr, &args->count) < 0)
return false;
/* totalcount is ignored */
if (xdr_stream_decode_u32(xdr, &totalcount) < 0)
return false;
return true;
}
bool
nfssvc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_writeargs *args = rqstp->rq_argp;
u32 beginoffset, totalcount;
if (!svcxdr_decode_fhandle(xdr, &args->fh))
return false;
/* beginoffset is ignored */
if (xdr_stream_decode_u32(xdr, &beginoffset) < 0)
return false;
if (xdr_stream_decode_u32(xdr, &args->offset) < 0)
return false;
/* totalcount is ignored */
if (xdr_stream_decode_u32(xdr, &totalcount) < 0)
return false;
/* opaque data */
if (xdr_stream_decode_u32(xdr, &args->len) < 0)
return false;
if (args->len > NFS_MAXDATA)
return false;
return xdr_stream_subsegment(xdr, &args->payload, args->len);
}
bool
nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_createargs *args = rqstp->rq_argp;
return svcxdr_decode_diropargs(xdr, &args->fh,
&args->name, &args->len) &&
svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
}
bool
nfssvc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_renameargs *args = rqstp->rq_argp;
return svcxdr_decode_diropargs(xdr, &args->ffh,
&args->fname, &args->flen) &&
svcxdr_decode_diropargs(xdr, &args->tfh,
&args->tname, &args->tlen);
}
bool
nfssvc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_linkargs *args = rqstp->rq_argp;
return svcxdr_decode_fhandle(xdr, &args->ffh) &&
svcxdr_decode_diropargs(xdr, &args->tfh,
&args->tname, &args->tlen);
}
bool
nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_symlinkargs *args = rqstp->rq_argp;
struct kvec *head = rqstp->rq_arg.head;
if (!svcxdr_decode_diropargs(xdr, &args->ffh, &args->fname, &args->flen))
return false;
if (xdr_stream_decode_u32(xdr, &args->tlen) < 0)
return false;
if (args->tlen == 0)
return false;
args->first.iov_len = head->iov_len - xdr_stream_pos(xdr);
args->first.iov_base = xdr_inline_decode(xdr, args->tlen);
if (!args->first.iov_base)
return false;
return svcxdr_decode_sattr(rqstp, xdr, &args->attrs);
}
bool
nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_readdirargs *args = rqstp->rq_argp;
if (!svcxdr_decode_fhandle(xdr, &args->fh))
return false;
if (xdr_stream_decode_u32(xdr, &args->cookie) < 0)
return false;
if (xdr_stream_decode_u32(xdr, &args->count) < 0)
return false;
return true;
}
/*
* XDR encode functions
*/
bool
nfssvc_encode_statres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_stat *resp = rqstp->rq_resp;
return svcxdr_encode_stat(xdr, resp->status);
}
bool
nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_attrstat *resp = rqstp->rq_resp;
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
case nfs_ok:
if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
return false;
break;
}
return true;
}
bool
nfssvc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_diropres *resp = rqstp->rq_resp;
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
case nfs_ok:
if (!svcxdr_encode_fhandle(xdr, &resp->fh))
return false;
if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
return false;
break;
}
return true;
}
bool
nfssvc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_readlinkres *resp = rqstp->rq_resp;
struct kvec *head = rqstp->rq_res.head;
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
case nfs_ok:
if (xdr_stream_encode_u32(xdr, resp->len) < 0)
return false;
svcxdr_encode_opaque_pages(rqstp, xdr, &resp->page, 0,
resp->len);
if (svc_encode_result_payload(rqstp, head->iov_len, resp->len) < 0)
return false;
break;
}
return true;
}
bool
nfssvc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_readres *resp = rqstp->rq_resp;
struct kvec *head = rqstp->rq_res.head;
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
case nfs_ok:
if (!svcxdr_encode_fattr(rqstp, xdr, &resp->fh, &resp->stat))
return false;
if (xdr_stream_encode_u32(xdr, resp->count) < 0)
return false;
svcxdr_encode_opaque_pages(rqstp, xdr, resp->pages,
rqstp->rq_res.page_base,
resp->count);
if (svc_encode_result_payload(rqstp, head->iov_len, resp->count) < 0)
return false;
break;
}
return true;
}
bool
nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_readdirres *resp = rqstp->rq_resp;
struct xdr_buf *dirlist = &resp->dirlist;
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
case nfs_ok:
svcxdr_encode_opaque_pages(rqstp, xdr, dirlist->pages, 0,
dirlist->len);
/* no more entries */
if (xdr_stream_encode_item_absent(xdr) < 0)
return false;
if (xdr_stream_encode_bool(xdr, resp->common.err == nfserr_eof) < 0)
return false;
break;
}
return true;
}
bool
nfssvc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr)
{
struct nfsd_statfsres *resp = rqstp->rq_resp;
struct kstatfs *stat = &resp->stats;
__be32 *p;
if (!svcxdr_encode_stat(xdr, resp->status))
return false;
switch (resp->status) {
case nfs_ok:
p = xdr_reserve_space(xdr, XDR_UNIT * 5);
if (!p)
return false;
*p++ = cpu_to_be32(NFS_MAXDATA);
*p++ = cpu_to_be32(stat->f_bsize);
*p++ = cpu_to_be32(stat->f_blocks);
*p++ = cpu_to_be32(stat->f_bfree);
*p = cpu_to_be32(stat->f_bavail);
break;
}
return true;
}
/**
* nfssvc_encode_nfscookie - Encode a directory offset cookie
* @resp: readdir result context
* @offset: offset cookie to encode
*
* The buffer space for the offset cookie has already been reserved
* by svcxdr_encode_entry_common().
*/
void nfssvc_encode_nfscookie(struct nfsd_readdirres *resp, u32 offset)
{
__be32 cookie = cpu_to_be32(offset);
if (!resp->cookie_offset)
return;
write_bytes_to_xdr_buf(&resp->dirlist, resp->cookie_offset, &cookie,
sizeof(cookie));
resp->cookie_offset = 0;
}
static bool
svcxdr_encode_entry_common(struct nfsd_readdirres *resp, const char *name,
int namlen, loff_t offset, u64 ino)
{
struct xdr_buf *dirlist = &resp->dirlist;
struct xdr_stream *xdr = &resp->xdr;
if (xdr_stream_encode_item_present(xdr) < 0)
return false;
/* fileid */
if (xdr_stream_encode_u32(xdr, (u32)ino) < 0)
return false;
/* name */
if (xdr_stream_encode_opaque(xdr, name, min(namlen, NFS2_MAXNAMLEN)) < 0)
return false;
/* cookie */
resp->cookie_offset = dirlist->len;
if (xdr_stream_encode_u32(xdr, ~0U) < 0)
return false;
return true;
}
/**
* nfssvc_encode_entry - encode one NFSv2 READDIR entry
* @data: directory context
* @name: name of the object to be encoded
* @namlen: length of that name, in bytes
* @offset: the offset of the previous entry
* @ino: the fileid of this entry
* @d_type: unused
*
* Return values:
* %0: Entry was successfully encoded.
* %-EINVAL: An encoding problem occurred, secondary status code in resp->common.err
*
* On exit, the following fields are updated:
* - resp->xdr
* - resp->common.err
* - resp->cookie_offset
*/
int nfssvc_encode_entry(void *data, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type)
{
struct readdir_cd *ccd = data;
struct nfsd_readdirres *resp = container_of(ccd,
struct nfsd_readdirres,
common);
unsigned int starting_length = resp->dirlist.len;
/* The offset cookie for the previous entry */
nfssvc_encode_nfscookie(resp, offset);
if (!svcxdr_encode_entry_common(resp, name, namlen, offset, ino))
goto out_toosmall;
xdr_commit_encode(&resp->xdr);
resp->common.err = nfs_ok;
return 0;
out_toosmall:
resp->cookie_offset = 0;
resp->common.err = nfserr_toosmall;
resp->dirlist.len = starting_length;
return -EINVAL;
}
/*
* XDR release functions
*/
void nfssvc_release_attrstat(struct svc_rqst *rqstp)
{
struct nfsd_attrstat *resp = rqstp->rq_resp;
fh_put(&resp->fh);
}
void nfssvc_release_diropres(struct svc_rqst *rqstp)
{
struct nfsd_diropres *resp = rqstp->rq_resp;
fh_put(&resp->fh);
}
void nfssvc_release_readres(struct svc_rqst *rqstp)
{
struct nfsd_readres *resp = rqstp->rq_resp;
fh_put(&resp->fh);
}
+110
View File
@@ -0,0 +1,110 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _FS_NFSD_PNFS_H
#define _FS_NFSD_PNFS_H 1
#ifdef CONFIG_NFSD_V4
#include <linux/exportfs.h>
#include <linux/nfsd/export.h>
#include "state.h"
#include "xdr4.h"
struct xdr_stream;
/* Cap exponential backoff between fence retries at 3 minutes */
#define MAX_FENCE_DELAY ((unsigned int)(3 * 60 * HZ))
struct nfsd4_deviceid_map {
struct list_head hash;
u64 idx;
int fsid_type;
u32 fsid[];
};
struct nfsd4_layout_ops {
u32 notify_types;
bool disable_recalls;
__be32 (*proc_getdeviceinfo)(struct super_block *sb,
struct svc_rqst *rqstp,
struct nfs4_client *clp,
struct nfsd4_getdeviceinfo *gdevp);
__be32 (*encode_getdeviceinfo)(struct xdr_stream *xdr,
const struct nfsd4_getdeviceinfo *gdevp);
__be32 (*proc_layoutget)(struct svc_rqst *rqstp, struct inode *inode,
const struct svc_fh *fhp, struct nfsd4_layoutget *lgp);
__be32 (*encode_layoutget)(struct xdr_stream *xdr,
const struct nfsd4_layoutget *lgp);
__be32 (*proc_layoutcommit)(struct inode *inode,
struct svc_rqst *rqstp,
struct nfsd4_layoutcommit *lcp);
bool (*fence_client)(struct nfs4_layout_stateid *ls,
struct nfsd_file *file);
};
extern const struct nfsd4_layout_ops *nfsd4_layout_ops[];
#ifdef CONFIG_NFSD_BLOCKLAYOUT
extern const struct nfsd4_layout_ops bl_layout_ops;
#endif
#ifdef CONFIG_NFSD_SCSILAYOUT
extern const struct nfsd4_layout_ops scsi_layout_ops;
#endif
#ifdef CONFIG_NFSD_FLEXFILELAYOUT
extern const struct nfsd4_layout_ops ff_layout_ops;
#endif
__be32 nfsd4_preprocess_layout_stateid(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate, stateid_t *stateid,
bool create, u32 layout_type, struct nfs4_layout_stateid **lsp);
__be32 nfsd4_insert_layout(struct nfsd4_layoutget *lgp,
struct nfs4_layout_stateid *ls);
__be32 nfsd4_return_file_layouts(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate,
struct nfsd4_layoutreturn *lrp);
__be32 nfsd4_return_client_layouts(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate,
struct nfsd4_layoutreturn *lrp);
int nfsd4_set_deviceid(struct nfsd4_deviceid *id, const struct svc_fh *fhp,
u32 device_generation);
struct nfsd4_deviceid_map *nfsd4_find_devid_map(int idx);
#endif /* CONFIG_NFSD_V4 */
#ifdef CONFIG_NFSD_PNFS
void nfsd4_setup_layout_type(struct svc_export *exp);
void nfsd4_return_all_client_layouts(struct nfs4_client *);
void nfsd4_return_all_file_layouts(struct nfs4_client *clp,
struct nfs4_file *fp);
void nfsd4_close_layout(struct nfs4_layout_stateid *ls);
int nfsd4_init_pnfs(void);
void nfsd4_exit_pnfs(void);
#else
struct nfs4_client;
struct nfs4_file;
struct nfs4_layout_stateid;
static inline void nfsd4_setup_layout_type(struct svc_export *exp)
{
}
static inline void nfsd4_return_all_client_layouts(struct nfs4_client *clp)
{
}
static inline void nfsd4_return_all_file_layouts(struct nfs4_client *clp,
struct nfs4_file *fp)
{
}
static inline void nfsd4_close_layout(struct nfs4_layout_stateid *ls)
{
}
static inline void nfsd4_exit_pnfs(void)
{
}
static inline int nfsd4_init_pnfs(void)
{
return 0;
}
#endif /* CONFIG_NFSD_PNFS */
#endif /* _FS_NFSD_PNFS_H */
+914
View File
@@ -0,0 +1,914 @@
/*
* Copyright (c) 2001 The Regents of the University of Michigan.
* All rights reserved.
*
* Kendrick Smith <kmsmith@umich.edu>
* Andy Adamson <andros@umich.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef _NFSD4_STATE_H
#define _NFSD4_STATE_H
#include <crypto/md5.h>
#include <linux/idr.h>
#include <linux/refcount.h>
#include <linux/sunrpc/svc_xprt.h>
#include "nfsfh.h"
#include "nfsd.h"
typedef struct {
u32 cl_boot;
u32 cl_id;
} clientid_t;
typedef struct {
clientid_t so_clid;
u32 so_id;
} stateid_opaque_t;
typedef struct {
u32 si_generation;
stateid_opaque_t si_opaque;
} stateid_t;
typedef struct {
stateid_t cs_stid;
#define NFS4_COPY_STID 1
#define NFS4_COPYNOTIFY_STID 2
unsigned char cs_type;
refcount_t cs_count;
} copy_stateid_t;
struct nfsd4_referring_call {
struct list_head __list;
u32 rc_sequenceid;
u32 rc_slotid;
};
struct nfsd4_referring_call_list {
struct list_head __list;
struct nfs4_sessionid rcl_sessionid;
int __nr_referring_calls;
struct list_head rcl_referring_calls;
};
struct nfsd4_callback {
struct nfs4_client *cb_clp;
struct rpc_message cb_msg;
#define NFSD4_CALLBACK_RUNNING (0)
#define NFSD4_CALLBACK_WAKE (1)
#define NFSD4_CALLBACK_REQUEUE (2)
unsigned long cb_flags;
const struct nfsd4_callback_ops *cb_ops;
struct work_struct cb_work;
int cb_seq_status;
int cb_status;
int cb_held_slot;
int cb_nr_referring_call_list;
struct list_head cb_referring_call_list;
};
struct nfsd4_callback_ops {
void (*prepare)(struct nfsd4_callback *);
int (*done)(struct nfsd4_callback *, struct rpc_task *);
void (*release)(struct nfsd4_callback *);
uint32_t opcode;
};
/*
* A core object that represents a "common" stateid. These are generally
* embedded within the different (more specific) stateid objects and contain
* fields that are of general use to any stateid.
*/
struct nfs4_stid {
refcount_t sc_count;
/* A new stateid is added to the cl_stateids idr early before it
* is fully initialised. Its sc_type is then zero. After
* initialisation the sc_type it set under cl_lock, and then
* never changes.
*/
#define SC_TYPE_OPEN BIT(0)
#define SC_TYPE_LOCK BIT(1)
#define SC_TYPE_DELEG BIT(2)
#define SC_TYPE_LAYOUT BIT(3)
unsigned short sc_type;
/* nn->deleg_lock protects sc_status for delegation stateids.
* ->cl_lock protects sc_status for open and lock stateids.
* ->st_mutex also protect sc_status for open stateids.
* ->ls_lock protects sc_status for layout stateids.
*/
/*
* For an open stateid kept around *only* to process close replays.
* For deleg stateid, kept in idr until last reference is dropped.
*/
#define SC_STATUS_CLOSED BIT(0)
/* For a deleg stateid kept around only to process free_stateid's: */
#define SC_STATUS_REVOKED BIT(1)
#define SC_STATUS_ADMIN_REVOKED BIT(2)
#define SC_STATUS_FREEABLE BIT(3)
#define SC_STATUS_FREED BIT(4)
unsigned short sc_status;
struct list_head sc_cp_list;
stateid_t sc_stateid;
spinlock_t sc_lock;
struct nfs4_client *sc_client;
struct nfs4_file *sc_file;
struct svc_export *sc_export;
void (*sc_free)(struct nfs4_stid *);
};
/* Keep a list of stateids issued by the COPY_NOTIFY, associate it with the
* parent OPEN/LOCK/DELEG stateid.
*/
struct nfs4_cpntf_state {
copy_stateid_t cp_stateid;
struct list_head cp_list; /* per parent nfs4_stid */
stateid_t cp_p_stateid; /* copy of parent's stateid */
clientid_t cp_p_clid; /* copy of parent's clid */
time64_t cpntf_time; /* last time stateid used */
};
/*
* RFC 7862 Section 4.8 states:
*
* | A copy offload stateid will be valid until either (A) the client
* | or server restarts or (B) the client returns the resource by
* | issuing an OFFLOAD_CANCEL operation or the client replies to a
* | CB_OFFLOAD operation.
*
* Because a client might not reply to a CB_OFFLOAD, or a reply
* might get lost due to connection loss, NFSD purges async copy
* state after a short period to prevent it from accumulating
* over time.
*/
#define NFSD_COPY_INITIAL_TTL 10
struct nfs4_cb_fattr {
struct nfsd4_callback ncf_getattr;
u32 ncf_cb_status;
/* from CB_GETATTR reply */
u64 ncf_cb_change;
u64 ncf_cb_fsize;
struct timespec64 ncf_cb_mtime;
struct timespec64 ncf_cb_atime;
bool ncf_file_modified;
u64 ncf_initial_cinfo;
u64 ncf_cur_fsize;
};
/*
* Represents a delegation stateid. The nfs4_client holds references to these
* and they are put when it is being destroyed or when the delegation is
* returned by the client:
*
* o 1 reference as long as a delegation is still in force (taken when it's
* alloc'd, put when it's returned or revoked)
*
* o 1 reference as long as a recall rpc is in progress (taken when the lease
* is broken, put when the rpc exits)
*
* o 1 more ephemeral reference for each nfsd thread currently doing something
* with that delegation without holding the cl_lock
*
* If the server attempts to recall a delegation and the client doesn't do so
* before a timeout, the server may also revoke the delegation. In that case,
* the object will either be destroyed (v4.0) or moved to a per-client list of
* revoked delegations (v4.1+).
*
* This object is a superset of the nfs4_stid.
*/
struct nfs4_delegation {
struct nfs4_stid dl_stid; /* must be first field */
struct list_head dl_perfile;
struct list_head dl_perclnt;
struct list_head dl_recall_lru; /* delegation recalled */
struct nfs4_clnt_odstate *dl_clnt_odstate;
time64_t dl_time;
u32 dl_type;
/* For recall: */
int dl_retries;
struct nfsd4_callback dl_recall;
bool dl_recalled;
bool dl_written;
bool dl_setattr;
/* for CB_GETATTR */
struct nfs4_cb_fattr dl_cb_fattr;
/* For delegated timestamps */
struct timespec64 dl_atime;
struct timespec64 dl_mtime;
struct timespec64 dl_ctime;
};
static inline bool deleg_is_read(u32 dl_type)
{
return (dl_type == OPEN_DELEGATE_READ || dl_type == OPEN_DELEGATE_READ_ATTRS_DELEG);
}
static inline bool deleg_is_write(u32 dl_type)
{
return (dl_type == OPEN_DELEGATE_WRITE || dl_type == OPEN_DELEGATE_WRITE_ATTRS_DELEG);
}
static inline bool deleg_attrs_deleg(u32 dl_type)
{
return dl_type == OPEN_DELEGATE_READ_ATTRS_DELEG ||
dl_type == OPEN_DELEGATE_WRITE_ATTRS_DELEG;
}
bool nfsd4_vet_deleg_time(struct timespec64 *cb, const struct timespec64 *orig,
const struct timespec64 *now);
#define cb_to_delegation(cb) \
container_of(cb, struct nfs4_delegation, dl_recall)
/* client delegation callback info */
struct nfs4_cb_conn {
/* SETCLIENTID info */
struct sockaddr_storage cb_addr;
struct sockaddr_storage cb_saddr;
size_t cb_addrlen;
u32 cb_prog; /* used only in 4.0 case;
per-session otherwise */
u32 cb_ident; /* minorversion 0 only */
struct svc_xprt *cb_xprt; /* minorversion 1 only */
};
static inline struct nfs4_delegation *delegstateid(struct nfs4_stid *s)
{
return container_of(s, struct nfs4_delegation, dl_stid);
}
/* Maximum number of slots per session. This is for sanity-check only.
* It could be increased if we had a mechanism to shutdown misbehaving clients.
* A large number can be needed to get good throughput on high-latency servers.
*/
#define NFSD_MAX_SLOTS_PER_SESSION 2048
/* Maximum session per slot cache size */
#define NFSD_SLOT_CACHE_SIZE 2048
/* Maximum number of NFSD_SLOT_CACHE_SIZE slots per session */
#define NFSD_CACHE_SIZE_SLOTS_PER_SESSION 32
#define NFSD_MAX_MEM_PER_SESSION \
(NFSD_CACHE_SIZE_SLOTS_PER_SESSION * NFSD_SLOT_CACHE_SIZE)
struct nfsd4_slot {
u32 sl_seqid;
__be32 sl_status;
struct svc_cred sl_cred;
u32 sl_index;
u32 sl_datalen;
u16 sl_opcnt;
u16 sl_generation;
#define NFSD4_SLOT_INUSE (1 << 0)
#define NFSD4_SLOT_CACHETHIS (1 << 1)
#define NFSD4_SLOT_INITIALIZED (1 << 2)
#define NFSD4_SLOT_CACHED (1 << 3)
#define NFSD4_SLOT_REUSED (1 << 4)
u8 sl_flags;
char sl_data[];
};
struct nfsd4_channel_attrs {
u32 headerpadsz;
u32 maxreq_sz;
u32 maxresp_sz;
u32 maxresp_cached;
u32 maxops;
u32 maxreqs;
u32 nr_rdma_attrs;
u32 rdma_attrs;
};
struct nfsd4_cb_sec {
u32 flavor; /* (u32)(-1) used to mean "no valid flavor" */
kuid_t uid;
kgid_t gid;
};
struct nfsd4_create_session {
clientid_t clientid;
struct nfs4_sessionid sessionid;
u32 seqid;
u32 flags;
struct nfsd4_channel_attrs fore_channel;
struct nfsd4_channel_attrs back_channel;
u32 callback_prog;
struct nfsd4_cb_sec cb_sec;
};
struct nfsd4_backchannel_ctl {
u32 bc_cb_program;
struct nfsd4_cb_sec bc_cb_sec;
};
struct nfsd4_bind_conn_to_session {
struct nfs4_sessionid sessionid;
u32 dir;
};
/* The single slot clientid cache structure */
struct nfsd4_clid_slot {
u32 sl_seqid;
__be32 sl_status;
struct nfsd4_create_session sl_cr_ses;
};
struct nfsd4_conn {
struct list_head cn_persession;
struct svc_xprt *cn_xprt;
struct svc_xpt_user cn_xpt_user;
struct nfsd4_session *cn_session;
/* CDFC4_FORE, CDFC4_BACK: */
unsigned char cn_flags;
};
/* Maximum number of slots that nfsd will use in the backchannel */
#define NFSD_BC_SLOT_TABLE_SIZE (sizeof(u32) * 8)
/*
* Representation of a v4.1+ session. These are refcounted in a similar fashion
* to the nfs4_client. References are only taken when the server is actively
* working on the object (primarily during the processing of compounds).
*/
struct nfsd4_session {
atomic_t se_ref;
spinlock_t se_lock;
u32 se_cb_slot_avail; /* bitmap of available slots */
u32 se_cb_highest_slot; /* highest slot client wants */
u32 se_cb_prog;
struct list_head se_hash; /* hash by sessionid */
struct list_head se_perclnt;
struct list_head se_all_sessions;/* global list of sessions */
struct nfs4_client *se_client;
struct nfs4_sessionid se_sessionid;
struct nfsd4_channel_attrs se_fchannel;
struct nfsd4_cb_sec se_cb_sec;
struct list_head se_conns;
u32 se_cb_seq_nr[NFSD_BC_SLOT_TABLE_SIZE];
struct xarray se_slots; /* forward channel slots */
u16 se_slot_gen;
bool se_dead;
u32 se_target_maxslots;
};
/* formatted contents of nfs4_sessionid */
struct nfsd4_sessionid {
clientid_t clientid;
u32 sequence;
u32 reserved;
};
/* Length of MD5 digest as hex, plus terminating '\0' */
#define HEXDIR_LEN (2 * MD5_DIGEST_SIZE + 1)
/*
* State Meaning Where set
* --------------------------------------------------------------------------
* | NFSD4_ACTIVE | Confirmed, active | Default |
* |------------------- ----------------------------------------------------|
* | NFSD4_COURTESY | Courtesy state. | nfs4_get_client_reaplist |
* | | Lease/lock/share | |
* | | reservation conflict | |
* | | can cause Courtesy | |
* | | client to be expired | |
* |------------------------------------------------------------------------|
* | NFSD4_EXPIRABLE | Courtesy client to be| nfs4_laundromat |
* | | expired by Laundromat| try_to_expire_client |
* | | due to conflict | |
* |------------------------------------------------------------------------|
*/
enum {
NFSD4_ACTIVE = 0,
NFSD4_COURTESY,
NFSD4_EXPIRABLE,
};
/*
* struct nfs4_client - one per client. Clientids live here.
*
* The initial object created by an NFS client using SETCLIENTID (for NFSv4.0)
* or EXCHANGE_ID (for NFSv4.1+). These objects are refcounted and timestamped.
* Each nfsd_net_ns object contains a set of these and they are tracked via
* short and long form clientid. They are hashed and searched for under the
* per-nfsd_net client_lock spinlock.
*
* References to it are only held during the processing of compounds, and in
* certain other operations. In their "resting state" they have a refcount of
* 0. If they are not renewed within a lease period, they become eligible for
* destruction by the laundromat.
*
* These objects can also be destroyed if the client sends certain forms of
* SETCLIENTID or EXCHANGE_ID operations.
*
* Care is taken *not* to do this however when the objects have an elevated
* refcount.
*
* o Each nfs4_client is hashed by clientid
*
* o Each nfs4_clients is also hashed by name (the opaque quantity initially
* sent by the client to identify itself).
*
* o cl_perclient list is used to ensure no dangling stateowner references
* when we expire the nfs4_client
*/
struct nfs4_client {
struct list_head cl_idhash; /* hash by cl_clientid.id */
struct rb_node cl_namenode; /* link into by-name trees */
struct list_head *cl_ownerstr_hashtbl;
struct list_head cl_openowners;
struct idr cl_stateids; /* stateid lookup */
struct list_head cl_delegations;
struct list_head cl_revoked; /* unacknowledged, revoked 4.1 state */
struct list_head cl_lru; /* tail queue */
#ifdef CONFIG_NFSD_PNFS
struct list_head cl_lo_states; /* outstanding layout states */
bool cl_fence_retry_warn;
#endif
struct xdr_netobj cl_name; /* id generated by client */
nfs4_verifier cl_verifier; /* generated by client */
time64_t cl_time; /* time of last lease renewal */
struct sockaddr_storage cl_addr; /* client ipaddress */
bool cl_mach_cred; /* SP4_MACH_CRED in force */
struct svc_cred cl_cred; /* setclientid principal */
clientid_t cl_clientid; /* generated by server */
nfs4_verifier cl_confirm; /* generated by server */
u32 cl_minorversion;
atomic_t cl_admin_revoked; /* count of admin-revoked states */
/* NFSv4.1 client implementation id: */
struct xdr_netobj cl_nii_domain;
struct xdr_netobj cl_nii_name;
struct timespec64 cl_nii_time;
/* for v4.0 and v4.1 callbacks: */
struct nfs4_cb_conn cl_cb_conn;
#define NFSD4_CLIENT_CB_UPDATE (0)
#define NFSD4_CLIENT_CB_KILL (1)
#define NFSD4_CLIENT_STABLE (2) /* client on stable storage */
#define NFSD4_CLIENT_RECLAIM_COMPLETE (3) /* reclaim_complete done */
#define NFSD4_CLIENT_CONFIRMED (4) /* client is confirmed */
#define NFSD4_CLIENT_UPCALL_LOCK (5) /* upcall serialization */
#define NFSD4_CLIENT_CB_FLAG_MASK (1 << NFSD4_CLIENT_CB_UPDATE | \
1 << NFSD4_CLIENT_CB_KILL)
unsigned long cl_flags;
struct workqueue_struct *cl_callback_wq;
const struct cred *cl_cb_cred;
struct rpc_clnt *cl_cb_client;
u32 cl_cb_ident;
#define NFSD4_CB_UP 0
#define NFSD4_CB_UNKNOWN 1
#define NFSD4_CB_DOWN 2
#define NFSD4_CB_FAULT 3
int cl_cb_state;
struct nfsd4_callback cl_cb_null;
struct nfsd4_session *cl_cb_session;
/* for all client information that callback code might need: */
spinlock_t cl_lock;
/* for nfs41 */
struct list_head cl_sessions;
struct nfsd4_clid_slot cl_cs_slot; /* create_session slot */
u32 cl_exchange_flags;
/* number of rpc's in progress over an associated session: */
atomic_t cl_rpc_users;
struct nfsdfs_client cl_nfsdfs;
struct nfs4_op_map cl_spo_must_allow;
/* debugging info directory under nfsd/clients/ : */
struct dentry *cl_nfsd_dentry;
/* 'info' file within that directory. Ref is not counted,
* but will remain valid iff cl_nfsd_dentry != NULL
*/
struct dentry *cl_nfsd_info_dentry;
struct rpc_wait_queue cl_cb_waitq; /* backchannel callers may */
/* wait here for slots */
struct net *net;
struct list_head async_copies; /* list of async copies */
spinlock_t async_lock; /* lock for async copies */
atomic_t cl_cb_inflight; /* Outstanding callbacks */
unsigned int cl_state;
atomic_t cl_delegs_in_recall;
struct nfsd4_cb_recall_any *cl_ra;
time64_t cl_ra_time;
#ifdef CONFIG_NFSD_SCSILAYOUT
struct xarray cl_dev_fences;
struct mutex cl_fence_mutex;
#endif
};
/* struct nfs4_client_reset
* one per old client. Populates reset_str_hashtbl. Filled from conf_id_hashtbl
* upon lease reset, or from upcall to state_daemon (to read in state
* from non-volitile storage) upon reboot.
*/
struct nfs4_client_reclaim {
struct list_head cr_strhash; /* hash by cr_name */
struct nfs4_client *cr_clp; /* pointer to associated clp */
struct xdr_netobj cr_name; /* recovery dir name */
struct xdr_netobj cr_princhash;
};
/*
* REPLAY_ISIZE is sized for an OPEN response with delegation:
* 4(status) + 8(stateid) + 20(changeinfo) + 4(rflags) +
* 8(verifier) + 4(deleg. type) + 8(deleg. stateid) +
* 4(deleg. recall flag) + 20(deleg. space limit) +
* ~32(deleg. ace) = 112 bytes
*
* Some responses can exceed this. A LOCK denial includes the conflicting
* lock owner, which can be up to 1024 bytes (NFS4_OPAQUE_LIMIT). When a
* response exceeds REPLAY_ISIZE, a buffer is dynamically allocated. If
* that allocation fails, only rp_status is saved. Enlarging this constant
* increases the size of every nfs4_stateowner.
*/
#define NFSD4_REPLAY_ISIZE 112
/*
* Replay buffer, where the result of the last seqid-mutating operation
* is cached.
*/
struct nfs4_replay {
__be32 rp_status;
unsigned int rp_buflen;
char *rp_buf; /* rp_ibuf or kmalloc'd */
struct knfsd_fh rp_openfh;
int rp_locked;
char rp_ibuf[NFSD4_REPLAY_ISIZE];
};
extern void nfs4_replay_free_cache(struct nfs4_replay *rp);
struct nfs4_stateowner;
struct nfs4_stateowner_operations {
void (*so_unhash)(struct nfs4_stateowner *);
void (*so_free)(struct nfs4_stateowner *);
};
/*
* A core object that represents either an open or lock owner. The object and
* lock owner objects have one of these embedded within them. Refcounts and
* other fields common to both owner types are contained within these
* structures.
*/
struct nfs4_stateowner {
struct list_head so_strhash;
struct list_head so_stateids;
struct nfs4_client *so_client;
const struct nfs4_stateowner_operations *so_ops;
/* after increment in nfsd4_bump_seqid, represents the next
* sequence id expected from the client: */
atomic_t so_count;
u32 so_seqid;
struct xdr_netobj so_owner; /* open owner name */
struct nfs4_replay so_replay;
bool so_is_open_owner;
};
/*
* When a file is opened, the client provides an open state owner opaque string
* that indicates the "owner" of that open. These objects are refcounted.
* References to it are held by each open state associated with it. This object
* is a superset of the nfs4_stateowner struct.
*/
struct nfs4_openowner {
struct nfs4_stateowner oo_owner; /* must be first field */
struct list_head oo_perclient;
/*
* We keep around openowners a little while after last close,
* which saves clients from having to confirm, and allows us to
* handle close replays if they come soon enough. The close_lru
* is a list of such openowners, to be reaped by the laundromat
* thread eventually if they remain unused:
*/
struct list_head oo_close_lru;
struct nfs4_ol_stateid *oo_last_closed_stid;
time64_t oo_time; /* time of placement on so_close_lru */
#define NFS4_OO_CONFIRMED 1
unsigned char oo_flags;
};
/*
* Represents a generic "lockowner". Similar to an openowner. References to it
* are held by the lock stateids that are created on its behalf. This object is
* a superset of the nfs4_stateowner struct.
*/
struct nfs4_lockowner {
struct nfs4_stateowner lo_owner; /* must be first element */
struct list_head lo_blocked; /* blocked file_locks */
};
static inline struct nfs4_openowner * openowner(struct nfs4_stateowner *so)
{
return container_of(so, struct nfs4_openowner, oo_owner);
}
static inline struct nfs4_lockowner * lockowner(struct nfs4_stateowner *so)
{
return container_of(so, struct nfs4_lockowner, lo_owner);
}
/*
* Per-client state indicating no. of opens and outstanding delegations
* on a file from a particular client.'od' stands for 'open & delegation'
*/
struct nfs4_clnt_odstate {
struct nfs4_client *co_client;
struct nfs4_file *co_file;
struct list_head co_perfile;
refcount_t co_odcount;
};
/*
* nfs4_file: a file opened by some number of (open) nfs4_stateowners.
*
* These objects are global. nfsd keeps one instance of a nfs4_file per
* filehandle (though it may keep multiple file descriptors for each). Each
* inode can have multiple filehandles associated with it, so there is
* (potentially) a many to one relationship between this struct and struct
* inode.
*/
struct nfs4_file {
refcount_t fi_ref;
struct inode * fi_inode;
bool fi_aliased;
spinlock_t fi_lock;
struct rhlist_head fi_rlist;
struct list_head fi_stateids;
union {
struct list_head fi_delegations;
struct rcu_head fi_rcu;
};
struct list_head fi_clnt_odstate;
/* One each for O_RDONLY, O_WRONLY, O_RDWR: */
struct nfsd_file *fi_fds[3];
/*
* Each open or lock stateid contributes 0-4 to the counts
* below depending on which bits are set in st_access_bitmap:
* 1 to fi_access[O_RDONLY] if NFS4_SHARE_ACCES_READ is set
* + 1 to fi_access[O_WRONLY] if NFS4_SHARE_ACCESS_WRITE is set
* + 1 to both of the above if NFS4_SHARE_ACCESS_BOTH is set.
*/
atomic_t fi_access[2];
u32 fi_share_deny;
struct nfsd_file *fi_deleg_file;
struct nfsd_file *fi_rdeleg_file;
int fi_delegees;
struct knfsd_fh fi_fhandle;
bool fi_had_conflict;
#ifdef CONFIG_NFSD_PNFS
struct list_head fi_lo_states;
atomic_t fi_lo_recalls;
#endif
};
/*
* A generic struct representing either a open or lock stateid. The nfs4_client
* holds a reference to each of these objects, and they in turn hold a
* reference to their respective stateowners. The client's reference is
* released in response to a close or unlock (depending on whether it's an open
* or lock stateid) or when the client is being destroyed.
*
* In the case of v4.0 open stateids, these objects are preserved for a little
* while after close in order to handle CLOSE replays. Those are eventually
* reclaimed via a LRU scheme by the laundromat.
*
* This object is a superset of the nfs4_stid. "ol" stands for "Open or Lock".
* Better suggestions welcome.
*/
struct nfs4_ol_stateid {
struct nfs4_stid st_stid;
struct list_head st_perfile;
struct list_head st_perstateowner;
struct list_head st_locks;
struct nfs4_stateowner *st_stateowner;
struct nfs4_clnt_odstate *st_clnt_odstate;
/*
* These bitmasks use 3 separate bits for READ, ALLOW, and BOTH; see the
* comment above bmap_to_share_mode() for explanation:
*/
unsigned char st_access_bmap;
unsigned char st_deny_bmap;
struct nfs4_ol_stateid *st_openstp;
struct mutex st_mutex;
};
static inline struct nfs4_ol_stateid *openlockstateid(struct nfs4_stid *s)
{
return container_of(s, struct nfs4_ol_stateid, st_stid);
}
struct nfs4_layout_stateid {
struct nfs4_stid ls_stid;
struct list_head ls_perclnt;
struct list_head ls_perfile;
spinlock_t ls_lock;
struct list_head ls_layouts;
u32 ls_layout_type;
struct nfsd_file *ls_file;
struct nfsd4_callback ls_recall;
stateid_t ls_recall_sid;
bool ls_recalled;
struct mutex ls_mutex;
struct delayed_work ls_fence_work;
unsigned int ls_fence_delay;
bool ls_fenced;
};
static inline struct nfs4_layout_stateid *layoutstateid(struct nfs4_stid *s)
{
return container_of(s, struct nfs4_layout_stateid, ls_stid);
}
/* flags for preprocess_seqid_op() */
#define RD_STATE 0x00000010
#define WR_STATE 0x00000020
enum nfsd4_cb_op {
NFSPROC4_CLNT_CB_NULL = 0,
NFSPROC4_CLNT_CB_RECALL,
NFSPROC4_CLNT_CB_LAYOUT,
NFSPROC4_CLNT_CB_OFFLOAD,
NFSPROC4_CLNT_CB_SEQUENCE,
NFSPROC4_CLNT_CB_NOTIFY_LOCK,
NFSPROC4_CLNT_CB_RECALL_ANY,
NFSPROC4_CLNT_CB_GETATTR,
};
/* Returns true iff a is later than b: */
static inline bool nfsd4_stateid_generation_after(stateid_t *a, stateid_t *b)
{
return (s32)(a->si_generation - b->si_generation) > 0;
}
/*
* When a client tries to get a lock on a file, we set one of these objects
* on the blocking lock. When the lock becomes free, we can then issue a
* CB_NOTIFY_LOCK to the server.
*/
struct nfsd4_blocked_lock {
struct list_head nbl_list;
struct list_head nbl_lru;
time64_t nbl_time;
struct file_lock nbl_lock;
struct knfsd_fh nbl_fh;
struct nfsd4_callback nbl_cb;
struct kref nbl_kref;
};
struct nfsd4_compound_state;
struct nfsd_net;
struct nfsd4_copy;
extern __be32 nfs4_preprocess_stateid_op(struct svc_rqst *rqstp,
struct nfsd4_compound_state *cstate, struct svc_fh *fhp,
stateid_t *stateid, int flags, struct nfsd_file **filp,
struct nfs4_stid **cstid);
__be32 nfsd4_lookup_stateid(struct nfsd4_compound_state *cstate,
stateid_t *stateid, unsigned short typemask,
unsigned short statusmask,
struct nfs4_stid **s, struct nfsd_net *nn);
struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *slab,
void (*sc_free)(struct nfs4_stid *));
int nfs4_init_copy_state(struct nfsd_net *nn, struct nfsd4_copy *copy);
void nfs4_free_copy_state(struct nfsd4_copy *copy);
struct nfs4_cpntf_state *nfs4_alloc_init_cpntf_state(struct nfsd_net *nn,
struct nfs4_stid *p_stid);
void nfs4_put_stid(struct nfs4_stid *s);
void nfs4_inc_and_copy_stateid(stateid_t *dst, struct nfs4_stid *stid);
void nfs4_remove_reclaim_record(struct nfs4_client_reclaim *, struct nfsd_net *);
extern void nfs4_release_reclaim(struct nfsd_net *);
extern struct nfs4_client_reclaim *nfsd4_find_reclaim_client(struct xdr_netobj name,
struct nfsd_net *nn);
extern __be32 nfs4_check_open_reclaim(struct nfs4_client *);
extern void nfsd4_probe_callback(struct nfs4_client *clp);
extern void nfsd4_probe_callback_sync(struct nfs4_client *clp);
extern void nfsd4_change_callback(struct nfs4_client *clp, struct nfs4_cb_conn *);
extern void nfsd41_cb_referring_call(struct nfsd4_callback *cb,
struct nfs4_sessionid *sessionid,
u32 slotid, u32 seqno);
extern void nfsd41_cb_destroy_referring_call_list(struct nfsd4_callback *cb);
extern void nfsd4_init_cb(struct nfsd4_callback *cb, struct nfs4_client *clp,
const struct nfsd4_callback_ops *ops, enum nfsd4_cb_op op);
extern bool nfsd4_run_cb(struct nfsd4_callback *cb);
static inline void nfsd4_try_run_cb(struct nfsd4_callback *cb)
{
if (!test_and_set_bit(NFSD4_CALLBACK_RUNNING, &cb->cb_flags))
WARN_ON_ONCE(!nfsd4_run_cb(cb));
}
extern void nfsd4_shutdown_callback(struct nfs4_client *);
extern void nfsd4_shutdown_copy(struct nfs4_client *clp);
void nfsd4_put_client(struct nfs4_client *clp);
void nfsd4_async_copy_reaper(struct nfsd_net *nn);
bool nfsd4_has_active_async_copies(struct nfs4_client *clp);
void nfsd_update_cmtime_attr(struct file *f, unsigned int flags);
extern struct nfs4_client_reclaim *nfs4_client_to_reclaim(struct xdr_netobj name,
struct xdr_netobj princhash, struct nfsd_net *nn);
extern bool nfs4_has_reclaimed_state(struct xdr_netobj name, struct nfsd_net *nn);
void put_nfs4_file(struct nfs4_file *fi);
extern void nfs4_put_cpntf_state(struct nfsd_net *nn,
struct nfs4_cpntf_state *cps);
extern __be32 manage_cpntf_state(struct nfsd_net *nn, stateid_t *st,
struct nfs4_client *clp,
struct nfs4_cpntf_state **cps);
static inline void get_nfs4_file(struct nfs4_file *fi)
{
refcount_inc(&fi->fi_ref);
}
struct nfsd_file *find_any_file(struct nfs4_file *f);
#ifdef CONFIG_NFSD_V4
void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb);
void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path);
void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb);
int nfsd_net_cb_init(struct nfsd_net *nn);
void nfsd_net_cb_shutdown(struct nfsd_net *nn);
#else
static inline void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb)
{
}
static inline void nfsd4_revoke_export_states(struct nfsd_net *nn,
const struct path *path)
{
}
static inline void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb)
{
}
static inline int nfsd_net_cb_init(struct nfsd_net *nn)
{
return 0;
}
static inline void nfsd_net_cb_shutdown(struct nfsd_net *nn)
{
}
#endif
/* grace period management */
bool nfsd4_force_end_grace(struct nfsd_net *nn);
/* nfs4recover operations */
extern int nfsd4_client_tracking_init(struct net *net);
extern void nfsd4_client_tracking_exit(struct net *net);
extern void nfsd4_client_record_create(struct nfs4_client *clp);
extern void nfsd4_client_record_remove(struct nfs4_client *clp);
extern int nfsd4_client_record_check(struct nfs4_client *clp);
extern void nfsd4_record_grace_done(struct nfsd_net *nn);
static inline bool try_to_expire_client(struct nfs4_client *clp)
{
cmpxchg(&clp->cl_state, NFSD4_COURTESY, NFSD4_EXPIRABLE);
return clp->cl_state == NFSD4_EXPIRABLE;
}
extern __be32 nfsd4_deleg_getattr_conflict(struct svc_rqst *rqstp,
struct dentry *dentry, struct nfs4_delegation **pdp);
struct nfsd4_get_dir_delegation;
struct nfs4_delegation *nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate,
struct nfsd4_get_dir_delegation *gdd,
struct nfsd_file *nf);
#endif /* NFSD4_STATE_H */
+86
View File
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0
/*
* procfs-based user access to knfsd statistics
*
* /proc/net/rpc/nfsd
*
* Format:
* rc <hits> <misses> <nocache>
* Statistsics for the reply cache
* fh <stale> <deprecated filehandle cache stats>
* statistics for filehandle lookup
* io <bytes-read> <bytes-written>
* statistics for IO throughput
* th <threads> <deprecated thread usage histogram stats>
* number of threads
* ra <deprecated ra-cache stats>
*
* plus generic RPC stats (see net/sunrpc/stats.c)
*
* Copyright (C) 1995, 1996, 1997 Olaf Kirch <okir@monad.swb.de>
*/
#include <linux/seq_file.h>
#include <linux/module.h>
#include <linux/sunrpc/stats.h>
#include <net/net_namespace.h>
#include "nfsd.h"
static int nfsd_show(struct seq_file *seq, void *v)
{
struct net *net = pde_data(file_inode(seq->file));
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
int i;
seq_printf(seq, "rc %lld %lld %lld\nfh %lld 0 0 0 0\nio %lld %lld\n",
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_HITS]),
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_MISSES]),
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_NOCACHE]),
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_FH_STALE]),
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_IO_READ]),
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_IO_WRITE]));
/* thread usage: */
seq_printf(seq, "th %u 0", atomic_read(&nfsd_th_cnt));
/* deprecated thread usage histogram stats */
for (i = 0; i < 10; i++)
seq_puts(seq, " 0.000");
/* deprecated ra-cache stats */
seq_puts(seq, "\nra 0 0 0 0 0 0 0 0 0 0 0 0\n");
/* show my rpc info */
svc_seq_show(seq, &nn->nfsd_svcstats);
#ifdef CONFIG_NFSD_V4
/* Show count for individual nfsv4 operations */
/* Writing operation numbers 0 1 2 also for maintaining uniformity */
seq_printf(seq, "proc4ops %u", LAST_NFS4_OP + 1);
for (i = 0; i <= LAST_NFS4_OP; i++) {
seq_printf(seq, " %lld",
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_NFS4_OP(i)]));
}
seq_printf(seq, "\nwdeleg_getattr %lld",
percpu_counter_sum_positive(&nn->counter[NFSD_STATS_WDELEG_GETATTR]));
seq_putc(seq, '\n');
#endif
return 0;
}
DEFINE_PROC_SHOW_ATTRIBUTE(nfsd);
struct proc_dir_entry *nfsd_proc_stat_init(struct net *net)
{
struct nfsd_net *nn = net_generic(net, nfsd_net_id);
return svc_proc_register(net, &nn->nfsd_svcstats, &nfsd_proc_ops);
}
void nfsd_proc_stat_shutdown(struct net *net)
{
svc_proc_unregister(net, "nfsd");
}
+76
View File
@@ -0,0 +1,76 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Statistics for NFS server.
*
* Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
*/
#ifndef _NFSD_STATS_H
#define _NFSD_STATS_H
#include <uapi/linux/nfsd/stats.h>
#include <linux/percpu_counter.h>
struct proc_dir_entry *nfsd_proc_stat_init(struct net *net);
void nfsd_proc_stat_shutdown(struct net *net);
static inline void nfsd_stats_rc_hits_inc(struct nfsd_net *nn)
{
percpu_counter_inc(&nn->counter[NFSD_STATS_RC_HITS]);
}
static inline void nfsd_stats_rc_misses_inc(struct nfsd_net *nn)
{
percpu_counter_inc(&nn->counter[NFSD_STATS_RC_MISSES]);
}
static inline void nfsd_stats_rc_nocache_inc(struct nfsd_net *nn)
{
percpu_counter_inc(&nn->counter[NFSD_STATS_RC_NOCACHE]);
}
static inline void nfsd_stats_fh_stale_inc(struct nfsd_net *nn,
struct svc_export *exp)
{
percpu_counter_inc(&nn->counter[NFSD_STATS_FH_STALE]);
if (exp && exp->ex_stats)
percpu_counter_inc(&exp->ex_stats->counter[EXP_STATS_FH_STALE]);
}
static inline void nfsd_stats_io_read_add(struct nfsd_net *nn,
struct svc_export *exp, s64 amount)
{
percpu_counter_add(&nn->counter[NFSD_STATS_IO_READ], amount);
if (exp && exp->ex_stats)
percpu_counter_add(&exp->ex_stats->counter[EXP_STATS_IO_READ], amount);
}
static inline void nfsd_stats_io_write_add(struct nfsd_net *nn,
struct svc_export *exp, s64 amount)
{
percpu_counter_add(&nn->counter[NFSD_STATS_IO_WRITE], amount);
if (exp && exp->ex_stats)
percpu_counter_add(&exp->ex_stats->counter[EXP_STATS_IO_WRITE], amount);
}
static inline void nfsd_stats_payload_misses_inc(struct nfsd_net *nn)
{
percpu_counter_inc(&nn->counter[NFSD_STATS_PAYLOAD_MISSES]);
}
static inline void nfsd_stats_drc_mem_usage_add(struct nfsd_net *nn, s64 amount)
{
percpu_counter_add(&nn->counter[NFSD_STATS_DRC_MEM_USAGE], amount);
}
static inline void nfsd_stats_drc_mem_usage_sub(struct nfsd_net *nn, s64 amount)
{
percpu_counter_sub(&nn->counter[NFSD_STATS_DRC_MEM_USAGE], amount);
}
#ifdef CONFIG_NFSD_V4
static inline void nfsd_stats_wdeleg_getattr_inc(struct nfsd_net *nn)
{
percpu_counter_inc(&nn->counter[NFSD_STATS_WDELEG_GETATTR]);
}
#endif
#endif /* _NFSD_STATS_H */
+4
View File
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-2.0
#define CREATE_TRACE_POINTS
#include "trace.h"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+168
View File
@@ -0,0 +1,168 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 1995-1997 Olaf Kirch <okir@monad.swb.de>
*/
#ifndef LINUX_NFSD_VFS_H
#define LINUX_NFSD_VFS_H
#include <linux/fs.h>
#include <linux/posix_acl.h>
#include "nfsfh.h"
#include "nfsd.h"
/*
* Flags for nfsd_permission
*/
#define NFSD_MAY_NOP 0
#define NFSD_MAY_EXEC 0x001 /* == MAY_EXEC */
#define NFSD_MAY_WRITE 0x002 /* == MAY_WRITE */
#define NFSD_MAY_READ 0x004 /* == MAY_READ */
#define NFSD_MAY_SATTR 0x008
#define NFSD_MAY_TRUNC 0x010
#define NFSD_MAY_NLM 0x020 /* request is from lockd */
#define NFSD_MAY_MASK 0x03f
/* extra hints to permission and open routines: */
#define NFSD_MAY_OWNER_OVERRIDE 0x040
#define NFSD_MAY_LOCAL_ACCESS 0x080 /* for device special files */
#define NFSD_MAY_BYPASS_GSS_ON_ROOT 0x100
#define NFSD_MAY_NOT_BREAK_LEASE 0x200
#define NFSD_MAY_BYPASS_GSS 0x400
#define NFSD_MAY_READ_IF_EXEC 0x800
#define NFSD_MAY_64BIT_COOKIE 0x1000 /* 64 bit readdir cookies for >= NFSv3 */
#define NFSD_MAY_LOCALIO 0x2000 /* for tracing, reflects when localio used */
#define NFSD_MAY_CREATE (NFSD_MAY_EXEC|NFSD_MAY_WRITE)
#define NFSD_MAY_REMOVE (NFSD_MAY_EXEC|NFSD_MAY_WRITE|NFSD_MAY_TRUNC)
struct nfsd_file;
/*
* Callback function for readdir
*/
typedef int (*nfsd_filldir_t)(void *, const char *, int, loff_t, u64, unsigned);
/* nfsd/vfs.c */
struct nfsd_attrs {
struct iattr *na_iattr; /* input */
struct xdr_netobj *na_seclabel; /* input */
struct posix_acl *na_pacl; /* input */
struct posix_acl *na_dpacl; /* input */
int na_labelerr; /* output */
int na_dpaclerr; /* output */
int na_paclerr; /* output */
};
static inline void nfsd_attrs_free(struct nfsd_attrs *attrs)
{
posix_acl_release(attrs->na_pacl);
posix_acl_release(attrs->na_dpacl);
}
static inline bool nfsd_attrs_valid(struct nfsd_attrs *attrs)
{
struct iattr *iap = attrs->na_iattr;
return (iap->ia_valid || (attrs->na_seclabel &&
attrs->na_seclabel->len) ||
attrs->na_pacl || attrs->na_dpacl);
}
__be32 nfserrno (int errno);
int nfsd_cross_mnt(struct svc_rqst *rqstp, struct dentry **dpp,
struct svc_export **expp);
__be32 nfsd_lookup(struct svc_rqst *, struct svc_fh *,
const char *, unsigned int, struct svc_fh *);
__be32 nfsd_lookup_dentry(struct svc_rqst *, struct svc_fh *,
const char *, unsigned int,
struct svc_export **, struct dentry **);
__be32 nfsd_setattr(struct svc_rqst *, struct svc_fh *,
struct nfsd_attrs *, const struct timespec64 *);
int nfsd_mountpoint(struct dentry *, struct svc_export *);
#ifdef CONFIG_NFSD_V4
__be32 nfsd4_vfs_fallocate(struct svc_rqst *, struct svc_fh *,
struct file *, loff_t, loff_t, int);
__be32 nfsd4_clone_file_range(struct svc_rqst *rqstp,
struct nfsd_file *nf_src, u64 src_pos,
struct nfsd_file *nf_dst, u64 dst_pos,
u64 count, bool sync);
#endif /* CONFIG_NFSD_V4 */
__be32 nfsd_create_locked(struct svc_rqst *, struct svc_fh *,
struct nfsd_attrs *attrs, int type, dev_t rdev,
struct svc_fh *res);
__be32 nfsd_create(struct svc_rqst *, struct svc_fh *,
char *name, int len, struct nfsd_attrs *attrs,
int type, dev_t rdev, struct svc_fh *res);
__be32 nfsd_access(struct svc_rqst *, struct svc_fh *, u32 *, u32 *);
__be32 nfsd_create_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct svc_fh *resfhp, struct nfsd_attrs *iap);
__be32 nfsd_commit(struct svc_rqst *rqst, struct svc_fh *fhp,
struct nfsd_file *nf, u64 offset, u32 count,
__be32 *verf);
#ifdef CONFIG_NFSD_V4
__be32 nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
char *name, void **bufp, int *lenp);
__be32 nfsd_listxattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
char **bufp, int *lenp);
__be32 nfsd_removexattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
char *name);
__be32 nfsd_setxattr(struct svc_rqst *rqstp, struct svc_fh *fhp,
char *name, void *buf, u32 len, u32 flags);
#endif
int nfsd_open_break_lease(struct inode *, int);
__be32 nfsd_open(struct svc_rqst *, struct svc_fh *, umode_t,
int, struct file **);
int nfsd_open_verified(struct svc_fh *fhp, umode_t type, int may_flags,
struct file **filp);
__be32 nfsd_splice_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct file *file, loff_t offset,
unsigned long *count,
u32 *eof);
__be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_file *nf, loff_t offset,
unsigned long *count, unsigned int base,
u32 *eof);
bool nfsd_read_splice_ok(struct svc_rqst *rqstp);
__be32 nfsd_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
loff_t offset, unsigned long *count,
u32 *eof);
__be32 nfsd_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
loff_t offset, const struct xdr_buf *payload,
unsigned long *cnt, int stable, __be32 *verf);
__be32 nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_file *nf, loff_t offset,
const struct xdr_buf *payload,
unsigned long *cnt, int stable, __be32 *verf);
__be32 nfsd_readlink(struct svc_rqst *, struct svc_fh *,
char *, int *);
__be32 nfsd_symlink(struct svc_rqst *, struct svc_fh *,
char *name, int len, char *path,
struct nfsd_attrs *attrs,
struct svc_fh *res);
__be32 nfsd_link(struct svc_rqst *, struct svc_fh *,
char *, int, struct svc_fh *);
ssize_t nfsd_copy_file_range(struct file *, u64,
struct file *, u64, u64);
__be32 nfsd_rename(struct svc_rqst *,
struct svc_fh *, char *, int,
struct svc_fh *, char *, int);
__be32 nfsd_unlink(struct svc_rqst *, struct svc_fh *, int type,
char *name, int len);
__be32 nfsd_readdir(struct svc_rqst *, struct svc_fh *,
loff_t *, struct readdir_cd *, nfsd_filldir_t);
__be32 nfsd_statfs(struct svc_rqst *, struct svc_fh *,
struct kstatfs *, int access);
int nfsd_get_case_info(struct dentry *dentry,
bool *case_insensitive,
bool *case_preserving);
__be32 nfsd_permission(struct svc_cred *cred, struct svc_export *exp,
struct dentry *dentry, int acc);
void nfsd_filp_close(struct file *fp);
#endif /* LINUX_NFSD_VFS_H */
+177
View File
@@ -0,0 +1,177 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* XDR types for nfsd. This is mainly a typing exercise. */
#ifndef LINUX_NFSD_H
#define LINUX_NFSD_H
#include <linux/vfs.h>
#include "nfsd.h"
#include "nfsfh.h"
struct nfsd_fhandle {
struct svc_fh fh;
};
struct nfsd_sattrargs {
struct svc_fh fh;
struct iattr attrs;
};
struct nfsd_diropargs {
struct svc_fh fh;
char * name;
unsigned int len;
};
struct nfsd_readargs {
struct svc_fh fh;
__u32 offset;
__u32 count;
};
struct nfsd_writeargs {
svc_fh fh;
__u32 offset;
__u32 len;
struct xdr_buf payload;
};
struct nfsd_createargs {
struct svc_fh fh;
char * name;
unsigned int len;
struct iattr attrs;
};
struct nfsd_renameargs {
struct svc_fh ffh;
char * fname;
unsigned int flen;
struct svc_fh tfh;
char * tname;
unsigned int tlen;
};
struct nfsd_linkargs {
struct svc_fh ffh;
struct svc_fh tfh;
char * tname;
unsigned int tlen;
};
struct nfsd_symlinkargs {
struct svc_fh ffh;
char * fname;
unsigned int flen;
char * tname;
unsigned int tlen;
struct iattr attrs;
struct kvec first;
};
struct nfsd_readdirargs {
struct svc_fh fh;
__u32 cookie;
__u32 count;
};
struct nfsd_stat {
__be32 status;
};
struct nfsd_attrstat {
__be32 status;
struct svc_fh fh;
struct kstat stat;
};
struct nfsd_diropres {
__be32 status;
struct svc_fh fh;
struct kstat stat;
};
struct nfsd_readlinkres {
__be32 status;
int len;
struct page *page;
};
struct nfsd_readres {
__be32 status;
struct svc_fh fh;
unsigned long count;
struct kstat stat;
struct page **pages;
};
struct nfsd_readdirres {
/* Components of the reply */
__be32 status;
int count;
/* Used to encode the reply's entry list */
struct xdr_stream xdr;
struct xdr_buf dirlist;
struct readdir_cd common;
unsigned int cookie_offset;
};
struct nfsd_statfsres {
__be32 status;
struct kstatfs stats;
};
/*
* Storage requirements for XDR arguments and results.
*/
union nfsd_xdrstore {
struct nfsd_sattrargs sattr;
struct nfsd_diropargs dirop;
struct nfsd_readargs read;
struct nfsd_writeargs write;
struct nfsd_createargs create;
struct nfsd_renameargs rename;
struct nfsd_linkargs link;
struct nfsd_symlinkargs symlink;
struct nfsd_readdirargs readdir;
};
#define NFS2_SVC_XDRSIZE sizeof(union nfsd_xdrstore)
bool nfssvc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_attrstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_diropres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_statfsres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfssvc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
void nfssvc_encode_nfscookie(struct nfsd_readdirres *resp, u32 offset);
int nfssvc_encode_entry(void *data, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type);
void nfssvc_release_attrstat(struct svc_rqst *rqstp);
void nfssvc_release_diropres(struct svc_rqst *rqstp);
void nfssvc_release_readres(struct svc_rqst *rqstp);
/* Helper functions for NFSv2 ACL code */
bool svcxdr_decode_fhandle(struct xdr_stream *xdr, struct svc_fh *fhp);
bool svcxdr_encode_stat(struct xdr_stream *xdr, __be32 status);
bool svcxdr_encode_fattr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
const struct svc_fh *fhp, const struct kstat *stat);
#endif /* LINUX_NFSD_H */
+314
View File
@@ -0,0 +1,314 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* XDR types for NFSv3 in nfsd.
*
* Copyright (C) 1996-1998, Olaf Kirch <okir@monad.swb.de>
*/
#ifndef _LINUX_NFSD_XDR3_H
#define _LINUX_NFSD_XDR3_H
#include "xdr.h"
struct nfsd3_sattrargs {
struct svc_fh fh;
struct iattr attrs;
int check_guard;
struct timespec64 guardtime;
};
struct nfsd3_diropargs {
struct svc_fh fh;
char * name;
unsigned int len;
};
struct nfsd3_accessargs {
struct svc_fh fh;
__u32 access;
};
struct nfsd3_readargs {
struct svc_fh fh;
__u64 offset;
__u32 count;
};
struct nfsd3_writeargs {
svc_fh fh;
__u64 offset;
__u32 count;
int stable;
__u32 len;
struct xdr_buf payload;
};
struct nfsd3_createargs {
struct svc_fh fh;
char * name;
unsigned int len;
int createmode;
struct iattr attrs;
__be32 * verf;
};
struct nfsd3_mknodargs {
struct svc_fh fh;
char * name;
unsigned int len;
__u32 ftype;
__u32 major, minor;
struct iattr attrs;
};
struct nfsd3_renameargs {
struct svc_fh ffh;
char * fname;
unsigned int flen;
struct svc_fh tfh;
char * tname;
unsigned int tlen;
};
struct nfsd3_linkargs {
struct svc_fh ffh;
struct svc_fh tfh;
char * tname;
unsigned int tlen;
};
struct nfsd3_symlinkargs {
struct svc_fh ffh;
char * fname;
unsigned int flen;
char * tname;
unsigned int tlen;
struct iattr attrs;
struct kvec first;
};
struct nfsd3_readdirargs {
struct svc_fh fh;
__u64 cookie;
__u32 count;
__be32 * verf;
};
struct nfsd3_commitargs {
struct svc_fh fh;
__u64 offset;
__u32 count;
};
struct nfsd3_getaclargs {
struct svc_fh fh;
__u32 mask;
};
struct posix_acl;
struct nfsd3_setaclargs {
struct svc_fh fh;
__u32 mask;
struct posix_acl *acl_access;
struct posix_acl *acl_default;
};
struct nfsd3_attrstat {
__be32 status;
struct svc_fh fh;
struct kstat stat;
};
/* LOOKUP, CREATE, MKDIR, SYMLINK, MKNOD */
struct nfsd3_diropres {
__be32 status;
struct svc_fh dirfh;
struct svc_fh fh;
};
struct nfsd3_accessres {
__be32 status;
struct svc_fh fh;
__u32 access;
struct kstat stat;
};
struct nfsd3_readlinkres {
__be32 status;
struct svc_fh fh;
__u32 len;
struct page **pages;
};
struct nfsd3_readres {
__be32 status;
struct svc_fh fh;
unsigned long count;
__u32 eof;
struct page **pages;
};
struct nfsd3_writeres {
__be32 status;
struct svc_fh fh;
unsigned long count;
int committed;
__be32 verf[2];
};
struct nfsd3_renameres {
__be32 status;
struct svc_fh ffh;
struct svc_fh tfh;
};
struct nfsd3_linkres {
__be32 status;
struct svc_fh tfh;
struct svc_fh fh;
};
struct nfsd3_readdirres {
/* Components of the reply */
__be32 status;
struct svc_fh fh;
__be32 verf[2];
/* Used to encode the reply's entry list */
struct xdr_stream xdr;
struct xdr_buf dirlist;
struct svc_fh scratch;
struct readdir_cd common;
unsigned int cookie_offset;
struct svc_rqst * rqstp;
};
struct nfsd3_fsstatres {
__be32 status;
struct kstatfs stats;
__u32 invarsec;
};
struct nfsd3_fsinfores {
__be32 status;
__u32 f_rtmax;
__u32 f_rtpref;
__u32 f_rtmult;
__u32 f_wtmax;
__u32 f_wtpref;
__u32 f_wtmult;
__u32 f_dtpref;
__u64 f_maxfilesize;
__u32 f_properties;
};
struct nfsd3_pathconfres {
__be32 status;
__u32 p_link_max;
__u32 p_name_max;
__u32 p_no_trunc;
__u32 p_chown_restricted;
bool p_case_insensitive;
bool p_case_preserving;
};
struct nfsd3_commitres {
__be32 status;
struct svc_fh fh;
__be32 verf[2];
};
struct nfsd3_getaclres {
__be32 status;
struct svc_fh fh;
int mask;
struct posix_acl *acl_access;
struct posix_acl *acl_default;
struct kstat stat;
};
/* dummy type for release */
struct nfsd3_fhandle_pair {
__u32 dummy;
struct svc_fh fh1;
struct svc_fh fh2;
};
/*
* Storage requirements for XDR arguments and results.
*/
union nfsd3_xdrstore {
struct nfsd3_sattrargs sattrargs;
struct nfsd3_diropargs diropargs;
struct nfsd3_readargs readargs;
struct nfsd3_writeargs writeargs;
struct nfsd3_createargs createargs;
struct nfsd3_renameargs renameargs;
struct nfsd3_linkargs linkargs;
struct nfsd3_symlinkargs symlinkargs;
struct nfsd3_readdirargs readdirargs;
struct nfsd3_diropres diropres;
struct nfsd3_accessres accessres;
struct nfsd3_readlinkres readlinkres;
struct nfsd3_readres readres;
struct nfsd3_writeres writeres;
struct nfsd3_renameres renameres;
struct nfsd3_linkres linkres;
struct nfsd3_readdirres readdirres;
struct nfsd3_fsstatres fsstatres;
struct nfsd3_fsinfores fsinfores;
struct nfsd3_pathconfres pathconfres;
struct nfsd3_commitres commitres;
struct nfsd3_getaclres getaclres;
};
#define NFS3_SVC_XDRSIZE sizeof(union nfsd3_xdrstore)
bool nfs3svc_decode_fhandleargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_sattrargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_diropargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_accessargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_readargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_writeargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_createargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_mkdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_mknodargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_renameargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_linkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_symlinkargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_readdirargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_readdirplusargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_decode_commitargs(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_getattrres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_wccstat(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_lookupres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_accessres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_readlinkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_readres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_writeres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_createres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_renameres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_linkres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_readdirres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_fsstatres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_fsinfores(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_pathconfres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
bool nfs3svc_encode_commitres(struct svc_rqst *rqstp, struct xdr_stream *xdr);
void nfs3svc_release_fhandle(struct svc_rqst *);
void nfs3svc_release_fhandle2(struct svc_rqst *);
void nfs3svc_encode_cookie3(struct nfsd3_readdirres *resp, u64 offset);
int nfs3svc_encode_entry3(void *data, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type);
int nfs3svc_encode_entryplus3(void *data, const char *name, int namlen,
loff_t offset, u64 ino, unsigned int d_type);
/* Helper functions for NFSv3 ACL code */
bool svcxdr_decode_nfs_fh3(struct xdr_stream *xdr, struct svc_fh *fhp);
bool svcxdr_encode_nfsstat3(struct xdr_stream *xdr, __be32 status);
bool svcxdr_encode_post_op_attr(struct svc_rqst *rqstp, struct xdr_stream *xdr,
const struct svc_fh *fhp);
#endif /* _LINUX_NFSD_XDR3_H */
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define NFS4_MAXTAGLEN 20
#define NFS4_enc_cb_null_sz 0
#define NFS4_dec_cb_null_sz 0
#define cb_compound_enc_hdr_sz 4
#define cb_compound_dec_hdr_sz (3 + (NFS4_MAXTAGLEN >> 2))
#define sessionid_sz (NFS4_MAX_SESSIONID_LEN >> 2)
#define enc_referring_call4_sz (1 + 1)
#define enc_referring_call_list4_sz (sessionid_sz + 1 + \
enc_referring_call4_sz)
#define cb_sequence_enc_sz (sessionid_sz + 4 + \
enc_referring_call_list4_sz)
#define cb_sequence_dec_sz (op_dec_sz + sessionid_sz + 4)
#define op_enc_sz 1
#define op_dec_sz 2
#define enc_nfs4_fh_sz (1 + (NFS4_FHSIZE >> 2))
#define enc_stateid_sz (NFS4_STATEID_SIZE >> 2)
#define NFS4_enc_cb_recall_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
1 + enc_stateid_sz + \
enc_nfs4_fh_sz)
#define NFS4_dec_cb_recall_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + \
op_dec_sz)
#define NFS4_enc_cb_layout_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
1 + 3 + \
enc_nfs4_fh_sz + 4)
#define NFS4_dec_cb_layout_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + \
op_dec_sz)
#define NFS4_enc_cb_notify_lock_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
2 + 1 + \
XDR_QUADLEN(NFS4_OPAQUE_LIMIT) + \
enc_nfs4_fh_sz)
#define NFS4_dec_cb_notify_lock_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + \
op_dec_sz)
#define enc_cb_offload_info_sz (1 + 1 + 2 + 1 + \
XDR_QUADLEN(NFS4_VERIFIER_SIZE))
#define NFS4_enc_cb_offload_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
enc_nfs4_fh_sz + \
enc_stateid_sz + \
enc_cb_offload_info_sz)
#define NFS4_dec_cb_offload_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + \
op_dec_sz)
#define NFS4_enc_cb_recall_any_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
1 + 1 + 1)
#define NFS4_dec_cb_recall_any_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + \
op_dec_sz)
/*
* 1: CB_GETATTR opcode (32-bit)
* N: file_handle
* 1: number of entry in attribute array (32-bit)
* 3: entry 0-2 in attribute array (32-bit * 3)
*/
#define NFS4_enc_cb_getattr_sz (cb_compound_enc_hdr_sz + \
cb_sequence_enc_sz + \
1 + enc_nfs4_fh_sz + 1 + 3)
/*
* 4: fattr_bitmap_maxsz
* 1: attribute array len
* 2: change attr (64-bit)
* 2: size (64-bit)
* 2: atime.seconds (64-bit)
* 1: atime.nanoseconds (32-bit)
* 2: mtime.seconds (64-bit)
* 1: mtime.nanoseconds (32-bit)
*/
#define NFS4_dec_cb_getattr_sz (cb_compound_dec_hdr_sz + \
cb_sequence_dec_sz + 4 + 1 + 2 + 2 + 2 + 1 + 2 + 1 + op_dec_sz)