Commit bf5344dc authored by Trond Myklebust's avatar Trond Myklebust Committed by Linus Torvalds

[PATCH] A basic NFSv4 client for 2.5.x

Now that all the hooks are in place, this large patch imports all
of the new code for the NFSv4 client.
  nfs4proc.c   - procedure vectors
  nfs4xdr.c    - XDR
  nfs4state.c  - state bookkeeping (very minimal for now)
  nfs4renewd.c - a daemon (implemented as an rpc_task) to keep
                 state from expiring on the server

Note: The RPCSEC_GSS authentication code is not yet included here.
  For the moment we make do with AUTH_UNIX aka. AUTH_SYS.

  Neither is the code to do upcalls to userland in order to do
  uid/gid <-> name mappings. Instead, stubs have been added to
  translate everything to 'nobody:nobody' == '-2:-2'
parent ee17e0d6
......@@ -8,6 +8,7 @@ nfs-y := dir.o file.o flushd.o inode.o nfs2xdr.o pagelist.o \
proc.o read.o symlink.o unlink.o write.o
nfs-$(CONFIG_ROOT_NFS) += nfsroot.o mount_clnt.o
nfs-$(CONFIG_NFS_V3) += nfs3proc.o nfs3xdr.o
nfs-$(CONFIG_NFS_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4renewd.o
nfs-$(CONFIG_NFS_DIRECTIO) += direct.o
nfs-objs := $(nfs-y)
......
......@@ -259,6 +259,12 @@ nfs_lock(struct file *filp, int cmd, struct file_lock *fl)
if (!inode)
return -EINVAL;
/* This will be in a forthcoming patch. */
if (NFS_PROTO(inode)->version == 4) {
printk(KERN_INFO "NFS: file locking over NFSv4 is not yet supported\n");
return -EIO;
}
/* No mandatory locks over NFS */
if ((inode->i_mode & (S_ISGID | S_IXGRP)) == S_ISGID)
return -ENOLCK;
......
......@@ -77,8 +77,13 @@ static struct rpc_version * nfs_version[] = {
NULL,
NULL,
&nfs_version2,
#ifdef CONFIG_NFS_V3
#if defined(CONFIG_NFS_V3)
&nfs_version3,
#elif defined(CONFIG_NFS_V4)
NULL,
#endif
#if defined(CONFIG_NFS_V4)
&nfs_version4,
#endif
};
......
/*
* fs/nfs/nfs4proc.c
*
* Client-side procedure declarations for NFSv4.
*
* Copyright (c) 2002 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.
*/
#include <linux/mm.h>
#include <linux/utsname.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_page.h>
#include <linux/smp_lock.h>
#define NFSDBG_FACILITY NFSDBG_PROC
#define GET_OP(cp,name) &cp->ops[cp->req_nops].u.name
#define OPNUM(cp) cp->ops[cp->req_nops].opnum
extern u32 *nfs4_decode_dirent(u32 *p, struct nfs_entry *entry, int plus);
static nfs4_stateid zero_stateid =
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
static spinlock_t renew_lock = SPIN_LOCK_UNLOCKED;
static void
nfs4_setup_compound(struct nfs4_compound *cp, struct nfs4_op *ops,
struct nfs_server *server, char *tag)
{
memset(cp, 0, sizeof(*cp));
cp->ops = ops;
cp->server = server;
#if NFS4_DEBUG
cp->taglen = strlen(tag);
cp->tag = tag;
#endif
}
static void
nfs4_setup_access(struct nfs4_compound *cp, u32 req_access, u32 *resp_supported, u32 *resp_access)
{
struct nfs4_access *access = GET_OP(cp, access);
access->ac_req_access = req_access;
access->ac_resp_supported = resp_supported;
access->ac_resp_access = resp_access;
OPNUM(cp) = OP_ACCESS;
cp->req_nops++;
}
static void
nfs4_setup_close(struct nfs4_compound *cp, nfs4_stateid stateid, u32 seqid)
{
struct nfs4_close *close = GET_OP(cp, close);
close->cl_stateid = stateid;
close->cl_seqid = seqid;
OPNUM(cp) = OP_CLOSE;
cp->req_nops++;
cp->renew_index = cp->req_nops;
}
static void
nfs4_setup_commit(struct nfs4_compound *cp, u64 start, u32 len, struct nfs_writeverf *verf)
{
struct nfs4_commit *commit = GET_OP(cp, commit);
commit->co_start = start;
commit->co_len = len;
commit->co_verifier = verf;
OPNUM(cp) = OP_COMMIT;
cp->req_nops++;
}
static void
nfs4_setup_create_dir(struct nfs4_compound *cp, struct qstr *name,
struct iattr *sattr, struct nfs4_change_info *info)
{
struct nfs4_create *create = GET_OP(cp, create);
create->cr_ftype = NF4DIR;
create->cr_namelen = name->len;
create->cr_name = name->name;
create->cr_attrs = sattr;
create->cr_cinfo = info;
OPNUM(cp) = OP_CREATE;
cp->req_nops++;
}
static void
nfs4_setup_create_symlink(struct nfs4_compound *cp, struct qstr *name,
struct qstr *linktext, struct iattr *sattr,
struct nfs4_change_info *info)
{
struct nfs4_create *create = GET_OP(cp, create);
create->cr_ftype = NF4LNK;
create->cr_textlen = linktext->len;
create->cr_text = linktext->name;
create->cr_namelen = name->len;
create->cr_name = name->name;
create->cr_attrs = sattr;
create->cr_cinfo = info;
OPNUM(cp) = OP_CREATE;
cp->req_nops++;
}
static void
nfs4_setup_create_special(struct nfs4_compound *cp, struct qstr *name,
dev_t dev, struct iattr *sattr,
struct nfs4_change_info *info)
{
int mode = sattr->ia_mode;
struct nfs4_create *create = GET_OP(cp, create);
BUG_ON(!(sattr->ia_valid & ATTR_MODE));
BUG_ON(!S_ISFIFO(mode) && !S_ISBLK(mode) && !S_ISCHR(mode) && !S_ISSOCK(mode));
if (S_ISFIFO(mode))
create->cr_ftype = NF4FIFO;
else if (S_ISBLK(mode)) {
create->cr_ftype = NF4BLK;
create->cr_specdata1 = MAJOR(dev);
create->cr_specdata2 = MINOR(dev);
}
else if (S_ISCHR(mode)) {
create->cr_ftype = NF4CHR;
create->cr_specdata1 = MAJOR(dev);
create->cr_specdata2 = MINOR(dev);
}
else
create->cr_ftype = NF4SOCK;
create->cr_namelen = name->len;
create->cr_name = name->name;
create->cr_attrs = sattr;
create->cr_cinfo = info;
OPNUM(cp) = OP_CREATE;
cp->req_nops++;
}
/*
* This is our standard bitmap for GETATTR requests.
*/
u32 nfs4_fattr_bitmap[2] = {
FATTR4_WORD0_TYPE
| FATTR4_WORD0_CHANGE
| FATTR4_WORD0_SIZE
| FATTR4_WORD0_FSID
| FATTR4_WORD0_FILEID,
FATTR4_WORD1_MODE
| FATTR4_WORD1_NUMLINKS
| FATTR4_WORD1_OWNER
| FATTR4_WORD1_OWNER_GROUP
| FATTR4_WORD1_RAWDEV
| FATTR4_WORD1_SPACE_USED
| FATTR4_WORD1_TIME_ACCESS
| FATTR4_WORD1_TIME_METADATA
| FATTR4_WORD1_TIME_MODIFY
};
u32 nfs4_statfs_bitmap[2] = {
FATTR4_WORD0_FILES_AVAIL
| FATTR4_WORD0_FILES_FREE
| FATTR4_WORD0_FILES_TOTAL,
FATTR4_WORD1_SPACE_AVAIL
| FATTR4_WORD1_SPACE_FREE
| FATTR4_WORD1_SPACE_TOTAL
};
u32 nfs4_fsinfo_bitmap[2] = {
FATTR4_WORD0_MAXFILESIZE
| FATTR4_WORD0_MAXREAD
| FATTR4_WORD0_MAXWRITE
| FATTR4_WORD0_LEASE_TIME,
0
};
u32 nfs4_pathconf_bitmap[2] = {
FATTR4_WORD0_MAXLINK
| FATTR4_WORD0_MAXNAME,
0
};
/* mount bitmap: fattr bitmap + lease time */
u32 nfs4_mount_bitmap[2] = {
FATTR4_WORD0_TYPE
| FATTR4_WORD0_CHANGE
| FATTR4_WORD0_SIZE
| FATTR4_WORD0_FSID
| FATTR4_WORD0_FILEID
| FATTR4_WORD0_LEASE_TIME,
FATTR4_WORD1_MODE
| FATTR4_WORD1_NUMLINKS
| FATTR4_WORD1_OWNER
| FATTR4_WORD1_OWNER_GROUP
| FATTR4_WORD1_RAWDEV
| FATTR4_WORD1_SPACE_USED
| FATTR4_WORD1_TIME_ACCESS
| FATTR4_WORD1_TIME_METADATA
| FATTR4_WORD1_TIME_MODIFY
};
static inline void
__nfs4_setup_getattr(struct nfs4_compound *cp, u32 *bitmap,
struct nfs_fattr *fattr,
struct nfs_fsstat *fsstat,
struct nfs_fsinfo *fsinfo,
struct nfs_pathconf *pathconf,
u32 *bmres)
{
struct nfs4_getattr *getattr = GET_OP(cp, getattr);
getattr->gt_bmval = bitmap;
getattr->gt_attrs = fattr;
getattr->gt_fsstat = fsstat;
getattr->gt_fsinfo = fsinfo;
getattr->gt_pathconf = pathconf;
getattr->gt_bmres = bmres;
OPNUM(cp) = OP_GETATTR;
cp->req_nops++;
}
static void
nfs4_setup_getattr(struct nfs4_compound *cp,
struct nfs_fattr *fattr,
u32 *bmres)
{
__nfs4_setup_getattr(cp, nfs4_fattr_bitmap, fattr,
NULL, NULL, NULL, bmres);
}
static void
nfs4_setup_getrootattr(struct nfs4_compound *cp,
struct nfs_fattr *fattr,
struct nfs_fsinfo *fsinfo,
u32 *bmres)
{
__nfs4_setup_getattr(cp, nfs4_mount_bitmap,
fattr, NULL, fsinfo, NULL, bmres);
}
static void
nfs4_setup_statfs(struct nfs4_compound *cp,
struct nfs_fsstat *fsstat,
u32 *bmres)
{
__nfs4_setup_getattr(cp, nfs4_statfs_bitmap,
NULL, fsstat, NULL, NULL, bmres);
}
static void
nfs4_setup_fsinfo(struct nfs4_compound *cp,
struct nfs_fsinfo *fsinfo,
u32 *bmres)
{
__nfs4_setup_getattr(cp, nfs4_fsinfo_bitmap,
NULL, NULL, fsinfo, NULL, bmres);
}
static void
nfs4_setup_pathconf(struct nfs4_compound *cp,
struct nfs_pathconf *pathconf,
u32 *bmres)
{
__nfs4_setup_getattr(cp, nfs4_pathconf_bitmap,
NULL, NULL, NULL, pathconf, bmres);
}
static void
nfs4_setup_getfh(struct nfs4_compound *cp, struct nfs_fh *fhandle)
{
struct nfs4_getfh *getfh = GET_OP(cp, getfh);
getfh->gf_fhandle = fhandle;
OPNUM(cp) = OP_GETFH;
cp->req_nops++;
}
static void
nfs4_setup_link(struct nfs4_compound *cp, struct qstr *name,
struct nfs4_change_info *info)
{
struct nfs4_link *link = GET_OP(cp, link);
link->ln_namelen = name->len;
link->ln_name = name->name;
link->ln_cinfo = info;
OPNUM(cp) = OP_LINK;
cp->req_nops++;
}
static void
nfs4_setup_lookup(struct nfs4_compound *cp, struct qstr *q)
{
struct nfs4_lookup *lookup = GET_OP(cp, lookup);
lookup->lo_name = q;
OPNUM(cp) = OP_LOOKUP;
cp->req_nops++;
}
static void
nfs4_setup_putfh(struct nfs4_compound *cp, struct nfs_fh *fhandle)
{
struct nfs4_putfh *putfh = GET_OP(cp, putfh);
putfh->pf_fhandle = fhandle;
OPNUM(cp) = OP_PUTFH;
cp->req_nops++;
}
static void
nfs4_setup_putrootfh(struct nfs4_compound *cp)
{
OPNUM(cp) = OP_PUTROOTFH;
cp->req_nops++;
}
static void
nfs4_setup_open(struct nfs4_compound *cp, int flags, struct qstr *name,
struct iattr *sattr, char *stateid, struct nfs4_change_info *cinfo,
u32 *rflags)
{
struct nfs4_open *open = GET_OP(cp, open);
BUG_ON(cp->flags);
open->op_share_access = flags & 3;
open->op_opentype = (flags & O_CREAT) ? NFS4_OPEN_CREATE : NFS4_OPEN_NOCREATE;
open->op_createmode = NFS4_CREATE_UNCHECKED;
open->op_attrs = sattr;
if (flags & O_EXCL) {
u32 *p = (u32 *) open->op_verifier;
p[0] = jiffies;
p[1] = current->pid;
open->op_createmode = NFS4_CREATE_EXCLUSIVE;
}
open->op_name = name;
open->op_stateid = stateid;
open->op_cinfo = cinfo;
open->op_rflags = rflags;
OPNUM(cp) = OP_OPEN;
cp->req_nops++;
cp->renew_index = cp->req_nops;
}
static void
nfs4_setup_open_confirm(struct nfs4_compound *cp, char *stateid)
{
struct nfs4_open_confirm *open_confirm = GET_OP(cp, open_confirm);
open_confirm->oc_stateid = stateid;
OPNUM(cp) = OP_OPEN_CONFIRM;
cp->req_nops++;
cp->renew_index = cp->req_nops;
}
static void
nfs4_setup_read(struct nfs4_compound *cp, u64 offset, u32 length,
struct page **pages, unsigned int pgbase, u32 *eofp, u32 *bytes_read)
{
struct nfs4_read *read = GET_OP(cp, read);
read->rd_offset = offset;
read->rd_length = length;
read->rd_pages = pages;
read->rd_pgbase = pgbase;
read->rd_eof = eofp;
read->rd_bytes_read = bytes_read;
OPNUM(cp) = OP_READ;
cp->req_nops++;
}
static void
nfs4_setup_readdir(struct nfs4_compound *cp, u64 cookie, u32 *verifier,
struct page **pages, unsigned int bufsize, struct dentry *dentry)
{
u32 *start, *p;
struct nfs4_readdir *readdir = GET_OP(cp, readdir);
BUG_ON(bufsize < 80);
readdir->rd_cookie = (cookie > 2) ? cookie : 0;
memcpy(readdir->rd_req_verifier, verifier, sizeof(nfs4_verifier));
readdir->rd_count = bufsize;
readdir->rd_bmval[0] = FATTR4_WORD0_FILEID;
readdir->rd_bmval[1] = 0;
readdir->rd_pages = pages;
readdir->rd_pgbase = 0;
OPNUM(cp) = OP_READDIR;
cp->req_nops++;
if (cookie >= 2)
return;
/*
* NFSv4 servers do not return entries for '.' and '..'
* Therefore, we fake these entries here. We let '.'
* have cookie 0 and '..' have cookie 1. Note that
* when talking to the server, we always send cookie 0
* instead of 1 or 2.
*/
start = p = (u32 *)kmap(*pages);
if (cookie == 0) {
*p++ = xdr_one; /* next */
*p++ = xdr_zero; /* cookie, first word */
*p++ = xdr_one; /* cookie, second word */
*p++ = xdr_one; /* entry len */
memcpy(p, ".\0\0\0", 4); /* entry */
p++;
*p++ = xdr_one; /* bitmap length */
*p++ = htonl(FATTR4_WORD0_FILEID); /* bitmap */
*p++ = htonl(8); /* attribute buffer length */
p = xdr_encode_hyper(p, NFS_FILEID(dentry->d_inode));
}
*p++ = xdr_one; /* next */
*p++ = xdr_zero; /* cookie, first word */
*p++ = xdr_two; /* cookie, second word */
*p++ = xdr_two; /* entry len */
memcpy(p, "..\0\0", 4); /* entry */
p++;
*p++ = xdr_one; /* bitmap length */
*p++ = htonl(FATTR4_WORD0_FILEID); /* bitmap */
*p++ = htonl(8); /* attribute buffer length */
p = xdr_encode_hyper(p, NFS_FILEID(dentry->d_parent->d_inode));
readdir->rd_pgbase = (char *)p - (char *)start;
readdir->rd_count -= readdir->rd_pgbase;
kunmap(*pages);
}
static void
nfs4_setup_readlink(struct nfs4_compound *cp, int count, struct page **pages)
{
struct nfs4_readlink *readlink = GET_OP(cp, readlink);
readlink->rl_count = count;
readlink->rl_pages = pages;
OPNUM(cp) = OP_READLINK;
cp->req_nops++;
}
static void
nfs4_setup_remove(struct nfs4_compound *cp, struct qstr *name, struct nfs4_change_info *cinfo)
{
struct nfs4_remove *remove = GET_OP(cp, remove);
remove->rm_namelen = name->len;
remove->rm_name = name->name;
remove->rm_cinfo = cinfo;
OPNUM(cp) = OP_REMOVE;
cp->req_nops++;
}
static void
nfs4_setup_rename(struct nfs4_compound *cp, struct qstr *old, struct qstr *new,
struct nfs4_change_info *old_cinfo, struct nfs4_change_info *new_cinfo)
{
struct nfs4_rename *rename = GET_OP(cp, rename);
rename->rn_oldnamelen = old->len;
rename->rn_oldname = old->name;
rename->rn_newnamelen = new->len;
rename->rn_newname = new->name;
rename->rn_src_cinfo = old_cinfo;
rename->rn_dst_cinfo = new_cinfo;
OPNUM(cp) = OP_RENAME;
cp->req_nops++;
}
static void
nfs4_setup_renew(struct nfs4_compound *cp)
{
OPNUM(cp) = OP_RENEW;
cp->req_nops++;
cp->renew_index = cp->req_nops;
}
static void
nfs4_setup_restorefh(struct nfs4_compound *cp)
{
OPNUM(cp) = OP_RESTOREFH;
cp->req_nops++;
}
static void
nfs4_setup_savefh(struct nfs4_compound *cp)
{
OPNUM(cp) = OP_SAVEFH;
cp->req_nops++;
}
static void
nfs4_setup_setattr(struct nfs4_compound *cp, char *stateid, struct iattr *iap)
{
struct nfs4_setattr *setattr = GET_OP(cp, setattr);
setattr->st_stateid = stateid;
setattr->st_iap = iap;
OPNUM(cp) = OP_SETATTR;
cp->req_nops++;
}
static void
nfs4_setup_setclientid(struct nfs4_compound *cp, u32 program, unsigned short port)
{
struct nfs4_setclientid *setclientid = GET_OP(cp, setclientid);
struct nfs_server *server = cp->server;
struct timeval tv;
u32 *p;
do_gettimeofday(&tv);
p = (u32 *)setclientid->sc_verifier;
*p++ = tv.tv_sec;
*p++ = tv.tv_usec;
setclientid->sc_name = server->ip_addr;
sprintf(setclientid->sc_netid, "udp");
sprintf(setclientid->sc_uaddr, "%s.%d.%d", server->ip_addr, port >> 8, port & 255);
setclientid->sc_prog = program;
setclientid->sc_cb_ident = 0;
OPNUM(cp) = OP_SETCLIENTID;
cp->req_nops++;
}
static void
nfs4_setup_setclientid_confirm(struct nfs4_compound *cp)
{
OPNUM(cp) = OP_SETCLIENTID_CONFIRM;
cp->req_nops++;
cp->renew_index = cp->req_nops;
}
static void
nfs4_setup_write(struct nfs4_compound *cp, u64 offset, u32 length, int stable,
struct page **pages, unsigned int pgbase, u32 *bytes_written,
struct nfs_writeverf *verf)
{
struct nfs4_write *write = GET_OP(cp, write);
write->wr_offset = offset;
write->wr_stable_how = stable;
write->wr_len = length;
write->wr_bytes_written = bytes_written;
write->wr_verf = verf;
write->wr_pages = pages;
write->wr_pgbase = pgbase;
OPNUM(cp) = OP_WRITE;
cp->req_nops++;
}
static inline void
process_lease(struct nfs4_compound *cp)
{
struct nfs_server *server;
/*
* Generic lease processing: If this operation contains a
* lease-renewing operation, and it succeeded, update the RENEW time
* in the superblock. Instead of the current time, we use the time
* when the request was sent out. (All we know is that the lease was
* renewed sometime between then and now, and we have to assume the
* worst case.)
*
* Notes:
* (1) renewd doesn't acquire the spinlock when messing with
* server->last_renewal; this is OK since rpciod always runs
* under the BKL.
* (2) cp->timestamp was set at the end of XDR encode.
*/
if (!cp->renew_index)
return;
if (!cp->toplevel_status || cp->resp_nops > cp->renew_index) {
server = cp->server;
spin_lock(&renew_lock);
if (server->last_renewal < cp->timestamp)
server->last_renewal = cp->timestamp;
spin_unlock(&renew_lock);
}
}
static int
nfs4_call_compound(struct nfs4_compound *cp, struct rpc_cred *cred, int flags)
{
int status;
struct rpc_message msg = {
.rpc_proc = NFSPROC4_COMPOUND,
.rpc_argp = cp,
.rpc_resp = cp,
.rpc_cred = cred,
};
status = rpc_call_sync(cp->server->client, &msg, flags);
if (!status)
process_lease(cp);
return status;
}
static inline void
process_cinfo(struct nfs4_change_info *info, struct nfs_fattr *fattr)
{
BUG_ON((fattr->valid & NFS_ATTR_FATTR) == 0);
BUG_ON((fattr->valid & NFS_ATTR_FATTR_V4) == 0);
if (fattr->change_attr == info->after) {
fattr->pre_change_attr = info->before;
fattr->valid |= NFS_ATTR_PRE_CHANGE;
fattr->timestamp = jiffies;
}
}
static int
do_open(struct inode *dir, struct qstr *name, int flags, struct iattr *sattr,
struct nfs_fattr *fattr, struct nfs_fh *fhandle, u32 *seqid, char *stateid)
{
struct nfs4_compound compound;
struct nfs4_op ops[7];
struct nfs4_change_info dir_cinfo;
struct nfs_fattr dir_attr;
u32 dir_bmres[2];
u32 bmres[2];
u32 rflags;
int status;
dir_attr.valid = 0;
fattr->valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "open");
nfs4_setup_putfh(&compound, NFS_FH(dir));
nfs4_setup_savefh(&compound);
nfs4_setup_open(&compound, flags, name, sattr, stateid, &dir_cinfo, &rflags);
nfs4_setup_getattr(&compound, fattr, bmres);
nfs4_setup_getfh(&compound, fhandle);
nfs4_setup_restorefh(&compound);
nfs4_setup_getattr(&compound, &dir_attr, dir_bmres);
if ((status = nfs4_call_compound(&compound, NULL, 0)))
return status;
process_cinfo(&dir_cinfo, &dir_attr);
nfs_refresh_inode(dir, &dir_attr);
if (!(rflags & NFS4_OPEN_RESULT_CONFIRM)) {
*seqid = 1;
return 0;
}
*seqid = 2;
nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "open_confirm");
nfs4_setup_putfh(&compound, fhandle);
nfs4_setup_open_confirm(&compound, stateid);
return nfs4_call_compound(&compound, NULL, 0);
}
static int
do_setattr(struct nfs_server *server, struct nfs_fattr *fattr,
struct nfs_fh *fhandle, struct iattr *sattr, char *stateid)
{
struct nfs4_compound compound;
struct nfs4_op ops[3];
u32 bmres[2];
fattr->valid = 0;
nfs4_setup_compound(&compound, ops, server, "setattr");
nfs4_setup_putfh(&compound, fhandle);
nfs4_setup_setattr(&compound, stateid, sattr);
nfs4_setup_getattr(&compound, fattr, bmres);
return nfs4_call_compound(&compound, NULL, 0);
}
static int
do_close(struct nfs_server *server, struct nfs_fh *fhandle, u32 seqid, char *stateid)
{
struct nfs4_compound compound;
struct nfs4_op ops[2];
nfs4_setup_compound(&compound, ops, server, "close");
nfs4_setup_putfh(&compound, fhandle);
nfs4_setup_close(&compound, stateid, seqid);
return nfs4_call_compound(&compound, NULL, 0);
}
static int
nfs4_proc_get_root(struct nfs_server *server, struct nfs_fh *fhandle,
struct nfs_fattr *fattr)
{
struct nfs4_compound compound;
struct nfs4_op ops[4];
struct nfs_fsinfo fsinfo;
u32 bmres[2];
unsigned char * p;
struct qstr q;
int status;
fattr->valid = 0;
if (!(server->nfs4_state = nfs4_get_client()))
return -ENOMEM;
/*
* SETCLIENTID.
* Until delegations are imported, we don't bother setting the program
* number and port to anything meaningful.
*/
nfs4_setup_compound(&compound, ops, server, "setclientid");
nfs4_setup_setclientid(&compound, 0, 0);
if ((status = nfs4_call_compound(&compound, NULL, 0)))
goto out;
/*
* SETCLIENTID_CONFIRM, plus root filehandle.
* We also get the lease time here.
*/
nfs4_setup_compound(&compound, ops, server, "setclientid_confirm");
nfs4_setup_setclientid_confirm(&compound);
nfs4_setup_putrootfh(&compound);
nfs4_setup_getrootattr(&compound, fattr, &fsinfo, bmres);
nfs4_setup_getfh(&compound, fhandle);
if ((status = nfs4_call_compound(&compound, NULL, 0)))
goto out;
/*
* Now that we have instantiated the clientid and determined
* the lease time, we can initialize the renew daemon for this
* server.
*/
server->lease_time = fsinfo.lease_time * HZ;
if ((status = nfs4_init_renewd(server)))
goto out;
/*
* Now we do a seperate LOOKUP for each component of the mount path.
* The LOOKUPs are done seperately so that we can conveniently
* catch an ERR_WRONGSEC if it occurs along the way...
*/
p = server->mnt_path;
for (;;) {
while (*p == '/')
p++;
if (!*p)
break;
q.name = p;
while (*p && (*p != '/'))
p++;
q.len = p - q.name;
nfs4_setup_compound(&compound, ops, server, "mount");
nfs4_setup_putfh(&compound, fhandle);
nfs4_setup_lookup(&compound, &q);
nfs4_setup_getattr(&compound, fattr, bmres);
nfs4_setup_getfh(&compound, fhandle);
status = nfs4_call_compound(&compound, NULL, 0);
if (!status)
continue;
if (status == -ENOENT) {
printk(KERN_NOTICE "NFS: mount path %s does not exist!\n", server->mnt_path);
printk(KERN_NOTICE "NFS: suggestion: try mounting '/' instead.\n");
}
break;
}
out:
return status;
}
static int
nfs4_proc_getattr(struct inode *inode, struct nfs_fattr *fattr)
{
struct nfs4_compound compound;
struct nfs4_op ops[2];
u32 bmres[2];
fattr->valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "getattr");
nfs4_setup_putfh(&compound, NFS_FH(inode));
nfs4_setup_getattr(&compound, fattr, bmres);
return nfs4_call_compound(&compound, NULL, 0);
}
static int
nfs4_proc_setattr(struct dentry *dentry, struct nfs_fattr *fattr,
struct iattr *sattr)
{
struct inode * inode = dentry->d_inode;
int size_change = sattr->ia_valid & ATTR_SIZE;
struct nfs_fh throwaway_fh;
u32 seqid;
nfs4_stateid stateid;
int status;
fattr->valid = 0;
if (size_change) {
status = do_open(dentry->d_parent->d_inode, &dentry->d_name,
NFS4_SHARE_ACCESS_WRITE, NULL, fattr,
&throwaway_fh, &seqid, stateid);
if (status)
return status;
/*
* Because OPEN is always done by name in nfsv4, it is
* possible that we opened a different file by the same
* name. We can recognize this race condition, but we
* can't do anything about it besides returning an error.
*
* XXX: Should we compare filehandles too, as in
* nfs_find_actor()?
*/
if (fattr->fileid != NFS_FILEID(inode)) {
printk(KERN_WARNING "nfs: raced in setattr, returning -EIO\n");
do_close(NFS_SERVER(inode), NFS_FH(inode), seqid, stateid);
return -EIO;
}
}
else
memcpy(stateid, zero_stateid, sizeof(nfs4_stateid));
status = do_setattr(NFS_SERVER(inode), fattr, NFS_FH(inode), sattr, stateid);
if (size_change)
do_close(NFS_SERVER(inode), NFS_FH(inode), seqid, stateid);
return status;
}
static int
nfs4_proc_lookup(struct inode *dir, struct qstr *name,
struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{
struct nfs4_compound compound;
struct nfs4_op ops[5];
struct nfs_fattr dir_attr;
u32 dir_bmres[2];
u32 bmres[2];
int status;
dir_attr.valid = 0;
fattr->valid = 0;
dprintk("NFS call lookup %s\n", name->name);
nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "lookup");
nfs4_setup_putfh(&compound, NFS_FH(dir));
nfs4_setup_getattr(&compound, &dir_attr, dir_bmres);
nfs4_setup_lookup(&compound, name);
nfs4_setup_getattr(&compound, fattr, bmres);
nfs4_setup_getfh(&compound, fhandle);
status = nfs4_call_compound(&compound, NULL, 0);
dprintk("NFS reply lookup: %d\n", status);
if (status >= 0)
status = nfs_refresh_inode(dir, &dir_attr);
return status;
}
static int
nfs4_proc_access(struct inode *inode, struct rpc_cred *cred, int mode)
{
struct nfs4_compound compound;
struct nfs4_op ops[3];
struct nfs_fattr fattr;
u32 bmres[2];
u32 req_access = 0, resp_supported, resp_access;
int status;
fattr.valid = 0;
/*
* Determine which access bits we want to ask for...
*/
if (mode & MAY_READ)
req_access |= NFS4_ACCESS_READ;
if (S_ISDIR(inode->i_mode)) {
if (mode & MAY_WRITE)
req_access |= NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND | NFS4_ACCESS_DELETE;
if (mode & MAY_EXEC)
req_access |= NFS4_ACCESS_LOOKUP;
}
else {
if (mode & MAY_WRITE)
req_access |= NFS4_ACCESS_MODIFY | NFS4_ACCESS_EXTEND;
if (mode & MAY_EXEC)
req_access |= NFS4_ACCESS_EXECUTE;
}
nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "access");
nfs4_setup_putfh(&compound, NFS_FH(inode));
nfs4_setup_getattr(&compound, &fattr, bmres);
nfs4_setup_access(&compound, req_access, &resp_supported, &resp_access);
status = nfs4_call_compound(&compound, cred, 0);
nfs_refresh_inode(inode, &fattr);
if (!status) {
if (req_access != resp_supported) {
printk(KERN_NOTICE "NFS: server didn't support all access bits!\n");
status = -ENOTSUPP;
}
else if (req_access != resp_access)
status = -EACCES;
}
return status;
}
/*
* TODO: For the time being, we don't try to get any attributes
* along with any of the zero-copy operations READ, READDIR,
* READLINK, WRITE.
*
* In the case of the first three, we want to put the GETATTR
* after the read-type operation -- this is because it is hard
* to predict the length of a GETATTR response in v4, and thus
* align the READ data correctly. This means that the GETATTR
* may end up partially falling into the page cache, and we should
* shift it into the 'tail' of the xdr_buf before processing.
* To do this efficiently, we need to know the total length
* of data received, which doesn't seem to be available outside
* of the RPC layer.
*
* In the case of WRITE, we also want to put the GETATTR after
* the operation -- in this case because we want to make sure
* we get the post-operation mtime and size. This means that
* we can't use xdr_encode_pages() as written: we need a variant
* of it which would leave room in the 'tail' iovec.
*
* Both of these changes to the XDR layer would in fact be quite
* minor, but I decided to leave them for a subsequent patch.
*/
static int
nfs4_proc_readlink(struct inode *inode, struct page *page)
{
struct nfs4_compound compound;
struct nfs4_op ops[2];
nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "readlink");
nfs4_setup_putfh(&compound, NFS_FH(inode));
nfs4_setup_readlink(&compound, PAGE_CACHE_SIZE, &page);
return nfs4_call_compound(&compound, NULL, 0);
}
static int
nfs4_proc_read(struct inode *inode, struct rpc_cred *cred,
struct nfs_fattr *fattr, int flags,
unsigned int base, unsigned int count,
struct page *page, int *eofp)
{
u64 offset = page_offset(page) + base;
struct nfs4_compound compound;
struct nfs4_op ops[2];
u32 bytes_read;
int status;
fattr->valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "read [sync]");
nfs4_setup_putfh(&compound, NFS_FH(inode));
nfs4_setup_read(&compound, offset, count, &page, base, eofp, &bytes_read);
status = nfs4_call_compound(&compound, cred, 0);
if (status >= 0)
status = bytes_read;
return status;
}
static int
nfs4_proc_write(struct inode *inode, struct rpc_cred *cred,
struct nfs_fattr *fattr, int flags,
unsigned int base, unsigned int count,
struct page *page, struct nfs_writeverf *verf)
{
u64 offset = page_offset(page) + base;
struct nfs4_compound compound;
struct nfs4_op ops[2];
u32 bytes_written;
int stable = (flags & NFS_RW_SYNC) ? NFS_FILE_SYNC : NFS_UNSTABLE;
int rpcflags = (flags & NFS_RW_SWAP) ? NFS_RPC_SWAPFLAGS : 0;
int status;
fattr->valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "write [sync]");
nfs4_setup_putfh(&compound, NFS_FH(inode));
nfs4_setup_write(&compound, offset, count, stable, &page, base, &bytes_written, verf);
status = nfs4_call_compound(&compound, cred, rpcflags);
if (status >= 0)
status = bytes_written;
return status;
}
static int
nfs4_proc_create(struct inode *dir, struct qstr *name, struct iattr *sattr,
int flags, struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{
int oflags;
u32 seqid;
nfs4_stateid stateid;
int status;
oflags = NFS4_SHARE_ACCESS_READ | O_CREAT | (flags & O_EXCL);
status = do_open(dir, name, oflags, sattr, fattr, fhandle, &seqid, stateid);
if (!status) {
if (flags & O_EXCL)
status = do_setattr(NFS_SERVER(dir), fattr, fhandle, sattr, stateid);
do_close(NFS_SERVER(dir), fhandle, seqid, stateid);
}
return status;
}
static int
nfs4_proc_remove(struct inode *dir, struct qstr *name)
{
struct nfs4_compound compound;
struct nfs4_op ops[3];
struct nfs4_change_info dir_cinfo;
struct nfs_fattr dir_attr;
u32 dir_bmres[2];
int status;
dir_attr.valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "remove");
nfs4_setup_putfh(&compound, NFS_FH(dir));
nfs4_setup_remove(&compound, name, &dir_cinfo);
nfs4_setup_getattr(&compound, &dir_attr, dir_bmres);
status = nfs4_call_compound(&compound, NULL, 0);
if (!status) {
process_cinfo(&dir_cinfo, &dir_attr);
nfs_refresh_inode(dir, &dir_attr);
}
return status;
}
struct unlink_desc {
struct nfs4_compound compound;
struct nfs4_op ops[3];
struct nfs4_change_info cinfo;
struct nfs_fattr attrs;
};
static int
nfs4_proc_unlink_setup(struct rpc_message *msg, struct dentry *dir, struct qstr *name)
{
struct unlink_desc * up;
struct nfs4_compound * cp;
u32 bmres[2];
up = (struct unlink_desc *) kmalloc(sizeof(*up), GFP_KERNEL);
if (!up)
return -ENOMEM;
cp = &up->compound;
nfs4_setup_compound(cp, up->ops, NFS_SERVER(dir->d_inode), "unlink_setup");
nfs4_setup_putfh(cp, NFS_FH(dir->d_inode));
nfs4_setup_remove(cp, name, &up->cinfo);
nfs4_setup_getattr(cp, &up->attrs, bmres);
msg->rpc_proc = NFSPROC4_COMPOUND;
msg->rpc_argp = cp;
msg->rpc_resp = cp;
return 0;
}
static int
nfs4_proc_unlink_done(struct dentry *dir, struct rpc_task *task)
{
struct rpc_message *msg = &task->tk_msg;
struct unlink_desc *up;
if (msg->rpc_argp) {
up = (struct unlink_desc *) msg->rpc_argp;
process_lease(&up->compound);
process_cinfo(&up->cinfo, &up->attrs);
nfs_refresh_inode(dir->d_inode, &up->attrs);
kfree(up);
msg->rpc_argp = NULL;
}
return 0;
}
static int
nfs4_proc_rename(struct inode *old_dir, struct qstr *old_name,
struct inode *new_dir, struct qstr *new_name)
{
struct nfs4_compound compound;
struct nfs4_op ops[7];
struct nfs4_change_info old_cinfo, new_cinfo;
struct nfs_fattr old_dir_attr, new_dir_attr;
u32 old_dir_bmres[2], new_dir_bmres[2];
int status;
old_dir_attr.valid = 0;
new_dir_attr.valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(old_dir), "rename");
nfs4_setup_putfh(&compound, NFS_FH(old_dir));
nfs4_setup_savefh(&compound);
nfs4_setup_putfh(&compound, NFS_FH(new_dir));
nfs4_setup_rename(&compound, old_name, new_name, &old_cinfo, &new_cinfo);
nfs4_setup_getattr(&compound, &new_dir_attr, new_dir_bmres);
nfs4_setup_restorefh(&compound);
nfs4_setup_getattr(&compound, &old_dir_attr, old_dir_bmres);
status = nfs4_call_compound(&compound, NULL, 0);
if (!status) {
process_cinfo(&old_cinfo, &old_dir_attr);
process_cinfo(&new_cinfo, &new_dir_attr);
nfs_refresh_inode(old_dir, &old_dir_attr);
nfs_refresh_inode(new_dir, &new_dir_attr);
}
return status;
}
static int
nfs4_proc_link(struct inode *inode, struct inode *dir, struct qstr *name)
{
struct nfs4_compound compound;
struct nfs4_op ops[7];
struct nfs4_change_info dir_cinfo;
struct nfs_fattr dir_attr, fattr;
u32 dir_bmres[2], bmres[2];
int status;
dir_attr.valid = 0;
fattr.valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(inode), "link");
nfs4_setup_putfh(&compound, NFS_FH(inode));
nfs4_setup_savefh(&compound);
nfs4_setup_putfh(&compound, NFS_FH(dir));
nfs4_setup_link(&compound, name, &dir_cinfo);
nfs4_setup_getattr(&compound, &dir_attr, dir_bmres);
nfs4_setup_restorefh(&compound);
nfs4_setup_getattr(&compound, &fattr, bmres);
status = nfs4_call_compound(&compound, NULL, 0);
if (!status) {
process_cinfo(&dir_cinfo, &dir_attr);
nfs_refresh_inode(dir, &dir_attr);
nfs_refresh_inode(inode, &fattr);
}
return status;
}
static int
nfs4_proc_symlink(struct inode *dir, struct qstr *name, struct qstr *path,
struct iattr *sattr, struct nfs_fh *fhandle,
struct nfs_fattr *fattr)
{
struct nfs4_compound compound;
struct nfs4_op ops[7];
struct nfs_fattr dir_attr;
u32 dir_bmres[2], bmres[2];
struct nfs4_change_info dir_cinfo;
int status;
dir_attr.valid = 0;
fattr->valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "symlink");
nfs4_setup_putfh(&compound, NFS_FH(dir));
nfs4_setup_savefh(&compound);
nfs4_setup_create_symlink(&compound, name, path, sattr, &dir_cinfo);
nfs4_setup_getattr(&compound, fattr, bmres);
nfs4_setup_getfh(&compound, fhandle);
nfs4_setup_restorefh(&compound);
nfs4_setup_getattr(&compound, &dir_attr, dir_bmres);
status = nfs4_call_compound(&compound, NULL, 0);
if (!status) {
process_cinfo(&dir_cinfo, &dir_attr);
nfs_refresh_inode(dir, &dir_attr);
}
return status;
}
static int
nfs4_proc_mkdir(struct inode *dir, struct qstr *name, struct iattr *sattr,
struct nfs_fh *fhandle, struct nfs_fattr *fattr)
{
struct nfs4_compound compound;
struct nfs4_op ops[7];
struct nfs_fattr dir_attr;
u32 dir_bmres[2], bmres[2];
struct nfs4_change_info dir_cinfo;
int status;
dir_attr.valid = 0;
fattr->valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "mkdir");
nfs4_setup_putfh(&compound, NFS_FH(dir));
nfs4_setup_savefh(&compound);
nfs4_setup_create_dir(&compound, name, sattr, &dir_cinfo);
nfs4_setup_getattr(&compound, fattr, bmres);
nfs4_setup_getfh(&compound, fhandle);
nfs4_setup_restorefh(&compound);
nfs4_setup_getattr(&compound, &dir_attr, dir_bmres);
status = nfs4_call_compound(&compound, NULL, 0);
if (!status) {
process_cinfo(&dir_cinfo, &dir_attr);
nfs_refresh_inode(dir, &dir_attr);
}
return status;
}
static int
nfs4_proc_readdir(struct dentry *dentry, struct rpc_cred *cred,
u64 cookie, struct page *page, unsigned int count, int plus)
{
struct inode *dir = dentry->d_inode;
struct nfs4_compound compound;
struct nfs4_op ops[2];
int status;
lock_kernel();
nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "readdir");
nfs4_setup_putfh(&compound, NFS_FH(dir));
nfs4_setup_readdir(&compound, cookie, NFS_COOKIEVERF(dir), &page, count, dentry);
status = nfs4_call_compound(&compound, cred, 0);
unlock_kernel();
return status;
}
static int
nfs4_proc_mknod(struct inode *dir, struct qstr *name, struct iattr *sattr,
dev_t rdev, struct nfs_fh *fh, struct nfs_fattr *fattr)
{
struct nfs4_compound compound;
struct nfs4_op ops[7];
struct nfs_fattr dir_attr;
u32 dir_bmres[2], bmres[2];
struct nfs4_change_info dir_cinfo;
int status;
dir_attr.valid = 0;
fattr->valid = 0;
nfs4_setup_compound(&compound, ops, NFS_SERVER(dir), "mknod");
nfs4_setup_putfh(&compound, NFS_FH(dir));
nfs4_setup_savefh(&compound);
nfs4_setup_create_special(&compound, name, rdev,sattr, &dir_cinfo);
nfs4_setup_getattr(&compound, fattr, bmres);
nfs4_setup_getfh(&compound, fh);
nfs4_setup_restorefh(&compound);
nfs4_setup_getattr(&compound, &dir_attr, dir_bmres);
status = nfs4_call_compound(&compound, NULL, 0);
if (!status) {
process_cinfo(&dir_cinfo, &dir_attr);
nfs_refresh_inode(dir, &dir_attr);
}
return status;
}
static int
nfs4_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle,
struct nfs_fsstat *fsstat)
{
struct nfs4_compound compound;
struct nfs4_op ops[2];
u32 bmres[2];
memset(fsstat, 0, sizeof(*fsstat));
nfs4_setup_compound(&compound, ops, server, "statfs");
nfs4_setup_putfh(&compound, fhandle);
nfs4_setup_statfs(&compound, fsstat, bmres);
return nfs4_call_compound(&compound, NULL, 0);
}
static int
nfs4_proc_fsinfo(struct nfs_server *server, struct nfs_fh *fhandle,
struct nfs_fsinfo *fsinfo)
{
struct nfs4_compound compound;
struct nfs4_op ops[2];
u32 bmres[2];
memset(fsinfo, 0, sizeof(*fsinfo));
nfs4_setup_compound(&compound, ops, server, "statfs");
nfs4_setup_putfh(&compound, fhandle);
nfs4_setup_fsinfo(&compound, fsinfo, bmres);
return nfs4_call_compound(&compound, NULL, 0);
}
static int
nfs4_proc_pathconf(struct nfs_server *server, struct nfs_fh *fhandle,
struct nfs_pathconf *pathconf)
{
struct nfs4_compound compound;
struct nfs4_op ops[2];
u32 bmres[2];
memset(pathconf, 0, sizeof(*pathconf));
nfs4_setup_compound(&compound, ops, server, "statfs");
nfs4_setup_putfh(&compound, fhandle);
nfs4_setup_pathconf(&compound, pathconf, bmres);
return nfs4_call_compound(&compound, NULL, 0);
}
static void
nfs4_read_done(struct rpc_task *task)
{
struct nfs_read_data *data = (struct nfs_read_data *) task->tk_calldata;
process_lease(&data->u.v4.compound);
nfs_readpage_result(task, data->u.v4.res_count, data->u.v4.res_eof);
}
static void
nfs4_proc_read_setup(struct nfs_read_data *data, unsigned int count)
{
struct rpc_task *task = &data->task;
struct nfs4_compound *cp = &data->u.v4.compound;
struct rpc_message msg = {
.rpc_proc = NFSPROC4_COMPOUND,
.rpc_argp = cp,
.rpc_resp = cp,
.rpc_cred = data->cred,
};
struct inode *inode = data->inode;
struct nfs_page *req = nfs_list_entry(data->pages.next);
int flags;
nfs4_setup_compound(cp, data->u.v4.ops, NFS_SERVER(inode), "read [async]");
nfs4_setup_putfh(cp, NFS_FH(inode));
nfs4_setup_read(cp, req_offset(req) + req->wb_offset,
count, data->pagevec, req->wb_offset,
&data->u.v4.res_eof,
&data->u.v4.res_count);
/* N.B. Do we need to test? Never called for swapfile inode */
flags = RPC_TASK_ASYNC | (IS_SWAPFILE(inode)? NFS_RPC_SWAPFLAGS : 0);
/* Finalize the task. */
rpc_init_task(task, NFS_CLIENT(inode), nfs4_read_done, flags);
task->tk_calldata = data;
/* Release requests */
task->tk_release = nfs_readdata_release;
rpc_call_setup(task, &msg, 0);
}
static void
nfs4_write_done(struct rpc_task *task)
{
struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata;
process_lease(&data->u.v4.compound);
nfs_writeback_done(task, data->u.v4.arg_stable,
data->u.v4.arg_count, data->u.v4.res_count);
}
static void
nfs4_proc_write_setup(struct nfs_write_data *data, unsigned int count, int how)
{
struct rpc_task *task = &data->task;
struct nfs4_compound *cp = &data->u.v4.compound;
struct rpc_message msg = {
.rpc_proc = NFSPROC4_COMPOUND,
.rpc_argp = cp,
.rpc_resp = cp,
.rpc_cred = data->cred,
};
struct inode *inode = data->inode;
struct nfs_page *req = nfs_list_entry(data->pages.next);
int stable;
int flags;
if (how & FLUSH_STABLE) {
if (!NFS_I(inode)->ncommit)
stable = NFS_FILE_SYNC;
else
stable = NFS_DATA_SYNC;
} else
stable = NFS_UNSTABLE;
nfs4_setup_compound(cp, data->u.v4.ops, NFS_SERVER(inode), "write [async]");
nfs4_setup_putfh(cp, NFS_FH(inode));
nfs4_setup_write(cp, req_offset(req) + req->wb_offset,
count, stable, data->pagevec, req->wb_offset,
&data->u.v4.res_count, &data->verf);
/* Set the initial flags for the task. */
flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC;
/* Finalize the task. */
rpc_init_task(task, NFS_CLIENT(inode), nfs4_write_done, flags);
task->tk_calldata = data;
/* Release requests */
task->tk_release = nfs_writedata_release;
rpc_call_setup(task, &msg, 0);
}
static void
nfs4_commit_done(struct rpc_task *task)
{
struct nfs_write_data *data = (struct nfs_write_data *) task->tk_calldata;
process_lease(&data->u.v4.compound);
nfs_commit_done(task);
}
static void
nfs4_proc_commit_setup(struct nfs_write_data *data, u64 start, u32 len, int how)
{
struct rpc_task *task = &data->task;
struct nfs4_compound *cp = &data->u.v4.compound;
struct rpc_message msg = {
.rpc_proc = NFSPROC4_COMPOUND,
.rpc_argp = cp,
.rpc_resp = cp,
.rpc_cred = data->cred,
};
struct inode *inode = data->inode;
int flags;
nfs4_setup_compound(cp, data->u.v4.ops, NFS_SERVER(inode), "commit [async]");
nfs4_setup_putfh(cp, NFS_FH(inode));
nfs4_setup_commit(cp, start, len, &data->verf);
/* Set the initial flags for the task. */
flags = (how & FLUSH_SYNC) ? 0 : RPC_TASK_ASYNC;
/* Finalize the task. */
rpc_init_task(task, NFS_CLIENT(inode), nfs4_commit_done, flags);
task->tk_calldata = data;
/* Release requests */
task->tk_release = nfs_writedata_release;
rpc_call_setup(task, &msg, 0);
}
/*
* nfs4_proc_renew(): This is not one of the nfs_rpc_ops; it is a special
* standalone procedure for queueing an asynchronous RENEW.
*/
struct renew_desc {
struct rpc_task task;
struct nfs4_compound compound;
struct nfs4_op ops[1];
};
static void
renew_done(struct rpc_task *task)
{
struct nfs4_compound *cp = (struct nfs4_compound *) task->tk_msg.rpc_argp;
process_lease(cp);
}
static void
renew_release(struct rpc_task *task)
{
kfree(task->tk_calldata);
task->tk_calldata = NULL;
}
int
nfs4_proc_renew(struct nfs_server *server)
{
struct renew_desc *rp;
struct rpc_task *task;
struct nfs4_compound *cp;
struct rpc_message msg;
rp = (struct renew_desc *) kmalloc(sizeof(*rp), GFP_KERNEL);
if (!rp)
return -ENOMEM;
cp = &rp->compound;
task = &rp->task;
nfs4_setup_compound(cp, rp->ops, server, "renew");
nfs4_setup_renew(cp);
msg.rpc_proc = NFSPROC4_COMPOUND;
msg.rpc_argp = cp;
msg.rpc_resp = cp;
msg.rpc_cred = NULL;
rpc_init_task(task, server->client, renew_done, RPC_TASK_ASYNC);
rpc_call_setup(task, &msg, 0);
task->tk_calldata = rp;
task->tk_release = renew_release;
return rpc_execute(task);
}
struct nfs_rpc_ops nfs_v4_clientops = {
.version = 4, /* protocol version */
.getroot = nfs4_proc_get_root,
.getattr = nfs4_proc_getattr,
.setattr = nfs4_proc_setattr,
.lookup = nfs4_proc_lookup,
.access = nfs4_proc_access,
.readlink = nfs4_proc_readlink,
.read = nfs4_proc_read,
.write = nfs4_proc_write,
.commit = NULL,
.create = nfs4_proc_create,
.remove = nfs4_proc_remove,
.unlink_setup = nfs4_proc_unlink_setup,
.unlink_done = nfs4_proc_unlink_done,
.rename = nfs4_proc_rename,
.link = nfs4_proc_link,
.symlink = nfs4_proc_symlink,
.mkdir = nfs4_proc_mkdir,
.rmdir = nfs4_proc_remove,
.readdir = nfs4_proc_readdir,
.mknod = nfs4_proc_mknod,
.statfs = nfs4_proc_statfs,
.fsinfo = nfs4_proc_fsinfo,
.pathconf = nfs4_proc_pathconf,
.decode_dirent = nfs4_decode_dirent,
.read_setup = nfs4_proc_read_setup,
.write_setup = nfs4_proc_write_setup,
.commit_setup = nfs4_proc_commit_setup,
};
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
/*
* fs/nfs/nfs4renewd.c
*
* Copyright (c) 2002 The Regents of the University of Michigan.
* All rights reserved.
*
* Kendrick Smith <kmsmith@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.
*
* Implementation of the NFSv4 "renew daemon", which wakes up periodically to
* send a RENEW, to keep state alive on the server. The daemon is implemented
* as an rpc_task, not a real kernel thread, so it always runs in rpciod's
* context. There is one renewd per nfs_server.
*
* TODO: If the send queue gets backlogged (e.g., if the server goes down),
* we will keep filling the queue with periodic RENEW requests. We need a
* mechanism for ensuring that if renewd successfully sends off a request,
* then it only wakes up when the request is finished. Maybe use the
* child task framework of the RPC layer?
*/
#include <linux/sched.h>
#include <linux/smp_lock.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/sunrpc/sched.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
static RPC_WAITQ(nfs4_renewd_queue, "nfs4_renewd_queue");
static void
renewd(struct rpc_task *task)
{
struct nfs_server *server = (struct nfs_server *)task->tk_calldata;
unsigned long lease = server->lease_time;
unsigned long last = server->last_renewal;
unsigned long timeout;
if (!server->nfs4_state)
timeout = (2 * lease) / 3;
else if (jiffies < last + lease/3)
timeout = (2 * lease) / 3 + last - jiffies;
else {
/* Queue an asynchronous RENEW. */
nfs4_proc_renew(server);
timeout = (2 * lease) / 3;
}
if (timeout < 5 * HZ) /* safeguard */
timeout = 5 * HZ;
task->tk_timeout = timeout;
task->tk_action = renewd;
task->tk_exit = NULL;
rpc_sleep_on(&nfs4_renewd_queue, task, NULL, NULL);
return;
}
int
nfs4_init_renewd(struct nfs_server *server)
{
struct rpc_task *task;
int status;
lock_kernel();
status = -ENOMEM;
task = rpc_new_task(server->client, NULL, RPC_TASK_ASYNC);
if (!task)
goto out;
task->tk_calldata = server;
task->tk_action = renewd;
status = rpc_execute(task);
out:
unlock_kernel();
return status;
}
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
/*
* fs/nfs/nfs4state.c
*
* Client-side XDR for NFSv4.
*
* Copyright (c) 2002 The Regents of the University of Michigan.
* All rights reserved.
*
* Kendrick Smith <kmsmith@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.
*
* Implementation of the NFSv4 state model. For the time being,
* this is minimal, but will be made much more complex in a
* subsequent patch.
*/
#include <linux/config.h>
#include <linux/slab.h>
#include <linux/nfs_fs.h>
/*
* nfs4_get_client(): returns an empty client structure
* nfs4_put_client(): drops reference to client structure
*
* Since these are allocated/deallocated very rarely, we don't
* bother putting them in a slab cache...
*/
struct nfs4_client *
nfs4_get_client(void)
{
struct nfs4_client *clp;
if ((clp = kmalloc(sizeof(*clp), GFP_KERNEL))) {
atomic_set(&clp->cl_count, 1);
clp->cl_clientid = 0;
INIT_LIST_HEAD(&clp->cl_lockowners);
}
return clp;
}
void
nfs4_put_client(struct nfs4_client *clp)
{
BUG_ON(!clp);
BUG_ON(!atomic_read(&clp->cl_count));
if (atomic_dec_and_test(&clp->cl_count)) {
BUG_ON(!list_empty(&clp->cl_lockowners));
kfree(clp);
}
}
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
/*
* fs/nfs/nfs4xdr.c
*
* Client-side XDR for NFSv4.
*
* Copyright (c) 2002 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.
*/
#include <linux/param.h>
#include <linux/time.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/utsname.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/in.h>
#include <linux/pagemap.h>
#include <linux/proc_fs.h>
#include <linux/kdev_t.h>
#include <linux/sunrpc/clnt.h>
#include <linux/nfs.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
/* Emperically, it seems that the NFS client gets confused if
* cookies larger than this are returned -- presumably a
* signedness issue?
*/
#define COOKIE_MAX 0x7fffffff
#define NFS4_CLIENTID(server) ((server)->nfs4_state->cl_clientid)
#define NFSDBG_FACILITY NFSDBG_XDR
/* Mapping from NFS error code to "errno" error code. */
#define errno_NFSERR_IO EIO
extern int nfs_stat_to_errno(int);
#define NFS4_enc_void_sz 0
#define NFS4_dec_void_sz 0
#define NFS4_enc_compound_sz 1024 /* XXX: large enough? */
#define NFS4_dec_compound_sz 1024 /* XXX: large enough? */
static struct {
unsigned int mode;
unsigned int nfs2type;
} nfs_type2fmt[] = {
{ 0, NFNON },
{ S_IFREG, NFREG },
{ S_IFDIR, NFDIR },
{ S_IFBLK, NFBLK },
{ S_IFCHR, NFCHR },
{ S_IFLNK, NFLNK },
{ S_IFSOCK, NFSOCK },
{ S_IFIFO, NFFIFO },
{ 0, NFNON },
{ 0, NFNON },
};
/*
* START OF "GENERIC" ENCODE ROUTINES.
* These may look a little ugly since they are imported from a "generic"
* set of XDR encode/decode routines which are intended to be shared by
* all of our NFSv4 implementations (OpenBSD, MacOS X...).
*
* If the pain of reading these is too great, it should be a straightforward
* task to translate them into Linux-specific versions which are more
* consistent with the style used in NFSv2/v3...
*/
#define ENCODE_HEAD \
u32 *p;
#define ENCODE_TAIL \
return 0
#define WRITE32(n) *p++ = htonl(n)
#define WRITE64(n) do { \
*p++ = htonl((u32)((n) >> 32)); \
*p++ = htonl((u32)(n)); \
} while (0)
#define WRITEMEM(ptr,nbytes) do { \
p = xdr_writemem(p, ptr, nbytes); \
} while (0)
#define RESERVE_SPACE(nbytes) do { BUG_ON(cp->p + XDR_QUADLEN(nbytes) > cp->end); p = cp->p; } while (0)
#define ADJUST_ARGS() cp->p = p
static inline
u32 *xdr_writemem(u32 *p, const void *ptr, int nbytes)
{
int tmp = XDR_QUADLEN(nbytes);
if (!tmp)
return p;
p[tmp-1] = 0;
memcpy(p, ptr, nbytes);
return p + tmp;
}
/*
* FIXME: The following dummy entries will be replaced once the userland
* upcall gets in...
*/
static int
encode_uid(char *p, uid_t uid)
{
strcpy(p, "nobody");
return 6;
}
/*
* FIXME: The following dummy entries will be replaced once the userland
* upcall gets in...
*/
static int
encode_gid(char *p, gid_t gid)
{
strcpy(p, "nobody");
return 6;
}
static int
encode_attrs(struct nfs4_compound *cp, struct iattr *iap)
{
char owner_name[256];
char owner_group[256];
int owner_namelen = 0;
int owner_grouplen = 0;
u32 *q;
int len;
u32 bmval0 = 0;
u32 bmval1 = 0;
int status;
ENCODE_HEAD;
/*
* We reserve enough space to write the entire attribute buffer at once.
* In the worst-case, this would be
* 12(bitmap) + 4(attrlen) + 8(size) + 4(mode) + 4(atime) + 4(mtime)
* = 36 bytes, plus any contribution from variable-length fields
* such as owner/group/acl's.
*/
len = 36;
/* Sigh */
if (iap->ia_valid & ATTR_UID) {
status = owner_namelen = encode_uid(owner_name, iap->ia_uid);
if (status < 0) {
printk(KERN_WARNING "nfs: couldn't resolve uid %d to string\n",
iap->ia_uid);
goto out;
}
len += XDR_QUADLEN(owner_namelen);
}
if (iap->ia_valid & ATTR_GID) {
status = owner_grouplen = encode_gid(owner_group, iap->ia_gid);
if (status < 0) {
printk(KERN_WARNING "nfs4: couldn't resolve gid %d to string\n",
iap->ia_gid);
goto out;
}
len += XDR_QUADLEN(owner_grouplen);
}
RESERVE_SPACE(len);
/*
* We write the bitmap length now, but leave the bitmap and the attribute
* buffer length to be backfilled at the end of this routine.
*/
WRITE32(2);
q = p;
p += 3;
if (iap->ia_valid & ATTR_SIZE) {
bmval0 |= FATTR4_WORD0_SIZE;
WRITE64(iap->ia_size);
}
if (iap->ia_valid & ATTR_MODE) {
bmval1 |= FATTR4_WORD1_MODE;
WRITE32(iap->ia_mode);
}
if (iap->ia_valid & ATTR_UID) {
bmval1 |= FATTR4_WORD1_OWNER;
WRITE32(owner_namelen);
WRITEMEM(owner_name, owner_namelen);
p += owner_namelen;
}
if (iap->ia_valid & ATTR_GID) {
bmval1 |= FATTR4_WORD1_OWNER_GROUP;
WRITE32(owner_grouplen);
WRITEMEM(owner_group, owner_grouplen);
p += owner_namelen;
}
if (iap->ia_valid & ATTR_ATIME_SET) {
bmval1 |= FATTR4_WORD1_TIME_ACCESS_SET;
WRITE32(NFS4_SET_TO_CLIENT_TIME);
WRITE32(0);
WRITE32(iap->ia_mtime);
WRITE32(0);
}
else if (iap->ia_valid & ATTR_ATIME) {
bmval1 |= FATTR4_WORD1_TIME_ACCESS_SET;
WRITE32(NFS4_SET_TO_SERVER_TIME);
}
if (iap->ia_valid & ATTR_MTIME_SET) {
bmval1 |= FATTR4_WORD1_TIME_MODIFY_SET;
WRITE32(NFS4_SET_TO_CLIENT_TIME);
WRITE32(0);
WRITE32(iap->ia_mtime);
WRITE32(0);
}
else if (iap->ia_valid & ATTR_MTIME) {
bmval1 |= FATTR4_WORD1_TIME_MODIFY_SET;
WRITE32(NFS4_SET_TO_SERVER_TIME);
}
ADJUST_ARGS();
/*
* Now we backfill the bitmap and the attribute buffer length.
*/
len = (char *)p - (char *)q - 12;
*q++ = htonl(bmval0);
*q++ = htonl(bmval1);
*q++ = htonl(len);
status = 0;
out:
return status;
}
static int
encode_access(struct nfs4_compound *cp, struct nfs4_access *access)
{
ENCODE_HEAD;
RESERVE_SPACE(8);
WRITE32(OP_ACCESS);
WRITE32(access->ac_req_access);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_close(struct nfs4_compound *cp, struct nfs4_close *close)
{
ENCODE_HEAD;
RESERVE_SPACE(20);
WRITE32(OP_CLOSE);
WRITE32(close->cl_seqid);
WRITEMEM(close->cl_stateid, sizeof(nfs4_stateid));
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_commit(struct nfs4_compound *cp, struct nfs4_commit *commit)
{
ENCODE_HEAD;
RESERVE_SPACE(16);
WRITE32(OP_COMMIT);
WRITE64(commit->co_start);
WRITE32(commit->co_len);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_create(struct nfs4_compound *cp, struct nfs4_create *create)
{
ENCODE_HEAD;
RESERVE_SPACE(8);
WRITE32(OP_CREATE);
WRITE32(create->cr_ftype);
ADJUST_ARGS();
switch (create->cr_ftype) {
case NF4LNK:
RESERVE_SPACE(4 + create->cr_textlen);
WRITE32(create->cr_textlen);
WRITEMEM(create->cr_text, create->cr_textlen);
ADJUST_ARGS();
break;
case NF4BLK: case NF4CHR:
RESERVE_SPACE(8);
WRITE32(create->cr_specdata1);
WRITE32(create->cr_specdata2);
ADJUST_ARGS();
break;
default:
break;
}
RESERVE_SPACE(4 + create->cr_namelen);
WRITE32(create->cr_namelen);
WRITEMEM(create->cr_name, create->cr_namelen);
ADJUST_ARGS();
return encode_attrs(cp, create->cr_attrs);
}
static int
encode_getattr(struct nfs4_compound *cp, struct nfs4_getattr *getattr)
{
ENCODE_HEAD;
RESERVE_SPACE(16);
WRITE32(OP_GETATTR);
WRITE32(2);
WRITE32(getattr->gt_bmval[0]);
WRITE32(getattr->gt_bmval[1]);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_getfh(struct nfs4_compound *cp)
{
ENCODE_HEAD;
RESERVE_SPACE(4);
WRITE32(OP_GETFH);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_link(struct nfs4_compound *cp, struct nfs4_link *link)
{
ENCODE_HEAD;
RESERVE_SPACE(8 + link->ln_namelen);
WRITE32(OP_LINK);
WRITE32(link->ln_namelen);
WRITEMEM(link->ln_name, link->ln_namelen);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_lookup(struct nfs4_compound *cp, struct nfs4_lookup *lookup)
{
int len = lookup->lo_name->len;
ENCODE_HEAD;
RESERVE_SPACE(8 + len);
WRITE32(OP_LOOKUP);
WRITE32(len);
WRITEMEM(lookup->lo_name->name, len);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_open(struct nfs4_compound *cp, struct nfs4_open *open)
{
static int global_id = 0;
int id = global_id++;
int status;
ENCODE_HEAD;
/* seqid, share_access, share_deny, clientid, ownerlen, owner, opentype */
RESERVE_SPACE(52);
WRITE32(OP_OPEN);
WRITE32(0); /* seqid */
WRITE32(open->op_share_access);
WRITE32(0); /* for us, share_deny== 0 always */
WRITE64(NFS4_CLIENTID(cp->server));
WRITE32(4);
WRITE32(id);
WRITE32(open->op_opentype);
ADJUST_ARGS();
if (open->op_opentype == NFS4_OPEN_CREATE) {
if (open->op_createmode == NFS4_CREATE_EXCLUSIVE) {
RESERVE_SPACE(12);
WRITE32(open->op_createmode);
WRITEMEM(open->op_verifier, sizeof(nfs4_verifier));
ADJUST_ARGS();
}
else if (open->op_attrs) {
RESERVE_SPACE(4);
WRITE32(open->op_createmode);
ADJUST_ARGS();
if ((status = encode_attrs(cp, open->op_attrs)))
return status;
}
else {
RESERVE_SPACE(12);
WRITE32(open->op_createmode);
WRITE32(0);
WRITE32(0);
ADJUST_ARGS();
}
}
RESERVE_SPACE(8 + open->op_name->len);
WRITE32(NFS4_OPEN_CLAIM_NULL);
WRITE32(open->op_name->len);
WRITEMEM(open->op_name->name, open->op_name->len);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_open_confirm(struct nfs4_compound *cp, struct nfs4_open_confirm *open_confirm)
{
ENCODE_HEAD;
/*
* Note: In this "stateless" implementation, the OPEN_CONFIRM
* seqid is always equal to 1.
*/
RESERVE_SPACE(24);
WRITE32(OP_OPEN_CONFIRM);
WRITEMEM(open_confirm->oc_stateid, sizeof(nfs4_stateid));
WRITE32(1);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_putfh(struct nfs4_compound *cp, struct nfs4_putfh *putfh)
{
int len = putfh->pf_fhandle->size;
ENCODE_HEAD;
RESERVE_SPACE(8 + len);
WRITE32(OP_PUTFH);
WRITE32(len);
WRITEMEM(putfh->pf_fhandle->data, len);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_putrootfh(struct nfs4_compound *cp)
{
ENCODE_HEAD;
RESERVE_SPACE(4);
WRITE32(OP_PUTROOTFH);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_read(struct nfs4_compound *cp, struct nfs4_read *read, struct rpc_rqst *req)
{
struct rpc_auth *auth = req->rq_task->tk_auth;
int replen;
ENCODE_HEAD;
RESERVE_SPACE(32);
WRITE32(OP_READ);
WRITE32(0); /* all-zero stateid! */
WRITE32(0);
WRITE32(0);
WRITE32(0);
WRITE64(read->rd_offset);
WRITE32(read->rd_length);
ADJUST_ARGS();
/* set up reply iovec
* toplevel status + taglen + rescount + OP_PUTFH + status
* + OP_READ + status + eof + datalen = 9
*/
replen = (RPC_REPHDRSIZE + auth->au_rslack + 9 + XDR_QUADLEN(cp->taglen)) << 2;
req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
xdr_inline_pages(&req->rq_rcv_buf, replen,
read->rd_pages, read->rd_pgbase, read->rd_length);
ENCODE_TAIL;
}
static int
encode_readdir(struct nfs4_compound *cp, struct nfs4_readdir *readdir, struct rpc_rqst *req)
{
struct rpc_auth *auth = req->rq_task->tk_auth;
int replen;
ENCODE_HEAD;
RESERVE_SPACE(40);
WRITE32(OP_READDIR);
WRITE64(readdir->rd_cookie);
WRITEMEM(readdir->rd_req_verifier, sizeof(nfs4_verifier));
WRITE32(readdir->rd_count >> 5); /* meaningless "dircount" field */
WRITE32(readdir->rd_count);
WRITE32(2);
WRITE32(readdir->rd_bmval[0]);
WRITE32(readdir->rd_bmval[1]);
ADJUST_ARGS();
/* set up reply iovec
* toplevel_status + taglen + rescount + OP_PUTFH + status
* + OP_READDIR + status + verifer(2) = 9
*/
replen = (RPC_REPHDRSIZE + auth->au_rslack + 9 + XDR_QUADLEN(cp->taglen)) << 2;
req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
xdr_inline_pages(&req->rq_rcv_buf, replen, readdir->rd_pages,
readdir->rd_pgbase, readdir->rd_count);
ENCODE_TAIL;
}
static int
encode_readlink(struct nfs4_compound *cp, struct nfs4_readlink *readlink, struct rpc_rqst *req)
{
struct rpc_auth *auth = req->rq_task->tk_auth;
int replen;
ENCODE_HEAD;
RESERVE_SPACE(4);
WRITE32(OP_READLINK);
ADJUST_ARGS();
/* set up reply iovec
* toplevel_status + taglen + rescount + OP_PUTFH + status
* + OP_READLINK + status = 7
*/
replen = (RPC_REPHDRSIZE + auth->au_rslack + 7 + XDR_QUADLEN(cp->taglen)) << 2;
req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
xdr_inline_pages(&req->rq_rcv_buf, replen, readlink->rl_pages, 0, readlink->rl_count);
ENCODE_TAIL;
}
static int
encode_remove(struct nfs4_compound *cp, struct nfs4_remove *remove)
{
ENCODE_HEAD;
RESERVE_SPACE(8 + remove->rm_namelen);
WRITE32(OP_REMOVE);
WRITE32(remove->rm_namelen);
WRITEMEM(remove->rm_name, remove->rm_namelen);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_rename(struct nfs4_compound *cp, struct nfs4_rename *rename)
{
ENCODE_HEAD;
RESERVE_SPACE(8 + rename->rn_oldnamelen);
WRITE32(OP_RENAME);
WRITE32(rename->rn_oldnamelen);
WRITEMEM(rename->rn_oldname, rename->rn_oldnamelen);
ADJUST_ARGS();
RESERVE_SPACE(8 + rename->rn_newnamelen);
WRITE32(rename->rn_newnamelen);
WRITEMEM(rename->rn_newname, rename->rn_newnamelen);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_renew(struct nfs4_compound *cp)
{
ENCODE_HEAD;
RESERVE_SPACE(12);
WRITE32(OP_RENEW);
WRITE64(NFS4_CLIENTID(cp->server));
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_restorefh(struct nfs4_compound *cp)
{
ENCODE_HEAD;
RESERVE_SPACE(4);
WRITE32(OP_RESTOREFH);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_savefh(struct nfs4_compound *cp)
{
ENCODE_HEAD;
RESERVE_SPACE(4);
WRITE32(OP_SAVEFH);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_setattr(struct nfs4_compound *cp, struct nfs4_setattr *setattr)
{
int status;
ENCODE_HEAD;
RESERVE_SPACE(20);
WRITE32(OP_SETATTR);
WRITEMEM(setattr->st_stateid, sizeof(nfs4_stateid));
ADJUST_ARGS();
if ((status = encode_attrs(cp, setattr->st_iap)))
return status;
ENCODE_TAIL;
}
static int
encode_setclientid(struct nfs4_compound *cp, struct nfs4_setclientid *setclientid)
{
u32 total_len;
u32 len1, len2, len3;
ENCODE_HEAD;
len1 = strlen(setclientid->sc_name);
len2 = strlen(setclientid->sc_netid);
len3 = strlen(setclientid->sc_uaddr);
total_len = XDR_QUADLEN(len1) + XDR_QUADLEN(len2) + XDR_QUADLEN(len3);
total_len = (total_len << 2) + 32;
RESERVE_SPACE(total_len);
WRITE32(OP_SETCLIENTID);
WRITEMEM(setclientid->sc_verifier, sizeof(nfs4_verifier));
WRITE32(len1);
WRITEMEM(setclientid->sc_name, len1);
WRITE32(setclientid->sc_prog);
WRITE32(len2);
WRITEMEM(setclientid->sc_netid, len2);
WRITE32(len3);
WRITEMEM(setclientid->sc_uaddr, len3);
WRITE32(setclientid->sc_cb_ident);
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_setclientid_confirm(struct nfs4_compound *cp)
{
ENCODE_HEAD;
RESERVE_SPACE(12 + sizeof(nfs4_verifier));
WRITE32(OP_SETCLIENTID_CONFIRM);
WRITE64(cp->server->nfs4_state->cl_clientid);
WRITEMEM(cp->server->nfs4_state->cl_confirm,sizeof(nfs4_verifier));
ADJUST_ARGS();
ENCODE_TAIL;
}
static int
encode_write(struct nfs4_compound *cp, struct nfs4_write *write, struct rpc_rqst *req)
{
struct xdr_buf *sndbuf = &req->rq_snd_buf;
ENCODE_HEAD;
RESERVE_SPACE(36);
WRITE32(OP_WRITE);
WRITE32(0xffffffff); /* magic stateid -1 */
WRITE32(0xffffffff);
WRITE32(0xffffffff);
WRITE32(0xffffffff);
WRITE64(write->wr_offset);
WRITE32(write->wr_stable_how);
WRITE32(write->wr_len);
ADJUST_ARGS();
sndbuf->len = xdr_adjust_iovec(sndbuf->head, p);
xdr_encode_pages(sndbuf, write->wr_pages, write->wr_pgbase, write->wr_len);
ENCODE_TAIL;
}
static int
encode_compound(struct nfs4_compound *cp, struct rpc_rqst *req)
{
int i, status = 0;
ENCODE_HEAD;
dprintk("encode_compound: tag=%.*s\n", (int)cp->taglen, cp->tag);
RESERVE_SPACE(12 + cp->taglen);
WRITE32(cp->taglen);
WRITEMEM(cp->tag, cp->taglen);
WRITE32(NFS4_MINOR_VERSION);
WRITE32(cp->req_nops);
ADJUST_ARGS();
for (i = 0; i < cp->req_nops; i++) {
switch (cp->ops[i].opnum) {
case OP_ACCESS:
status = encode_access(cp, &cp->ops[i].u.access);
break;
case OP_CLOSE:
status = encode_close(cp, &cp->ops[i].u.close);
break;
case OP_COMMIT:
status = encode_commit(cp, &cp->ops[i].u.commit);
break;
case OP_CREATE:
status = encode_create(cp, &cp->ops[i].u.create);
break;
case OP_GETATTR:
status = encode_getattr(cp, &cp->ops[i].u.getattr);
break;
case OP_GETFH:
status = encode_getfh(cp);
break;
case OP_LINK:
status = encode_link(cp, &cp->ops[i].u.link);
break;
case OP_LOOKUP:
status = encode_lookup(cp, &cp->ops[i].u.lookup);
break;
case OP_OPEN:
status = encode_open(cp, &cp->ops[i].u.open);
break;
case OP_OPEN_CONFIRM:
status = encode_open_confirm(cp, &cp->ops[i].u.open_confirm);
break;
case OP_PUTFH:
status = encode_putfh(cp, &cp->ops[i].u.putfh);
break;
case OP_PUTROOTFH:
status = encode_putrootfh(cp);
break;
case OP_READ:
status = encode_read(cp, &cp->ops[i].u.read, req);
break;
case OP_READDIR:
status = encode_readdir(cp, &cp->ops[i].u.readdir, req);
break;
case OP_READLINK:
status = encode_readlink(cp, &cp->ops[i].u.readlink, req);
break;
case OP_REMOVE:
status = encode_remove(cp, &cp->ops[i].u.remove);
break;
case OP_RENAME:
status = encode_rename(cp, &cp->ops[i].u.rename);
break;
case OP_RENEW:
status = encode_renew(cp);
break;
case OP_RESTOREFH:
status = encode_restorefh(cp);
break;
case OP_SAVEFH:
status = encode_savefh(cp);
break;
case OP_SETATTR:
status = encode_setattr(cp, &cp->ops[i].u.setattr);
break;
case OP_SETCLIENTID:
status = encode_setclientid(cp, &cp->ops[i].u.setclientid);
break;
case OP_SETCLIENTID_CONFIRM:
status = encode_setclientid_confirm(cp);
break;
case OP_WRITE:
status = encode_write(cp, &cp->ops[i].u.write, req);
break;
default:
BUG();
}
if (status)
return status;
}
ENCODE_TAIL;
}
/*
* END OF "GENERIC" ENCODE ROUTINES.
*/
/*
* Encode void argument
*/
static int
nfs4_xdr_enc_void(struct rpc_rqst *req, u32 *p, void *dummy)
{
req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
return 0;
}
/*
* Encode COMPOUND argument
*/
static int
nfs4_xdr_enc_compound(struct rpc_rqst *req, u32 *p, struct nfs4_compound *cp)
{
int status;
struct xdr_buf *sndbuf = &req->rq_snd_buf;
cp->p = p;
cp->end = (u32 *) ((char *)req->rq_svec[0].iov_base + req->rq_svec[0].iov_len);
status = encode_compound(cp, req);
cp->timestamp = jiffies;
if (!status && !sndbuf->page_len)
req->rq_slen = xdr_adjust_iovec(sndbuf->head, cp->p);
return status;
}
/*
* START OF "GENERIC" DECODE ROUTINES.
* These may look a little ugly since they are imported from a "generic"
* set of XDR encode/decode routines which are intended to be shared by
* all of our NFSv4 implementations (OpenBSD, MacOS X...).
*
* If the pain of reading these is too great, it should be a straightforward
* task to translate them into Linux-specific versions which are more
* consistent with the style used in NFSv2/v3...
*/
#define DECODE_HEAD \
u32 *p; \
int status
#define DECODE_TAIL \
status = 0; \
out: \
return status; \
xdr_error: \
printk(KERN_NOTICE "xdr error! (%s:%d)\n", __FILE__, __LINE__); \
status = -EIO; \
goto out
#define READ32(x) (x) = ntohl(*p++)
#define READ64(x) do { \
(x) = (u64)ntohl(*p++) << 32; \
(x) |= ntohl(*p++); \
} while (0)
#define READTIME(x) do { \
p++; \
(x) = (u64)ntohl(*p++) << 32; \
(x) |= ntohl(*p++); \
} while (0)
#define COPYMEM(x,nbytes) do { \
memcpy((x), p, nbytes); \
p += XDR_QUADLEN(nbytes); \
} while (0)
#define READ_BUF(nbytes) do { \
if (nbytes > (u32)((char *)cp->end - (char *)cp->p)) \
goto xdr_error; \
p = cp->p; \
cp->p += XDR_QUADLEN(nbytes); \
} while (0)
/*
* FIXME: The following dummy entry will be replaced once the userland
* upcall gets in...
*/
static int
decode_uid(char *p, u32 len, uid_t *uid)
{
*uid = -2;
return 0;
}
/*
* FIXME: The following dummy entry will be replaced once the userland
* upcall gets in...
*/
static int
decode_gid(char *p, u32 len, gid_t *gid)
{
*gid = -2;
return 0;
}
static int
decode_change_info(struct nfs4_compound *cp, struct nfs4_change_info *cinfo)
{
DECODE_HEAD;
READ_BUF(20);
READ32(cinfo->atomic);
READ64(cinfo->before);
READ64(cinfo->after);
DECODE_TAIL;
}
static int
decode_access(struct nfs4_compound *cp, int nfserr, struct nfs4_access *access)
{
u32 supp, acc;
DECODE_HEAD;
if (!nfserr) {
READ_BUF(8);
READ32(supp);
READ32(acc);
status = -EIO;
if ((supp & ~access->ac_req_access) || (acc & ~supp)) {
printk(KERN_NOTICE "NFS: server returned bad bits in access call!\n");
goto out;
}
*access->ac_resp_supported = supp;
*access->ac_resp_access = acc;
}
DECODE_TAIL;
}
static int
decode_close(struct nfs4_compound *cp, int nfserr, struct nfs4_close *close)
{
DECODE_HEAD;
if (!nfserr) {
READ_BUF(sizeof(nfs4_stateid));
COPYMEM(close->cl_stateid, sizeof(nfs4_stateid));
}
DECODE_TAIL;
}
static int
decode_commit(struct nfs4_compound *cp, int nfserr, struct nfs4_commit *commit)
{
DECODE_HEAD;
if (!nfserr) {
READ_BUF(8);
COPYMEM(commit->co_verifier->verifier, 8);
}
DECODE_TAIL;
}
static int
decode_create(struct nfs4_compound *cp, int nfserr, struct nfs4_create *create)
{
u32 bmlen;
DECODE_HEAD;
if (!nfserr) {
if ((status = decode_change_info(cp, create->cr_cinfo)))
goto out;
READ_BUF(4);
READ32(bmlen);
if (bmlen > 2)
goto xdr_error;
READ_BUF(bmlen << 2);
}
DECODE_TAIL;
}
extern u32 nfs4_fattr_bitmap[2];
extern u32 nfs4_fsinfo_bitmap[2];
extern u32 nfs4_fsstat_bitmap[2];
extern u32 nfs4_pathconf_bitmap[2];
static int
decode_getattr(struct nfs4_compound *cp, int nfserr, struct nfs4_getattr *getattr)
{
struct nfs_fattr *nfp = getattr->gt_attrs;
struct nfs_fsstat *fsstat = getattr->gt_fsstat;
struct nfs_fsinfo *fsinfo = getattr->gt_fsinfo;
struct nfs_pathconf *pathconf = getattr->gt_pathconf;
u32 bmlen;
u32 bmval0 = 0;
u32 bmval1 = 0;
u32 attrlen;
u32 dummy32;
u32 len = 0;
unsigned int type;
int fmode = 0;
DECODE_HEAD;
if (nfserr)
goto success;
READ_BUF(4);
READ32(bmlen);
if (bmlen > 2)
goto xdr_error;
READ_BUF((bmlen << 2) + 4);
if (bmlen > 0)
READ32(bmval0);
if (bmlen > 1)
READ32(bmval1);
READ32(attrlen);
if ((bmval0 & ~getattr->gt_bmval[0]) ||
(bmval1 & ~getattr->gt_bmval[1])) {
dprintk("read_attrs: server returned bad attributes!\n");
goto xdr_error;
}
getattr->gt_bmres[0] = bmval0;
getattr->gt_bmres[1] = bmval1;
/*
* In case the server doesn't return some attributes,
* we initialize them here to some nominal values..
*/
if (nfp) {
nfp->valid = NFS_ATTR_FATTR | NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4;
nfp->nlink = 1;
nfp->timestamp = jiffies;
}
if (fsinfo) {
fsinfo->rtmult = fsinfo->wtmult = 512; /* ??? */
fsinfo->lease_time = 60;
}
if (bmval0 & FATTR4_WORD0_TYPE) {
READ_BUF(4);
len += 4;
READ32(type);
if (type < NF4REG || type > NF4NAMEDATTR) {
dprintk("read_attrs: bad type %d\n", type);
goto xdr_error;
}
nfp->type = nfs_type2fmt[type].nfs2type;
fmode = nfs_type2fmt[type].mode;
dprintk("read_attrs: type=%d\n", (u32)nfp->type);
}
if (bmval0 & FATTR4_WORD0_CHANGE) {
READ_BUF(8);
len += 8;
READ64(nfp->change_attr);
dprintk("read_attrs: changeid=%Ld\n", (u64)nfp->change_attr);
}
if (bmval0 & FATTR4_WORD0_SIZE) {
READ_BUF(8);
len += 8;
READ64(nfp->size);
dprintk("read_attrs: size=%Ld\n", (u64)nfp->size);
}
if (bmval0 & FATTR4_WORD0_FSID) {
READ_BUF(16);
len += 16;
READ64(nfp->fsid_u.nfs4.major);
READ64(nfp->fsid_u.nfs4.minor);
dprintk("read_attrs: fsid=0x%Lx/0x%Lx\n",
nfp->fsid_u.nfs4.major, nfp->fsid_u.nfs4.minor);
}
if (bmval0 & FATTR4_WORD0_LEASE_TIME) {
READ_BUF(4);
len += 4;
READ32(fsinfo->lease_time);
dprintk("read_attrs: lease_time=%d\n", fsinfo->lease_time);
}
if (bmval0 & FATTR4_WORD0_FILEID) {
READ_BUF(8);
len += 8;
READ64(nfp->fileid);
dprintk("read_attrs: fileid=%Ld\n", nfp->fileid);
}
if (bmval0 & FATTR4_WORD0_FILES_AVAIL) {
READ_BUF(8);
len += 8;
READ64(fsstat->afiles);
dprintk("read_attrs: files_avail=0x%Lx\n", fsstat->afiles);
}
if (bmval0 & FATTR4_WORD0_FILES_FREE) {
READ_BUF(8);
len += 8;
READ64(fsstat->ffiles);
dprintk("read_attrs: files_free=0x%Lx\n", fsstat->ffiles);
}
if (bmval0 & FATTR4_WORD0_FILES_TOTAL) {
READ_BUF(8);
len += 8;
READ64(fsstat->tfiles);
dprintk("read_attrs: files_tot=0x%Lx\n", fsstat->tfiles);
}
if (bmval0 & FATTR4_WORD0_MAXFILESIZE) {
READ_BUF(8);
len += 8;
READ64(fsinfo->maxfilesize);
dprintk("read_attrs: maxfilesize=0x%Lx\n", fsinfo->maxfilesize);
}
if (bmval0 & FATTR4_WORD0_MAXLINK) {
READ_BUF(4);
len += 4;
READ32(pathconf->max_link);
dprintk("read_attrs: maxlink=%d\n", pathconf->max_link);
}
if (bmval0 & FATTR4_WORD0_MAXNAME) {
READ_BUF(4);
len += 4;
READ32(pathconf->max_namelen);
dprintk("read_attrs: maxname=%d\n", pathconf->max_namelen);
}
if (bmval0 & FATTR4_WORD0_MAXREAD) {
READ_BUF(8);
len += 8;
READ64(fsinfo->rtmax);
fsinfo->rtpref = fsinfo->dtpref = fsinfo->rtmax;
dprintk("read_attrs: maxread=%d\n", fsinfo->rtmax);
}
if (bmval0 & FATTR4_WORD0_MAXWRITE) {
READ_BUF(8);
len += 8;
READ64(fsinfo->wtmax);
fsinfo->wtpref = fsinfo->wtmax;
dprintk("read_attrs: maxwrite=%d\n", fsinfo->wtmax);
}
if (bmval1 & FATTR4_WORD1_MODE) {
READ_BUF(4);
len += 4;
READ32(dummy32);
nfp->mode = (dummy32 & ~S_IFMT) | fmode;
dprintk("read_attrs: mode=0%o\n", nfp->mode);
}
if (bmval1 & FATTR4_WORD1_NUMLINKS) {
READ_BUF(4);
len += 4;
READ32(nfp->nlink);
dprintk("read_attrs: nlinks=0%o\n", nfp->nlink);
}
if (bmval1 & FATTR4_WORD1_OWNER) {
READ_BUF(4);
len += 4;
READ32(dummy32); /* name length */
if (dummy32 > XDR_MAX_NETOBJ) {
dprintk("read_attrs: name too long!\n");
goto xdr_error;
}
READ_BUF(dummy32);
len += (XDR_QUADLEN(dummy32) << 2);
if ((status = decode_uid((char *)p, dummy32, &nfp->uid))) {
dprintk("read_attrs: gss_get_num failed!\n");
goto out;
}
dprintk("read_attrs: uid=%d\n", (int)nfp->uid);
}
if (bmval1 & FATTR4_WORD1_OWNER_GROUP) {
READ_BUF(4);
len += 4;
READ32(dummy32);
if (dummy32 > XDR_MAX_NETOBJ) {
dprintk("read_attrs: name too long!\n");
goto xdr_error;
}
READ_BUF(dummy32);
len += (XDR_QUADLEN(dummy32) << 2);
if ((status = decode_gid((char *)p, dummy32, &nfp->gid))) {
dprintk("read_attrs: gss_get_num failed!\n");
goto out;
}
dprintk("read_attrs: gid=%d\n", (int)nfp->gid);
}
if (bmval1 & FATTR4_WORD1_RAWDEV) {
READ_BUF(8);
len += 8;
READ32(dummy32);
nfp->rdev = (dummy32 << MINORBITS);
READ32(dummy32);
nfp->rdev |= (dummy32 & MINORMASK);
dprintk("read_attrs: rdev=%d\n", nfp->rdev);
}
if (bmval1 & FATTR4_WORD1_SPACE_AVAIL) {
READ_BUF(8);
len += 8;
READ64(fsstat->abytes);
dprintk("read_attrs: savail=0x%Lx\n", fsstat->abytes);
}
if (bmval1 & FATTR4_WORD1_SPACE_FREE) {
READ_BUF(8);
len += 8;
READ64(fsstat->fbytes);
dprintk("read_attrs: sfree=0x%Lx\n", fsstat->fbytes);
}
if (bmval1 & FATTR4_WORD1_SPACE_TOTAL) {
READ_BUF(8);
len += 8;
READ64(fsstat->tbytes);
dprintk("read_attrs: stotal=0x%Lx\n", fsstat->tbytes);
}
if (bmval1 & FATTR4_WORD1_SPACE_USED) {
READ_BUF(8);
len += 8;
READ64(nfp->du.nfs3.used);
dprintk("read_attrs: sused=0x%Lx\n", nfp->du.nfs3.used);
}
if (bmval1 & FATTR4_WORD1_TIME_ACCESS) {
READ_BUF(12);
len += 12;
READTIME(nfp->atime);
dprintk("read_attrs: atime=%d\n", (int)nfp->atime);
}
if (bmval1 & FATTR4_WORD1_TIME_METADATA) {
READ_BUF(12);
len += 12;
READTIME(nfp->ctime);
dprintk("read_attrs: ctime=%d\n", (int)nfp->ctime);
}
if (bmval1 & FATTR4_WORD1_TIME_MODIFY) {
READ_BUF(12);
len += 12;
READTIME(nfp->mtime);
dprintk("read_attrs: mtime=%d\n", (int)nfp->mtime);
}
if (len != attrlen)
goto xdr_error;
success:
DECODE_TAIL;
}
static int
decode_getfh(struct nfs4_compound *cp, int nfserr, struct nfs4_getfh *getfh)
{
struct nfs_fh *fh = getfh->gf_fhandle;
int len;
DECODE_HEAD;
/* Zero handle first to allow comparisons */
memset(fh, 0, sizeof(*fh));
if (!nfserr) {
READ_BUF(4);
READ32(len);
if (len > NFS_MAXFHSIZE)
goto xdr_error;
fh->size = len;
READ_BUF(len);
COPYMEM(fh->data, len);
}
DECODE_TAIL;
}
static int
decode_link(struct nfs4_compound *cp, int nfserr, struct nfs4_link *link)
{
int status = 0;
if (!nfserr)
status = decode_change_info(cp, link->ln_cinfo);
return status;
}
static int
decode_open(struct nfs4_compound *cp, int nfserr, struct nfs4_open *open)
{
u32 bmlen, delegation_type;
DECODE_HEAD;
if (!nfserr) {
READ_BUF(sizeof(nfs4_stateid));
COPYMEM(open->op_stateid, sizeof(nfs4_stateid));
decode_change_info(cp, open->op_cinfo);
READ_BUF(8);
READ32(*open->op_rflags);
READ32(bmlen);
if (bmlen > 10)
goto xdr_error;
READ_BUF((bmlen << 2) + 4);
p += bmlen;
READ32(delegation_type);
if (delegation_type != NFS4_OPEN_DELEGATE_NONE)
goto xdr_error;
}
DECODE_TAIL;
}
static int
decode_open_confirm(struct nfs4_compound *cp, int nfserr, struct nfs4_open_confirm *open_confirm)
{
DECODE_HEAD;
if (!nfserr) {
READ_BUF(sizeof(nfs4_stateid));
COPYMEM(open_confirm->oc_stateid, sizeof(nfs4_stateid));
}
DECODE_TAIL;
}
static int
decode_read(struct nfs4_compound *cp, int nfserr, struct nfs4_read *read)
{
u32 throwaway;
DECODE_HEAD;
if (!nfserr) {
READ_BUF(8);
if (read->rd_eof)
READ32(*read->rd_eof);
else
READ32(throwaway);
READ32(*read->rd_bytes_read);
if (*read->rd_bytes_read > read->rd_length)
goto xdr_error;
}
DECODE_TAIL;
}
static int
decode_readdir(struct nfs4_compound *cp, int nfserr, struct rpc_rqst *req, struct nfs4_readdir *readdir)
{
struct xdr_buf *rcvbuf = &req->rq_rcv_buf;
struct page *page = *rcvbuf->pages;
unsigned int pglen = rcvbuf->page_len;
u32 *end, *entry;
u32 len, attrlen, word;
int i;
DECODE_HEAD;
if (!nfserr) {
READ_BUF(8);
COPYMEM(readdir->rd_resp_verifier, 8);
BUG_ON(pglen > PAGE_CACHE_SIZE);
p = (u32 *) kmap(page);
end = (u32 *) ((char *)p + pglen + readdir->rd_pgbase);
while (*p++) {
entry = p - 1;
if (p + 3 > end)
goto short_pkt;
p += 2; /* cookie */
len = ntohl(*p++); /* filename length */
if (len > NFS4_MAXNAMLEN) {
printk(KERN_WARNING "NFS: giant filename in readdir (len 0x%x)\n", len);
goto err_unmap;
}
p += XDR_QUADLEN(len);
if (p + 1 > end)
goto short_pkt;
len = ntohl(*p++); /* bitmap length */
if (len > 10) {
printk(KERN_WARNING "NFS: giant bitmap in readdir (len 0x%x)\n", len);
goto err_unmap;
}
if (p + len + 1 > end)
goto short_pkt;
attrlen = 0;
for (i = 0; i < len; i++) {
word = ntohl(*p++);
if (!word)
continue;
else if (i == 0 && word == FATTR4_WORD0_FILEID) {
attrlen = 8;
continue;
}
printk(KERN_WARNING "NFS: unexpected bitmap word in readdir (0x%x)\n", word);
goto err_unmap;
}
if (ntohl(*p++) != attrlen) {
printk(KERN_WARNING "NFS: unexpected attrlen in readdir\n");
goto err_unmap;
}
p += XDR_QUADLEN(attrlen);
if (p + 1 > end)
goto short_pkt;
}
kunmap(page);
}
DECODE_TAIL;
short_pkt:
printk(KERN_NOTICE "NFS: short packet in readdir reply!\n");
/* truncate listing */
kunmap(page);
entry[0] = entry[1] = 0;
return 0;
err_unmap:
kunmap(page);
return -errno_NFSERR_IO;
}
static int
decode_readlink(struct nfs4_compound *cp, int nfserr, struct rpc_rqst *req, struct nfs4_readlink *readlink)
{
struct xdr_buf *rcvbuf = &req->rq_rcv_buf;
u32 *strlen;
u32 len;
char *string;
if (!nfserr) {
/*
* The XDR encode routine has set things up so that
* the link text will be copied directly into the
* buffer. We just have to do overflow-checking,
* and and null-terminate the text (the VFS expects
* null-termination).
*/
strlen = (u32 *) kmap(rcvbuf->pages[0]);
len = ntohl(*strlen);
if (len > PAGE_CACHE_SIZE - 5) {
printk(KERN_WARNING "nfs: server returned giant symlink!\n");
kunmap(rcvbuf->pages[0]);
return -EIO;
}
*strlen = len;
string = (char *)(strlen + 1);
string[len] = '\0';
kunmap(rcvbuf->pages[0]);
}
return 0;
}
static int
decode_remove(struct nfs4_compound *cp, int nfserr, struct nfs4_remove *remove)
{
int status;
status = 0;
if (!nfserr)
status = decode_change_info(cp, remove->rm_cinfo);
return status;
}
static int
decode_rename(struct nfs4_compound *cp, int nfserr, struct nfs4_rename *rename)
{
int status = 0;
if (!nfserr) {
if ((status = decode_change_info(cp, rename->rn_src_cinfo)))
goto out;
if ((status = decode_change_info(cp, rename->rn_dst_cinfo)))
goto out;
}
out:
return status;
}
static int
decode_setattr(struct nfs4_compound *cp)
{
u32 bmlen;
DECODE_HEAD;
READ_BUF(4);
READ32(bmlen);
if (bmlen > 10)
goto xdr_error;
READ_BUF(bmlen << 2);
DECODE_TAIL;
}
static int
decode_setclientid(struct nfs4_compound *cp, int nfserr)
{
DECODE_HEAD;
if (!nfserr) {
READ_BUF(8 + sizeof(nfs4_verifier));
READ64(cp->server->nfs4_state->cl_clientid);
COPYMEM(cp->server->nfs4_state->cl_confirm, sizeof(nfs4_verifier));
}
else if (nfserr == NFSERR_CLID_INUSE) {
u32 len;
/* skip netid string */
READ_BUF(4);
READ32(len);
READ_BUF(len);
/* skip uaddr string */
READ_BUF(4);
READ32(len);
READ_BUF(len);
}
DECODE_TAIL;
}
static int
decode_write(struct nfs4_compound *cp, int nfserr, struct nfs4_write *write)
{
DECODE_HEAD;
if (!nfserr) {
READ_BUF(16);
READ32(*write->wr_bytes_written);
if (*write->wr_bytes_written > write->wr_len)
goto xdr_error;
READ32(write->wr_verf->committed);
COPYMEM(write->wr_verf->verifier, 8);
}
DECODE_TAIL;
}
static int
decode_compound(struct nfs4_compound *cp, struct rpc_rqst *req)
{
u32 taglen;
u32 opnum, nfserr;
DECODE_HEAD;
READ_BUF(8);
READ32(cp->toplevel_status);
READ32(taglen);
/*
* We need this if our zero-copy I/O is going to work. Rumor has
* it that the spec will soon mandate it...
*/
if (taglen != cp->taglen)
dprintk("nfs4: non-conforming server returns tag length mismatch!\n");
READ_BUF(taglen + 4);
p += XDR_QUADLEN(taglen);
READ32(cp->resp_nops);
if (cp->resp_nops > cp->req_nops) {
dprintk("nfs4: resp_nops > req_nops!\n");
goto xdr_error;
}
for (cp->nops = 0; cp->nops < cp->resp_nops; cp->nops++) {
READ_BUF(8);
READ32(opnum);
if (opnum != cp->ops[cp->nops].opnum) {
dprintk("nfs4: operation mismatch!\n");
goto xdr_error;
}
READ32(nfserr);
if (cp->nops == cp->resp_nops - 1) {
if (nfserr != cp->toplevel_status) {
dprintk("nfs4: status mismatch!\n");
goto xdr_error;
}
}
else if (nfserr) {
dprintk("nfs4: intermediate status nonzero!\n");
goto xdr_error;
}
cp->ops[cp->nops].nfserr = nfserr;
switch (opnum) {
case OP_ACCESS:
status = decode_access(cp, nfserr, &cp->ops[cp->nops].u.access);
break;
case OP_CLOSE:
status = decode_close(cp, nfserr, &cp->ops[cp->nops].u.close);
break;
case OP_COMMIT:
status = decode_commit(cp, nfserr, &cp->ops[cp->nops].u.commit);
break;
case OP_CREATE:
status = decode_create(cp, nfserr, &cp->ops[cp->nops].u.create);
break;
case OP_GETATTR:
status = decode_getattr(cp, nfserr, &cp->ops[cp->nops].u.getattr);
break;
case OP_GETFH:
status = decode_getfh(cp, nfserr, &cp->ops[cp->nops].u.getfh);
break;
case OP_LINK:
status = decode_link(cp, nfserr, &cp->ops[cp->nops].u.link);
break;
case OP_LOOKUP:
status = 0;
break;
case OP_OPEN:
status = decode_open(cp, nfserr, &cp->ops[cp->nops].u.open);
break;
case OP_OPEN_CONFIRM:
status = decode_open_confirm(cp, nfserr, &cp->ops[cp->nops].u.open_confirm);
break;
case OP_PUTFH:
status = 0;
break;
case OP_PUTROOTFH:
status = 0;
break;
case OP_READ:
status = decode_read(cp, nfserr, &cp->ops[cp->nops].u.read);
break;
case OP_READDIR:
status = decode_readdir(cp, nfserr, req, &cp->ops[cp->nops].u.readdir);
break;
case OP_READLINK:
status = decode_readlink(cp, nfserr, req, &cp->ops[cp->nops].u.readlink);
break;
case OP_RESTOREFH:
status = 0;
break;
case OP_REMOVE:
status = decode_remove(cp, nfserr, &cp->ops[cp->nops].u.remove);
break;
case OP_RENAME:
status = decode_rename(cp, nfserr, &cp->ops[cp->nops].u.rename);
break;
case OP_RENEW:
status = 0;
break;
case OP_SAVEFH:
status = 0;
break;
case OP_SETATTR:
status = decode_setattr(cp);
break;
case OP_SETCLIENTID:
status = decode_setclientid(cp, nfserr);
break;
case OP_SETCLIENTID_CONFIRM:
status = 0;
break;
case OP_WRITE:
status = decode_write(cp, nfserr, &cp->ops[cp->nops].u.write);
break;
default:
BUG();
return -EIO;
}
if (status)
goto xdr_error;
}
DECODE_TAIL;
}
/*
* END OF "GENERIC" DECODE ROUTINES.
*/
/*
* Decode void reply
*/
static int
nfs4_xdr_dec_void(struct rpc_rqst *req, u32 *p, void *dummy)
{
return 0;
}
/*
* Decode COMPOUND response
*/
static int
nfs4_xdr_dec_compound(struct rpc_rqst *rqstp, u32 *p, struct nfs4_compound *cp)
{
int status;
cp->p = p;
cp->end = (u32 *) ((u8 *) rqstp->rq_rvec->iov_base + rqstp->rq_rvec->iov_len);
if ((status = decode_compound(cp, rqstp)))
goto out;
status = 0;
if (cp->toplevel_status)
status = -nfs_stat_to_errno(cp->toplevel_status);
out:
return status;
}
u32 *
nfs4_decode_dirent(u32 *p, struct nfs_entry *entry, int plus)
{
u32 len;
if (!*p++) {
if (!*p)
return ERR_PTR(-EAGAIN);
entry->eof = 1;
return ERR_PTR(-EBADCOOKIE);
}
entry->prev_cookie = entry->cookie;
p = xdr_decode_hyper(p, &entry->cookie);
entry->len = ntohl(*p++);
entry->name = (const char *) p;
p += XDR_QUADLEN(entry->len);
if (entry->cookie > COOKIE_MAX)
entry->cookie = COOKIE_MAX;
/*
* In case the server doesn't return an inode number,
* we fake one here. (We don't use inode number 0,
* since glibc seems to choke on it...)
*/
entry->ino = 1;
len = ntohl(*p++); /* bitmap length */
p += len;
len = ntohl(*p++); /* attribute buffer length */
if (len)
p = xdr_decode_hyper(p, &entry->ino);
entry->eof = !p[0] && p[1];
return p;
}
#ifndef MAX
# define MAX(a, b) (((a) > (b))? (a) : (b))
#endif
#define PROC(proc, argtype, restype) \
{ "nfs4_" #proc, \
(kxdrproc_t) nfs4_xdr_##argtype, \
(kxdrproc_t) nfs4_xdr_##restype, \
MAX(NFS4_##argtype##_sz,NFS4_##restype##_sz) << 2, \
0 \
}
static struct rpc_procinfo nfs4_procedures[] = {
PROC(null, enc_void, dec_void),
PROC(compound, enc_compound, dec_compound)
};
struct rpc_version nfs_version4 = {
.number = 4,
.nrprocs = sizeof(nfs4_procedures)/sizeof(nfs4_procedures[0]),
.procs = nfs4_procedures
};
/*
* Local variables:
* c-basic-offset: 8
* End:
*/
......@@ -472,6 +472,25 @@ extern void * nfs_root_data(void);
#define NFS_JUKEBOX_RETRY_TIME (5 * HZ)
#ifdef CONFIG_NFS_V4
struct nfs4_client {
atomic_t cl_count; /* refcount */
u64 cl_clientid; /* constant */
nfs4_verifier cl_confirm;
/*
* Starts a list of lockowners, linked through lo_list.
*/
struct list_head cl_lockowners; /* protected by state_spinlock */
};
/* nfs4proc.c */
extern int nfs4_proc_renew(struct nfs_server *server);
/* nfs4renewd.c */
extern int nfs4_init_renewd(struct nfs_server *server);
#endif /* CONFIG_NFS_V4 */
#ifdef CONFIG_NFS_V4
extern struct nfs4_client *nfs4_get_client(void);
......
......@@ -325,6 +325,219 @@ struct nfs3_readdirres {
int plus;
};
#ifdef CONFIG_NFS_V4
typedef u64 clientid4;
struct nfs4_change_info {
u32 atomic;
u64 before;
u64 after;
};
struct nfs4_access {
u32 ac_req_access; /* request */
u32 * ac_resp_supported; /* response */
u32 * ac_resp_access; /* response */
};
struct nfs4_close {
char * cl_stateid; /* request */
u32 cl_seqid; /* request */
};
struct nfs4_commit {
u64 co_start; /* request */
u32 co_len; /* request */
struct nfs_writeverf * co_verifier; /* response */
};
struct nfs4_create {
u32 cr_ftype; /* request */
union { /* request */
struct {
u32 textlen;
const char * text;
} symlink; /* NF4LNK */
struct {
u32 specdata1;
u32 specdata2;
} device; /* NF4BLK, NF4CHR */
} u;
u32 cr_namelen; /* request */
const char * cr_name; /* request */
struct iattr * cr_attrs; /* request */
struct nfs4_change_info * cr_cinfo; /* response */
};
#define cr_textlen u.symlink.textlen
#define cr_text u.symlink.text
#define cr_specdata1 u.device.specdata1
#define cr_specdata2 u.device.specdata2
struct nfs4_getattr {
u32 * gt_bmval; /* request */
struct nfs_fattr * gt_attrs; /* response */
struct nfs_fsstat * gt_fsstat; /* response */
struct nfs_fsinfo * gt_fsinfo; /* response */
struct nfs_pathconf * gt_pathconf; /* response */
u32 * gt_bmres; /* response */
};
struct nfs4_getfh {
struct nfs_fh * gf_fhandle; /* response */
};
struct nfs4_link {
u32 ln_namelen; /* request */
const char * ln_name; /* request */
struct nfs4_change_info * ln_cinfo; /* response */
};
struct nfs4_lookup {
struct qstr * lo_name; /* request */
};
struct nfs4_open {
u32 op_share_access; /* request */
u32 op_opentype; /* request */
u32 op_createmode; /* request */
union { /* request */
struct iattr * attrs; /* UNCHECKED, GUARDED */
nfs4_verifier verifier; /* EXCLUSIVE */
} u;
struct qstr * op_name; /* request */
char * op_stateid; /* response */
struct nfs4_change_info * op_cinfo; /* response */
u32 * op_rflags; /* response */
};
#define op_attrs u.attrs
#define op_verifier u.verifier
struct nfs4_open_confirm {
char * oc_stateid; /* request */
};
struct nfs4_putfh {
struct nfs_fh * pf_fhandle; /* request */
};
struct nfs4_read {
u64 rd_offset; /* request */
u32 rd_length; /* request */
u32 *rd_eof; /* response */
u32 *rd_bytes_read; /* response */
struct page ** rd_pages; /* zero-copy data */
unsigned int rd_pgbase; /* zero-copy data */
};
struct nfs4_readdir {
u64 rd_cookie; /* request */
nfs4_verifier rd_req_verifier; /* request */
u32 rd_count; /* request */
u32 rd_bmval[2]; /* request */
nfs4_verifier rd_resp_verifier; /* response */
struct page ** rd_pages; /* zero-copy data */
unsigned int rd_pgbase; /* zero-copy data */
};
struct nfs4_readlink {
u32 rl_count; /* zero-copy data */
struct page ** rl_pages; /* zero-copy data */
};
struct nfs4_remove {
u32 rm_namelen; /* request */
const char * rm_name; /* request */
struct nfs4_change_info * rm_cinfo; /* response */
};
struct nfs4_rename {
u32 rn_oldnamelen; /* request */
const char * rn_oldname; /* request */
u32 rn_newnamelen; /* request */
const char * rn_newname; /* request */
struct nfs4_change_info * rn_src_cinfo; /* response */
struct nfs4_change_info * rn_dst_cinfo; /* response */
};
struct nfs4_setattr {
char * st_stateid; /* request */
struct iattr * st_iap; /* request */
};
struct nfs4_setclientid {
nfs4_verifier sc_verifier; /* request */
char * sc_name; /* request */
u32 sc_prog; /* request */
char sc_netid[4]; /* request */
char sc_uaddr[24]; /* request */
u32 sc_cb_ident; /* request */
};
struct nfs4_write {
u64 wr_offset; /* request */
u32 wr_stable_how; /* request */
u32 wr_len; /* request */
u32 * wr_bytes_written; /* response */
struct nfs_writeverf * wr_verf; /* response */
struct page ** wr_pages; /* zero-copy data */
unsigned int wr_pgbase; /* zero-copy data */
};
struct nfs4_op {
u32 opnum;
u32 nfserr;
union {
struct nfs4_access access;
struct nfs4_close close;
struct nfs4_commit commit;
struct nfs4_create create;
struct nfs4_getattr getattr;
struct nfs4_getfh getfh;
struct nfs4_link link;
struct nfs4_lookup lookup;
struct nfs4_open open;
struct nfs4_open_confirm open_confirm;
struct nfs4_putfh putfh;
struct nfs4_read read;
struct nfs4_readdir readdir;
struct nfs4_readlink readlink;
struct nfs4_remove remove;
struct nfs4_rename rename;
struct nfs4_setattr setattr;
struct nfs4_setclientid setclientid;
struct nfs4_write write;
} u;
};
struct nfs4_compound {
unsigned int flags; /* defined below */
struct nfs_server * server;
/* RENEW information */
int renew_index;
unsigned long timestamp;
/* scratch variables for XDR encode/decode */
int nops;
u32 * p;
u32 * end;
/* the individual COMPOUND operations */
struct nfs4_op *ops;
/* request */
int req_nops;
u32 taglen;
char * tag;
/* response */
int resp_nops;
int toplevel_status;
};
#endif /* CONFIG_NFS_V4 */
struct nfs_read_data {
struct rpc_task task;
struct inode *inode;
......@@ -338,7 +551,12 @@ struct nfs_read_data {
struct nfs_readres res;
} v3; /* also v2 */
#ifdef CONFIG_NFS_V4
/* NFSv4 data will come here... */
struct {
struct nfs4_compound compound;
struct nfs4_op ops[3];
u32 res_count;
u32 res_eof;
} v4;
#endif
} u;
};
......@@ -353,11 +571,17 @@ struct nfs_write_data {
struct page *pagevec[NFS_WRITE_MAXIOV];
union {
struct {
struct nfs_writeargs args;
struct nfs_writeres res;
struct nfs_writeargs args; /* argument struct */
struct nfs_writeres res; /* result struct */
} v3;
#ifdef CONFIG_NFS_V4
/* NFSv4 data to come here... */
struct {
struct nfs4_compound compound;
struct nfs4_op ops[3];
u32 arg_count;
u32 arg_stable;
u32 res_count;
} v4;
#endif
} u;
};
......@@ -430,8 +654,10 @@ struct nfs_rpc_ops {
*/
extern struct nfs_rpc_ops nfs_v2_clientops;
extern struct nfs_rpc_ops nfs_v3_clientops;
extern struct nfs_rpc_ops nfs_v4_clientops;
extern struct rpc_version nfs_version2;
extern struct rpc_version nfs_version3;
extern struct rpc_version nfs_version4;
extern struct rpc_program nfs_program;
extern struct rpc_stat nfs_rpcstat;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment