自打知道 FUSE 以来都觉得亲手写一个 FUSE 文件系统是很好玩的事情,但是因为没好的自己能够很快实现的点子所以一直没动手。前段时间需要从 Android xrecovery 备份中取得一旧版本的应用,才决定动手的,顺便也练习一下很久没怎么用到的 C 语言。至于为什么不用 Python,好像那个 Python 绑定不太稳定的样子,Python 3 版更是如此。而且我也不希望效率太差。
首先介绍一下,所谓的「dedupefs」,就是把 Android xrecovery 的「dedupe」备份格式的数据挂载成文件系统来查看。其实仅仅只是想查看的话,把那个 dedupe 目录下的东东 gcc 一下就可以创建和解开 dedupe 的备份了,只是占用很多磁盘空间而已。
dedupe 的格式很简单,一个文本文件描述文件信息(时间、路径、大小、类型等),一个目录里全是 sha256 命名的文件来存储文件的数据,以便在备份时不同的备份中的相同文件只保存一次。
FUSE 嘛,我好像从来没看到过完整一点的文档,就是官方 API 文档也经常语焉不详。dedupefs 是参考 rofs 写的。dedupefs 也是只读的。
挂载之前,先得把 dedupe 的纯文本格式处理一下。纯文本适合存储和人阅读,但是查询效率低下。我决定用更适合处理纯文本的 Python,把数据存储到 GNU dbm 键值对数据库中,然后 dedupefs 直接读取数据库就好了。(于是顺便学会了在 C 中使用 GNU dbm :-))数据的组织方式如下:
-
d
+ 文件路径:该目录下的文件名列表 -
f
+ 文件路径:该文件的信息
这样要读取一个目录下的文件列表就查 d
开头的项,要取得一个文件的信息(stat
)或者打开文件,就读 f
开头的。
下边是编码和调试过程中的经验与收获:
- GNU dbm 没说它是线程安全的,所以它不是线程安全的。但是 FUSE 又是多线程的(调试用的单线程模式我就不玩的),所以读取数据库时要加锁。
- GNU dbm 查询结果数据是要调用者来 free 的。
-
因为涉及到二进制数据交换(Python <-> C),所以要注意在结构体声明时围上
#pragma pack(push, 1)
和#pragma pack(pop)
,以免对齐不一致造成数据错误。 - valgrind 用来诊断内存访问错误效果非常棒!
-
FUSE 的
struct fuse_file_info
里有个fh
域可以用来存文件描述符,这样就不用像 rofs 那样每次读取都要打开一遍文件了。 -
FUSE 读取用的回调函数传的
offset
一定要用,要首先lseek(finfo->fh, offset, SEEK_SET);
一下,不然指不定读取到什么地方的数据了。 -
FUSE 文件系统可以忽略文件权限,所以自己不在
open
和access
里判断的话,就可以访问到明明看上去不能访问的文件(这正在我想要的)。 -
du
命令读取文件占用磁盘空间时使用了struct stat
的st_blocks
域。如果在 FUSE 程序里不管它的话,那么 du 将总是报告占用了 0 字节的空间……这里的块大小总是 512 字节。
第一次写 FUSE 程序,虽然文档差了一点,但用起来还是挺方便 =w=
哦对了,android-dedupefs 的仓库链接。