Libsqlfs
版权所有 2011-2013,Guardian Project 的各个开发者。版权所有 2006,Palmsource,Inc.(ACCESS 公司)。
Libsqlfs 是免费/开源软件,根据 GNU Lesser General Public License 版本 2 或 Free Software Foundation 发布的后续版本进行分发。有关完整的许可条款,请参阅文件 COPYING。
libsqlfs 库在 SQLite 数据库之上实现了 POSIX 风格的文件系统。它允许应用程序以单个文件的形式访问完整的读写文件系统,包括其自己的文件层次结构和命名空间。这对于需要结构化存储的应用程序很有用,例如在文档中嵌入文档,或管理配置数据或首选项。Libsqlfs 可以用作共享库,也可以构建为 FUSE(用户空间中的 Linux 文件系统)模块,允许通过操作系统级别的文件系统接口由普通应用程序访问 libsqlfs 数据库。
PalmSource 的软件开发者最初创建了 libsqlfs。这个库是极受欢迎的开源 SQLite 数据库软件的补充。libsqlfs 是作为 PalmSource 的 ALP 手机平台的一部分创建的,但它也适用于许多其他应用程序。
Guardian Project 采用了 libsqlfs 与 SQLCipher 结合使用,SQLCipher 是 SQLite3 的一个自定义版本,其中包括对数据库内容加密的支持。这使得它成为一个自包含的加密文件系统。IOCipher 是基于 libsqlfs 的一个项目,它使用 java.io API 提供一个虚拟加密文件系统。
libsqlfs 库为应用程序提供了一种简单的方式,将整个读写文件系统放入主文件系统中的一个文件作为关系数据库的单个文件。这样的文件系统可以轻松移动、备份或恢复为单个文件。但该文件系统也可以作为单个文件访问。这提供了极大的灵活性和便利性。
我们认为满足需求的最简单方法是编写一个支持 SQL 数据库上 POSIX 文件系统语义的库。这带来了真实数据库的好处,例如事务和并发控制,并允许我们完全控制偏好的模式,因此我们可以允许额外的元数据,如值类型、权限和访问控制列表。我们的 libsqlfs 注册表可以容纳诸如数字之类的较小偏好值,以及诸如视频剪辑之类的较大二进制对象。该库提供了一般文件系统层,将文件系统映射到 SQLite 数据库上,并支持 POSIX 文件系统语义。
为了加快开发速度,我们将文件系统映射层构建为一个用户空间的文件系统(FUSE)模块。FUSE 是另一个开源项目。它是一个支持用户级文件系统实现的内核模块。我们的设计允许 libsqlfs 在 OS 级别实现真正的文件系统,并在其上应用真实的文件系统操作。我们测试了在 libsqlfs 上重复执行 gcc 和 Linux 内核的完整构建过程,并成功地运行了 fsx.c,苹果文件系统测试工具,针对 libsqlfs。
今天,ALP 全局设置组件使用 libsqlfs 作为后端存储。Libsqlfs 为应用程序提供了一种简单的方法,支持完全包含在关系数据库中的只读/写文件系统,作为主机文件系统中的一个单独文件,无需使用 SQL 语句。Libsqlfs 提供了 GConf 存储功能的一组超集,可以用作其他桌面偏好服务的存储后端。Libsqlfs 也有助于开发人员需要组织数据的地方,有时将其视为一个文件,而有时将其视为一组可独立写入的文件。
Libsqlfs 提供了一个基于 GNU autoconf/automake 的构建系统,用于将库作为应用程序库构建。要构建,请遵循正常的 GNU 配置惯例。通常,以下命令即可完成
./configure --prefix= make && make install
如果未指定,默认为 /usr/local。
为了将安装到系统目录(如 /usr/local),您必须是 root。
除非通过配置选项指定,否则都会构建静态库和共享库。
如果要将它作为 FUSE 模块构建,您需要在您的系统上安装 libfuse。这比直接 API 测试得少。
运行脚本后,您应该有一个名为 fuse_sqlfs 的可执行文件。以 root 身份运行它以在 libsqlfs 上启动 FUSE 会话
fuse_sqlfs
然后,您应该看到 libsqlfs 文件空间暴露出来,可以通过 . 被常规应用程序访问
示例
fuse_sqlfs /mnt/sqlfs &
ls /mnt/sqlfs
SQLite 数据库的位置现在硬编码在 fuse_main.c 中。将 sqlfs_init() 参数更改为满足您的需求。
它打开的数据库文件目前硬编码在 fuse_sqlfs.c 中为 /tmp/fsdata。如果您想使用不同的数据库文件或提供一个加密文件的密钥,只需编辑 fuse_sqlfs.c 并重新构建。
有关使用 libsqlfs 的示例应用程序,请参阅 tests/ 目录中的测试程序。
libsqlfs 有两种操作模式:“初始化/销毁”和“打开/关闭”。“初始化/销毁”模式需要在使用任何操作之前调用 sqlfs_init(),然后每个线程根据需要动态分配一个 sqlfs_t。这是 FUSE 使用的模式。在使用完所有操作后,必须调用 sqlfs_destroy() 进行清理。
"打开/关闭"更像是打开一个文件。当程序使用此功能时需要“打开”或“挂载”状态时,才使用它。这是IOCipher使用的模式。
libsqlfs起初是一个FUSE模块,因此它实现了FUSE 2.5.3版本中定义的初始化。一个libsqlfs会话由sqlfs_t类型对象表示。所有API都需要对有效的sqlfs_t有一个明确的引用。具体来说,以下文件系统原语都得到了实现:
int sqlfs_proc_getattr(sqlfs_t *, const char *path, struct stat *stbuf); int sqlfs_proc_access(sqlfs_t *, const char *path, int mask); int sqlfs_proc_readlink(sqlfs_t *, const char *path, char *buf, size_t size); int sqlfs_proc_readdir(sqlfs_t *, const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi); int sqlfs_proc_mknod(sqlfs_t *, const char *path, mode_t mode, dev_t rdev); int sqlfs_proc_mkdir(sqlfs_t *, const char *path, mode_t mode); int sqlfs_proc_unlink(sqlfs_t *, const char *path); int sqlfs_proc_rmdir(sqlfs_t *, const char *path); int sqlfs_proc_symlink(sqlfs_t *, const char *path, const char *to); int sqlfs_proc_rename(sqlfs_t *, const char *from, const char *to); int sqlfs_proc_link(sqlfs_t *, const char *from, const char *to); int sqlfs_proc_chmod(sqlfs_t *, const char *path, mode_t mode); int sqlfs_proc_chown(sqlfs_t *, const char *path, uid_t uid, gid_t gid); int sqlfs_proc_truncate(sqlfs_t *, const char *path, off_t size); int sqlfs_proc_utime(sqlfs_t *, const char *path, struct utimbuf *buf); int sqlfs_proc_open(sqlfs_t *, const char *path, struct fuse_file_info *fi); int sqlfs_proc_read(sqlfs_t *, const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi); int sqlfs_proc_write(sqlfs_t *, const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi); int sqlfs_proc_statfs(sqlfs_t *, const char *path, struct statvfs *stbuf); int sqlfs_proc_release(sqlfs_t *, const char *path, struct fuse_file_info *fi); int sqlfs_proc_fsync(sqlfs_t *, const char *path, int isfdatasync, struct fuse_file_info *fi);
它们的语义由FUSE文档和相应的Unix文件系统调用定义。遵循FUSE约定,所有文件或密钥路径必须绝对且以'/'开始。应用程序可以在传递给这些FUSE原语例程之前提供自己的相对路径逻辑。
此外,其他API提供环境设置、事务支持和便利函数。
int sqlfs_init(const char *) 初始化libsqlfs库并设置默认数据库文件名。
int sqlfs_destroy() 当所有操作完成后,清理sqlfs_init()后的资源。
int sqlfs_open(const char *db, sqlfs_t **) 创建到libsqlfs数据库的新连接。第一个参数,如果不为NULL,则指定一个与默认数据库不同的数据库文件。
int sqlfs_open_key(const char *db_file, const char *key, sqlfs_t **sqlfs); 创建到加密的libsqlfs数据库的新连接,并使用提供的密码解锁。第一个参数,如果不为NULL,则指定一个与默认数据库不同的数据库文件。
int sqlfs_close(sqlfs_t *); 关闭并释放libsqlfs连接。
您可以使用以下函数在低于FUSE API的水平上对文件系统进行操作
int sqlfs_del_tree(sqlfs_t *sqlfs, const char *key); 删除整个子树。
int sqlfs_get_value(sqlfs_t *sqlfs, const char *key, key_value *value, size_t begin, size_t end); 从文件(偏移量begin和end之间的范围)读取内容
int sqlfs_set_value(sqlfs_t *sqlfs, const char *key, const key_value *value, size_t begin, size_t end); 将值的 内容写入指定范围(偏移量begin和end之间的文件)内
int sqlfs_get_attr(sqlfs_t *sqlfs, const char *key, key_attr *attr); 读取文件的元数据
int sqlfs_set_attr(sqlfs_t *sqlfs, const char *key, const key_attr *attr); 写入文件的元数据
int sqlfs_set_type(sqlfs_t *sqlfs, const char *key, const char *type); 设置文件内容的“类型”。
int sqlfs_begin_transaction(sqlfs_t *sqlfs); 该函数用于开始一个SQLite事务
int sqlfs_complete_transaction(sqlfs_t *sqlfs, int i); 该函数用于结束一个SQLite事务
文件系统是通过将块分配给文件的方式实现的。文件系统存储在一个SQLite表中,包含以下列:
full key path | type | inode | uid | gid | mode | acl | attributes | atime | mtime | ctime | size | block_size text | text | integer | integer | integer | integer | text | text | integer | integer | integer | integer | integer
键路径必须是一个使用"/"作为路径分隔符的绝对路径。路径区分大小写。与键路径相关联的数据类型可以是以下之一:"int","double","string","dir","sym link" 以及 "blob"。通常,数据会被分配为8k的blob,代表文件系统的块。对于文件数据使用"int","double"和"string"应尽量避免,因为它们不具备通用性。每个块占用一个数据库中的BLOB对象,按照块号索引,块号从0开始。
使用以下方式创建表行:
CREATE TABLE meta_data(key text, type text, inode integer, uid integer, gid integer, mode integer, acl text, attribute text, atime integer, mtime integer, ctime integer, size integer, block_size integer, primary key (key), unique(key)); CREATE TABLE value_data (key text, block_no integer, data_block blob, unique(key, block_no)); CREATE INDEX meta_index ON meta_data (key); CREATE INDEX value_index ON value_data (key, block_no);
在代码中使用了SQL事务来提高效率。注意事务支持“级别”;即,事务调用可以嵌套,并且libsqlfs维护当前事务级别的内部计数值。实际SQLite事务仅在级别超过0时启动,仅在级别降至0时结束。
libsqlfs会话由sqlfs_t类型的对象表示。所有API都需要一个对有效sqlfs_t的引用。每个文件都是libsqlfs内部词汇中的一个“键”。文件元数据以key_attr结构的对象表示。文件内容由key_value结构表示。
除了额外的“类型”之外,文件元数据与预期的常规POSIX文件属性相同,该类型无法通过正常的文件属性函数可见。“类型”用于支持配置注册应用程序的特定需求,可以是以下之一:
空目录整数(32位)双精度浮点数(C语言中的双精度浮点数)字符串(C语言中以空字符结尾的字符串)符号链接(符号链接)布尔型(布尔值)列表(Glib值列表)二进制对象
注意,其他文件系统原语都没有使用“类型”;对于它们来说,所有文件都是blob。在这个阶段,“类型”是针对使用libsqlfs的应用程序的应用逻辑高层使用的。
目前尚未实现一些功能
为了修复锁定问题但提高整体性能,begin_transaction立即获取一个保留锁。这减少了延迟事务中发生的写入锁竞争,并且相比立即独占锁的独占事务性能要好得多。
最初,代码中有几种不同的锁定技术,其中一些被注释掉了,并且实际上只有一种是使用的:sqlite 'begin exclusive'。下面有一个pthread mutex锁,粒度相当大。然后在sqlfs_t_init中,有sqlite3_busy_timeout(),这是为了确保如果 "/" 不存在时创建操作不会失败。
最初,'begin exclusive'只在使用LIBFUSE模式时使用,而不在使用独立库模式时使用,其中使用的是'begin'。但我们发现它太不可靠了,所以我们也将独立模式切换到使用'begin exclusive'。
https://www.sqlite.org/lockingv3.html https://www.sqlite.org/lang_transaction.html https://www.sqlite.org/c3ref/busy_handler.html
在tests/子目录中包含了一个测试套件。它们是C程序和bash脚本的组合。有多种方式可以运行测试。以下是运行所有测试的步骤
make check
如果您想查看所有消息,请开启详细模式
make check V=1
您也可以选择您想要运行的测试
make check TESTS=fuse_sqlfs.test
截至目前,libsqlfs已在32位i386、64位amd64和ARM(Android和Palm Treo 650手机)上进行了测试。它运行在GNU/Linux(Debian、Ubuntu、Mint、Fedora)和Android上,并且很可能在大多数UNIX上运行。
目前,当libsqlfs作为库使用时,已在GNU/Linux(Debian、Mint、Ubuntu、Red Hat、Fedora)上进行了测试,尽管它应该在SQLite可以运行的任何UNIX类似的平台中可用,只需进行最少量的修改。它也应该在Cygwin环境中工作,但这尚未进行测试。欢迎为不同平台的支持提供补丁。
在OS级别使用时,libsqlfs仅支持Linux内核上的FUSE。它应该在Mac OS X上可以通过fuse4x使其工作,以及FreeBSD或Solaris,前提是它们有与Linux FUSE兼容的FUSE。
截至目前,仅支持SQLite和SQLCipher。SQLCipher是SQLite的一个版本,它提供每页AES-256加密。
在sqlfs.c的实现中使用了一个名为INDEX的宏。它为使用索引号的每个写入数据库的函数重新定义。然后,使用该索引号在PREPARE_STMT和DONE_PREPARE宏中与数据库交互。
有关更多信息,请联系:[email protected]
原始作者为:Peter van der Linden [email protected] Andy Tai, [email protected]