Ext2文件系统深度剖析|扩展属性分析

文件的扩展属性

扩展属性(xattrs)提供了一个机制用来将键值对(Key/Value)永久地关联到文件,让现有的文件系统得以支持在原始设计中未提供的功能。扩展属性是文件系统不可知论者,应用程序可以通过一个标准的接口来操纵他们,此接口不因文件系统而异。每个扩展属性可以通过唯一的键来区分,键的内容必须是有效的UTF-8,格式为namespace.attribute,每个键采用完全限定的形式,也就是键必需有一个确定的前缀(例如user)。

Linux操作系统有如下集中扩展属性:

  • system:用于实现利用扩展属性的内核功能,例如访问控制表。eg:system.posix_acl_access便是位于此用户空间的扩展属性,用户是否可以读取或写入这些属性取决于所使用的安全模块。
  • security:用于实现安全模块。
  • trusted:把受限制的信息存入用户空间。
  • user:一般进程所使用的标准命名空间,经过一般文件权限位来控制此命名空间的访问。

Ext2扩展属性是怎么在磁盘存储的

本文主要介绍一下Ext2文件系统中扩展属性的相关内容,包括磁盘数据布局和创建流程等。在Ext2文件系统中,扩展属性存储在一个单独的磁盘逻辑块中,其位置由inode中的i_file_acl成员指定。如图所示是键值对在该逻辑块中的布局示意图。其前32个字节是一个描述头(ext2_xattr_header),描述该逻辑块基本使用情况。而下面紧跟着的是扩展属性项(ext2_xattr_entry),扩展属性项描述了扩展属性的键名称等信息,同时包含值的偏移等内容。这里需要说明的是扩展属性项是从上往下生长的,而值则是从下往上生长

Ext2文件系统深度剖析|扩展属性分析

图1 扩展属性布局图

如下代码是描述头(ext2_xattr_header)的结构体定义,里面有魔数、引用计数和哈希值等内容。这里魔数的作用是确认该逻辑块的内容是扩展属性逻辑块,避免代码Bug或者磁盘损坏等情况下给用户返回错误的结果。引用计数和哈希值的作用是实现多文件的扩展属性共享。所谓扩展属性共享是指,如果多个文件的扩展属性完全一样的情况下,这些文件的扩展属性将采用相同的磁盘逻辑块存储,这样可以大大的节省存储空间。另外,Ext2借用的哈希缓存,将文件属性的哈希值存储在其中,用于快速判断文件是否存在相同的扩展属性逻辑块。

struct ext2_xattr_header {
     __le32 h_magic; /* magic number for identification */
     __le32 h_refcount; /* reference count */
     __le32 h_blocks; /* number of disk blocks used */
     __le32 h_hash; /* hash value of all attributes */
     __u32 h_reserved[4]; /* zero right now */
};

扩展属性项在磁盘上是从上往下生长的,但需要注意的是由于每个扩展属性的键名称的长度不一定一样,因此该结构体的大小也是变化的。由于上述原因,我们无法直接找到某一个扩展属性项的位置,必需从头到位进行遍历。由于描述头的大小是确定的,这样第一个扩展属性项就可以找到,而下一个扩展属性项就可以根据本扩展属性项的位置及其中的e_name_len成员计算得到。

struct ext2_xattr_entry {
     __u8 e_name_len; /* length of name */
     __u8 e_name_index; /* attribute name index */
     __le16 e_value_offs; /* offset in disk block of value */
     __le32 e_value_block; /* disk block attribute is stored on (n/i) */
     __le32 e_value_size; /* size of attribute value */
     __le32 e_hash; /* hash value of name and value */
     char e_name[0]; /* attribute name */
};

设置扩展属性的VFS流程

操作系统提供了一些API来设置文件的扩展属性,分别是setxattr、fsetxattr和lsetxattr。这几个函数应用场景略有差异,但功能基本一致。本文以fsetxattr为例进行介绍。假设用户调用该接口为某个文件设置user前缀的扩展属性,此时的整个函数调用栈如图所示。本调用栈包含三部分内容,分别是用户态接口、VFS调研栈和Ext2文件系统调用栈。

Ext2文件系统深度剖析|扩展属性分析

图2 扩展属性设置流程

通过上图可以看出在VFS做了很多事情,最后通过函数指针的方式调用到Ext2文件系统的扩展属性设置接口。整个流程中除了Ext2文件系统实现的设置扩展属性的函数(ext2_xattr_set)逻辑相对复杂外,整个函数栈的代码逻辑非常简单,这里就不过多介绍了。但是,这里有几点需要说明的:

扫描二维码关注公众号,回复: 6099963 查看本文章

属性设置的公共接口

这个调用就是上图中i_op->setxattr的调用,其中i_op是inode中的一个成员,这个指针是分配inode节点的时候初始化的。这个函数屏蔽了不同的扩展属性(上文已经交代Linux文件系统有trusted和user等多种扩展属性)的处理方法集合。需要注意的是Ext2文件系统并没有实现自己的特有函数,而是调用了VFS提供的公共函数(generic_setxattr)。上述i_op是一个结构体指针,其中包含属性及扩展属性操作的所有接口,如下代码所示。

const struct inode_operations ext2_file_inode_operations = { 
    #ifdef CONFIG_EXT2_FS_XATTR
     .setxattr = generic_setxattr,
     .getxattr = generic_getxattr,
     .listxattr = ext2_listxattr,
     .removexattr = generic_removexattr,
    #endif
     .setattr = ext2_setattr,
     .get_acl = ext2_get_acl,
     .set_acl = ext2_set_acl,
     .fiemap = ext2_fiemap,
};

关于设置扩展属性的公共实现函数比较简单,具体如下所示。

int generic_setxattr(struct dentry *dentry, 
 const char *name, 
 const void *value, 
 size_t size, 
 int flags)
{ 
     const struct xattr_handler *handler;
     if (size == 0)
         value = ""; /* empty EA, do not remove */
     /* 根据扩展属性的键(Key)获取处理该动作的函数集指针handler。
     * 例如设置user类型的扩展属性,则name格式为user.xxx */
     handler = xattr_resolve_name(dentry->d_sb->s_xattr, &name); 
     if (!handler) 
         return -EOPNOTSUPP; 
     /* 调用具体类型扩展属性的处理函数, 例如对于user扩展属性,则
     * 调用ext2_xattr_user_handler进行处理。 */
     return handler->set(dentry, name, value, size, 
                             flags, handler->flags);
}

user扩展属性设置接口

这个调用就是上图中handler->set的调用,这个指针是在文件系统挂载的时候初始化的,其内容初始化了超级块的成员变量s_xattr。其中handler指针是根据用户传入的键名称确定的。具体获取是在函数xattr_resolve_name中实现的,其对超级块中的s_xattr变量进行遍历(匹配扩展属性的键前缀,流入user),从而找到可以处理该扩展属性的handler。如下代码是Ext2文件系统初始化超级快s_xattr用的数据,里面包含Ext2文件系统支持的所有扩展属性类型。

const struct xattr_handler *ext2_xattr_handlers[] = {
     &ext2_xattr_user_handler,
     &ext2_xattr_trusted_handler, 
    #ifdef CONFIG_EXT2_FS_POSIX_ACL
     &posix_acl_access_xattr_handler,
     &posix_acl_default_xattr_handler, 
    #endif#ifdef CONFIG_EXT2_FS_SECURITY
     &ext2_xattr_security_handler, 
    #endif 
     NULL 
};

这样,经过几层调用之后就可以调用到Ext2文件系统中关于user扩展属性的设置接口。

Ext2扩展属性接口

对于user类型的扩展属性,其函数集为ext2_xattr_user_handler,如下是具体的定义。这里面实现了该类型扩展属性的查询和设置等接口。

const struct xattr_handler ext2_xattr_user_handler = { 
     .prefix = XATTR_USER_PREFIX,
     .list = ext2_xattr_user_list,
     .get = ext2_xattr_user_get,
     .set = ext2_xattr_user_set,
};

设置扩展属性的接口是ext2_xattr_user_set,如下是该函数的具体实现,从代码中可以看出该函数主要调用了ext2_xattr_set函数,这个函数实现了对添加扩展属性的操作。

static int ext2_xattr_user_set(struct dentry *dentry, 
  const char *name, 
  const void *value, 
  size_t size, 
  int flags, 
  int type){ 
     if (strcmp(name, "") == 0) 
         return -EINVAL; 
     if (!test_opt(dentry->d_sb, XATTR_USER)) 
         return -EOPNOTSUPP; 
     return ext2_xattr_set(d_inode(dentry), 
                     EXT2_XATTR_INDEX_USER,
                     name, value, size, flags);
}

函数ext2_xattr_set的实现非常长,大概300多行,因此这里就不贴代码了。本文具体介绍一下该函数的实现逻辑主要是查找键,然后根据查找的结果进行处理。如果能找到该键则更新值,如果找不到则新建一个键值对。这里有几个注意点,具体实现细节请自行阅读代码。

  • 设置扩展属性时可能是第一个,此时会分配新的磁盘空间
  • 设置扩展属性需要计算剩余空间,如果剩余空间不够,则创建失败
  • 设置扩展属性接口同时实现了更新扩展属性值的功能,但如果新值得长度大于旧值,则需要进行特殊处理
  • 在数据布局的次序上,键和值并没有严格的顺序

在实现逻辑中需要注意的一点是,用户在调用接口的时候可以传递附加标识,比如XATTR_REPLACE和XATTR_CREATE等。例如XATTR_REPLACE表示用户期望进行扩展属性值得替换操作,如果没有找到扩展属性的键,则返回失败。XATTR_CREATE则表示只进行创建操作,如果已经存在期望的键则失败。

猜你喜欢

转载自blog.csdn.net/shuningzhang/article/details/89394222