nfs 文件句柄_NFS端口

2022-11-02 17:07:47 浏览数 (1)

上一篇文章中我们以REMOVE请求为例讲解了NFS请求的处理过程,其中提到了文件句柄的概念,NFS需要根据文件句柄查找一个文件,这篇文章中我们就来聊聊文件句柄。在普通的文件系统中,我们用文件索引节点编号(ino)表示一个文件。ino就是一个数字,ino保存在磁盘中,整个文件系统中任何两个文件的ino都不相同,因此给定一个ino,我们就能找到对应的文件。当使用NFS文件系统时就出现问题了,我们无法通过文件索引编号找到对应的文件。下面的例子中我们将一个文件系统挂载在另一个文件系统之上导出了。

mount /dev/sdb1 /tmp/nfs/root/mount /tmp/nfs/root 192.168.0.0/16(sec=sys,rw,sync) /tmp/nfs/root/mount 192.168.0.0/16(nohide,sec=sys,rw,sync)

当客户端执行 mount -t nfs nfs_server:/tmp/nfs/root /tmp/mnt后,客户端挂载了服务器端的两个文件系统/tmp/nfs/root和/tmp/nfs/root/mount。

因此,当NFS客户端给出一个文件索引节点编号时,服务器端无法确定到底是哪个文件系统中的索引编号,也就无法找到对应的文件。为了区分不同的文件系统,NFS用文件句柄标识一个文件,文件句柄中既包含了服务器端文件系统的信息,也包含了文件的信息。服务器端解析客户端传递过来的文件句柄,定位客户端请求的文件。对NFS客户端来说,文件句柄是透明的,客户端不关心文件句柄的构成方式,也不对文件句柄进行解析。只需要将文件句柄传递给服务器端就可以了。服务器端可以向文件句柄中加入任何信息,只要保证能根据文件句柄查找到对应的文件就可以了。

1.文件句柄的数据结构

NFS不同版本对文件句柄的长度进行了不同的限制,NFSv2中文件句柄长度固定为32字节。NFSv3中文件句柄长度可变,但是不能超过64字节。NFSv4中文件句柄长度可变,但是不能超过128字节。Linux中,服务器端一个文件句柄的数据结构如下:

[cpp] view plain copy

print ?

  1. struct knfsd_fh {
  2. // 文件句柄的长度
  3. unsigned int fh_size; /* significant for NFSv3.
  4. * Points to the current size while building
  5. * a new file handle
  6. */
  7. union {
  8. struct nfs_fhbase_old fh_old;
  9. __u32 fh_pad[NFS4_FHSIZE/4];
  10. struct nfs_fhbase_new fh_new;
  11. } fh_base; // 文件句柄内容
  12. };

knfsd_fh由两个字段构成:fh_size表示文件句柄的实际长度,fh_base表示文件句柄的内容。文件句柄的构成方式有三种:fh_old、fh_pad、fh_new,这里我们只讲解fh_new的构成方式。在这种方式中,文件句柄用数据结构nfs_fhbase_new表示。

[cpp] view plain copy

print ?

  1. struct nfs_fhbase_new {
  2. __u8 fb_version; // 1 /* == 1, even => nfs_fhbase_old */
  3. __u8 fb_auth_type;
  4. __u8 fb_fsid_type;
  5. __u8 fb_fileid_type;
  6. __u32 fb_auth[1];
  7. /* __u32 fb_fsid[0]; floating */
  8. /* __u32 fb_fileid[0]; floating */
  9. };

fb_version表示版本号,固定为1。

fb_auth_type表示文件句柄是否经过了MD5校验,0表示没有经过校验,1表示进行了校验处理。目前的实现方式中固定为0。

fb_fsid_type表示fsid的构成方式,fsid表示一个文件系统。

fb_fileid_type表示fileid的构成方式,fileid表示一个文件。

fb_auth 如果fb_auth_type为1,fb_auth表示文件句柄MD5校验值。如果fb_auth_type为0,则没有这个字段。

fb_fsid 是一个文件系统的标识,这个字段的长度可变,根据fb_fsid_type进行设置。

fb_fileid 是一个文件的标识,这个字段的长度也可变,根据fb_fileid_type进行设置。

fb_fsid_type表示文件系统的标识方式,linux定义了八种方式,这八种方式定义在文件fs/nfsd/nfsfh.h中。

[cpp] view plain copy

print ?

  1. enum nfsd_fsid {
  2. FSID_DEV = 0, // 4字节设备编号 4字节文件系统根节点索引编号
  3. FSID_NUM, // 如果用户在导出文件系统时设置了fsid,则是这种方式,每个文件系统用一个fsid表示。 4字节fsid.
  4. FSID_MAJOR_MINOR, // 这种方式已经废弃了.
  5. FSID_ENCODE_DEV, // 4字节设备编号(经过编码了) 4字节文件系统根节点索引编号
  6. FSID_UUID4_INUM, // 4字节文件系统根设备索引节点 4字节UUID
  7. FSID_UUID8, // 8字节UUID
  8. FSID_UUID16, // 16字节UUID
  9. FSID_UUID16_INUM, // 8字节文件系统根节点编号 16字节UUID
  10. };

fb_fsid_type是管理员配置/etc/exports文件时设置的,函数set_version_and_fsid_type()会根据配置情况挑选一种合适的标识方式,我们在后面会讲解这个函数。

fb_fileid_type表示文件的标识方式,Linux定义了很多种

[cpp] view plain copy

print ?

  1. enum fid_type {
  2. /*
  3. * The root, or export point, of the filesystem.
  4. * (Never actually passed down to the filesystem.
  5. */
  6. FILEID_ROOT = 0,
  7. /*
  8. * 32bit inode number, 32 bit generation number.
  9. */
  10. FILEID_INO32_GEN = 1,
  11. /*
  12. * 32bit inode number, 32 bit generation number,
  13. * 32 bit parent directory inode number.
  14. */
  15. FILEID_INO32_GEN_PARENT = 2,
  16. /*
  17. * 64 bit object ID, 64 bit root object ID,
  18. * 32 bit generation number.
  19. */
  20. FILEID_BTRFS_WITHOUT_PARENT = 0x4d,
  21. /*
  22. * 64 bit object ID, 64 bit root object ID,
  23. * 32 bit generation number,
  24. * 64 bit parent object ID, 32 bit parent generation.
  25. */
  26. FILEID_BTRFS_WITH_PARENT = 0x4e,
  27. /*
  28. * 64 bit object ID, 64 bit root object ID,
  29. * 32 bit generation number,
  30. * 64 bit parent object ID, 32 bit parent generation,
  31. * 64 bit parent root object ID.
  32. */
  33. FILEID_BTRFS_WITH_PARENT_ROOT = 0x4f,
  34. /*
  35. * 32 bit block number, 16 bit partition reference,
  36. * 16 bit unused, 32 bit generation number.
  37. */
  38. FILEID_UDF_WITHOUT_PARENT = 0x51,
  39. /*
  40. * 32 bit block number, 16 bit partition reference,
  41. * 16 bit unused, 32 bit generation number,
  42. * 32 bit parent block number, 32 bit parent generation number
  43. */
  44. FILEID_UDF_WITH_PARENT = 0x52,
  45. /*
  46. * 64 bit checkpoint number, 64 bit inode number,
  47. * 32 bit generation number.
  48. */
  49. FILEID_NILFS_WITHOUT_PARENT = 0x61,
  50. /*
  51. * 64 bit checkpoint number, 64 bit inode number,
  52. * 32 bit generation number, 32 bit parent generation.
  53. * 64 bit parent inode number.
  54. */
  55. FILEID_NILFS_WITH_PARENT = 0x62,
  56. };

注释中已经写得很清楚了,就不翻译了。但是FILEID_INO32_GEN_PARENT需要一点解释,这种方式的fileid长度可能1是2字节,还可能是16字节,如果是16字节,还包括32 bit parent directory generation number。另外需要注意的是,fb_fileid_type不是管理员配置的,而是文件系统的属性,由具体文件系统设置。函数_fh_update()会根据实际情况挑选一种合适的fileid,我们在后面进行讲解。

2.svc_expkey和svc_export

在讲解文件句柄的构造函数之前我们先讲两个数据结构svc_expkey和svc_export。svc_expkey结构的定义如下:

[cpp] view plain copy

print ?

  1. struct svc_expkey {
  2. struct cache_head h; // 这是一个cache.
  3. struct auth_domain * ek_client; // 这是认证域 key1
  4. int ek_fsidtype; // fsid类型 key2
  5. u32 ek_fsid[6]; // 这是fsid key3
  6. struct path ek_path; // 这个路径. result 这是文件系统根节点的路径.
  7. };

svc_expkey是一类RPC缓存,这个RPC缓存与rpc.mountd交互的接口是/proc/net/rpc/nfsd.fh,这个缓存的作用是查找指定文件系统的路径,并检查用户对这个文件系统的访问权限。查找条件有三个:

ek_client: 这里保存了用户所属于的认证域,这是通过UNIX认证中的svcauth_unix_set_client()设置的,前面的文章中已经讲过这个函数了。

ek_fsidtype: 这是文件系统中fsid的构成方式

ek_fsid: 这是文件句柄的fsid。

内核将ek_client、ek_fsidtype、ek_fsid写入文件/proc/net/rpc/nfsd.fh/channel中,rpc.mountd中的函数nfsd_fh根据ek_fsidtype和ek_fsid组装出fsid,与导出的每个文件系统进行比较。如果fsid相同,再将ek_client和允许挂载这个文件系统的客户端列表进行比较,如果相同表示ek_client指定的客户端可以访问这个文件系统,就将文件系统的根节点路径写入channel文件中,内核解析channel文件中的数据,更新svc_expkey结构中的ek_path。

svc_export结构的定义如下

[cpp] view plain copy

print ?

  1. struct svc_export {
  2. struct cache_head h; // 这是导出文件系统的标志位信息
  3. struct auth_domain * ex_client; // key1 这是认证域
  4. int ex_flags; // 这是导出文件系统的标志位信息 都有哪些标志呢
  5. struct path ex_path; // key2 这是导出的文件系统的根节点
  6. uid_t ex_anon_uid; // 匿名用户id
  7. gid_t ex_anon_gid; // 匿名用户组id
  8. int ex_fsid; // 如果挂载文件系统时设置了fsid=root,则ex_fsid=0
  9. unsigned char * ex_uuid; /* 16 byte fsid */ // 文件系统的uuid
  10. struct nfsd4_fs_locations ex_fslocs; // NFSv4中,文件系统的位置
  11. int ex_nflavors; // 这是文件系统支持的认证方式种类
  12. struct exp_flavor_info ex_flavors[MAX_SECINFO_LIST]; // 这是这个导出的文件系统支持的认证方式
  13. struct cache_detail *cd;
  14. };

这也是一类RPC缓存,这个RPC缓存与rpc.mountd交互的接口是/proc/net/rpc/nfsd.export,这个缓存的作用是根据文件系统根节点的路径查询文件系统的详细信息,这些信息就是管理员导出文件系统时设置的一些信息。rpc.mountd中的处理函数是nfsd_export()。nfsd_export()将ex_path指定的路径与每个文件系统根节点的路径进行比较,如果相同就将这个文件系统的信息写入channel文件中,内核解析channel文件中的数据,填充svc_export结构中的各个字段。

3.文件句柄的构造函数

Linux中生成文件句柄的函数是 __be32 fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry, struct svc_fh *ref_fh) 这个函数的参数说明如下: fhp是输出参数,生成的文件句柄就保存在这个变量中了 exp是文件所在的文件系统 dentry是文件的目录项结构 ref_fh是创建文件句柄时使用的一个参考值,一般是父目录的文件句柄,这个值可以为NULL.

步骤1.取出文件节点和父节点的信息

[cpp] view plain copy

print ?

  1. struct inode * inode = dentry->d_inode;
  2. struct dentry *parent = dentry->d_parent;
  3. __u32 *datap;
  4. dev_t ex_dev = exp_sb(exp)->s_dev;

步骤2.设置文件句柄中的fb_version和fb_fsid_type

[cpp] view plain copy

print ?

  1. set_version_and_fsid_type(fhp, exp, ref_fh);

这个函数定义如下:

fhp是输出参数,将要组装的文件句柄就保存在这里

exp是输入参数,这是文件所在文件系统的数据结构

ref_fh是输入参数,这是一个参考文件句柄,一般是父目录的文件句柄

[cpp] view plain copy

print ?

  1. static void set_version_and_fsid_type(struct svc_fh *fhp, struct svc_export *exp, struct svc_fh *ref_fh)
  2. {
  3. u8 version;
  4. u8 fsid_type;
  5. retry:
  6. version = 1;
  7. // 如果有父节点
  8. if (ref_fh && ref_fh->fh_export == exp) {
  9. version = ref_fh->fh_handle.fh_version; // 跟父节点的version一样
  10. fsid_type = ref_fh->fh_handle.fh_fsid_type; // 取出父节点的fsid类型
  11. ref_fh = NULL; // 置空
  12. switch (version) { // 版本
  13. case 0xca:
  14. fsid_type = FSID_DEV;
  15. break;
  16. case 1:
  17. break;
  18. default:
  19. goto retry;
  20. }
  21. /*
  22. * As the fsid -> filesystem mapping was guided by
  23. * user-space, there is no guarantee that the filesystem
  24. * actually supports that fsid type. If it doesn’t we
  25. * loop around again without ref_fh set.
  26. */
  27. if (!fsid_type_ok_for_exp(fsid_type, exp))
  28. goto retry;
  29. }
  30. // 以下内容表示没有父节点
  31. else if (exp->ex_flags & NFSEXP_FSID) { // 当用户导出文件系统时设置了fsid= 就会设置这个标志
  32. fsid_type = FSID_NUM;
  33. } else if (exp->ex_uuid) { // ex_uuid是32个16进制数字 UUID
  34. if (fhp->fh_maxsize >= 64) { // NFSv3、NFSv4
  35. if (is_root_export(exp)) // 如果导出的是整个文件系统,就采用FSID_UUID16
  36. fsid_type = FSID_UUID16;
  37. else
  38. fsid_type = FSID_UUID16_INUM; // 否则就采用FSID_UUID16_INUM
  39. } else { // NFSv2
  40. if (is_root_export(exp)) // 导出的是整个文件系统,就采用FSID_UUID8
  41. fsid_type = FSID_UUID8;
  42. else
  43. fsid_type = FSID_UUID4_INUM; // 否则就采用FSID_UUID4_INUM
  44. }
  45. }
  46. // 下面两种方式导出文件系统时既没有设置fsid,也没有设置UUID
  47. else if (!old_valid_dev(exp_sb(exp)->s_dev))
  48. /* for newer device numbers, we must use a newer fsid format */
  49. fsid_type = FSID_ENCODE_DEV; // 如果设备号采用新的表示方法,就采用FSID_ENCODE_DEV
  50. else
  51. fsid_type = FSID_DEV; // 如果设备号采用旧的表示方法,就采用FSID_DEV
  52. fhp->fh_handle.fh_version = version; //
  53. if (version)
  54. fhp->fh_handle.fh_fsid_type = fsid_type;
  55. }

这个函数根据管理员导出文件系统时设置在文件/etc/exports中的信息确定了文件句柄中fsid的类型,这个函数会根据不同设置选择一种合适的fsid类型。

步骤3.设置文件句柄中的fb_fsid

[cpp] view plain copy

print ?

  1. int len;
  2. fhp->fh_handle.fh_auth_type = 0; // 文件句柄不进行MD5校验
  3. datap = fhp->fh_handle.fh_auth 0; // 由于不进行MD5校验,fb_auth字段为空,因此fb_fsid字段的位置是fb_auth 0
  4. // 设置fb_fsid
  5. mk_fsid(fhp->fh_handle.fh_fsid_type, datap, ex_dev, // fsid类型,fb_fsid的位置,文件系统设备号
  6. exp->ex_path.dentry->d_inode->i_ino, // 文件系统根节点的索引编号,
  7. exp->ex_fsid, exp->ex_uuid); // 文件系统的fsid,文件系统的uuid

这里有一些宏定义

[cpp] view plain copy

print ?

  1. #define fh_version fh_base.fh_new.fb_version
  2. #define fh_fsid_type fh_base.fh_new.fb_fsid_type
  3. #define fh_auth_type fh_base.fh_new.fb_auth_type
  4. #define fh_fileid_type fh_base.fh_new.fb_fileid_type
  5. #define fh_auth fh_base.fh_new.fb_auth
  6. #define fh_fsid fh_base.fh_new.fb_auth

Linux中,NFS没有对文件句柄进行MD5认证,因此fh_auth_type固定为0,因此文件句柄中没有fh_auth字段,fh_auth是文件句柄中fh_auth的位置,fh_auth 0就是fh_fsid的位置指针了。文件句柄中的fh_fsid是通过函数mk_fsid()设置的,这是一个简单的函数,根据fh_fsid_type的值进行不同的设置,这个函数的定义如下:

[cpp] view plain copy

print ?

  1. static inline void mk_fsid(int vers, u32 *fsidv, dev_t dev, ino_t ino,
  2. u32 fsid, unsigned char *uuid)
  3. {
  4. u32 *up;
  5. switch(vers) {
  6. case FSID_DEV:
  7. fsidv[0] = htonl((MAJOR(dev)<<16) |
  8. MINOR(dev));
  9. fsidv[1] = ino_t_to_u32(ino);
  10. break;
  11. case FSID_NUM:
  12. fsidv[0] = fsid;
  13. break;
  14. case FSID_MAJOR_MINOR:
  15. fsidv[0] = htonl(MAJOR(dev));
  16. fsidv[1] = htonl(MINOR(dev));
  17. fsidv[2] = ino_t_to_u32(ino);
  18. break;
  19. case FSID_ENCODE_DEV:
  20. fsidv[0] = new_encode_dev(dev);
  21. fsidv[1] = ino_t_to_u32(ino);
  22. break;
  23. case FSID_UUID4_INUM:
  24. /* 4 byte fsid and inode number */
  25. up = (u32*)uuid;
  26. fsidv[0] = ino_t_to_u32(ino);
  27. fsidv[1] = up[0] ^ up[1] ^ up[2] ^ up[3];
  28. break;
  29. case FSID_UUID8:
  30. /* 8 byte fsid */
  31. up = (u32*)uuid;
  32. fsidv[0] = up[0] ^ up[2];
  33. fsidv[1] = up[1] ^ up[3];
  34. break;
  35. case FSID_UUID16:
  36. /* 16 byte fsid – NFSv3 only */
  37. memcpy(fsidv, uuid, 16);
  38. break;
  39. case FSID_UUID16_INUM:
  40. /* 8 byte inode and 16 byte fsid */
  41. *(u64*)fsidv = (u64)ino;
  42. memcpy(fsidv 2, uuid, 16);
  43. break;
  44. default: BUG();
  45. }
  46. }

步骤4.设置fsid

fsid标识了文件系统中一个具体的文件,设置fsid的程序端如下:

[cpp] view plain copy

print ?

  1. len = key_len(fhp->fh_handle.fh_fsid_type);
  2. datap = len/4; // datap的类型为 __u32 *datap;
  3. // 文件句柄现在的长度,4表示fb_version fb_auth_type fb_fsid_type fb_fileid_type的长度,这4个字段类型都是__u8。
  4. fhp->fh_handle.fh_size = 4 len;
  5. if (inode)
  6. _fh_update(fhp, exp, dentry);

key_len()根据fh_fsid_type计算fsid的长度。fh_fsid_type不同,文件句柄中的fsid构成方式就不同,各种类型的fsid构成方式在函数mk_fsid()中。key_len()就是简单返回了各种fsid的长度,这个函数定义如下:

[cpp] view plain copy

print ?

  1. static inline int key_len(int type)
  2. {
  3. switch(type) {
  4. case FSID_DEV: return 8;
  5. case FSID_NUM: return 4;
  6. case FSID_MAJOR_MINOR: return 12;
  7. case FSID_ENCODE_DEV: return 8;
  8. case FSID_UUID4_INUM: return 8;
  9. case FSID_UUID8: return 8;
  10. case FSID_UUID16: return 16;
  11. case FSID_UUID16_INUM: return 24;
  12. default: return 0;
  13. }
  14. }

实际组装文件句柄中fsid字段的函数是_fh_update(),这个函数定义如下:

参数fhp: 输入参数,组装出的文件句柄保存在这里

参数exp:文件所在文件系统的数据结构

参数dentry:文件的目录项结构

[cpp] view plain copy

print ?

  1. static void _fh_update(struct svc_fh *fhp, struct svc_export *exp,
  2. struct dentry *dentry)
  3. {
  4. // 文件不是导出文件系统中的根节点.
  5. if (dentry != exp->ex_path.dentry) {
  6. // 这是文件句柄中fsid的位置指针.
  7. struct fid *fid = (struct fid *)
  8. (fhp->fh_handle.fh_auth fhp->fh_handle.fh_size/4 – 1);
  9. // fhp->fh_maxsize是文件句柄的最大长度,取值为32(NFSv2)、64(NFSv3)、128(NFSv4)
  10. // fhp->fh_handle.fh_size是文件句柄中已经组装内容的长度
  11. int maxsize = (fhp->fh_maxsize – fhp->fh_handle.fh_size)/4; // fileid的最大长度
  12. int subtreecheck = !(exp->ex_flags & NFSEXP_NOSUBTREECHECK); // 这是一个权限检查项
  13. // 这个函数在设置文件句柄中fsid的类型和fsid的值.
  14. // maxsize既是输入参数,也是输出参数。作为输入参数表示fsid的最大长度,不能超出这个长度.
  15. // 作为输出参数,表示组装出的fsid的实际长度,都是以__u32为单位
  16. fhp->fh_handle.fh_fileid_type =
  17. exportfs_encode_fh(dentry, fid, &maxsize, subtreecheck);
  18. // 这就是文件句柄的实际长度了.
  19. fhp->fh_handle.fh_size = maxsize * 4; // 现在的文件句柄长度
  20. } else { // 这是导出文件系统的根节点,fileid类型为FILEID_ROOT,不需要设置fileid字段。
  21. fhp->fh_handle.fh_fileid_type = FILEID_ROOT;
  22. }
  23. }

代码中已经对函数流程做了注释,这里解析一下subtree_check。subtree_check是管理员导出文件系统时设置在/etc/exports中的一个配置项。管理员导出的一个NFS文件系统可能不是一个完成的文件系统,而只是文件系统中的一部分。比如根系统(/)安装在磁盘/dev/sda1上,这是一个完成的文件系统,而/tmp/nfs/root只是文件系统中的一个子树,管理员可以只导出这个文件子树。当客户端访问文件前,服务器端需要先检查客户端对文件的访问权限。如果设置了no_subtree_check,则服务器端只检查在导出文件系统中的访问权限就可以了,如果设置了subtree_check,则服务器端还需要检查上层路径的访问权限,也就是说服务器需要检查客户端对/tmp/nfs/、/tmp的访问权限。如果客户端具有/tmp/nfs/root/的访问权限,但是没有上层路径的访问权限,也不能访问/tmp/nfs/root/中的数据。

如果文件节点是导出文件系统的根节点,则fsid类型设置为FILEID_ROOT,fsid字段为空。如果不是根节点,则调用函数exportfs_encode_fh()组装fsid,这个函数位于fs/exportfs/expfs.c中,定义如下:

参数dentry: 文件的目录项结构

参数fid: 这就是文件句柄中fileid的缓存,只不过转换成struct fid结构了

参数max_len: 作为输入参数,这是fileid的最大长度;作为输出参数,这是fielid的实际长度

[cpp] view plain copy

print ?

  1. int exportfs_encode_fh(struct dentry *dentry, struct fid *fid, int *max_len,
  2. int connectable)
  3. {
  4. // 取出文件系统中export_operations操作函数集合
  5. const struct export_operations *nop = dentry->d_sb->s_export_op;
  6. int error;
  7. struct dentry *p = NULL;
  8. // 找到文件的索引节点
  9. struct inode *inode = dentry->d_inode, *parent = NULL;
  10. if (connectable && !S_ISDIR(inode->i_mode)) {
  11. p = dget_parent(dentry); // 获取父目录的目录项结构
  12. /*
  13. * note that while p might’ve ceased to be our parent already,
  14. * it’s still pinned by and still positive.
  15. */
  16. parent = p->d_inode; // 这是父目录的索引节点
  17. }
  18. if (nop->encode_fh)
  19. error = nop->encode_fh(inode, fid->raw, max_len, parent);
  20. else
  21. error = export_encode_fh(inode, fid, max_len, parent);
  22. dput(p);
  23. return error;
  24. }

export_operations是与文件系统相关的一个数据结构,这个数据结构中定义了构造和解析文件句柄中fileid字段的函数,这个数据结构的定义如下:

[cpp] view plain copy

print ?

  1. struct export_operations {
  2. int (*encode_fh)(struct inode *inode, __u32 *fh, int *max_len,
  3. struct inode *parent);
  4. struct dentry * (*fh_to_dentry)(struct super_block *sb, struct fid *fid,
  5. int fh_len, int fh_type);
  6. struct dentry * (*fh_to_parent)(struct super_block *sb, struct fid *fid,
  7. int fh_len, int fh_type);
  8. int (*get_name)(struct dentry *parent, char *name,
  9. struct dentry *child);
  10. struct dentry * (*get_parent)(struct dentry *child);
  11. int (*commit_metadata)(struct inode *inode);
  12. };

EXT2文件系统中,这个数据结构的值如下:

[cpp] view plain copy

print ?

  1. static const struct export_operations ext2_export_ops = {
  2. .fh_to_dentry = ext2_fh_to_dentry, // 根据fileid查找文件的目录项结构
  3. .fh_to_parent = ext2_fh_to_parent, // 根据fileid查找父目录的目录项结构
  4. .get_parent = ext2_get_parent, // 根据文件的目录项结构查找父目录的目录项结构
  5. };

struct fid结构的定义如下:

[cpp] view plain copy

print ?

  1. struct fid {
  2. union {
  3. struct {
  4. u32 ino;
  5. u32 gen;
  6. u32 parent_ino;
  7. u32 parent_gen;
  8. } i32;
  9. struct {
  10. u32 block;
  11. u16 partref;
  12. u16 parent_partref;
  13. u32 generation;
  14. u32 parent_block;
  15. u32 parent_generation;
  16. } udf;
  17. __u32 raw[0];
  18. };
  19. };

这个结构中保存的就是文件句柄中的fileid。 exportfs_encode_fh()会检查文件系统是否定义了encode_fh()函数,如果没有定义就调用通用的组装函数export_encode_fh()。EXT2文件系统中没有定义这个函数,因此如果服务器端导出的是EXT2文件系统,则调用export_encode_fh()组装fileid。

[cpp] view plain copy

print ?

  1. static int export_encode_fh(struct inode *inode, struct fid *fid,
  2. int *max_len, struct inode *parent)
  3. {
  4. int len = *max_len; // fileid的最大长度
  5. int type = FILEID_INO32_GEN; // 这是fileid类型,默认为FILEID_INO32_GEN
  6. if (parent && (len < 4)) {
  7. *max_len = 4;
  8. return 255; // 255是一个预留值,当返回255时表示fileid组装过程出错
  9. } else if (len < 2) {
  10. *max_len = 2;
  11. return 255;
  12. }
  13. len = 2;
  14. fid->i32.ino = inode->i_ino; // inode编号
  15. fid->i32.gen = inode->i_generation; // 索引节点版本号
  16. if (parent) {
  17. fid->i32.parent_ino = parent->i_ino; // 父节点的inode编号
  18. fid->i32.parent_gen = parent->i_generation; // 父节点的版本号
  19. len = 4;
  20. type = FILEID_INO32_GEN_PARENT; // 设置fileid类型
  21. }
  22. *max_len = len;
  23. return type;
  24. }

通过这个函数我们可以看到,如果服务器端设置了subtree_check,则fileid的类型是FILEID_INO32_GEN_PARENT,fileid长度为16字节,包含inode number(4字节)、generation number(4字节)、parent inode number(4字节)、parent generation number(4字节)。如果服务器端设置了no_subtree_check,则fileid的类型是FILEID_INO32_GEN,fileid长度为8字节,包含inode number(4字节)、generation number(4字节)。

最后再说说文件索引节点中的generation number。EXT2文件系统超级块结构ext2_sb_info中存在一个变量s_next_generation,每创建一个新文件,这个变量的值为赋给文件索引节点结构inode中的i_generation,同时s_next_generation加一。i_generation相等于文件的一个验证信息。如果用户删除了EXT2文件系统中一个文件,然后再创建一个同名文件,新创建文件的ino编号很可能和原文件ino编号相同,但是i_generation已经发生了变化。由于文件句柄中包含了i_generation,因此NFS文件系统可以检查出文件是否还是原来的文件,如果不是原来的文件,则NFS返回错误码NFS3ERR_STALE(NFSv3),表示文件句柄已经过期了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/180695.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全系列IDE使用 售后保障 童叟无欺

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

0 人点赞