From b9f6ee3643c88ac109fc69f8c47eddd098c4892e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 19:28:27 +0200 Subject: [PATCH] test: add more cases for sfsTest --- src/sfsTest.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/src/sfsTest.c b/src/sfsTest.c index 6b77e90..88bec8f 100644 --- a/src/sfsTest.c +++ b/src/sfsTest.c @@ -1105,3 +1105,162 @@ KTEST(sfsFileGrowsThroughTripleIndirectBlock) KT_TRUE(t, sfsTruncate(f, startOffset)); KT_EQ(t, sfsSizeOf(f), startOffset); + uint8* pattern = (uint8*)kmalloc(sfsTripleSpan); + uint8* verify = (uint8*)kmalloc(sfsTripleSpan); + KT_NOTNULL(t, pattern); + KT_NOTNULL(t, verify); + if (!pattern || !verify) { + kfree(pattern); + kfree(verify); + return; + } + for (int i = 0; i < sfsTripleSpan; i++) + pattern[i] = (uint8)(i * 29 + 7); + + /* Six blocks land in the double-indirect region (16516..16521), the + other six only exist through the new triple-indirect block + (16522..16527) -- one write straddles both. */ + KT_EQ(t, sfsWriteAt(f, startOffset, pattern, sfsTripleSpan), sfsTripleSpan); + KT_EQ(t, sfsSizeOf(f), startOffset + sfsTripleSpan); + + memset(verify, 0, sfsTripleSpan); + KT_EQ(t, sfsReadAt(f, startOffset, verify, sfsTripleSpan), sfsTripleSpan); + KT_EQ(t, memcmp(verify, pattern, sfsTripleSpan), 0); + + /* Patch a window straddling the double-indirect/triple-indirect + boundary itself (logical block 16521/16522) -- same "boundary patch" + shape as sfsFileGrowsThroughDoubleIndirectBlock, one level deeper. */ + uint8 patch[48]; + memset(patch, 0xAB, sizeof(patch)); + uint64 patchOffset = (uint64)sfsTripleBoundaryBlock * sfsBlockSize - 24; + KT_EQ(t, sfsWriteAt(f, patchOffset, patch, sizeof(patch)), (int64)sizeof(patch)); + + KT_EQ(t, sfsReadAt(f, startOffset, verify, sfsTripleSpan), sfsTripleSpan); + uint64 patchInSpan = patchOffset - startOffset; + KT_EQ(t, memcmp(verify + patchInSpan, patch, sizeof(patch)), 0); + KT_EQ(t, memcmp(verify, pattern, patchInSpan), 0); + + kfree(pattern); + kfree(verify); + #undef sfsTripleSpan + #undef sfsTripleStartBlock + #undef sfsTripleSpanBlocks + #undef sfsTripleBoundaryBlock +} + +KTEST(sfsTruncateFreesTripleIndirectBlocks) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("hugershrink.bin"); + KT_TRUE(t, f > 0); + + /* Baseline taken after creating the file, same reasoning as + sfsTruncateFreesDoubleIndirectBlocks: the root directory's own data + block is unrelated bookkeeping this test must not conflate with + hugershrink.bin's own content. */ + uint64 baseline = sfsFreeDataBlocks(); + KT_GT(t, baseline, 0); + + /* Grow straight to the triple-indirect boundary via sfsTruncate (O(1), + a genuine sparse hole -- see sfsFileGrowsThroughTripleIndirectBlock + above) and write ENTIRELY inside the triple-indirect region, never + touching a single/double-indirect block. That keeps inode->indirect + and inode->doubleIndirect at 0, so the shrink-to-0 below only ever + does a cheap in-memory "still unallocated" check across the whole + single+double logical range instead of re-reading real holder blocks + for it -- proportional to the actual work, not to how far into the + address space the file happens to reach. */ + #define sfsTripleBoundaryBlock 16522 + #define sfsShrink2SpanBlocks 8 + #define sfsShrink2Span (sfsShrink2SpanBlocks * sfsBlockSize) + uint64 offset = (uint64)sfsTripleBoundaryBlock * sfsBlockSize; + KT_TRUE(t, sfsTruncate(f, offset)); + + uint8* chunk = (uint8*)kmalloc(sfsShrink2Span); + KT_NOTNULL(t, chunk); + if (!chunk) + return; + memset(chunk, 0x62, sfsShrink2Span); + KT_EQ(t, sfsWriteAt(f, offset, chunk, sfsShrink2Span), sfsShrink2Span); + kfree(chunk); + #undef sfsShrink2Span + #undef sfsShrink2SpanBlocks + #undef sfsTripleBoundaryBlock + + /* This file now owns: 1 triple-indirect holder + 1 mid holder + 1 + leaf-pointer holder + a handful of triple-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. */ + KT_EQ(t, sfsWriteAt(f, 0, "reused", 6), 6); + KT_EQ(t, sfsSizeOf(f), 6); +} + +KTEST(sfsHardlinkSharesInodeAndData) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("original.txt"); + KT_TRUE(t, f > 0); + const char* msg = "shared bytes"; + uint64 len = strlen(msg); + KT_EQ(t, sfsWriteAt(f, 0, msg, len), (int64)len); + KT_EQ(t, sfsLinkCountOf(f), 1); + + int32 linked = sfsLink("original.txt", "alias.txt"); + KT_EQ(t, linked, f); + KT_EQ(t, sfsLinkCountOf(f), 2); + KT_EQ(t, sfsLookup("alias.txt"), f); + + /* A write through the alias must be visible through the original name: + they are not two files that happen to match, they are the same + inode. */ + KT_EQ(t, sfsWriteAt(sfsLookup("alias.txt"), len, " too", 4), 4); + char back[32]; + memset(back, 0, sizeof(back)); + KT_EQ(t, sfsReadAt(f, 0, back, len + 4), (int64)(len + 4)); + KT_EQ(t, memcmp(back, "shared bytes too", len + 4), 0); +} + +KTEST(sfsHardlinkRejectsDirectoriesAndBadArguments) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + KT_TRUE(t, sfsCreateDir("adir") > 0); + KT_EQ(t, sfsLink("adir", "adiralias"), -1); + + int32 f = sfsCreateFile("real.txt"); + KT_TRUE(t, f > 0); + KT_EQ(t, sfsLink("does/not/exist.txt", "alias.txt"), -1); + + KT_TRUE(t, sfsCreateFile("taken.txt") > 0); + KT_EQ(t, sfsLink("real.txt", "taken.txt"), -1); + + /* None of the rejected attempts may have left a stray link count or + directory entry behind. */ + KT_EQ(t, sfsLinkCountOf(f), 1); + KT_EQ(t, sfsLookup("alias.txt"), -1); +} + +KTEST(sfsRemoveOneHardlinkKeepsOtherAlive) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("first.txt"); + KT_TRUE(t, f > 0); + KT_EQ(t, sfsWriteAt(f, 0, "durable", 7), 7); + KT_TRUE(t, sfsLink("first.txt", "second.txt") > 0); + + KT_TRUE(t, sfsRemove("first.txt")); + KT_EQ(t, sfsLookup("first.txt"), -1); +