PureFS
Filesystems |
---|
Virtual Filesystems |
Disk Filesystems |
CD/DVD Filesystems |
Network Filesystems |
Flash Filesystems |
PureFS is a simple disk file system designed to make working with disks easier.
Due to its structure, it is possible to use a second file system on the disk, you just need to create a 3rd or 4th entry for PureFS in the MBR partition table(SystemID = 0x15).
PureFS 1.1.0
Important: At this time, it is not practical to use this version of the file system as the primary one. It would be more correct to use it only on virtual machines, since they read and write data to a file without seeking, and not to a real disk.
There are only 4 structures:
- PureFsHeader
- PureFsPartitionHeader
- PureFsFile
- PureFsData
Note: all values must be written in Little-Endian format!
PureFsHeader
Contains information about file system.
Offset | Size | Name | Value/Description |
---|---|---|---|
0x00 | 0x08 | Signature | `->PureFS` |
0x08 | 0x04 | Version | Version number as a hex value (for ex.: 1.1.0 = 0x010100) |
0x0C | 0x08 | FirstPartition | Offset to first partition (in sectors) |
0x14 | 0x08 | NumPartitions | Number of PureFS partitions |
0x1C | 0x08 | Checksum | With a byte-by-byte sum of all fields above it should give 0 |
0x1D | 0x1E3 | Reserved | Reserved for future use |
PureFsPartitionHeader
Contains information about partition.
Offset | Size | Name | Value/Description |
---|---|---|---|
0x00 | 0x40 | Name | Name of the partition |
0x40 | 0x08 | Attributes | Partition attributes |
0x48 | 0x08 | NumBlocks | Maximum number of blocks(sectors) in partition |
0x50 | 0x08 | NumFiles | Current number of files in partition |
0x58 | 0x08 | FirstFile | Offset to first file block (in sectors) |
0x60 | 0x08 | LastFile | Offset to last file block (in sectors) |
0x68 | 0x08 | FirstData | Offset to first data block (in sectors) |
0x70 | 0x08 | LastData | Offset to last data block (in sectors) |
0x78 | 0x08 | NextPartition | Offset to next partition (in sectors) |
0x80 | 0x180 | Reserved | Reserved for future use |
Attributes:
- SYSTEM(0x01) - partition required for the OS to function
- BOOTABLE(0x02) - the partition contains a program that can be loaded by the bootloader
- HIDDEN(0x04)
- READONLY(0x08)
- WRITEONLY(0x10)
NextPartition: if this is the last partition, this field is 0
PureFsFile
Contains information about file.
Offset | Size | Name | Value/Description |
---|---|---|---|
0x00 | 0x100 | Name | File name (ASCII) |
0x100 | 0x08 | Attributes | File attributes |
0x108 | 0x08 | NumBlocks | Number of data blocks(`PureFsData`) of the file |
0x110 | 0x08 | OriginalSize | Original file data size |
0x118 | 0x08 | FirstBlock | Offset to first data block(`PureFsData`) (in sectors) |
0x120 | 0x04 | PathHash | Murmur3 32-bit hash of the file path on the partition |
0x124 | 0x04 | ParentHash | Murmur3 32-bit hash of the path to the file's parent directory |
0x128 | 0xD8 | Reserved | Reserved for future use |
Attributes:
- SYSTEM(0x01) - file required for the OS to function
- EXECUTABLE(0x02) - it is possible to run the file as a program
- HIDDEN(0x04)
- READONLY(0x08)
- WRITEONLY(0x10)
- DIRECTORY(0x20) - it is a directory and not a file
- SYMLINK(0x40) - it is a symbolic link to a file
OriginalSize: if the file is empty or it is a directory, it is equal to 0
FirstBlock: if the file is empty or it is a directory, it is equal to 0
PureFsData
Contains one of the file's data parts.
Offset | Size | Name | Value/Description |
---|---|---|---|
0x00 | 0x1F8 | Data | One of the file data parts |
0x1F8 | 0x08 | NextData | Offset to next data block(`PureFsData`) (in sectors) |
NextData: if this is the last block of data, this field is 0
Implementation Of Some Functions
purefs.h:
#pragma once
#ifndef PUREFS_H
#define PUREFS_H
#include <stdint.h>
#include <string.h>
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
#pragma pack(push, 1)
#define PUREFS_SIGNATURE "->PureFS"
#define PUREFS_V1_1_0 0x010100
#define PUREFS_SYSID 0x15
#define PUREFS_MURMUR3_SEED 0x4E53544B
typedef struct _PureFsHeader {
i8 Signature[8];
u32 Version;
u64 FirstPartition;
u64 NumPartitions;
u8 Checksum;
u8 Reserved[0x1E3];
} PureFsHeader;
#define PUREFS_PARTITION_ATR_SYSTEM 0x01
#define PUREFS_PARTITION_ATR_BOOTABLE 0x02
#define PUREFS_PARTITION_ATR_HIDDEN 0x04
#define PUREFS_PARTITION_ATR_READONLY 0x08
#define PUREFS_PARTITION_ATR_WRITEONLY 0x10
typedef struct _PureFsPartitionHeader {
i8 Name[64];
u64 Attributes;
u64 NumBlocks;
u64 NumFiles;
u64 FirstFile;
u64 LastFile;
u64 FirstData;
u64 LastData;
u64 NextPartition;
u8 Reserved[0x180];
} PureFsPartitionHeader;
#define PUREFS_FILE_ATR_SYSTEM 0x01
#define PUREFS_FILE_ATR_EXECUTABLE 0x02
#define PUREFS_FILE_ATR_HIDDEN 0x04
#define PUREFS_FILE_ATR_READONLY 0x08
#define PUREFS_FILE_ATR_WRITEONLY 0x10
#define PUREFS_FILE_ATR_DIRECTORY 0x20
#define PUREFS_FILE_ATR_SYMLINK 0x40
typedef struct _PureFsFile {
i8 Name[256];
u64 Attributes;
u64 NumBlocks;
u64 OriginalSize;
u64 FirstBlock;
u32 PathHash;
u32 ParentHash;
u8 Reserved[0xD8];
} PureFsFile;
typedef struct _PureFsData {
u8 Data[504];
u64 NextData;
} PureFsData;
#pragma pack(pop)
extern size_t ReadSector(void *disk, u64 lba, u8 *buf);
extern u32 murmur3_32(u8 *key, size_t len, u32 seed);
size_t PureFsCheckHeader(PureFsHeader *h);
u64 PureFsReadHeader(void *disk, PureFsHeader *h);
u64 PureFsReadPartitionHeader(void *disk, const char *name, PureFsHeader *fsh, PureFsPartitionHeader *ph);
u64 PureFsReadFile(void *disk, const char *path, PureFsHeader *h, PureFsFile *buf1, u8 *buf2);
#endif
purefs.c:
#include <purefs.h>
// Returns: 0 - invalid PureFS header, 1 - valid
size_t PureFsCheckHeader(PureFsHeader *h) {
u8 csum;
u8 i;
if (!strcmp(h->Signature, PUREFS_SIGNATURE) || h->Version != PUREFS_V1_1_0) return 0;
csum = 0;
for (i = 0; i < sizeof(PureFsHeader); ++i) csum += ((u8*)h)[i];
return !csum;
}
// Returns LBA of the sector with PureFsHeader or 0
u64 PureFsReadHeader(void *disk, PureFsHeader *h) {
u8 MBR[512];
size_t off;
size_t i;
u64 lba;
if (!h || !ReadSector(disk, 0, &MBR[0])) return 0;
// Finding a PureFS entry in the MBR partition table
lba = 0;
off = 440 + 4 + 2;
for (i = 0; i < 4; ++i) {
// We check that the partition is active and its ID is PureFS(0x15)
if (*((u8*)off) & 0x80 && *((u8*)(off + 4)) == PUREFS_SYSID) {
lba = (u64)*((u32*)(off + 8));
if (lba) break;
}
off += 16;
}
if (!lba || !ReadSector(disk, lba, h) || !PureFsCheckHeader(h)) return 0;
return lba;
}
// Returns LBA of the sector with PureFsPartitionHeader or 0
u64 PureFsReadPartitionHeader(void *disk, const char *name, PureFsHeader *fsh, PureFsPartitionHeader *ph) {
u64 lba;
if (!fsh->FirstPartition || !fsh->NumPartitions) return 0;
lba = (u64)fsh->FirstPartition;
do {
if (!ReadSector(disk, lba, ph)) return 0;
if (!strcmp(ph->Name, name)) return lba;
lba += ph->NextPartition;
} while (ph->NextPartition);
return 0;
}
// Returns LBA of the sector with PureFsFile or 0
u64 PureFsReadFile(void *disk, const char *path, PureFsHeader *h, PureFsFile *buf1, u8 *buf2) {
PureFsPartitionHeader ph;
char *separator;
char tmp[512];
PureFsFile f;
size_t found;
size_t len;
u32 hash;
u64 lba;
u64 i;
u64 j;
if (!h || !h->FirstPartition || !h->NumPartitions) return 0;
while (*path == '/') ++path;
len = strlen(path);
separator = strchr(path, '/');
if (!separator) return 0;
len = (size_t)separator - (size_t)path;
memcpy(tmp, path, len);
tmp[len] = 0;
path = (char*)((size_t)separator + 1);
// Get file path hash (PARTITION NAME (and the next separator '/') IS NOT INCLUDED!)
hash = murmur3_32(path, strlen(path), PUREFS_MURMUR3_SEED);
// Looking for a partition by name
found = 0;
lba = (u64)h->FirstPartition;
do {
if (!ReadSector(disk, lba, &ph)) return 0;
if (!strcmp(ph.Name, tmp)) {
found = 1;
break;
}
lba += ph.NextPartition;
} while (ph.NextPartition);
if (!found || !ph.NumBlocks || !ph.NumFiles || !ph.FirstFile) return 0;
// Looking for file
found = 0;
lba += ph.FirstFile;
for (i = 0; i < ph.NumFiles; ++i) {
if (!ReadSector(disk, lba, &f)) continue;
if (f.PathHash == hash) {
found = 1;
break;
}
++lba;
}
if (!found) return 0;
// Copy PureFsFile and read data as needed
if (buf1) memcpy(buf1, &f, sizeof(PureFsFile));
if (!(f.Attributes & PUREFS_FILE_ATR_SYMLINK) && f.FirstBlock && buf2) {
len = f.OriginalSize;
j = lba + f.FirstBlock;
for (i = 0; i < f.NumBlocks - 1; ++i) {
if (!ReadSector(disk, j, tmp) || !((PureFsData*)tmp)->NextData) return 0;
memcpy(buf2, tmp, 504);
buf2 = (u8*)((size_t)buf2 + 504);
len -= 504;
j += ((PureFsData*)tmp)->NextData;
}
if (!ReadSector(disk, j, tmp)) return 0;
memcpy(buf2, tmp, len);
}
return lba;
}
FAQs
- How can I create a PureFS image? - the program for this is currently under development.
- How are symbolic links different from files? - Symbolic links are the same files, only the `PureFsFile::FirstBlock` field contains the hash of the path to the file they point to in the first 32 bits, without data blocks.
- How are the directories `.` and `..` written? - These are symbolic links.
- What is the hash of the root directory? - 0x00000000