FUSE文件系统扩展属性支持
背景来源
学习文件系统最好的办法莫过于在文件系统代码里面能实际的改点东西,不过文件系统的代码都非常成熟,很少给我们机会去修改。不过好在FUSE文件系统没有实现扩展属性功能,而产品恰恰有这方面的需求,这就给了我们实现扩展属性的机会。
扩展属性(Extended attributes)
扩展文件属性是文件系统的一个功能。它允许用户将计算机文件与未被文件系统所解释的元数据关联起来。与之相对应的是正规文件属性,其具有经文件系统严格定义的意义(例如文件系统权限或者文件创建以及修改时间等)。与通常能具有最大文件大小的forks不同,扩展文件属性通常被限制为远小于最大文件大小。其典型应用包括存储文档作者、普通文本文件的字符编码或者校验码。
扩展属性支持接口
FUSE文件系统已经定义好了,扩展属性功能的接口函数,一共四个API,为setxattr,getxattr,listxattr,removexattr。在FUSE的守护进程的代码里面,也已经定义好了几个空的回调函数分别为pf_setxattr,pf_getxattr,pf_listxattr,pf_removexattr。因此实现FUSE文件系统的扩展属性支持就是需要实现这四个回调函数。
// Set an extended attribute
void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
const char *value, size_t size, int flags);
// Get an extended attribute
void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
size_t size);
// List extended attribute names
void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
// Remove an extended attribute
void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
内核空间扩展属性支持
仅仅在用户空间实现扩展属性的几个回调函数还不行,因为在Android 11的内核4.19平台上是SDCARDFS和FUSE共存的,FUSE是在SDCARDFS基础之上的,而内核里面的SDCARDFS文件系统也不支持扩展属性,所以要实现FUSE的扩展属性支持,先要在内核空间实现SDCARDFS的扩展属性支持。内核里面文件扩展属性接口也是标准的,每种文件系统类型实现对应的回调函数即可,内核里面只需要实现setxattr, getxattr, listxattr的回调函数,removexattr的功能是在setxattr里面实现的。至于如何把回调函数注册到文件系统中,参考已经支持扩展属性功能的文件系统就可以,如F2FS或EXT4。
不支持在 Docs 外粘贴 block
扩展属性设置接口实现实例
在描述了实现FUSE扩展属性接口的方法后,我们以setxattr为例看一下具体实现的代码
内核空间SDCARDFS的setxattr的回调函数
int __sdcardfs_xattr_set(struct dentry *dentry, const char *name, const void *value,
size_t size, int flags)
{
ssize_t res;
struct path lower_path;
struct dentry *lower_dentry;
const struct cred *saved_cred = NULL;
struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb);
struct inode *inode;
// 从dentry获取inode
inode = d_inode(dentry);
// 切换为可以访问底层文件系统的uid和gid
saved_cred = override_fsids(sbi, SDCARDFS_I(inode)->data);
if (!saved_cred)
return -ENOMEM;
// 加锁获取底层文件系统的路径
sdcardfs_get_lower_path(dentry, &lower_path);
// 获取底层文件系统的dentry
lower_dentry = lower_path.dentry;
// 对dentry加锁
dget(lower_dentry);
if (value)
// 通过vfs_setxattr接口调用底层文件系统的处理函数,f2fs_setxattr
res = vfs_setxattr(lower_dentry, name, value, size, flags);
else {
WARN_ON(flags != XATTR_REPLACE);
// 删除xattr的分支,通过vfs_removexattr,最终仍然会调用到f2fs_setxattr
res = vfs_removexattr(lower_dentry, name);
}
// 释放dentry的锁
dput(lower_dentry);
// 释放路径的锁
sdcardfs_put_lower_path(dentry, &lower_path);
// 恢复sdcardfs的uid和gid
revert_fsids(saved_cred);
return res;
}
用户空间FUSE的setxattr的回调函数
static void pf_setxattr(fuse_req_t req, fuse_ino_t ino, const char* name,
const char* value, size_t size, int flags)
{
ATRACE_CALL();
struct fuse* fuse = get_fuse(req);
char buf[1024] = "";
int ret;
// 获取 fuse的 node结构体
node* node = fuse->FromInode(ino);
if (!node) {
fuse_reply_err(req, ENOENT);
return;
}
// 路径的访问权限检查
string path = node->BuildPath();
if (!is_app_accessible_path(fuse->mp, path, req->ctx.uid)) {
fuse_reply_err(req, ENOENT);
return;
}
TRACE_NODE(node, req);
// 通过标准的系统调用setxattr调用底层的sdcardfs的回调函数
ret = setxattr(path.c_str(), name, value, size, flags);
if (ret < 0) {
fuse_reply_err(req, errno);
return;
}
// 返回处理结果
fuse_reply_err(req, 0);
}
总结
从上面的实现代码来看,FUSE文件系统添加扩展属性支持还是比较简单的,只是按照文件系统的标准接口添加几个回调函数即可,总结一下
- 扩展属性的内容实际存储在底层文件系统F2FS里面,SDCARDFS和FUSE都是属于RAM文件系统,具有overlay的特性。
- 内核空间的回调函数里面是通过vfs_XXX的接口调用底层文件系统的回调函数
- 用户空间的回调函数是通过标准的文件系统的系统调用函数来调用到内核态的vfs层,然后调用到SDCARDFS的回调函数
- 当然在完整的实际代码实现中其他模块也有一些小的修改,如扩展属性需要增加一种前缀来解决权限问题,f2fs层需要进行小的修改来适配新的属性前缀等。