From ae47c8258fd8235409fda43aee31ad22f044ed6e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 19:27:37 +0200 Subject: [PATCH] test: extend coverage of sfsTest --- src/sfsTest.c | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/src/sfsTest.c b/src/sfsTest.c index 8ebe567..6b77e90 100644 --- a/src/sfsTest.c +++ b/src/sfsTest.c @@ -943,3 +943,165 @@ KTEST(sfsRemoveSymlinkDoesNotAffectTarget) KT_EQ(t, memcmp(back, "untouched", 9), 0); } +KTEST(sfsSymlinkPersistsAcrossUnmountRemount) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("real.txt"); + KT_TRUE(t, f > 0); + KT_EQ(t, sfsWriteAt(f, 0, "on disk for real", 17), 17); + int32 link = sfsCreateSymlink("link.txt", "real.txt"); + KT_TRUE(t, link > 0); + + sfsUnmount(); + KT_TRUE(t, sfsMount()); + + KT_EQ(t, sfsLookup("link.txt"), f); + KT_EQ(t, sfsLookupNoFollow("link.txt"), link); + KT_EQ(t, sfsTypeOf(link), sfsTypeSymlink); + + char target[32]; + memset(target, 0, sizeof(target)); + KT_TRUE(t, sfsReadlink(link, target, sizeof(target))); + KT_STREQ(t, target, "real.txt"); +} + +KTEST(sfsFileGrowsThroughDoubleIndirectBlock) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("huge.bin"); + KT_TRUE(t, f > 0); + + /* sfsDirectBlocks (10) + sfsPtrsPerBlock (128) = 138 logical blocks are + reachable through direct + single indirect addressing; block 138 + onward can only be reached through the double indirect block added + in this wave. Starting a few blocks before that boundary and writing + past it exercises the crossing and the double-indirect-only region + in one write (sfsWriteAt zero-fills 0..134 first, all still within + single-indirect range, before this pattern lands on 135..144). */ + #define sfsDoubleSpanBlocks 10 + #define sfsDoubleStartBlock 135 + #define sfsDoubleSpan (sfsDoubleSpanBlocks * sfsBlockSize) + uint8* pattern = (uint8*)kmalloc(sfsDoubleSpan); + uint8* verify = (uint8*)kmalloc(sfsDoubleSpan); + KT_NOTNULL(t, pattern); + KT_NOTNULL(t, verify); + if (!pattern || !verify) { + kfree(pattern); + kfree(verify); + return; + } + for (int i = 0; i < sfsDoubleSpan; i++) + pattern[i] = (uint8)(i * 17 + 3); + + uint64 offset = (uint64)sfsDoubleStartBlock * sfsBlockSize; + KT_EQ(t, sfsWriteAt(f, offset, pattern, sfsDoubleSpan), sfsDoubleSpan); + KT_EQ(t, sfsSizeOf(f), offset + sfsDoubleSpan); + + memset(verify, 0, sfsDoubleSpan); + KT_EQ(t, sfsReadAt(f, offset, verify, sfsDoubleSpan), sfsDoubleSpan); + KT_EQ(t, memcmp(verify, pattern, sfsDoubleSpan), 0); + + /* Patch a window straddling the single-indirect/double-indirect + boundary itself (logical block 137/138) and confirm only that slice + changed -- the same "boundary patch" shape as + sfsFileGrowsThroughIndirectBlock, one level deeper. */ + uint8 patch[48]; + memset(patch, 0xCC, sizeof(patch)); + uint64 patchOffset = 138 * sfsBlockSize - 24; + KT_EQ(t, sfsWriteAt(f, patchOffset, patch, sizeof(patch)), (int64)sizeof(patch)); + + KT_EQ(t, sfsReadAt(f, offset, verify, sfsDoubleSpan), sfsDoubleSpan); + uint64 patchInSpan = patchOffset - offset; + KT_EQ(t, memcmp(verify + patchInSpan, patch, sizeof(patch)), 0); + KT_EQ(t, memcmp(verify, pattern, patchInSpan), 0); + + kfree(pattern); + kfree(verify); + #undef sfsDoubleSpan + #undef sfsDoubleStartBlock + #undef sfsDoubleSpanBlocks +} + +KTEST(sfsTruncateFreesDoubleIndirectBlocks) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("hugeshrink.bin"); + KT_TRUE(t, f > 0); + + /* Baseline is taken AFTER creating the file, not before: creating the + very first entry in a previously-empty root directory grows the + root's own data block permanently (it is a directory entry, not file + content, and stays even after truncating the FILE back to empty) -- + that is correct, unrelated bookkeeping this test must not conflate + with whether truncating hugeshrink.bin's own content gives its + blocks back. */ + uint64 baseline = sfsFreeDataBlocks(); + KT_GT(t, baseline, 0); + + #define sfsShrinkSpanBlocks 8 + #define sfsShrinkStartBlock 136 + #define sfsShrinkSpan (sfsShrinkSpanBlocks * sfsBlockSize) + uint8* chunk = (uint8*)kmalloc(sfsShrinkSpan); + KT_NOTNULL(t, chunk); + if (!chunk) + return; + memset(chunk, 0x91, sfsShrinkSpan); + uint64 offset = (uint64)sfsShrinkStartBlock * sfsBlockSize; + KT_EQ(t, sfsWriteAt(f, offset, chunk, sfsShrinkSpan), sfsShrinkSpan); + kfree(chunk); + #undef sfsShrinkSpan + #undef sfsShrinkStartBlock + #undef sfsShrinkSpanBlocks + + /* This file now owns: 10 direct + 1 single-indirect holder + 128 + single-indirect data blocks + 1 double-indirect holder + 1 inner + indirect holder + a handful of double-indirect data blocks. Every + one of those must come back. */ + KT_LT(t, sfsFreeDataBlocks(), baseline); + + KT_TRUE(t, sfsTruncate(f, 0)); + KT_EQ(t, sfsSizeOf(f), 0); + KT_EQU(t, sfsFreeDataBlocks(), baseline); + + /* And the inode itself must still be usable afterward, not left in some + half-truncated state. */ + KT_EQ(t, sfsWriteAt(f, 0, "reused", 6), 6); + KT_EQ(t, sfsSizeOf(f), 6); +} + +KTEST(sfsFileGrowsThroughTripleIndirectBlock) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("huger.bin"); + KT_TRUE(t, f > 0); + + /* sfsDirectBlocks (10) + sfsPtrsPerBlock (128) + sfsPtrsPerBlock^2 + (16384) = 16522 logical blocks are reachable through direct + single + + double indirect addressing; block 16522 onward can only be reached + through the triple indirect block added in this wave. Physically + reaching that boundary with real writes from offset 0 would mean + sfsWriteAt zero-filling ~8 MiB of real leading blocks first (its own + "no sparse holes" contract, see its doc comment) -- correct, but far + too slow for a headless boot-time gate. sfsTruncate's own grow path + is a genuine sparse hole instead (see sfsTruncateInode: growing only + ever rewrites the size field, no block is touched), the same + lseek-past-EOF-then-write shape a real filesystem's holes have -- so + pre-growing with it reaches the boundary for free, in O(1), then a + real write of a handful of blocks exercises the actual double/triple + crossing without paying for the untouched blocks in between. */ + #define sfsTripleBoundaryBlock 16522 + #define sfsTripleSpanBlocks 12 + #define sfsTripleStartBlock (sfsTripleBoundaryBlock - 6) + #define sfsTripleSpan (sfsTripleSpanBlocks * sfsBlockSize) + uint64 startOffset = (uint64)sfsTripleStartBlock * sfsBlockSize; + KT_TRUE(t, sfsTruncate(f, startOffset)); + KT_EQ(t, sfsSizeOf(f), startOffset); +