纯 C 中的嵌入式文件系统






4.89/5 (4投票s)
我们实现了一个嵌入式文件系统,以可移植的 C 语言形式为用户代码提供文件服务。
引言
许多程序在运行时需要大量用户未提供的数据。典型的例子是 UI 元素的图像和帮助页面的文本。然而,程序可能需要几乎任何东西。例如,一个填字游戏设计程序需要一个包含英语中所有单词的列表。
有许多方法可以解决这个问题。一种方法是将数据嵌入到编程语言的大型数组中。另一种方法是使用开发程序时随 IDE 一起打包的资源编译器。还有一种方法是随程序附带一个包含必要文件的支持目录。
第三种方法是最容易开发的,它不依赖第三方支持程序,并且在开发的所有阶段都易于编辑和更新文件。但它存在一个问题,即通常不允许将可执行文件与必需的支持目录一起分发。对于消费者产品,特别是游戏,客户可以轻易访问数据。即使是面向技术用户的产品,支持目录也令人烦恼,并且可能使安装复杂化。
因此,解决方案是将支持目录打包,并将其嵌入到可执行文件中。然后提供方法,使程序能够像挂载一样访问文件。换句话说,我们将它们挂载到一个嵌入式虚拟文件系统上。该系统比序列化数据并将其嵌入为数组要昂贵一些,但更容易管理,因为读取外部数据的例程可以读取内部数据,并且因为支持目录可以在最后一刻打包,从而大大简化了编辑。而且还可以允许写入。当可执行文件重新运行时,内部文件系统将重置为默认状态。但是,实现一个“保存设置”的功能非常简单,所有修改过的数据都集中在同一个文件系统中。然后重新引入支持目录。但它可能包含一个单一的扁平文件,虽然它是在用户机器上创建的,但不必随可执行文件一起分发,这是一个很小但经常至关重要的区别。
因此,系统的核心是一个程序,用于序列化一个目录并将其放入可执行文件中,可以使用资源编译器或简单地嵌入字符串,然后是一个文件系统对象,用于反序列化目录并提供对具有层级结构的文件一样的访问,最后是一个例程,用于再次序列化整个系统,以便保存和恢复状态。并且文件系统对象应该用完全可移植的代码编写。它没有任何理由无法在任何平台上工作,从相当小的嵌入式系统到功能齐全的现代 PC。
该系统是为 Baby X 编写的,Baby X 是一个用于小型程序的跨平台 GUI 工具包。虽然可以使用 IDE 资源编译器与 Baby X 一起使用,但程序将无法与其它 IDE 一起使用,因此需要能够轻松地将目录导入 Baby X 程序。但是,系统中没有特定于 Baby X 的内容,它可以在任何独立程序中使用。
背景
作为一个最低限度,虚拟文件系统包含两个组件:一个用于序列化目录以便嵌入的程序,以及一套用于恢复层级结构并将嵌入式数据作为文件访问的例程。
有两种广泛使用的程序可以序列化目录。一种是 Unix 程序“tar”,另一种是压缩实用程序 pkzip。它们都广泛可用,尽管“tar”在 Windows 上不作为标准提供,而 pkzip 在 Unix 上稍微不被推荐,更倾向于生成 .gz 文件的程序。然而,.tar 文件和 .zip 文件都不适合作为要嵌入的序列化目录的文件格式。它们都是二进制的,并且解析起来很复杂。它们还有校验和,可以维护数据的完整性,但使写入更加困难。选择任何一个都会在文件系统例程本身中引入大量代码。
嵌入到应用程序中的 BBX_FileSystem 对象。
因此,我选择了 XML 格式。数据未压缩。而且由于 XML 对于这种类型的数据具有相对较轻的标记,因此文本文件是人类可读且可编辑的,除了文本编辑器,不需要任何特殊工具。文本文件对轻微损坏也很健壮。二进制文件本身就不可读。您可以使用任何编码。我选择了 uuencoding 作为广泛使用的标准。一个损坏的位会破坏许多二进制文件。但是,只有文件本身易受攻击,XML 目录是健壮的。XML 也易于解析,因此可以在运行时将复杂性排除在文件系统之外。而且它几乎是自文档化的。在看到一个示例文件后,一个经验丰富的程序员应该能够编写一个解析器,而无需正式的文件格式规范。
使用我们自己的文件格式意味着我们必须编写序列化目录的程序。然而,由于格式简单,编写这样一个程序并不困难。虽然我们打算将文件嵌入到可执行文件中,并且严格来说我们不需要一个反序列化到目录的程序,但同时提供该程序也是很自然的。现在我们有了一个通用目录归档系统,可以在 tar 不适用时使用。事实上,我们还提供了一套程序来列出归档中的文件、提取它们以及插入新文件。
然后,所有编辑器中最强大的就是一个小型的 shell。所以你可以在 XML 上运行一个 Unix shell,并且拥有像 cd 和 ls 这样的工具来导航和列出文件,以及 cp、rm 和 mv 来移动它们。还有用于原地编辑的设施。它还具有添加外部命令的功能,目前用于提供 grep。我们还有一个 Basic 解释器。 因为,最终,计算机可以被视为一个文件系统的编辑器。
文件系统本身是用 C 编写的。虽然 C++ 的继承非常适合这项工作,并且可以通过派生自 iostream
类来实现一个非常简洁的文件系统。但是,C++ 很难集成到非 C++ 代码中。使用 C,相对容易为大多数可以调用用 C 调用约定编写的外部函数的语言提供绑定。
而且由于 C 标准库的工作方式,我们必须实现 fopen()
和 fclose()
,然后用户就可以在 stdio.h
中以正常方式使用其余文件函数,以读写文件中的字节并操作文件指针。我们通过创建带 tmpfile()
的临时流来实现这一点。这是一种效率不是非常高的方法,但对于现代托管平台上内存充裕的小程序来说,这足够了,并且对于资源受限的嵌入式系统来说,只要文件很小,这也足够了。
现在,在开发过程中,程序员可能希望从外部支持目录加载文件。然后,在他开发时,他拥有主机操作系统提供的所有便利功能,可以轻松编辑该目录中的文件,并立即让他的程序获得结果。然后,在开发结束时,他选择了一小组稳定且必需的支持文件。这时,目录就可以被序列化和嵌入了。更改仍然可能,但它们更多是性能上的。因此,如果文件系统有两个模式会有所帮助。在开发模式下,它只是 stdio
的一个薄层抽象,并将读取请求传递给主机上的支持目录。在嵌入式模式下,它从嵌入式系统提供文件。程序员可以在这两种模式之间切换。
所以,这就是已经开发的系统。它由几个组件组成:运行时文件服务器,用于生成 XML 和编辑它的支持程序,XML 文件格式的文档(如果需要),一个将 XML 转换为嵌入式 C 字符串的程序,以及一个小型但功能齐全的 shell,用于对 XML 进行完全的编辑控制。
FileSystem XML 格式
FileSystem 是一种用于存储目录的 XML 文件格式。它面向 Baby X,并与 BabyXFS(Baby X 文件系统)程序和函数套件一起工作。但是,它独立于 Baby X,并且对任何人开放使用。
什么是 FileSystem XML
FileSystem XML 是一种存储分层数据的格式。根标签是 <FileSystem>,然后是两个后代标签 <directory> 和 <file>。文件是包含数据的叶节点,而目录可以嵌套并包含文件和其他目录。
示例 FileSystem XML 文件
<FileSystem>>
<directory name="owl_and_text">
<directory name="owl">
<file name="Cartoon_Owl_clip_art.png" type="binary">
<![CDATA[M)"E3'U@":H````0#)A$12!````$```@/(8````PF!UQ;````!,G4')$`NZ,'
MI#``;<02$%$5H50S;='=<6=E_W4U4ETHND5R]*N!&<PQ<=<H'@0HX0"F$(GE
M4V-+)9A-;.DE-9A`!(]$(+_1.LA`$@L`!,$P8<!;<O)9)9999)9)9-2SH13O
M7V?W/IQ:D0V28T>.[UGG^^FO7]>?W^[95X_[`-<HSFE\,I#Y:4GFRS2KV*TH
M25I)2E:!LLH4`%PZ5I"P)_>CJ5IZ8)3EZ,1B'O[0Q2T3P88(6O+6\R2"6FV!
More uuencoding removed for brevity
M+"V\!9?&L7&70/8OM,$601^1[G\T8[--DD-W#)]9Z"^TZ'P$M.L/@GXKFYLI
M,7!2BWZH1C967$-;OEPX-/2\H+LLX:V^Q#C-U0L7+44\[Y`$E!2U??NCM>3J
MCD7'DNLEFC%WJN(J_;'.,^P3$/V"*-AFW@OOC3$__B21_%V'AX-M`3WF!E%E
M:*7=)U7,^6T_<U].$>?>%8^\T3QSTY@8OU7(N-RE?BV'$/-;KX!G`Z,K%715
M%B[CGH^`\8`/0-%@O(#I_P#X%M7;!XVX-?;W=Y"/+;;W*]8:Z/_O!MW)SX-<
.9_(`````)5D3$YJ0@)(I
]]>
</file>
</directory>
<file name="readme.txt" type="text">
In the beginning was the Word, and the Word was with God, and the Word was God.
The same was in the beginning with God.
All things were made by Him, and without Him was made nothing that was made:
in Him was life, and the life was the Light of men;
and the Light shineth in darkness, and the darkness did not comprehend it.
</file>
</directory>
</FileSystem>
![]() | 而且它相当简单且自解释。目录有名称,但除此之外只是容器。而文件有名称,可以是二进制或文本。文本以纯 ASCII 存储,而二进制数据则经过 uuencode 处理并放入 CDATA 部分。所以,这是一种非常干净的格式。 |
<FileSystem> 标签
<FileSystem>
<directory name="archive">
<file name="readme.txt" type="text">
and round the neck of the bottles was a paper label, with the
words ‘DRINK ME’ beautifully printed on it in large letters
</file>
<directory>
</FileSystem>
FileSystem 标签标识格式。它应该是根元素,并有一个子元素,始终是一个目录。根目录的名称标识 FileSystem 的名称。FileSystem 元素内只允许目录和文件标签元素。
<directory> 标签
<directory name="archive">
<file name="readme.txt" type = >
and round the neck of the bottles was a paper label, with the
words ‘DRINK ME’ beautifully printed on it in large letters
</file>
<directory name="subfolder">
</directory>
<directory>
directory 标签有一个必需的元素,即名称。它可以按任何顺序包含文件和目录。目录都是普通的,不应具有暗示常见计算机系统中的特殊目录(如符号链接)的名称,例如“..”。目录可以为空。
<file> 标签
<file name="readme.txt" type="text">
and round the neck of the bottles was a paper label, with the
words ‘DRINK ME’ beautifully printed on it in large letters
</file>
<file name="rubbish.bin" type="binary">
<![CDATA[M)"E3'U@":H````0#)A$1%
]]>
</file>
file 标签是包含数据的叶元素。它有两个必需的属性:名称和类型,类型必须是“text”或“binary”。文本数据是纯文本,而二进制数据是 uuencode 的。uuencoding 是一种常见的系统,解码器广泛可用,当然伴随的源代码中也有代码。
主要考虑的是解析极其简单且健壮。文本文件是人类可读的。因此,如果 FileSystem 存档中的文件出现问题,您不需要任何特殊的软件来诊断问题并修复它。只需要一个能够处理大文件的文本编辑器。没有办法使二进制文件人类可读,但 uuencoding 是最简单、使用最广泛的二进制到文本协议之一。几乎任何有编程经验的人都可以编写一个解码器,uuencoding 是一种用于将二进制编码为 ASCII 的相当简单的系统。如果文件损坏并且您丢失了数据,一个业余程序员很可能拥有修复它的技能。
空白处理
XML 中的空白处理很困难。大多数文本文件对添加或修剪一些空白具有健壮性。然而,理想情况下,用户希望文本空白被完美保留。因此,生成文件的程序 babyxfs_dirtoxml 使用了以下系统。在打开的 <file> 标签后立即添加一个换行符。然后在数据后立即添加一个换行符。然后添加一系列制表符来缩进结束标签。因此,通过将开头和结尾的换行符替换为 null,您可以获得原始文本。
以下代码用于修剪文本数据。
if (!strcmp(type, "text"))
{
leading = 0;
len = (int) strlen(data);
for (i = 0; data[i]; i++)
if (!isspace((unsigned char) data[i]) || data[i] == '\n')
break;
if (data[i] == '\n')
leading = i + 1;
trailing = 0;
i = len - 1;
for (i = len - 1; i > 0; i--)
if (!isspace((unsigned char) data[i]) || data[i] == '\n')
break;
if (i > 0 && data[i] == '\n')
trailing = len - i;
if (trailing + leading >= len )
;
else
{
if (fwrite(data + leading, 1, len - trailing - leading, fp) != len - trailing - leading)
goto error_exit;
}
}
您应该使用此算法来修剪文本数据。如果空白未被操纵,前导空格始终为 1,而尾随空格始终是元素的嵌套级别加一,并且是终止文本数据的换行符。
请注意,XML 只允许换行符、回车符和制表符作为 32 以下(空格)的控制字符。一些文本文件有换页符、退格符或其他字符。因此,“binary”标签并不一定意味着数据在主机计算机上是二进制的。
动机
Baby X 资源编译器需要一种方式来打包目录。由于资源编译器的精神是极致的可移植性,因此二进制格式是不可接受的。XML 是自然的选择。还有一种愿望是展示 XML 解析器,它在这方面表现得非常出色。
Baby X 的设计目的是在大型计算机上运行小型或“婴儿”程序,因此没有太多必要通过非常压缩的格式来节省内存。相反,我选择了一种健壮且易于使用的格式,面向业余程序员,当然,职业程序员也完全欢迎使用它,并且像 Baby X 的所有内容一样,它对任何人免费使用。
支持的实用程序
这些实用程序旨在使 FileSystem XML 的工作变得轻松愉快。唯一必不可少的实用程序是 BabyXFS_DirToXML,它将目录序列化为 XML。您始终可以使用主机操作系统来编辑目录。但是,通常更方便的是能够直接检查和编辑 FileSystem XML 文件,而这些实用程序支持这一点。
这些实用程序还使用嵌入的 BBX_FileSystem
对象。因此,它们是关于如何使用该系统的示例代码来源。
BabyXFS_DirToXML
此程序将目录转换为 FileSystem XML,并将结果写入标准输出。
最好将结果写入标准输出,因为它便于管道操作,并且因为它使调用程序无需创建任何文件即可正确调用,然后重定向,这样在弄清楚如何调用程序时就不会创建任何错误的文件。
问题在于尽可能便携地编写它。当前版本使用 Posix,它得到了广泛支持。
BabyXFS_XMLToDir
此程序接收一个 FileSystem XML 文件并从中创建一个目录。
它是 BabyXFS_DirToXML 的配套程序。它稍微复杂一些,因为解析 XML 比写入 XML 更难,并且它使用了 mini XML 解析器,该解析器轻量级且具有非常好的错误报告能力。
同样,这不能以可移植的方式编写,因为它必须在主机上创建目录,因此它使用 Posix,得到了广泛支持。
BabyxFS_Extract
此程序根据路径从 FileSystem XML 文件中提取文件。
它找到文件并将其写入标准输出。因此,FileSystem XML 文件变成了一个方便的公文包。任何时候您需要一个文件,文本或二进制文件,您只需提取它。
BabyXFS_Insert
此程序将一个文件插入到给定路径的 FileSystem XML 文件中。
由于 XML 的结构,插入文件非常容易,并且不需要对文档的其余部分进行任何更改,这对于版本控制很有用。它是您可能想要进行的ただ一行的基本编辑之一,而无需反序列化、将文件添加到刚创建的目录、然后再次序列化的整个过程。
BabyXFS_Rm
此程序从 FilesSystem XML 文件中删除一个文件。
删除文件是另一个必需的编辑功能。同样,无需反序列化整个目录即可方便地完成此操作。您也可以手动用文本编辑器删除文件——XML 足够简单,您可以在不真正冒损坏文件其余部分风险的情况下做到这一点。
与 BabyXFS_Extract 和 BabyXFS_Insert 一起,您可以提取一个文件,在主机计算机上对其进行编辑,然后重新插入,并删除旧版本,这样您就拥有了一套强大的编辑程序。
BabyXFS_Ls
此程序列出 FileSystem XML 文件中给定目录的内容。
如果您收到一个大型、未知的 FileSystem XML 文件,这是必不可少的。您需要能够在查询内容之前列出它包含的文件。此版本接受通配符或 glob。
BabyXFS_Shell
此程序运行一个交互式的类 Unix shell,使用 FileSystem XML 文件作为挂载的文件系统。
运行一个 FileSystem XML 文件的 shell 并将其用作后备存储,是编辑 FileSystem XML 的最强大方法。因为归根结底,shell 就是一个文件系统的编辑器。
作为 MiniBasic 的作者,将 Basic 脚本添加到 shell 中,将其变成一个独立的迷你计算机的诱惑是无法抗拒的。在这里我们无法充分介绍 shell。
但可以说,您登录 shell,然后就可以使用 cd、ls、cp、rm、mv 和 cat,这些是导航和移动文件所需的基本 Unix 命令。然后我们有特色命令:import 和 export,它们将文件写入主机操作系统并上传。还有 system,所以您可以对这些文件调用主机操作系统命令。然后有一个 grep,还有一个相当完善的帮助系统。
另一个必需的命令是 edit,所以您可以就地编辑文件,这对于可移植 shell 来说有点麻烦,因为在可移植 C 中无法捕获箭头键。即使有,编写编辑器也是一项艰巨的任务。所以 edit 命令默认调用“nano”程序,并且有一个选项可以设置您自己的编辑器。
BBX_FileSystem 对象
我们创建一个 BBX_FileSystem *
作为虚拟对象。通常它将是一个单例,要么是全局的,要么是 main 的局部变量并向下传递,但这并非必需,您可以创建任意数量的文件系统。
最基本的是,我们需要实现 bbx_filesystem_fopen()
和 bbx_filesysem_fclose()
。bbx_filesystem_fopen()
返回一个 FILE *
,调用者可以使用任何 stdio.h
函数(除了 fclose()
和 freopen()
等非常规函数)来使用它。我们需要 bbx_filesystem_fclose()
,因为如果流用于写入,我们将在此时将数据刷新到存储。我们还限制用户打开超过 FOPEN_MAX
个文件,就像 stdio 一样。并且我们会在 BBX_FileSystem
对象被销毁且仍有打开文件时发出警告。虽然尚未实现,但通过关闭,也可以提供锁定和线程安全。
现在 BBX_FileSystem
的主要问题是它很“贪婪”。原始数据以 FileSystem XML 的形式嵌入为 ASCII 字符串。然后我们运行一个 XML 解析器来解析字符串,并将整个文档加载到 RAM 中。然后,在提供每个单独文件时,它会被复制到一个内存中的临时文件中。对于在 PC 上运行的程序来说,这不太可能成为问题。它们安装了许多 GB 的 RAM,这是一种非常丰富的资源。但是 C 通常用于小型系统。因此,未来的工作可能会改变 BBX_FileSystem
的内部结构,使其不那么浪费。
另一个特性是在 stdio 和内部字符串之间切换。在实践中,这应该极大地简化开发。
由于所有操作都是虚拟的,因此 BBX_FileSystem
速度极快。为了使其速度更快,我提供了 slurp 函数来将整个文件读入用户空间内存,以避免创建临时流的开销 tmpfile()
。
这里有一个示例程序。
#include <stdio.h>
#include <stdlib.h>
#include "bbx_filesystem.h"
#include "xmlparser2.h"
int main(void)
{
FILE *fp = 0;
XMLDOC *doc = 0;
BBX_FileSystem *bbx_fs= 0;
int err;
bbx_fs = bbx_filesystem();
err = bbx_filesystem_set(bbx_fs, xmlstring, BBX_FS_STRING);
/* comment in for stdio
err = bbx_filesystem_set(bbx_fs, "C://Users/FredBloggs/Rogue", BBX_FS_STDIO);
*/
if (err)
{
fprintf(stderr, "Can't set up XML filessystem\n");
exit(EXIT_FAILURE);
}
fp = bbx_filesystem_fopen(bbx_fs, "/Rogue/Levels/level1.xml", "r");
if (!fp)
{
fprintf(stderr, "Can't open target file on bbx_filesystem\n");
exit(EXIT_FAILURE);
}
/* We've got a FILE *, use in normal way, e.g. load a level in XML and play */
doc = loadxmldocument(fp);
play_rogue(doc);
err = bbx_filesystem_fclose(bbx_fs, fp);
if (err)
fprintf(stderr, "error closing xml file\n");
bbx_filesystem_kill(bbx_fs_xml);
return 0;
}
没什么特别难的。它易于使用和设置。 需要记住的主要规则是始终编写通过 stdio 流执行 IO 的函数,以便它们可以接受路径或打开的流。但是,如果这样做,修改并不困难。但您需要替换所有对 fopen()
和 fclose()
的调用。
另一个问题是是否支持写入。如果您不支持写入,那么编写一个非常高效的小型嵌入式系统服务器会容易得多,因为您可以直接从嵌入式字符串读取内存。然而,该系统在支持写入的情况下潜力要大得多,而且它现在可以实现嵌入式数据数组无法实现的功能。
使用代码
FileSystem 代码公开了一个对象,即 BBX_FileSystem,并由三个源文件组成
- xmlparser2.c
- bbx_write_source_archive.c
- bbx_filesystem.c
它们还带有相关的头文件。您需要将这些文件添加到您的应用程序中才能使用该文件系统。
函数
这些是库中的函数。
//
// bbx_filesystem.h
// babyxfs
//
// Created by Malcolm McLean on 31/05/2024.
//
#ifndef bbx_filesystem_h
#define bbx_filesystem_h
#define BBX_FS_STDIO 1
#define BBX_FS_STRING 2
typedef struct bbx_filesystem BBX_FileSystem;
BBX_FileSystem *bbx_filesystem(void);
void bbx_filesystem_kill(BBX_FileSystem *bbx_fs);
int bbx_filesystem_set(BBX_FileSystem *bbx_fs, const char *pathorxml, int mode);
FILE *bbx_filesystem_fopen(BBX_FileSystem *bbx_fs, const char *path, const char *mode);
int bbx_filesystem_fclose(BBX_FileSystem *bbx_fs, FILE *fp);
char *bbx_filesystem_slurp(BBX_FileSystem *bbx_fs, const char *path, const char *mode);
unsigned char *bbx_filesystem_slurpb(BBX_FileSystem *bbx_fs, const char *path, const char *mode, int *N);
int bbx_filesystem_unlink(BBX_FileSystem *bbx_fs, const char *path);
const char *bbx_filesystem_getname(BBX_FileSystem *bbx_fs);
int bbx_filesystem_setreadir(BBX_FileSystem *bbx_fs, char **(*fptr)(const char *path, void *ptr), void *ptr);
int bbx_filesystem_dump(BBX_FileSystem *bbx_fs, FILE *fp);
char **bbx_filesystem_mkdir(BBX_FileSystem *bbx_fs, const char *path);
char **bbx_filesystem_rmdir(BBX_FileSystem *bbx_fs, const char *path);
char **bbx_filesystem_list(BBX_FileSystem *bbx_fs, const char *path);
#endif /* bbx_filesystem_h */
bbx_filesystem
构造一个空的 BBX_FileSystem
对象。
BBX_FileSystem *bbx_filesystem(void);
Returns: the constructed BBX_FileSystem object.
bbx_filesystem_kill
销毁一个 BBX_FileSystem
对象。
int bbx_filesystem_set(BBX_FileSystem *bbx_fs,
const char *pathorxml, int mode);
Params: bbx_fs - the BBX_FileSystem object. pathorxml - the host directory to mount, or FileSystemXML. mode - BBX_FS_STDIO - pathorxml is a directory on the host. BBX_FS_STRING - pathorxml is FileSystem XML (as a string in memory, not a path). Returns: 0 on success, -1 on failure.
将 FileSystem XML 字符串传递给它来挂载系统。或者传递一个主机目录,尽管 BBX_FS_STDIO
模式未经完全测试。函数返回后,您可以释放 XML 字符串,它之后不再使用它。
bbx_filesystem_fopen
从 BBX_FileSystem
对象打开一个文件。
FILE *bbx_filesystem_fopen(BBX_FileSystem *bbx_fs,
const char *path, const char *mode);
Params: bbx_fs - the BBX_FileSystem object. path - the path to the file to open mode - the mode, "r" to read and "w" to write. Returns: pointer to the opened stream, 0 on failure.
这是 BBX_FileSystem
的 fopen()
函数。FILE *
必须使用 bbx_filesystem_fclose()
关闭,它们不能传递给 stdio fclose()
。但否则,fputc()
和 fprintf()
等 stdio 函数可以对它们进行调用。
bbx_filesystem_fclose
关闭从 BBX_FileSystem
对象打开的文件。
int bbx_filesystem_fclose(BBX_FileSystem *bbx_fs, FILE *fp);
Params: bbx_fs - the BBX_FileSystem object. fp - a FILE pointer returned from bbx_filesystem_fopen. Returns: on success, -1 on failure (which can happen).
这是 BBX_FileSystem
的 fclose()
函数。FILE *
必须是从 bbx_filesystem_fopen
返回的。所有使用 bbx_filesysytem_fopen
打开的文件指针都必须使用此函数关闭,并且绝不能将使用 stdio.h 函数创建的 FILE *
传递给它。请注意,如果文件是以“w”模式打开的,该函数可能会失败。这对于 stdio fclose()
也是如此,但很少有程序员 bother to check。使用此函数,它可能会失败。
bbx_filesystem_slurp
从 BBX_FileSystem
对象加载整个文本文件。
char *bbx_filesystem_slurp(BBX_FileSystem *bbx_fs,
const char *path, const char *mode);
Params: bbx_fs - the BBX_FileSystem object. path - the path to the file. mode - read mode, should be "r" but some host operating systems will insist on "rt". Returns: allocated pointer to file contents, 0 on failure.
加载整个文件。如果文件是二进制的,您将获得奇怪的结果。在 BBX_FS_STRING
模式下,模式应为“r”或至少是一个以“r”开头的小字符串。该参数用于强制那些坚持“rt”或替代项的 stdio 实现执行正确的操作。
bbx_filesystem_slurpb
从 BBX_FileSystem 对象加载整个二进制文件。
unsigned char *bbx_filesystem_slurpb(BBX_FileSystem *bbx_fs,
const char *path, const char *mode, int *N);
Params: bbx_fs - the BBX_FileSystem object. path - the pathn to the file. mode - read mode, should be "r" but some host operating systemes will insist on "rb". N - return for the number of bytes read. Returns: allocated pointer to file contents.
加载整个文件。如果文件是文本,您将获得终止的 null 字符,但依赖它是不好的做法。完成后需要释放指针。
bbx_filesystem_unlink
从 BBX_FileSystem
对象中删除一个文件。
int bbx_filesystem_unlink(BBX_FileSystem *bbx_fs, const char *path);
Params: bbx_fs - the BBX_FileSystem object. path - the path to the file to delete. Returns: 0 on success, -1 on fail.
它将文件从目录树中解除链接。在 BBX_FS_STRING
模式下,这等同于删除。在 stdio 系统上,解除链接可能不会删除文件,并且可能存在指向同一文件的其他链接。
bbx_filesystem_getname
获取 BBX_FileSystem
对象中挂载的文件系统的名称。
const char *bbx_filesystem_getname(BBX_FileSystem *bbx_fs);
Params: bbx_fs - the BBX_FileSystem object. Returns: the name of the filessyrm mounted within it.
FileSystem XML 应该有一个作为子元素的单个目录节点,这是数据的根。名称是该节点的名称。
bbx_filesystem_setreadir
在主机挂载的 BBX_FileSystem
系统上设置一个用于读取目录的函数。
int bbx_filesystem_setreadir(BBX_FileSystem *bbx_fs,
char **(*fptr)(const char *path, void *ptr), void *ptr);
Params: bbx_fs - the BBX_FileSystem object. fptr - a function which will read a directory on a hosted file system. Returns: 0 on success, -1 on failure.
ANSI C 没有提供读取目录的函数,因此 bbx_filesystem_list
函数只能通过为 BBX_FS_STDIO
系统提供此函数来实现。它应该返回当前工作目录中所有文件的列表。
bbx_filesystem_dump
将 BBX_Filesystem
对象的所有内容写入 FileSysytem XML 文件。
int bbx_filesystem_dump(BBX_FileSystem *bbx_fs, FILE *fp);
Params: bbx_fs - the BBX_FileSystem object. fp - stream to write XML to. Returns: 0 on success, -1 on failure.
这是保存系统状态的一种简单方法。但是,它只适用于 BBX_FS_STRING 模式的文件系统。
bbx_filesystem_mkdir
在 BBX_FileSystem
对象上创建一个目录。
char **bbx_filesystem_rmdir(BBX_FileSystem *bbx_fs, const char *path);
Params: bbx_fs - the BBX_FileSystem object. pth - path to a empty directory. Returns: a programming error, the pointer is useless.
这只能对 BBX_FS_STRING
系统进行。BBX_FS_STDIO
系统没有办法。目录必须为空。否则,很容易意外删除一个父节点而丢失所有数据。
bbx_filesystem_list
列出 BBX_FileSystem
目录的内容。
char **bbx_filesystem_list(BBX_FileSystem *bbx_fs, const char *path);
Params: bbx_fs - the BBX_FileSystem object. path - path to a directory. Returns: allocated pointer to allocated list of strings.
这会将目录的内容作为分配的字符串的简单列表返回,形式为
mydirectory/ myfolder/ mymate.txt myenemy.bin anotherfolder/
目录以尾随的 / 表示。不应将顺序视为有任何意义。
关注点
Baby X 程序需要一种方法来轻松导入目录,这就是答案。编写它是非常愉快的项目,并且它利用了 XML 解析器。亮点是实现了 shell。只有 shell 的“严肃”部分是稳定的。实际上,人们只会使用基本的 Unix 命令。他们不会尝试在 shell 本身中进行复杂的编辑。当然,我们必须提供脚本功能。
主要的挑战是使所有内容都具有可移植性,而不调用标准库中不存在的函数。当从目录创建 XML 文件或从 XML 文件创建目录时,这是不可能的。因此,我不得不妥协并使用 Posix。还有一个标准的 C++ “filesystem”头文件,它提供了使用目录的功能,但它处于开发早期阶段。主要问题似乎是政治性的,以及一些大型软件公司拒绝承认 UTF-8 是能够正常工作的 Unicode 标准。有人可能想编写 babyxfs_dirtixml 和 babyxfs_xmltodir,以便它们使用 Windows 原生的目录 API (FindFirstFile, FindNextFile)。
但我们有一个完全可移植的 ANSI C 编写的 shell。它本身就是一个迷你计算机。能够拥有这一点相当了不起。
历史
代码维护在
https://github.com/MalcolmMcLean/babyxrc
这是第一个版本。这是一个持续的项目,会有更新。