test: add more cases for sfsTest

This commit is contained in:
dualfroz committed 2026-09-11 19:28:27 +02:00
1 parent ae47c8258f
commit b9f6ee3643
1 file changed
+159
+159
View File
@@ -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);