大家好,欢迎来到IT知识分享网。
文件的扩展属性
扩展属性(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_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文件系统调用栈。
通过上图可以看出在VFS做了很多事情,最后通过函数指针的方式调用到Ext2文件系统的扩展属性设置接口。整个流程中除了Ext2文件系统实现的设置扩展属性的函数(ext2_xattr_set)逻辑相对复杂外,整个函数栈的代码逻辑非常简单,这里就不过多介绍了。但是,这里有几点需要说明的:
属性设置的公共接口
这个调用就是上图中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则表示只进行创建操作,如果已经存在期望的键则失败。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/61070.html