【PNFS学习】编解码实验记录

1 实验要求

修改编解码函数,在客户端执行链接操作后,能在日志中打印出f_blocks、f_bfree、f_bavail、f_files、f_ffree信息。

2 相关知识

2.1 rpc发送过程

【PNFS学习】编解码实验记录_第1张图片

2.2 详解statfs

过程上图是客户端和服务器之间发送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,编码解码的过程就完成了。

3 修改思路

客户端编码,服务器端解码,都是围绕与操作相关的信息。可以在服务器端的编码函数里通过vfs_statfs函数获取与文件系统相关的信息,写到xdr中,然后再客户端的解码函数中把信息从xdr中拿出来。

两边的操作函数不用修改,只需要把文件系统信息的获取放到编解码函数中就可以了。

容易出错的地方就是,客户端解码时,要注意从xdr中取数据的顺序,如果顺序出错了取出的数据就不对了。

4 修改内容

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中,我们把新增的数据放在了后面。

5 实验结果

【PNFS学习】编解码实验记录_第2张图片

 

【PNFS学习】编解码实验记录_第3张图片

你可能感兴趣的:(pnfs学习)