PureFS

From OSDev.wiki
Revision as of 14:07, 23 May 2024 by osdev>Drkeph (Added some information)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
Filesystems
Virtual Filesystems

VFS

Disk Filesystems
CD/DVD Filesystems
Network Filesystems
Flash Filesystems

PureFS is a simple disk file system designed to quickly read information and data from files and directories.
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

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 file system 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 file system to function
  • EXECUTABLE(0x02) - it is possible to run the file as a program
  • HIDDEN(0x04)
  • READONLY(0x08)
  • WRITEONLY(0x10)
  • DIRECTORY(0x20) - marks that it is a directory and not 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
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);
size_t PureFsReadHeader(void *disk, PureFsHeader *h);
size_t PureFsReadPartitionHeader(void *disk, const char *name, PureFsHeader *fsh, PureFsPartitionHeader *ph);
size_t 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 < 0x1D; ++i) csum == ((u8*)h)[i];
	return !csum;
}

// Returns LBA of the sector with PureFsHeader or 0
size_t 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;
		}
	}

	if (!lba || !ReadSector(disk, lba, h) || !PureFsCheckHeader(h)) return 0;
	return lba;
}

// Returns LBA of the sector with PureFsPartitionHeader or 0
size_t 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)) continue;
		if (!strcmp(ph->Name, name)) return lba;

		lba = ph->NextPartition;
	} while (ph->NextPartition);

	return 0;
}

// Returns LBA of the sector with PureFsFile or 0
size_t 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;

	// Get file path hash
	hash = murmur3_32(path, strlen(path), PUREFS_MURMUR3_SEED);
	
	while (*path == '/') ++path;
	separator = strchr(path, '/');
	if (!separator) return 0;

	len = (size_t)separator - (size_t)path;
	memcpy(tmp, path, len);
	tmp[len] = 0;

	// Looking for a partition by name
	found = 0;
	lba = (u64)h->FirstPartition;
	do {
		if (!ReadSector(disk, lba, &ph)) continue;
		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.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;
		}

		if (!ReadSector(disk, j, tmp)) return 0;
		memcpy(buf2, tmp, len);
	}

	return lba;
}

See also

murmur hash v3