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);
|
||||
}
|
||||
|
||||
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