1427 lines
45 KiB
C
1427 lines
45 KiB
C
#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;
|
|
|
|
uint32* outerPtrs = (uint32*)outerBuf;
|
|
uint8 innerBuf[sfsBlockSize];
|
|
bool outerNeedsFlush = false;
|
|
if (outerPtrs[outerIndex] == 0) {
|
|
int32 nb = allocDataBlockAbs();
|
|
if (nb < 0)
|
|
return false;
|
|
outerPtrs[outerIndex] = (uint32)nb;
|
|
inode->blockCount++;
|
|
outerNeedsFlush = true;
|
|
memset(innerBuf, 0, sizeof(innerBuf));
|
|
if (!blockWrite(outerPtrs[outerIndex], innerBuf))
|
|
return false;
|
|
} else if (!blockRead(outerPtrs[outerIndex], innerBuf)) {
|
|
return false;
|
|
}
|
|
if (outerNeedsFlush && !blockWrite(inode->doubleIndirect, outerBuf))
|
|
return false;
|
|
|
|
uint32* innerPtrs = (uint32*)innerBuf;
|
|
if (innerPtrs[innerIndex] == 0) {
|
|
int32 nb = allocDataBlockAbs();
|
|
if (nb < 0)
|
|
return false;
|
|
innerPtrs[innerIndex] = (uint32)nb;
|
|
inode->blockCount++;
|
|
if (!blockWrite(outerPtrs[outerIndex], innerBuf))
|
|
return false;
|
|
}
|
|
*outAbs = innerPtrs[innerIndex];
|
|
return true;
|
|
}
|
|
|
|
/* Same three-level shape as the double-indirect block above, one level
|
|
deeper: tripleIndirect -> top (128 mid-block pointers) -> mid (128
|
|
leaf-block pointers) -> leaf (128 data-block pointers) -> data. */
|
|
uint32 tripleIndex = doubleIndex - sfsPtrsPerBlock * sfsPtrsPerBlock;
|
|
uint32 topIndex = tripleIndex / (sfsPtrsPerBlock * sfsPtrsPerBlock);
|
|
uint32 midIndex = (tripleIndex / sfsPtrsPerBlock) % sfsPtrsPerBlock;
|
|
uint32 leafIndex = tripleIndex % sfsPtrsPerBlock;
|
|
if (topIndex >= sfsPtrsPerBlock)
|
|
return false; /* past the addressing limit (~1 GiB) */
|
|
|
|
uint8 topBuf[sfsBlockSize];
|
|
if (!loadOrAllocPointerBlock(inode, &inode->tripleIndirect, topBuf))
|
|
return false;
|
|
|
|
uint32* topPtrs = (uint32*)topBuf;
|
|
uint8 midBuf[sfsBlockSize];
|
|
bool topNeedsFlush = false;
|
|
if (topPtrs[topIndex] == 0) {
|
|
int32 nb = allocDataBlockAbs();
|
|
if (nb < 0)
|
|
return false;
|
|
topPtrs[topIndex] = (uint32)nb;
|
|
inode->blockCount++;
|
|
topNeedsFlush = true;
|
|
memset(midBuf, 0, sizeof(midBuf));
|
|
if (!blockWrite(topPtrs[topIndex], midBuf))
|
|
return false;
|
|
} else if (!blockRead(topPtrs[topIndex], midBuf)) {
|
|
return false;
|
|
}
|
|
if (topNeedsFlush && !blockWrite(inode->tripleIndirect, topBuf))
|
|
return false;
|
|
|
|
uint32* midPtrs = (uint32*)midBuf;
|
|
uint8 leafBuf[sfsBlockSize];
|
|
bool midNeedsFlush = false;
|
|
if (midPtrs[midIndex] == 0) {
|
|
int32 nb = allocDataBlockAbs();
|
|
if (nb < 0)
|
|
return false;
|
|
midPtrs[midIndex] = (uint32)nb;
|
|
inode->blockCount++;
|
|
midNeedsFlush = true;
|
|
memset(leafBuf, 0, sizeof(leafBuf));
|
|
if (!blockWrite(midPtrs[midIndex], leafBuf))
|
|
return false;
|
|
} else if (!blockRead(midPtrs[midIndex], leafBuf)) {
|
|
return false;
|
|
}
|
|
if (midNeedsFlush && !blockWrite(topPtrs[topIndex], midBuf))
|
|
return false;
|
|
|
|
uint32* leafPtrs = (uint32*)leafBuf;
|
|
if (leafPtrs[leafIndex] == 0) {
|
|
int32 nb = allocDataBlockAbs();
|
|
if (nb < 0)
|
|
return false;
|
|
leafPtrs[leafIndex] = (uint32)nb;
|
|
inode->blockCount++;
|
|
if (!blockWrite(midPtrs[midIndex], leafBuf))
|
|
return false;
|
|
}
|
|
*outAbs = leafPtrs[leafIndex];
|
|
return true;
|
|
}
|
|
|
|
static bool getBlockForRead(const SfsInode* inode, uint32 logicalIndex, uint32* outAbs)
|
|
{
|
|
if (logicalIndex < sfsDirectBlocks) {
|
|
*outAbs = inode->direct[logicalIndex];
|
|
return *outAbs != 0;
|
|
}
|
|
|
|
uint32 indIndex = logicalIndex - sfsDirectBlocks;
|
|
if (indIndex < sfsPtrsPerBlock) {
|
|
if (inode->indirect == 0)
|
|
return false;
|
|
uint8 indBuf[sfsBlockSize];
|
|
if (!blockRead(inode->indirect, indBuf))
|
|
return false;
|
|
uint32* ptrs = (uint32*)indBuf;
|
|
*outAbs = ptrs[indIndex];
|
|
return *outAbs != 0;
|
|
}
|
|
|
|
uint32 doubleIndex = indIndex - sfsPtrsPerBlock;
|
|
if (doubleIndex < sfsPtrsPerBlock * sfsPtrsPerBlock) {
|
|
uint32 outerIndex = doubleIndex / sfsPtrsPerBlock;
|
|
uint32 innerIndex = doubleIndex % sfsPtrsPerBlock;
|
|
if (inode->doubleIndirect == 0)
|
|
return false;
|
|
|
|
uint8 outerBuf[sfsBlockSize];
|
|
if (!blockRead(inode->doubleIndirect, outerBuf))
|
|
return false;
|
|
uint32* outerPtrs = (uint32*)outerBuf;
|
|
if (outerPtrs[outerIndex] == 0)
|
|
return false;
|
|
|
|
uint8 innerBuf[sfsBlockSize];
|
|
if (!blockRead(outerPtrs[outerIndex], innerBuf))
|
|
return false;
|
|
uint32* innerPtrs = (uint32*)innerBuf;
|
|
*outAbs = innerPtrs[innerIndex];
|
|
return *outAbs != 0;
|
|
}
|
|
|
|
uint32 tripleIndex = doubleIndex - sfsPtrsPerBlock * sfsPtrsPerBlock;
|
|
uint32 topIndex = tripleIndex / (sfsPtrsPerBlock * sfsPtrsPerBlock);
|
|
uint32 midIndex = (tripleIndex / sfsPtrsPerBlock) % sfsPtrsPerBlock;
|
|
uint32 leafIndex = tripleIndex % sfsPtrsPerBlock;
|
|
if (topIndex >= sfsPtrsPerBlock || inode->tripleIndirect == 0)
|
|
return false;
|
|
|
|
uint8 topBuf[sfsBlockSize];
|
|
if (!blockRead(inode->tripleIndirect, topBuf))
|
|
return false;
|
|
uint32* topPtrs = (uint32*)topBuf;
|
|
if (topPtrs[topIndex] == 0)
|
|
return false;
|
|
|
|
uint8 midBuf[sfsBlockSize];
|
|
if (!blockRead(topPtrs[topIndex], midBuf))
|
|
return false;
|
|
uint32* midPtrs = (uint32*)midBuf;
|
|
if (midPtrs[midIndex] == 0)
|
|
return false;
|
|
|
|
uint8 leafBuf[sfsBlockSize];
|
|
if (!blockRead(midPtrs[midIndex], leafBuf))
|
|
return false;
|
|
uint32* leafPtrs = (uint32*)leafBuf;
|
|
*outAbs = leafPtrs[leafIndex];
|
|
return *outAbs != 0;
|
|
}
|
|
|
|
static bool sfsWriteRaw(SfsInode* inode, int32 inodeNum, uint64 position, const void* data, uint64 length)
|
|
{
|
|
if (length == 0)
|
|
return true;
|
|
|
|
const uint8* src = (const uint8*)data;
|
|
uint64 done = 0;
|
|
while (done < length) {
|
|
uint64 pos = position + done;
|
|
uint32 logicalBlock = (uint32)(pos / sfsBlockSize);
|
|
uint32 within = (uint32)(pos % sfsBlockSize);
|
|
uint32 chunk = sfsBlockSize - within;
|
|
if ((uint64)chunk > length - done)
|
|
chunk = (uint32)(length - done);
|
|
|
|
uint32 blockAbs;
|
|
if (!getOrAllocBlockForWrite(inode, logicalBlock, &blockAbs))
|
|
return false;
|
|
|
|
uint8 blockBuf[sfsBlockSize];
|
|
if (chunk == sfsBlockSize) {
|
|
if (src)
|
|
memcpy(blockBuf, src + done, sfsBlockSize);
|
|
else
|
|
memset(blockBuf, 0, sfsBlockSize);
|
|
} else {
|
|
if (!blockRead(blockAbs, blockBuf))
|
|
return false;
|
|
if (src)
|
|
memcpy(blockBuf + within, src + done, chunk);
|
|
else
|
|
memset(blockBuf + within, 0, chunk);
|
|
}
|
|
if (!blockWrite(blockAbs, blockBuf))
|
|
return false;
|
|
|
|
done += chunk;
|
|
}
|
|
|
|
uint64 newSize = position + length;
|
|
if (newSize > inode->size)
|
|
inode->size = (uint32)newSize;
|
|
inode->mtime = inode->ctime = (uint32)rtcNow();
|
|
return writeInodeRaw(inodeNum, inode);
|
|
}
|
|
|
|
static bool sfsTruncateInode(SfsInode* inode, int32 inodeNum, uint32 newSize)
|
|
{
|
|
uint32 oldBlocks = inode->size ? (inode->size + sfsBlockSize - 1) / sfsBlockSize : 0;
|
|
uint32 neededBlocks = newSize ? (newSize + sfsBlockSize - 1) / sfsBlockSize : 0;
|
|
|
|
for (uint32 i = neededBlocks; i < oldBlocks; i++) {
|
|
uint32 abs = 0;
|
|
if (i < sfsDirectBlocks) {
|
|
abs = inode->direct[i];
|
|
inode->direct[i] = 0;
|
|
} else {
|
|
uint32 indIndex = i - sfsDirectBlocks;
|
|
if (indIndex < sfsPtrsPerBlock) {
|
|
if (inode->indirect == 0)
|
|
continue;
|
|
uint8 indBuf[sfsBlockSize];
|
|
if (!blockRead(inode->indirect, indBuf))
|
|
continue;
|
|
uint32* ptrs = (uint32*)indBuf;
|
|
abs = ptrs[indIndex];
|
|
ptrs[indIndex] = 0;
|
|
blockWrite(inode->indirect, indBuf);
|
|
} else {
|
|
uint32 doubleIndex = indIndex - sfsPtrsPerBlock;
|
|
if (doubleIndex < sfsPtrsPerBlock * sfsPtrsPerBlock) {
|
|
uint32 outerIndex = doubleIndex / sfsPtrsPerBlock;
|
|
uint32 innerIndex = doubleIndex % sfsPtrsPerBlock;
|
|
if (inode->doubleIndirect == 0)
|
|
continue;
|
|
uint8 outerBuf[sfsBlockSize];
|
|
if (!blockRead(inode->doubleIndirect, outerBuf))
|
|
continue;
|
|
uint32* outerPtrs = (uint32*)outerBuf;
|
|
if (outerPtrs[outerIndex] == 0)
|
|
continue;
|
|
uint8 innerBuf[sfsBlockSize];
|
|
if (!blockRead(outerPtrs[outerIndex], innerBuf))
|
|
continue;
|
|
uint32* innerPtrs = (uint32*)innerBuf;
|
|
abs = innerPtrs[innerIndex];
|
|
innerPtrs[innerIndex] = 0;
|
|
blockWrite(outerPtrs[outerIndex], innerBuf);
|
|
} else {
|
|
uint32 tripleIndex = doubleIndex - sfsPtrsPerBlock * sfsPtrsPerBlock;
|
|
uint32 topIndex = tripleIndex / (sfsPtrsPerBlock * sfsPtrsPerBlock);
|
|
uint32 midIndex = (tripleIndex / sfsPtrsPerBlock) % sfsPtrsPerBlock;
|
|
uint32 leafIndex = tripleIndex % sfsPtrsPerBlock;
|
|
if (inode->tripleIndirect == 0 || topIndex >= sfsPtrsPerBlock)
|
|
continue;
|
|
uint8 topBuf[sfsBlockSize];
|
|
if (!blockRead(inode->tripleIndirect, topBuf))
|
|
continue;
|
|
uint32* topPtrs = (uint32*)topBuf;
|
|
if (topPtrs[topIndex] == 0)
|
|
continue;
|
|
uint8 midBuf[sfsBlockSize];
|
|
if (!blockRead(topPtrs[topIndex], midBuf))
|
|
continue;
|
|
uint32* midPtrs = (uint32*)midBuf;
|
|
if (midPtrs[midIndex] == 0)
|
|
continue;
|
|
uint8 leafBuf[sfsBlockSize];
|
|
if (!blockRead(midPtrs[midIndex], leafBuf))
|
|
continue;
|
|
uint32* leafPtrs = (uint32*)leafBuf;
|
|
abs = leafPtrs[leafIndex];
|
|
leafPtrs[leafIndex] = 0;
|
|
blockWrite(midPtrs[midIndex], leafBuf);
|
|
}
|
|
}
|
|
}
|
|
if (abs != 0) {
|
|
freeDataBlockAbs(abs);
|
|
inode->blockCount--;
|
|
}
|
|
}
|
|
|
|
/* Pointer-holder blocks themselves (the indirect block, every inner
|
|
indirect block plus the outer double-indirect block, and every
|
|
top/mid/leaf-pointer block of the triple-indirect chain) are
|
|
reclaimed all-or-nothing once nothing addressed through them is
|
|
needed anymore -- the loop above already frees every leaf data block
|
|
that was in range, so by this point each holder either still guards
|
|
live data (neededBlocks reaches into its range: leave it alone) or is
|
|
entirely spent (free it, and everything it pointed at). Checked
|
|
highest level first (triple, then double, then single) so a shrink
|
|
that drops the whole file frees every level in one pass. */
|
|
if (neededBlocks <= sfsDirectBlocks + sfsPtrsPerBlock + sfsPtrsPerBlock * sfsPtrsPerBlock
|
|
&& inode->tripleIndirect != 0) {
|
|
uint8 topBuf[sfsBlockSize];
|
|
if (blockRead(inode->tripleIndirect, topBuf)) {
|
|
uint32* topPtrs = (uint32*)topBuf;
|
|
for (uint32 j = 0; j < sfsPtrsPerBlock; j++) {
|
|
if (topPtrs[j] == 0)
|
|
continue;
|
|
uint8 midBuf[sfsBlockSize];
|
|
if (blockRead(topPtrs[j], midBuf)) {
|
|
uint32* midPtrs = (uint32*)midBuf;
|
|
for (uint32 k = 0; k < sfsPtrsPerBlock; k++) {
|
|
if (midPtrs[k] != 0) {
|
|
freeDataBlockAbs(midPtrs[k]);
|
|
inode->blockCount--;
|
|
}
|
|
}
|
|
}
|
|
freeDataBlockAbs(topPtrs[j]);
|
|
inode->blockCount--;
|
|
}
|
|
}
|
|
freeDataBlockAbs(inode->tripleIndirect);
|
|
inode->tripleIndirect = 0;
|
|
inode->blockCount--;
|
|
}
|
|
|
|
if (neededBlocks <= sfsDirectBlocks + sfsPtrsPerBlock && inode->doubleIndirect != 0) {
|
|
uint8 outerBuf[sfsBlockSize];
|
|
if (blockRead(inode->doubleIndirect, outerBuf)) {
|
|
uint32* outerPtrs = (uint32*)outerBuf;
|
|
for (uint32 j = 0; j < sfsPtrsPerBlock; j++) {
|
|
if (outerPtrs[j] != 0) {
|
|
freeDataBlockAbs(outerPtrs[j]);
|
|
inode->blockCount--;
|
|
}
|
|
}
|
|
}
|
|
freeDataBlockAbs(inode->doubleIndirect);
|
|
inode->doubleIndirect = 0;
|
|
inode->blockCount--;
|
|
}
|
|
|
|
if (neededBlocks <= sfsDirectBlocks && inode->indirect != 0) {
|
|
freeDataBlockAbs(inode->indirect);
|
|
inode->indirect = 0;
|
|
inode->blockCount--;
|
|
}
|
|
|
|
inode->size = newSize;
|
|
inode->mtime = inode->ctime = (uint32)rtcNow();
|
|
return writeInodeRaw(inodeNum, inode);
|
|
}
|
|
|
|
static bool splitPath(const char* path, char* parentOut, uint64 parentCap, char* leafOut, uint64 leafCap)
|
|
{
|
|
uint64 len = strlen(path);
|
|
while (len > 0 && path[len - 1] == '/')
|
|
len--;
|
|
if (len == 0)
|
|
return false;
|
|
|
|
int64 slash = -1;
|
|
for (int64 i = (int64)len - 1; i >= 0; i--)
|
|
if (path[i] == '/') {
|
|
slash = i;
|
|
break;
|
|
}
|
|
|
|
uint64 leafStart, leafLen;
|
|
if (slash < 0) {
|
|
parentOut[0] = 0;
|
|
leafStart = 0;
|
|
leafLen = len;
|
|
} else {
|
|
uint64 plen = (uint64)slash;
|
|
if (plen >= parentCap)
|
|
plen = parentCap - 1;
|
|
memcpy(parentOut, path, plen);
|
|
parentOut[plen] = 0;
|
|
leafStart = (uint64)slash + 1;
|
|
leafLen = len - leafStart;
|
|
}
|
|
|
|
if (leafLen == 0 || leafLen >= leafCap)
|
|
return false;
|
|
memcpy(leafOut, path + leafStart, leafLen);
|
|
leafOut[leafLen] = 0;
|
|
return true;
|
|
}
|
|
|
|
static int32 dirFind(int32 dirInode, const char* name, uint64 nameLen)
|
|
{
|
|
SfsDirEntry entry;
|
|
uint64 pos = 0;
|
|
for (;;) {
|
|
int64 got = sfsReadAt(dirInode, pos, &entry, sizeof(entry));
|
|
if (got != (int64)sizeof(entry))
|
|
break;
|
|
if (strlen(entry.name) == nameLen && memcmp(entry.name, name, nameLen) == 0)
|
|
return (int32)entry.inode;
|
|
pos += sizeof(entry);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static bool dirAdd(int32 dirInode, const char* name, uint64 nameLen, int32 childInode, uint32 type)
|
|
{
|
|
if (nameLen == 0 || nameLen >= sfsNameMax)
|
|
return false;
|
|
if (dirFind(dirInode, name, nameLen) >= 0)
|
|
return false;
|
|
|
|
SfsDirEntry entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
entry.inode = (uint32)childInode;
|
|
entry.type = type;
|
|
memcpy(entry.name, name, nameLen);
|
|
entry.name[nameLen] = 0;
|
|
|
|
uint64 size = sfsSizeOf(dirInode);
|
|
return sfsWriteAt(dirInode, size, &entry, sizeof(entry)) == (int64)sizeof(entry);
|
|
}
|
|
|
|
static bool dirRemove(int32 dirInode, const char* name, uint64 nameLen)
|
|
{
|
|
SfsDirEntry entry;
|
|
uint64 pos = 0;
|
|
int64 foundPos = -1;
|
|
for (;;) {
|
|
int64 got = sfsReadAt(dirInode, pos, &entry, sizeof(entry));
|
|
if (got != (int64)sizeof(entry))
|
|
break;
|
|
if (strlen(entry.name) == nameLen && memcmp(entry.name, name, nameLen) == 0) {
|
|
foundPos = (int64)pos;
|
|
break;
|
|
}
|
|
pos += sizeof(entry);
|
|
}
|
|
if (foundPos < 0)
|
|
return false;
|
|
|
|
uint64 size = sfsSizeOf(dirInode);
|
|
uint64 lastPos = size - sizeof(SfsDirEntry);
|
|
if ((uint64)foundPos != lastPos) {
|
|
SfsDirEntry last;
|
|
if (sfsReadAt(dirInode, lastPos, &last, sizeof(last)) != (int64)sizeof(last))
|
|
return false;
|
|
if (sfsWriteAt(dirInode, (uint64)foundPos, &last, sizeof(last)) != (int64)sizeof(last))
|
|
return false;
|
|
}
|
|
|
|
SfsInode inode;
|
|
if (!readInode(dirInode, &inode))
|
|
return false;
|
|
return sfsTruncateInode(&inode, dirInode, (uint32)lastPos);
|
|
}
|
|
|
|
static int32 createNode(const char* path, uint32 type)
|
|
{
|
|
if (!isMounted)
|
|
return -1;
|
|
|
|
char parentBuf[256];
|
|
char leaf[sfsNameMax];
|
|
if (!splitPath(path, parentBuf, sizeof(parentBuf), leaf, sizeof(leaf)))
|
|
return -1;
|
|
|
|
int32 parentInode = sfsLookup(parentBuf);
|
|
if (parentInode < 0 || sfsTypeOf(parentInode) != sfsTypeDir)
|
|
return -1;
|
|
/* Real Unix directory-write semantics: creating a NEW entry needs
|
|
write permission on the PARENT directory, mirroring vfs.c's
|
|
vfsMkdir/resolveOpen create-branch's own equivalent tmpfs check. */
|
|
if (!vfsPermitted(sfsModeOf(parentInode), sfsUidOf(parentInode), sfsGidOf(parentInode),
|
|
currentThreadUid(), currentThreadGid(), fsModeOwnerWrite))
|
|
return -1;
|
|
if (dirFind(parentInode, leaf, strlen(leaf)) >= 0)
|
|
return -1;
|
|
|
|
int32 newInode = allocInodeNum();
|
|
if (newInode < 0)
|
|
return -1;
|
|
|
|
SfsInode inode;
|
|
memset(&inode, 0, sizeof(inode));
|
|
inode.type = type;
|
|
inode.linkCount = 1;
|
|
inode.mtime = inode.ctime = (uint32)rtcNow();
|
|
inode.mode = (type == sfsTypeDir) ? fsModeDefaultDir : fsModeDefaultFile;
|
|
inode.uid = currentThreadUid();
|
|
inode.gid = currentThreadGid();
|
|
if (!writeInodeRaw(newInode, &inode)) {
|
|
freeInodeNum(newInode);
|
|
return -1;
|
|
}
|
|
|
|
if (!dirAdd(parentInode, leaf, strlen(leaf), newInode, type)) {
|
|
freeInodeNum(newInode);
|
|
return -1;
|
|
}
|
|
return newInode;
|
|
}
|
|
|
|
static bool readSymlinkTarget(int32 inodeNum, char* buf, uint64 cap)
|
|
{
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return false;
|
|
if (inode.type != sfsTypeSymlink)
|
|
return false;
|
|
if (cap == 0 || (uint64)inode.size >= cap)
|
|
return false;
|
|
|
|
int64 got = sfsReadAt(inodeNum, 0, buf, inode.size);
|
|
if (got != (int64)inode.size)
|
|
return false;
|
|
buf[inode.size] = 0;
|
|
return true;
|
|
}
|
|
|
|
/* Walks path one component at a time from the root, exactly like the old
|
|
flat sfsLookup used to, except a symlink component (other than possibly
|
|
the last one, depending on followFinal) is resolved by re-running this
|
|
same walk on its target and splicing the result back in as `cur` before
|
|
continuing with whatever of the original path remains. depth counts hops
|
|
already taken across that splicing, so a cycle (or merely a very long
|
|
chain) fails cleanly with -1 instead of recursing without bound. */
|
|
static int32 lookupFrom(const char* path, bool followFinal, int depth)
|
|
{
|
|
if (!isMounted)
|
|
return -1;
|
|
if (depth > sfsMaxSymlinkHops)
|
|
return -1;
|
|
|
|
int32 cur = sfsRootInode;
|
|
const char* p = path ? path : "";
|
|
while (*p == '/')
|
|
p++;
|
|
|
|
while (*p) {
|
|
const char* start = p;
|
|
while (*p && *p != '/')
|
|
p++;
|
|
uint64 len = (uint64)(p - start);
|
|
if (len == 0 || len >= sfsNameMax)
|
|
return -1;
|
|
if (sfsTypeOf(cur) != sfsTypeDir)
|
|
return -1;
|
|
/* Real POSIX search permission on every directory ANCESTOR walked
|
|
through, mirroring vfs.c's lookupNode's own equivalent check for
|
|
tmpfs -- resolving a name inside `cur` needs the owner-or-other
|
|
exec bit for the calling thread's uid, not just "cur is a
|
|
directory". */
|
|
if (!vfsPermitted(sfsModeOf(cur), sfsUidOf(cur), sfsGidOf(cur),
|
|
currentThreadUid(), currentThreadGid(), fsModeOwnerExec))
|
|
return -1;
|
|
int32 child = dirFind(cur, start, len);
|
|
if (child < 0)
|
|
return -1;
|
|
|
|
while (*p == '/')
|
|
p++;
|
|
bool isLast = (*p == 0);
|
|
|
|
if (sfsTypeOf(child) == sfsTypeSymlink && (!isLast || followFinal)) {
|
|
char target[sfsSymlinkTargetMax + 1];
|
|
if (!readSymlinkTarget(child, target, sizeof(target)))
|
|
return -1;
|
|
int32 resolved = lookupFrom(target, true, depth + 1);
|
|
if (resolved < 0)
|
|
return -1;
|
|
child = resolved;
|
|
}
|
|
|
|
cur = child;
|
|
}
|
|
return cur;
|
|
}
|
|
|
|
/* --- public API --- */
|
|
|
|
bool sfsFormat(void)
|
|
{
|
|
if (!ataPresent())
|
|
return false;
|
|
|
|
uint64 sectors = ataSectorCount();
|
|
if (sectors <= sfsBaseLba + sfsReserveEnd + 64)
|
|
return false;
|
|
|
|
SfsSuperblock nsb;
|
|
memset(&nsb, 0, sizeof(nsb));
|
|
nsb.magic = sfsMagic;
|
|
nsb.blockSize = sfsBlockSize;
|
|
nsb.totalBlocks = (uint32)(sectors - sfsBaseLba - sfsReserveEnd);
|
|
nsb.inodeCount = sfsMaxNodes;
|
|
|
|
nsb.inodeBitmapStart = 1;
|
|
nsb.inodeBitmapBlocks = (nsb.inodeCount + 8 * sfsBlockSize - 1) / (8 * sfsBlockSize);
|
|
if (nsb.inodeBitmapBlocks == 0)
|
|
nsb.inodeBitmapBlocks = 1;
|
|
|
|
nsb.blockBitmapStart = nsb.inodeBitmapStart + nsb.inodeBitmapBlocks;
|
|
nsb.blockBitmapBlocks = (nsb.totalBlocks + 8 * sfsBlockSize - 1) / (8 * sfsBlockSize);
|
|
if (nsb.blockBitmapBlocks == 0)
|
|
nsb.blockBitmapBlocks = 1;
|
|
|
|
nsb.inodeTableStart = nsb.blockBitmapStart + nsb.blockBitmapBlocks;
|
|
uint32 inodesPerBlock = sfsBlockSize / sizeof(SfsInode);
|
|
nsb.inodeTableBlocks = (nsb.inodeCount + inodesPerBlock - 1) / inodesPerBlock;
|
|
|
|
nsb.dataStart = nsb.inodeTableStart + nsb.inodeTableBlocks;
|
|
nsb.rootInode = sfsRootInode;
|
|
if (nsb.dataStart >= nsb.totalBlocks)
|
|
return false;
|
|
|
|
/* Committed to actually wiping from here on (every earlier return above
|
|
is a rejected format that touched nothing) -- every inode number's
|
|
old meaning is about to become garbage, so anything that cached one
|
|
from before this point must be told apart from anything cached
|
|
after. */
|
|
formatGeneration++;
|
|
|
|
if (inodeBitmap) {
|
|
kfree(inodeBitmap);
|
|
inodeBitmap = nullptr;
|
|
}
|
|
if (blockBitmap) {
|
|
kfree(blockBitmap);
|
|
blockBitmap = nullptr;
|
|
}
|
|
|
|
inodeBitmap = (uint8*)kmalloc((uint64)nsb.inodeBitmapBlocks * sfsBlockSize);
|
|
blockBitmap = (uint8*)kmalloc((uint64)nsb.blockBitmapBlocks * sfsBlockSize);
|
|
if (!inodeBitmap || !blockBitmap) {
|
|
if (inodeBitmap) kfree(inodeBitmap);
|
|
if (blockBitmap) kfree(blockBitmap);
|
|
inodeBitmap = nullptr;
|
|
blockBitmap = nullptr;
|
|
return false;
|
|
}
|
|
memset(inodeBitmap, 0, (uint64)nsb.inodeBitmapBlocks * sfsBlockSize);
|
|
memset(blockBitmap, 0, (uint64)nsb.blockBitmapBlocks * sfsBlockSize);
|
|
|
|
sb = nsb;
|
|
isMounted = true;
|
|
|
|
uint8 zeroBlk[sfsBlockSize];
|
|
memset(zeroBlk, 0, sizeof(zeroBlk));
|
|
for (uint32 i = 0; i < sb.inodeTableBlocks; i++)
|
|
blockWrite(sb.inodeTableStart + i, zeroBlk);
|
|
|
|
bitSet(inodeBitmap, 0);
|
|
bitSet(inodeBitmap, sfsRootInode);
|
|
|
|
SfsInode rootInode;
|
|
memset(&rootInode, 0, sizeof(rootInode));
|
|
rootInode.type = sfsTypeDir;
|
|
rootInode.linkCount = 1;
|
|
rootInode.mtime = rootInode.ctime = (uint32)rtcNow();
|
|
rootInode.mode = fsModeDefaultDir;
|
|
rootInode.uid = currentThreadUid();
|
|
rootInode.gid = currentThreadGid();
|
|
writeInodeRaw(sfsRootInode, &rootInode);
|
|
|
|
flushInodeBitmap();
|
|
flushBlockBitmap();
|
|
flushSuperblock();
|
|
return true;
|
|
}
|
|
|
|
bool sfsMount(void)
|
|
{
|
|
if (!ataPresent())
|
|
return false;
|
|
|
|
uint8 buf[sfsBlockSize];
|
|
if (!ataReadSectors(sfsBaseLba, 1, buf))
|
|
return false;
|
|
|
|
SfsSuperblock onDisk;
|
|
memcpy(&onDisk, buf, sizeof(onDisk));
|
|
if (onDisk.magic != sfsMagic)
|
|
return sfsFormat();
|
|
|
|
sb = onDisk;
|
|
|
|
if (inodeBitmap) {
|
|
kfree(inodeBitmap);
|
|
inodeBitmap = nullptr;
|
|
}
|
|
if (blockBitmap) {
|
|
kfree(blockBitmap);
|
|
blockBitmap = nullptr;
|
|
}
|
|
|
|
inodeBitmap = (uint8*)kmalloc((uint64)sb.inodeBitmapBlocks * sfsBlockSize);
|
|
blockBitmap = (uint8*)kmalloc((uint64)sb.blockBitmapBlocks * sfsBlockSize);
|
|
if (!inodeBitmap || !blockBitmap)
|
|
return false;
|
|
|
|
for (uint32 i = 0; i < sb.inodeBitmapBlocks; i++)
|
|
if (!blockRead(sb.inodeBitmapStart + i, inodeBitmap + (uint64)i * sfsBlockSize))
|
|
return false;
|
|
for (uint32 i = 0; i < sb.blockBitmapBlocks; i++)
|
|
if (!blockRead(sb.blockBitmapStart + i, blockBitmap + (uint64)i * sfsBlockSize))
|
|
return false;
|
|
|
|
isMounted = true;
|
|
return true;
|
|
}
|
|
|
|
void sfsUnmount(void)
|
|
{
|
|
if (!isMounted)
|
|
return;
|
|
flushInodeBitmap();
|
|
flushBlockBitmap();
|
|
flushSuperblock();
|
|
if (inodeBitmap) {
|
|
kfree(inodeBitmap);
|
|
inodeBitmap = nullptr;
|
|
}
|
|
if (blockBitmap) {
|
|
kfree(blockBitmap);
|
|
blockBitmap = nullptr;
|
|
}
|
|
isMounted = false;
|
|
}
|
|
|
|
bool sfsMounted(void)
|
|
{
|
|
return isMounted;
|
|
}
|
|
|
|
uint64 sfsCapacityBytes(void)
|
|
{
|
|
if (!isMounted)
|
|
return 0;
|
|
return (uint64)(sb.totalBlocks - sb.dataStart) * sfsBlockSize;
|
|
}
|
|
|
|
uint64 sfsFreeDataBlocks(void)
|
|
{
|
|
if (!isMounted)
|
|
return 0;
|
|
uint32 dataBlockCount = sb.totalBlocks - sb.dataStart;
|
|
uint64 free = 0;
|
|
for (uint32 i = 0; i < dataBlockCount; i++)
|
|
if (!bitTest(blockBitmap, i))
|
|
free++;
|
|
return free;
|
|
}
|
|
|
|
int32 sfsLookup(const char* path)
|
|
{
|
|
return lookupFrom(path, true, 0);
|
|
}
|
|
|
|
int32 sfsLookupNoFollow(const char* path)
|
|
{
|
|
return lookupFrom(path, false, 0);
|
|
}
|
|
|
|
int32 sfsCreateFile(const char* path)
|
|
{
|
|
return createNode(path, sfsTypeFile);
|
|
}
|
|
|
|
int32 sfsCreateDir(const char* path)
|
|
{
|
|
return createNode(path, sfsTypeDir);
|
|
}
|
|
|
|
int32 sfsCreateFifo(const char* path)
|
|
{
|
|
return createNode(path, sfsTypeFifo);
|
|
}
|
|
|
|
uint64 sfsGeneration(void)
|
|
{
|
|
return formatGeneration;
|
|
}
|
|
|
|
int32 sfsCreateSymlink(const char* path, const char* target)
|
|
{
|
|
if (!isMounted || !target)
|
|
return -1;
|
|
uint64 targetLen = strlen(target);
|
|
if (targetLen == 0 || targetLen > sfsSymlinkTargetMax)
|
|
return -1;
|
|
|
|
int32 newInode = createNode(path, sfsTypeSymlink);
|
|
if (newInode < 0)
|
|
return -1;
|
|
|
|
if (sfsWriteAt(newInode, 0, target, targetLen) != (int64)targetLen) {
|
|
char parentBuf[256];
|
|
char leaf[sfsNameMax];
|
|
if (splitPath(path, parentBuf, sizeof(parentBuf), leaf, sizeof(leaf))) {
|
|
int32 parentInode = sfsLookup(parentBuf);
|
|
if (parentInode >= 0)
|
|
dirRemove(parentInode, leaf, strlen(leaf));
|
|
}
|
|
freeInodeNum(newInode);
|
|
return -1;
|
|
}
|
|
return newInode;
|
|
}
|
|
|
|
bool sfsReadlink(int32 inodeNum, char* buf, uint64 cap)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return false;
|
|
return readSymlinkTarget(inodeNum, buf, cap);
|
|
}
|
|
|
|
int32 sfsLink(const char* existingPath, const char* newPath)
|
|
{
|
|
if (!isMounted)
|
|
return -1;
|
|
|
|
int32 target = sfsLookup(existingPath);
|
|
if (target < 0)
|
|
return -1;
|
|
|
|
SfsInode inode;
|
|
if (!readInode(target, &inode))
|
|
return -1;
|
|
if (inode.type == sfsTypeDir)
|
|
return -1;
|
|
|
|
char parentBuf[256];
|
|
char leaf[sfsNameMax];
|
|
if (!splitPath(newPath, parentBuf, sizeof(parentBuf), leaf, sizeof(leaf)))
|
|
return -1;
|
|
|
|
int32 parentInode = sfsLookup(parentBuf);
|
|
if (parentInode < 0 || sfsTypeOf(parentInode) != sfsTypeDir)
|
|
return -1;
|
|
/* Same parent-directory write check as createNode/sfsRemove above --
|
|
adding a new hardlink NAME needs write permission on the PARENT of
|
|
the new name, not on the existing target being linked to. */
|
|
if (!vfsPermitted(sfsModeOf(parentInode), sfsUidOf(parentInode), sfsGidOf(parentInode),
|
|
currentThreadUid(), currentThreadGid(), fsModeOwnerWrite))
|
|
return -1;
|
|
if (dirFind(parentInode, leaf, strlen(leaf)) >= 0)
|
|
return -1;
|
|
|
|
inode.linkCount++;
|
|
inode.ctime = (uint32)rtcNow();
|
|
if (!writeInodeRaw(target, &inode))
|
|
return -1;
|
|
|
|
if (!dirAdd(parentInode, leaf, strlen(leaf), target, inode.type)) {
|
|
inode.linkCount--;
|
|
writeInodeRaw(target, &inode);
|
|
return -1;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
uint32 sfsLinkCountOf(int32 inodeNum)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return 0;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return 0;
|
|
return inode.linkCount;
|
|
}
|
|
|
|
bool sfsRemove(const char* path)
|
|
{
|
|
if (!isMounted)
|
|
return false;
|
|
|
|
char parentBuf[256];
|
|
char leaf[sfsNameMax];
|
|
if (!splitPath(path, parentBuf, sizeof(parentBuf), leaf, sizeof(leaf)))
|
|
return false;
|
|
|
|
int32 parentInode = sfsLookup(parentBuf);
|
|
if (parentInode < 0)
|
|
return false;
|
|
/* Same parent-directory write check as createNode above -- removing an
|
|
entry needs write permission on the PARENT, not the entry itself,
|
|
mirroring vfs.c's vfsUnlink/vfsRmdir's own equivalent tmpfs check. */
|
|
if (!vfsPermitted(sfsModeOf(parentInode), sfsUidOf(parentInode), sfsGidOf(parentInode),
|
|
currentThreadUid(), currentThreadGid(), fsModeOwnerWrite))
|
|
return false;
|
|
int32 target = dirFind(parentInode, leaf, strlen(leaf));
|
|
if (target < 0)
|
|
return false;
|
|
|
|
SfsInode inode;
|
|
if (!readInode(target, &inode))
|
|
return false;
|
|
if (inode.type == sfsTypeDir && inode.size > 0)
|
|
return false;
|
|
|
|
if (!dirRemove(parentInode, leaf, strlen(leaf)))
|
|
return false;
|
|
|
|
if (inode.linkCount > 1) {
|
|
inode.linkCount--;
|
|
inode.ctime = (uint32)rtcNow();
|
|
return writeInodeRaw(target, &inode);
|
|
}
|
|
|
|
if (!sfsTruncateInode(&inode, target, 0))
|
|
return false;
|
|
freeInodeNum(target);
|
|
return true;
|
|
}
|
|
|
|
uint64 sfsSizeOf(int32 inodeNum)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return 0;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return 0;
|
|
return inode.size;
|
|
}
|
|
|
|
uint32 sfsTypeOf(int32 inodeNum)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return sfsTypeFree;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return sfsTypeFree;
|
|
return inode.type;
|
|
}
|
|
|
|
uint64 sfsMtimeOf(int32 inodeNum)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return 0;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return 0;
|
|
return inode.mtime;
|
|
}
|
|
|
|
uint64 sfsCtimeOf(int32 inodeNum)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return 0;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return 0;
|
|
return inode.ctime;
|
|
}
|
|
|
|
uint32 sfsModeOf(int32 inodeNum)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return 0;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return 0;
|
|
return inode.mode;
|
|
}
|
|
|
|
bool sfsChmod(int32 inodeNum, uint32 mode)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return false;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return false;
|
|
inode.mode = mode;
|
|
inode.ctime = (uint32)rtcNow();
|
|
return writeInodeRaw(inodeNum, &inode);
|
|
}
|
|
|
|
uint32 sfsUidOf(int32 inodeNum)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return 0;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return 0;
|
|
return inode.uid;
|
|
}
|
|
|
|
bool sfsChown(int32 inodeNum, uint32 newUid)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return false;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return false;
|
|
inode.uid = newUid;
|
|
inode.ctime = (uint32)rtcNow();
|
|
return writeInodeRaw(inodeNum, &inode);
|
|
}
|
|
|
|
uint32 sfsGidOf(int32 inodeNum)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return 0;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return 0;
|
|
return inode.gid;
|
|
}
|
|
|
|
bool sfsChgrp(int32 inodeNum, uint32 newGid)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return false;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return false;
|
|
inode.gid = newGid;
|
|
inode.ctime = (uint32)rtcNow();
|
|
return writeInodeRaw(inodeNum, &inode);
|
|
}
|
|
|
|
bool sfsTruncate(int32 inodeNum, uint64 newSize)
|
|
{
|
|
if (!isMounted || inodeNum <= 0 || newSize > 0xFFFFFFFFu)
|
|
return false;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return false;
|
|
if (inode.type != sfsTypeFile)
|
|
return false;
|
|
return sfsTruncateInode(&inode, inodeNum, (uint32)newSize);
|
|
}
|
|
|
|
int64 sfsReadAt(int32 inodeNum, uint64 offset, void* buffer, uint64 length)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return -1;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return -1;
|
|
if (inode.type != sfsTypeFile && inode.type != sfsTypeDir && inode.type != sfsTypeSymlink)
|
|
return -1;
|
|
if (offset >= inode.size)
|
|
return 0;
|
|
|
|
uint64 want = length;
|
|
if (want > (uint64)inode.size - offset)
|
|
want = (uint64)inode.size - offset;
|
|
|
|
uint8* dst = (uint8*)buffer;
|
|
uint64 done = 0;
|
|
while (done < want) {
|
|
uint64 pos = offset + done;
|
|
uint32 logicalBlock = (uint32)(pos / sfsBlockSize);
|
|
uint32 within = (uint32)(pos % sfsBlockSize);
|
|
uint32 chunk = sfsBlockSize - within;
|
|
if ((uint64)chunk > want - done)
|
|
chunk = (uint32)(want - done);
|
|
|
|
uint32 blockAbs;
|
|
if (getBlockForRead(&inode, logicalBlock, &blockAbs)) {
|
|
uint8 blockBuf[sfsBlockSize];
|
|
if (!blockRead(blockAbs, blockBuf))
|
|
break;
|
|
memcpy(dst + done, blockBuf + within, chunk);
|
|
} else {
|
|
memset(dst + done, 0, chunk);
|
|
}
|
|
done += chunk;
|
|
}
|
|
return (int64)done;
|
|
}
|
|
|
|
int64 sfsWriteAt(int32 inodeNum, uint64 offset, const void* buffer, uint64 length)
|
|
{
|
|
if (!isMounted || inodeNum <= 0)
|
|
return -1;
|
|
SfsInode inode;
|
|
if (!readInode(inodeNum, &inode))
|
|
return -1;
|
|
if (inode.type != sfsTypeFile && inode.type != sfsTypeDir && inode.type != sfsTypeSymlink)
|
|
return -1;
|
|
if (length == 0)
|
|
return 0;
|
|
|
|
if (offset > inode.size) {
|
|
if (!sfsWriteRaw(&inode, inodeNum, inode.size, nullptr, offset - inode.size))
|
|
return -1;
|
|
}
|
|
if (!sfsWriteRaw(&inode, inodeNum, offset, buffer, length))
|
|
return -1;
|
|
return (int64)length;
|
|
}
|
|
|
|
bool sfsReaddirAt(int32 dirInode, uint32 index, char* nameOut, uint32 nameCap, uint32* typeOut)
|
|
{
|
|
if (!isMounted || sfsTypeOf(dirInode) != sfsTypeDir)
|
|
return false;
|
|
|
|
SfsDirEntry entry;
|
|
uint64 pos = (uint64)index * sizeof(SfsDirEntry);
|
|
if (sfsReadAt(dirInode, pos, &entry, sizeof(entry)) != (int64)sizeof(entry))
|
|
return false;
|
|
|
|
uint64 nlen = strlen(entry.name);
|
|
if (nlen >= nameCap)
|
|
nlen = nameCap - 1;
|
|
memcpy(nameOut, entry.name, nlen);
|
|
nameOut[nlen] = 0;
|
|
if (typeOut)
|
|
*typeOut = entry.type;
|
|
return true;
|
|
}
|