修改编解码函数,在客户端执行链接操作后,能在日志中打印出f_blocks、f_bfree、f_bavail、f_files、f_ffree信息。
过程上图是客户端和服务器之间发送rpc的简单流程图,下面结合statfs函数,具体解释发送rpc过程中的编解码过程(以下顺序是按照实际编解码的过程)。
Client端:
操作函数:
static int nfs4_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle, struct nfs_fsstat *fsstat)
{
struct nfs4_exception exception = { };
int err;
do {
err = nfs4_handle_exception(server,
_nfs4_proc_statfs(server, fhandle, fsstat),
&exception);
} while (exception.retry);
return err;
}
static int _nfs4_proc_statfs(struct nfs_server *server, struct nfs_fh *fhandle,
struct nfs_fsstat *fsstat)
{
struct nfs4_statfs_arg args = {
.fh = fhandle,
.bitmask = server->attr_bitmask,
};
struct nfs4_statfs_res res = {
.fsstat = fsstat,
};
struct rpc_message msg = {
.rpc_proc = &nfs4_procedures[NFSPROC4_CLNT_STATFS],
.rpc_argp = &args,
.rpc_resp = &res,
};
nfs_fattr_init(fsstat->fattr);
return nfs4_call_sync(server, &msg, &args, &res, 0);
}
(1)rpc_message_msg是定义与rpc报文相关的结构体。
rpc_proc是例程;
rpc_argp保存的是将要被编码,然后通过rpc发到server端的数据;
rpc_resp保存的是服务器端编码后通过发过来的数据;
args和res的定义在上面
(2)nfs4_call_sync是发送rpc的函数
编码函数:
static int nfs4_xdr_enc_statfs(struct rpc_rqst *req, __be32 *p, const struct nfs4_statfs_arg *args)
{
struct xdr_stream xdr;
struct compound_hdr hdr = {
.minorversion = nfs4_xdr_minorversion(&args->seq_args),
};
xdr_init_encode(&xdr, &req->rq_snd_buf, p);
encode_compound_hdr(&xdr, req, &hdr);
encode_sequence(&xdr, &args->seq_args, &hdr);
encode_putfh(&xdr, args->fh, &hdr);
encode_getattr_two(&xdr, args->bitmask[0] & nfs4_statfs_bitmap[0],
args->bitmask[1] & nfs4_statfs_bitmap[1], &hdr);
encode_nops(&hdr);
return 0;
}
编码函数里包含很多其他操作的编码函数,compound、sequence、putfh,这些在link操作里也是有的,应该是所有操作都要有这些通用的编码函数。
encode_getattr_two是与statfs操作直接相关的编码函数。xdr:存放编码后的数据;第二个和第三个参数:statfs操作想传递给服务器端的数据,是一个标志位。
static void encode_getattr_two(struct xdr_stream *xdr, uint32_t bm0, uint32_t bm1, struct compound_hdr *hdr)
{
__be32 *p;
p = reserve_space(xdr, 16);
*p++ = cpu_to_be32(OP_GETATTR);
*p++ = cpu_to_be32(2);
*p++ = cpu_to_be32(bm0);
*p = cpu_to_be32(bm1);
hdr->nops++;
hdr->replen += decode_getattr_maxsz;
}
reserve_space:在xdr中申请16字节的空间,p指向该空间的起始地址;cpu_to_be32:转换成32位的数据;
在xdr中,写入四种数据:操作名称、2、两个标志位。第一个数据——操作名称,任何操作的编码函数都要写,后几个数据是该操作想传递给server端的信息、不同的操作是不一样的。
Server端:
解码函数:
解码的顺序会按照编码的顺序进行,编码顺序是:encode_compound_hdr、encode_sequence、encode_putfh、encode_getattr_two。当然,与statfs相关的是最后一个getattr的解码函数。
nfsd4_decode_putfh(struct nfsd4_compoundargs *argp, struct nfsd4_putfh *putfh)
{
DECODE_HEAD;
READ_BUF(4);
READ32(putfh->pf_fhlen);
if (putfh->pf_fhlen > NFS4_FHSIZE)
goto xdr_error;
READ_BUF(putfh->pf_fhlen);
SAVEMEM(putfh->pf_fhval, putfh->pf_fhlen);
DECODE_TAIL;
}
nfsd4_decode_getattr(struct nfsd4_compoundargs *argp, struct nfsd4_getattr *getattr)
{
return nfsd4_decode_bitmap(argp, getattr->ga_bmval);
}
拿putfh的解码函数对比,发现第一个参数是相同的,第二个参数一个是"struct nfsd4_putfh"、一个是"struct nfsd4_getattr",即因操作不同而不同。
nfsd4_decode_getattr要做的是把客户端传过来的数据解码,当时传过来的是操作名、2、两个标志位,两个标志位是在ga_bmval中
nfsd4_decode_bitmap(struct nfsd4_compoundargs *argp, u32 *bmval)
{
u32 bmlen;
DECODE_HEAD;
bmval[0] = 0;
bmval[1] = 0;
bmval[2] = 0;
READ_BUF(4);
READ32(bmlen);
if (bmlen > 1000)
goto xdr_error;
READ_BUF(bmlen << 2);
if (bmlen > 0)
READ32(bmval[0]);
if (bmlen > 1)
READ32(bmval[1]);
if (bmlen > 2)
READ32(bmval[2]);
DECODE_TAIL;
}
READ32:以32位的形式读数据。可以看出,解码就是以一定的形式读数据。
操作函数:
remove操作,从客户端会传过来:父目录的文件句柄和要删除的文件名。服务器端通过解码获得信息后,在操作函数中会进行操作,把文件删除。对于statfs来说,操作函数要做的就是获取文件系统信息了。
nfsd_proc_statfs(struct svc_rqst * rqstp, struct nfsd_fhandle *argp,
struct nfsd_statfsres *resp)
{
__be32 nfserr;
dprintk("nfsd: STATFS %s\n", SVCFH_fmt(&argp->fh));
nfserr = nfsd_statfs(rqstp, &argp->fh, &resp->stats,
NFSD_MAY_BYPASS_GSS_ON_ROOT);
fh_put(&argp->fh);
return nfserr;
}
形参nfsd_statfsres *resp存放结果信息,对应了客户端操作函数里定义的“.rpc_resp = &res”
argp->fh:当前文件系统的句柄
nfsd_statfs(struct svc_rqst *rqstp, struct svc_fh *fhp, struct kstatfs *stat, int access)
{
__be32 err = fh_verify(rqstp, fhp, 0, NFSD_MAY_NOP | access);
if (!err && vfs_statfs(fhp->fh_dentry,stat))
err = nfserr_io;
return err;
}
调用vfs层的函数:vfs_statfs来获取当前文件系统的信息。所有的操作,link、remove等等,在服务器端最终都是通过调用vfs层的函数来实现的。
结构体stat用来保存文件系统信息,它的定义如下:
struct kstatfs {
long f_type;
long f_bsize; //每个数据块大小
u64 f_blocks; //数据块总数
u64 f_bfree; //空闲数据块数
u64 f_bavail; //非超级用户可获得的数据块数
u64 f_files; //总文件节点
u64 f_ffree; //可用文件节点
__kernel_fsid_t f_fsid;
long f_namelen;
long f_frsize;
long f_flags;
long f_spare[4];
};
编码函数:
与statfs相关的编码函数是下面这个,还有与其他操作相关的编码函数我们就不关心了。
nfsd4_encode_getattr(struct nfsd4_compoundres *resp, __be32 nfserr, struct nfsd4_getattr *getattr)
{
struct svc_fh *fhp = getattr->ga_fhp;
int buflen;
if (nfserr)
return nfserr;
buflen = resp->end - resp->p - (COMPOUND_ERR_SLACK_SPACE >> 2);
nfserr = nfsd4_encode_fattr(fhp, fhp->fh_export, fhp->fh_dentry,
resp->p, &buflen, getattr->ga_bmval,
resp->rqstp, 0);
if (!nfserr)
resp->p += buflen;
return nfserr;
}
编码函数是做完该做的操作以后,服务器端把一些信息编码发给客户端,然后客户端再解码就可以获取到这些信息了。
真正做编码操作的是nfsd4_encode_fattr这个函数。
nfsd4_encode_fattr(struct svc_fh *fhp, struct svc_export *exp,
struct dentry *dentry, __be32 *buffer, int *countp, u32 *bmval,
struct svc_rqst *rqstp, int ignore_crossmnt){
………………
err = vfs_statfs(dentry, &statfs)
if (bmval1 & FATTR4_WORD1_SPACE_AVAIL) {
if ((buflen -= 8) < 0)
goto out_resource;
dummy64 = (u64)statfs.f_bavail * (u64)statfs.f_bsize;
WRITE64(dummy64);
}
if (bmval1 & FATTR4_WORD1_SPACE_FREE) {
if ((buflen -= 8) < 0)
goto out_resource;
dummy64 = (u64)statfs.f_bfree * (u64)statfs.f_bsize;
WRITE64(dummy64);
}
if (bmval1 & FATTR4_WORD1_SPACE_TOTAL) {
if ((buflen -= 8) < 0)
goto out_resource;
dummy64 = (u64)statfs.f_blocks * (u64)statfs.f_bsize;
WRITE64(dummy64);
………………
}
这个函数很长,我们关心的是和文件系统相关的信息是如何编码的,就是上面这些代码。
先获取文件系统相关的信息,然后通过WRITE64函数把信息写到xdr中,然后客户端的解码函数就可以从xdr中直接读出来了。
Client端:
解码函数:
static int nfs4_xdr_dec_statfs(struct rpc_rqst *req, __be32 *p,
struct nfs4_statfs_res *res)
{
struct xdr_stream xdr;
struct compound_hdr hdr;
int status;
xdr_init_decode(&xdr, &req->rq_rcv_buf, p);
status = decode_compound_hdr(&xdr, &hdr);
if (!status)
status = decode_sequence(&xdr, &res->seq_res, req);
if (!status)
status = decode_putfh(&xdr);
if (!status)
status = decode_statfs(&xdr, res->fsstat);
return status;
}
可以看到会进行一系列解码,但是我们只关心decode_statfs这个解码函数。
static int decode_statfs(struct xdr_stream *xdr, struct nfs_fsstat *fsstat)
{
__be32 *savep;
uint32_t attrlen, bitmap[3] = {0};
int status;
if ((status = decode_op_hdr(xdr, OP_GETATTR)) != 0)
goto xdr_error;
if ((status = decode_attr_bitmap(xdr, bitmap)) != 0)
goto xdr_error;
if ((status = decode_attr_length(xdr, &attrlen, &savep)) != 0)
goto xdr_error;
if ((status = decode_attr_files_avail(xdr, bitmap, &fsstat->afiles)) != 0)
goto xdr_error;
if ((status = decode_attr_files_free(xdr, bitmap, &fsstat->ffiles)) != 0)
goto xdr_error;
if ((status = decode_attr_files_total(xdr, bitmap, &fsstat->tfiles)) != 0)
goto xdr_error;
if ((status = decode_attr_space_avail(xdr, bitmap, &fsstat->abytes)) != 0)
goto xdr_error;
……………………
status = verify_attr_len(xdr, savep, attrlen);
xdr_error:
dprintk("%s: xdr returned %d!\n", __func__, -status);
return status;
}
所有的解码函数里面都是大同小异,我们只看其中一个
static int decode_attr_files_avail(struct xdr_stream *xdr, uint32_t *bitmap, uint64_t *res)
{
__be32 *p;
int status = 0;
*res = 0;
if (unlikely(bitmap[0] & (FATTR4_WORD0_FILES_AVAIL - 1U)))
return -EIO;
if (likely(bitmap[0] & FATTR4_WORD0_FILES_AVAIL)) {
p = xdr_inline_decode(xdr, 8);
if (unlikely(!p))
goto out_overflow;
xdr_decode_hyper(p, res);
bitmap[0] &= ~FATTR4_WORD0_FILES_AVAIL;
}
dprintk("%s: files avail=%Lu\n", __func__, (unsigned long long)*res);
return status;
out_overflow:
print_overflow_msg(__func__, xdr);
return -EIO;
}
在这个函数里面,重要的两步是:xdr_inline_decode和xdr_decode_hyper,
xdr_inline_decode(xdr,8):在xdr中取出长度为8字节的数据
xdr_decode_hyper(p,res):把p中的数据写到res中
到此,客户端和服务器端之间传递rpc,编码解码的过程就完成了。
客户端编码,服务器端解码,都是围绕与操作相关的信息。可以在服务器端的编码函数里通过vfs_statfs函数获取与文件系统相关的信息,写到xdr中,然后再客户端的解码函数中把信息从xdr中拿出来。
两边的操作函数不用修改,只需要把文件系统信息的获取放到编解码函数中就可以了。
容易出错的地方就是,客户端解码时,要注意从xdr中取数据的顺序,如果顺序出错了取出的数据就不对了。
Server端编码:
nfsd4_encode_link(struct nfsd4_compoundres *resp, __be32 nfserr, struct nfsd4_link *link)
{
__be32 *p;
u64 dummy64;
if (!nfserr) {
RESERVE_SPACE(60);
write_cinfo(&p, &link->li_cinfo);
struct kstatfs statfs;
vfs(resp->cstate.current_fh.fh_dentry, &statfs);
dummy64 = (u64)statfs.f_blocks*(u64)statfs.f_bsize;
WRITE64(dummy64);
dummy64 = (u64)statfs.f_bfree*(u64)statfs.f_bsize;
WRITE64(dummy64);
dummy64 = (u64)statfs.f_bavail*(u64)statfs.f_bsize;
WRITE64(dummy64);
dummy64 = (u64)statfs.f_files;
WRITE64(dummy64);
dummy64 = (u64)statfs.f_ffree;
WRITE64(dummy64);
ADJUST_ARGS();
}
return nfserr;
}
write_cinfo(&p, &link->li_cinfo)是函数本身的编码函数,现在我们把文件系统的信息加到原本的编码数据之后。
RESERVE_SPACE是给xdr申请空间, RESERVE_SPACE(20)--> RESERVE_SPACE(60),因为现在加了5种数据,每种数据64位(8字节)
Client端解码:
static int decode_link(struct xdr_stream *xdr, struct nfs4_change_info *cinfo)
{
int status;
__be32 *p;
status = decode_op_hdr(xdr, OP_LINK);
if (status)
return status;
status=decode_change_info(xdr, cinfo);
struct nfs_fsstat statfs;
statfs.abytes=0;
statfs.afiles=0;
statfs.fbytes=0;
statfs.ffiles=0;
statfs.tfiles=0;
p = xdr_inline_decode(xdr, 8);
xdr_decode_hyper(p, &statfs.abytes);
p = xdr_inline_decode(xdr, 8);
xdr_decode_hyper(p, &statfs.afiles);
p = xdr_inline_decode(xdr, 8);
xdr_decode_hyper(p, &statfs.fbytes);
p = xdr_inline_decode(xdr, 8);
xdr_decode_hyper(p, &statfs.ffiles);
p = xdr_inline_decode(xdr, 8);
xdr_decode_hyper(p, &statfs.tfiles);
return status;
}
status=decode_change_info是 函数本身的解码函数,我们把加入的解码函数放在它后面,是因为在xdr中,我们把新增的数据放在了后面。