一个简单的文件系统






4.90/5 (7投票s)
将一个简单的文件系统实现为一个文件。
引言
今天我们将研究一个简单文件系统的规范,然后研究一个用 C# 编写的实现。
背景
不久前,我编写了一个程序,该程序能够使用文件作为文件系统。我一直在互联网上寻找适合我需求的文件系统。这两个链接对于那些想要了解许多不同文件系统的人来说是很好的资源。https://en.wikipedia.org/wiki/List_of_file_systems 和 http://wiki.osdev.org/File_Systems 在查找的过程中,我开始好奇简单文件系统能有多简单。我搜索了一些,但我的 Google 搜索技巧不够强大。所以我决定自己编写一个文件系统。您可以在下载部分找到规范。
只读文件系统
我编写的文件系统称为 Rofs 或只读文件系统。它非常易于使用。以下是包含 Rofs 的磁盘概述。
该系统允许轻松读取,但由于其简单性,它没有属性、容错等功能。有关具体信息,请查看下载部分中的 pdf 文件。
让我们开始编码!
我已经从原始项目中提取了我的实现,该项目是一个可以处理多个文件系统的控制台程序。我们感兴趣的类是Rofs
类。让我们看看它的方法。
/// <summary>
/// Initializes a new instance of the <see cref="Rofs"/> class.
/// </summary>
/// <param name="stream">Stream.</param>
public Rofs(Stream stream) : base(stream)
{
if (!IsRofs(stream))
throw new ArgumentException("The stream doesn't contain a rofs.", "stream");
fileEntries = ReadStructure<ushort>(516);
}
构造函数很简单。检查流是否包含 Rofs 并确定其中的文件数量。
/// <summary>
/// Determines if the <paramref name="disk"/> contains a rofs.
/// </summary>
/// <param name="disk">The stream that represents a hard disk.</param>
/// <returns>True if the disk contains a rofs.</returns>
public static bool IsRofs(Stream disk)
{
disk.Seek(0x0200, SeekOrigin.Begin);
byte[] magic = { 0x52, 0x6f, 0x66, 0x73 };
byte[] data = new byte[4];
disk.Read(data, 0, 4);
return magic.SequenceEqual(data);
}
许多文件系统都有一个用于标识文件系统的魔术数字。在这里,我们检查磁盘上特定位置的一组特定字节,如果该数组与特定的魔术数字匹配,则它是一个 Rofs。正如您可能猜到的那样,这并非万无一失,但它极不可能失败。
/// <summary>
/// Checks if a file exists on the file system.
/// </summary>
/// <param name="path">The path of the file.</param>
/// <returns>true if the file exists; otherwise false.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="path"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// The <paramref name="path"/> contains at least one non ISO-Latin-1 character.
/// </exception>
public override bool Exist(string path)
{
if (path == null)
throw new ArgumentNullException("path");
else if (iso.GetByteCount(path) != path.Length)
throw new ArgumentException("Contains a non ISO-Latin-1 character.", "path");
foreach (FileEntry entry in GetFiles())
{
if (entry.Name == path)
return true;
}
return false;
}
此方法非常简单。循环遍历所有文件条目并检查名称是否匹配,没什么大不了的。
/// <summary> /// Gets all file entries in the file system. /// </summary> /// <returns>The next file entry in the system.</returns> private IEnumerable<FileEntry> GetFiles() { for (ushort i = 0; i < fileEntries; i++) yield return ReadStructure<FileEntry>(518 + 72 * i); }
此方法检索所有文件条目。文件条目是一个结构体,包含文件的名称、位置和大小。这主要用于检索文件。地址 518 是文件条目数组的起始位置。72 是文件条目的大小。使用这两个数字并知道磁盘上的文件总数,我们可以轻松找到磁盘上的所有文件条目。
/// <summary>
/// Opens an already existing file.
/// </summary>
/// <param name="path">An absolute path for the file.</param>
/// <returns>The opened file stream.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="path"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// The <paramref name="path"/> contains at least one non ISO-Latin-1 character.
/// </exception>
/// <exception cref="FileNotFoundException">
/// The file referenced by <paramref name="path"/> does
/// not exist on this system.
/// </exception>
public override Stream Open(string path)
{
if (path == null)
throw new ArgumentNullException("path");
else if (iso.GetByteCount(path) != path.Length)
throw new ArgumentException("Contains a non ISO-Latin-1 character.", "path");
else if (!Exist(path))
throw new FileNotFoundException("The file doesn't exist.", path);
FileEntry entry = GetFile(path);
if (entry != null)
{
FileStream file = new FileStream();
byte[] data = ReadData(entry.Start, entry.Size);
file.Write(data, 0, data.Length);
file.Seek(0, SeekOrigin.Begin);
return file;
}
else
throw new FileNotFoundException("The file could not be found.", path);
}
首先,我们需要找到要打开的文件的文件条目。然后我们检查它的存在。现在我们知道文件存在,我们打开它进行读取。我们将数据从文件系统读取并存储在流中。
/// <summary>
/// Gets the file entry specified by the given <paramref name="path"/>.
/// </summary>
/// <returns>The file entry if it exists; otherwise null.</returns>
/// <param name="path">Path.</param>
private FileEntry GetFile(string path)
{
foreach (FileEntry entry in GetFiles())
{
if (entry.Name == path)
return entry;
}
return null;
}
此方法通过循环遍历每个文件条目,然后将其名称与给定名称进行匹配,从而简化了查找特定文件条目的任务。
/// <summary>
/// Creates a new rofs on the disk.
/// </summary>
/// <param name="destination">The stream where the file system is written to.</param>
/// <param name="files">
/// The files the file system should contain.
/// The key is the name of the file and the stream is the file contents.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="destination"/> is null.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="files"/> contains to many.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="files"/> contains a file path
/// that contains at least one non ISO-Latin-1 character.
/// </exception>
public static void CreateDisk(Stream destination, IDictionary<string, Stream> files)
{
if (destination == null)
throw new ArgumentNullException("destination");
else if (files == null)
throw new ArgumentNullException("files");
if (files.Count > ushort.MaxValue)
throw new ArgumentOutOfRangeException("files", "Too many files.");
byte[] bytes = { 0x52, 0x6f, 0x66, 0x73 };
destination.Seek(512, SeekOrigin.Begin);
destination.Write(bytes, 0, bytes.Length);
bytes = BitConverter.GetBytes((ushort)files.Count);
destination.Write(bytes, 0, bytes.Length);
List<FileEntry> entries = new List<FileEntry>();
uint start = 518 + (uint)(files.Count * 72);
destination.Seek(start, SeekOrigin.Begin);
foreach (KeyValuePair<string, Stream> file in files)
{
if (file.Key.Length != iso.GetByteCount(file.Key))
throw new ArgumentException("Path \"" + file.Key + "\" contains a non ISO-Latin-1 character.", "files");
FileEntry entry = new FileEntry();
entry.Name = file.Key;
entry.Start = start;
entry.Size = (uint)file.Value.Length;
entries.Add(entry);
start += entry.Size;
file.Value.CopyTo(destination);
}
destination.Seek(518, SeekOrigin.Begin);
for (int i = 0; i < entries.Count; i++)
{
bytes = Structures.ToBytes(entries[i]);
destination.Write(bytes, 0, bytes.Length);
}
}
好的,这是最大的方法。由于 Rofs 的结构,当我们预先知道它应该包含的所有文件时,创建它要容易得多。以后可以添加更多文件,但这需要对磁盘上的内容进行大量的重新排列。
但是,在这种情况下,我们预先知道所有文件。首先,我们跳过前 512 个字节,这些字节保留用于引导代码。接下来,我们将魔术数字写入流。现在我们需要告诉 Rofs 它将包含多少个文件。我们可以通过查看 files 字典的长度来知道这一点。
接下来,我们确定文件内容的起始位置。请记住,我们知道文件条目从 518 开始,它们的大小为 72 字节,文件内容放置在最后一个文件条目之后。该等式中唯一的变量是文件条目的数量。由此我们得到公式 y = 72x + 518。这里 x 是文件条目的数量,y 是数据区域的地址。然后,我们通过循环遍历所有给定的文件来写入文件内容。我们确保文件名有效。现在我们有了文件条目,我们可以将它们写入磁盘,这只需将每个FileEntry
转换为byte[]
并将其写入磁盘即可。文件条目的写入顺序不会影响磁盘的有效性。