test: extend coverage of sfsTest
This commit is contained in:
1 parent
fe18aab0a9
commit
ae47c8258f
1 file changed
+162
+162
@@ -943,3 +943,165 @@ KTEST(sfsRemoveSymlinkDoesNotAffectTarget)
|
|||||||
KT_EQ(t, memcmp(back, "untouched", 9), 0);
|
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);
|
||||||
|
|
||||||
Reference in new issue
Block a user