#include "sfs.h" #include "ata.h" #include "heap.h" #include "string.h" #include "rtc.h" #include "fs.h" #include "sched.h" #include "vfs.h" #define sfsDirectBlocks 10 #define sfsPtrsPerBlock (sfsBlockSize / sizeof(uint32)) /* 128 */ /* 88 bytes: 512/88 = 5 inodes per block (72 bytes/block left unused -- inodeLocation and the format-time inodeTableBlocks math already derive everything from sizeof(SfsInode) generically, so this shrinking density is the honest cost of the extra fields, not a layout hack, same story as 84 bytes/6-per-block was before gid). Addressing is direct (10 blocks) + single indirect (128 blocks) + double indirect (128 * 128 blocks) + triple indirect (128 * 128 * 128 blocks): a max file size of roughly (10 + 128 + 128*128 + 128*128*128) * 512 bytes ~= 1032 MiB, versus ~8 MiB with direct/single/double alone. mtime/ctime (rtc.h's rtcNow(), seconds since the Unix epoch, truncated to 32 bits -- valid through year 2106, same honest scope as rtc.c's own 2000-2099 century limit), mode (fs.h's fsModeOwner*, real chmod(2) bits), uid (fs.h's FbStat.uid, sched.h's currentThreadUid() at creation) and now gid (fs.h's FbStat.gid, sched.h's currentThreadGid() at creation) are the newest fields, genuinely growing the struct like tripleIndirect did before them -- there is no padding left to repurpose. */ typedef struct SfsInode { uint32 type; uint32 size; uint32 linkCount; uint32 blockCount; uint32 direct[sfsDirectBlocks]; uint32 indirect; uint32 doubleIndirect; uint32 tripleIndirect; uint32 mtime; uint32 ctime; uint32 mode; uint32 uid; uint32 gid; } SfsInode; /* Exactly 64 bytes: 512/64 = 8 directory entries per block. name[56] leaves room for sfsNameMax (55) chars plus the null terminator. */ typedef struct SfsDirEntry { uint32 inode; uint32 type; char name[56]; } SfsDirEntry; typedef struct SfsSuperblock { uint32 magic; uint32 blockSize; uint32 totalBlocks; uint32 inodeCount; uint32 inodeBitmapStart; uint32 inodeBitmapBlocks; uint32 blockBitmapStart; uint32 blockBitmapBlocks; uint32 inodeTableStart; uint32 inodeTableBlocks; uint32 dataStart; uint32 rootInode; } SfsSuperblock; #define sfsMagic 0x53465331u /* "SFS1" */ static SfsSuperblock sb; static uint8* inodeBitmap = nullptr; static uint8* blockBitmap = nullptr; static bool isMounted = false; /* See sfsGeneration()'s doc comment (sfs.h). Starts at 1 (not 0) so a caller that zero-initializes its own cache can use generation 0 to mean "never bound to anything yet" without that ever colliding with a real one. */ static uint64 formatGeneration = 1; /* --- internal helpers (forward-declared so definition order can follow the dependency graph without header noise) --- */ static bool bitTest(const uint8* map, uint32 bit); static void bitSet(uint8* map, uint32 bit); static void bitClear(uint8* map, uint32 bit); static bool blockRead(uint32 blockAbs, void* buffer); static bool blockWrite(uint32 blockAbs, const void* buffer); static bool flushInodeBitmap(void); static bool flushBlockBitmap(void); static bool flushSuperblock(void); static void inodeLocation(int32 inodeNum, uint32* blockAbs, uint32* index); static bool readInode(int32 inodeNum, SfsInode* out); static bool writeInodeRaw(int32 inodeNum, const SfsInode* in); static int32 allocInodeNum(void); static void freeInodeNum(int32 inodeNum); static int32 allocDataBlockAbs(void); static void freeDataBlockAbs(uint32 blockAbs); static bool loadOrAllocPointerBlock(SfsInode* inode, uint32* holderAbs, uint8* buf); static bool getOrAllocBlockForWrite(SfsInode* inode, uint32 logicalIndex, uint32* outAbs); static bool getBlockForRead(const SfsInode* inode, uint32 logicalIndex, uint32* outAbs); static bool sfsWriteRaw(SfsInode* inode, int32 inodeNum, uint64 position, const void* data, uint64 length); static bool sfsTruncateInode(SfsInode* inode, int32 inodeNum, uint32 newSize); static bool splitPath(const char* path, char* parentOut, uint64 parentCap, char* leafOut, uint64 leafCap); static int32 dirFind(int32 dirInode, const char* name, uint64 nameLen); static bool dirAdd(int32 dirInode, const char* name, uint64 nameLen, int32 childInode, uint32 type); static bool dirRemove(int32 dirInode, const char* name, uint64 nameLen); static int32 createNode(const char* path, uint32 type); static bool readSymlinkTarget(int32 inodeNum, char* buf, uint64 cap); static int32 lookupFrom(const char* path, bool followFinal, int depth); static bool bitTest(const uint8* map, uint32 bit) { return (map[bit / 8] >> (bit % 8)) & 1u; } static void bitSet(uint8* map, uint32 bit) { map[bit / 8] = (uint8)(map[bit / 8] | (uint8)(1u << (bit % 8))); } static void bitClear(uint8* map, uint32 bit) { map[bit / 8] = (uint8)(map[bit / 8] & (uint8)~(1u << (bit % 8))); } static bool blockRead(uint32 blockAbs, void* buffer) { return ataReadSectors(sfsBaseLba + blockAbs, 1, buffer); } static bool blockWrite(uint32 blockAbs, const void* buffer) { return ataWriteSectors(sfsBaseLba + blockAbs, 1, buffer); } static bool flushInodeBitmap(void) { for (uint32 i = 0; i < sb.inodeBitmapBlocks; i++) if (!blockWrite(sb.inodeBitmapStart + i, inodeBitmap + (uint64)i * sfsBlockSize)) return false; return true; } static bool flushBlockBitmap(void) { for (uint32 i = 0; i < sb.blockBitmapBlocks; i++) if (!blockWrite(sb.blockBitmapStart + i, blockBitmap + (uint64)i * sfsBlockSize)) return false; return true; } static bool flushSuperblock(void) { uint8 buf[sfsBlockSize]; memset(buf, 0, sizeof(buf)); memcpy(buf, &sb, sizeof(sb)); return blockWrite(0, buf); } static void inodeLocation(int32 inodeNum, uint32* blockAbs, uint32* index) { uint32 perBlock = sfsBlockSize / sizeof(SfsInode); *blockAbs = sb.inodeTableStart + (uint32)inodeNum / perBlock; *index = (uint32)inodeNum % perBlock; } /* Shared bound check for readInode/writeInodeRaw below: every REAL inode number allocInodeNum ever hands out satisfies 1 <= n < sb.inodeCount (the loop above never even considers slot 0, reserved as an eternal sentinel). Every one of this file's own internal callers already passes a value it got from allocInodeNum/sfsLookup/dirFind, so this never rejects a legitimate call -- it exists to stop an out-of-range inodeNum (a caller bug, or a deliberately-invalid probe like sfsModeOf(9999)) from silently computing a block address past the real inode table and reading/writing whatever data block happens to live there instead of failing cleanly. */ static bool inodeNumInRange(int32 inodeNum) { return inodeNum > 0 && (uint32)inodeNum < sb.inodeCount; } static bool readInode(int32 inodeNum, SfsInode* out) { if (!inodeNumInRange(inodeNum)) return false; uint32 blk, idx; inodeLocation(inodeNum, &blk, &idx); uint8 buf[sfsBlockSize]; if (!blockRead(blk, buf)) return false; memcpy(out, buf + (uint64)idx * sizeof(SfsInode), sizeof(SfsInode)); return true; } static bool writeInodeRaw(int32 inodeNum, const SfsInode* in) { if (!inodeNumInRange(inodeNum)) return false; uint32 blk, idx; inodeLocation(inodeNum, &blk, &idx); uint8 buf[sfsBlockSize]; if (!blockRead(blk, buf)) return false; memcpy(buf + (uint64)idx * sizeof(SfsInode), in, sizeof(SfsInode)); return blockWrite(blk, buf); } static int32 allocInodeNum(void) { for (uint32 i = 1; i < sb.inodeCount; i++) { if (!bitTest(inodeBitmap, i)) { bitSet(inodeBitmap, i); flushInodeBitmap(); return (int32)i; } } return -1; } static void freeInodeNum(int32 inodeNum) { bitClear(inodeBitmap, (uint32)inodeNum); flushInodeBitmap(); } static int32 allocDataBlockAbs(void) { uint32 dataBlockCount = sb.totalBlocks - sb.dataStart; for (uint32 i = 0; i < dataBlockCount; i++) { if (!bitTest(blockBitmap, i)) { bitSet(blockBitmap, i); flushBlockBitmap(); uint32 abs = sb.dataStart + i; uint8 zero[sfsBlockSize]; memset(zero, 0, sizeof(zero)); blockWrite(abs, zero); return (int32)abs; } } return -1; } static void freeDataBlockAbs(uint32 blockAbs) { bitClear(blockBitmap, blockAbs - sb.dataStart); flushBlockBitmap(); } /* Allocates (if needed) blockAbs's holderAbs on first use and returns the holder block's contents in buf, zeroed for a fresh allocation. Shared by the single-indirect and each level of the double-indirect path below, so the "allocate a pointer block, zero it, write it back" dance is written once. *holderAbs is updated (and inode->blockCount bumped) when a new block is allocated. */ static bool loadOrAllocPointerBlock(SfsInode* inode, uint32* holderAbs, uint8* buf) { if (*holderAbs == 0) { int32 nb = allocDataBlockAbs(); if (nb < 0) return false; *holderAbs = (uint32)nb; inode->blockCount++; memset(buf, 0, sfsBlockSize); return blockWrite(*holderAbs, buf); } return blockRead(*holderAbs, buf); } static bool getOrAllocBlockForWrite(SfsInode* inode, uint32 logicalIndex, uint32* outAbs) { if (logicalIndex < sfsDirectBlocks) { if (inode->direct[logicalIndex] == 0) { int32 nb = allocDataBlockAbs(); if (nb < 0) return false; inode->direct[logicalIndex] = (uint32)nb; inode->blockCount++; } *outAbs = inode->direct[logicalIndex]; return true; } uint32 indIndex = logicalIndex - sfsDirectBlocks; if (indIndex < sfsPtrsPerBlock) { uint8 indBuf[sfsBlockSize]; if (!loadOrAllocPointerBlock(inode, &inode->indirect, indBuf)) return false; uint32* ptrs = (uint32*)indBuf; if (ptrs[indIndex] == 0) { int32 nb = allocDataBlockAbs(); if (nb < 0) return false; ptrs[indIndex] = (uint32)nb; inode->blockCount++; if (!blockWrite(inode->indirect, indBuf)) return false; } *outAbs = ptrs[indIndex]; return true; } uint32 doubleIndex = indIndex - sfsPtrsPerBlock; if (doubleIndex < sfsPtrsPerBlock * sfsPtrsPerBlock) { uint32 outerIndex = doubleIndex / sfsPtrsPerBlock; uint32 innerIndex = doubleIndex % sfsPtrsPerBlock; uint8 outerBuf[sfsBlockSize]; if (!loadOrAllocPointerBlock(inode, &inode->doubleIndirect, outerBuf)) return false;