test: cover sfsTest

This commit is contained in:
dualfroz committed 2026-09-11 19:08:23 +02:00
1 parent 28a39f4234
commit fe18aab0a9
1 file changed
+162
+162
View File
@@ -781,3 +781,165 @@ KTEST(sfsSymlinkResolvesToTargetFile)
if (!KT_TRUE(t, sfsFormat())) if (!KT_TRUE(t, sfsFormat()))
return; return;
int32 f = sfsCreateFile("real.txt");
KT_TRUE(t, f > 0);
const char* msg = "reached through a link";
uint64 len = strlen(msg);
KT_EQ(t, sfsWriteAt(f, 0, msg, len), (int64)len);
int32 link = sfsCreateSymlink("link.txt", "real.txt");
KT_TRUE(t, link > 0);
KT_NE(t, link, f);
/* sfsLookup follows the symlink: it must land on the SAME inode as
looking the real file up directly, not a copy of it. */
KT_EQ(t, sfsLookup("link.txt"), f);
KT_EQ(t, sfsTypeOf(sfsLookup("link.txt")), sfsTypeFile);
char back[64];
memset(back, 0, sizeof(back));
KT_EQ(t, sfsReadAt(sfsLookup("link.txt"), 0, back, len), (int64)len);
KT_EQ(t, memcmp(back, msg, len), 0);
}
KTEST(sfsReadlinkReturnsStoredTargetAndNoFollowSeesTheLinkItself)
{
if (!KT_TRUE(t, sfsFormat()))
return;
int32 f = sfsCreateFile("real.txt");
KT_TRUE(t, f > 0);
int32 link = sfsCreateSymlink("link.txt", "real.txt");
KT_TRUE(t, link > 0);
char target[32];
memset(target, 0, sizeof(target));
KT_TRUE(t, sfsReadlink(link, target, sizeof(target)));
KT_STREQ(t, target, "real.txt");
/* No-follow gives back the symlink's own inode (and its own type),
distinct from what sfsLookup (which follows) would give. */
int32 noFollow = sfsLookupNoFollow("link.txt");
KT_EQ(t, noFollow, link);
KT_EQ(t, sfsTypeOf(noFollow), sfsTypeSymlink);
KT_NE(t, noFollow, f);
}
KTEST(sfsReadlinkRejectsNonSymlinkAndUndersizedBuffer)
{
if (!KT_TRUE(t, sfsFormat()))
return;
int32 f = sfsCreateFile("plain.txt");
KT_TRUE(t, f > 0);
char buf[8];
KT_FALSE(t, sfsReadlink(f, buf, sizeof(buf)));
int32 link = sfsCreateSymlink("link.txt", "a target long enough to not fit");
KT_TRUE(t, link > 0);
char tiny[4];
KT_FALSE(t, sfsReadlink(link, tiny, sizeof(tiny)));
}
KTEST(sfsSymlinkToDirectoryAllowsDescendingThroughIt)
{
if (!KT_TRUE(t, sfsFormat()))
return;
KT_TRUE(t, sfsCreateDir("realdir") > 0);
int32 f = sfsCreateFile("realdir/inside.txt");
KT_TRUE(t, f > 0);
KT_EQ(t, sfsWriteAt(f, 0, "found me", 8), 8);
int32 link = sfsCreateSymlink("linkdir", "realdir");
KT_TRUE(t, link > 0);
KT_EQ(t, sfsTypeOf(link), sfsTypeSymlink);
/* The symlink is an INTERMEDIATE component here (there is more path
after it), which must still be followed even though sfsLookup's
"final component" special case does not apply. */
int32 viaLink = sfsLookup("linkdir/inside.txt");
KT_EQ(t, viaLink, f);
/* Creating a new file through the symlinked directory must land in the
real directory, reachable from both paths. */
KT_TRUE(t, sfsCreateFile("linkdir/new.txt") > 0);
KT_EQ(t, sfsLookup("realdir/new.txt"), sfsLookup("linkdir/new.txt"));
}
KTEST(sfsDanglingSymlinkFailsLookupButReadlinkStillWorks)
{
if (!KT_TRUE(t, sfsFormat()))
return;
int32 link = sfsCreateSymlink("broken.txt", "does/not/exist.txt");
KT_TRUE(t, link > 0);
KT_EQ(t, sfsLookup("broken.txt"), -1);
char target[64];
memset(target, 0, sizeof(target));
KT_TRUE(t, sfsReadlink(link, target, sizeof(target)));
KT_STREQ(t, target, "does/not/exist.txt");
}
KTEST(sfsSymlinkCycleFailsCleanlyInsteadOfHanging)
{
if (!KT_TRUE(t, sfsFormat()))
return;
KT_TRUE(t, sfsCreateSymlink("a", "b") > 0);
KT_TRUE(t, sfsCreateSymlink("b", "a") > 0);
/* The whole point: this must return with an error, not recurse forever
(or overflow the caller's stack) chasing a->b->a->b->... */
KT_EQ(t, sfsLookup("a"), -1);
KT_EQ(t, sfsLookup("b"), -1);
/* The table must still be in a usable state afterward - a failed
resolution is not the same thing as a corrupted filesystem. */
KT_TRUE(t, sfsCreateFile("stillworks.txt") > 0);
}
KTEST(sfsCreateSymlinkRejectsEmptyOrOversizedTarget)
{
if (!KT_TRUE(t, sfsFormat()))
return;
KT_EQ(t, sfsCreateSymlink("empty.txt", ""), -1);
char huge[sfsSymlinkTargetMax + 2];
memset(huge, 'x', sizeof(huge) - 1);
huge[sizeof(huge) - 1] = 0;
KT_EQ(t, sfsCreateSymlink("huge.txt", huge), -1);
/* Rejected attempts must not leave a half-created inode or directory
entry behind. */
KT_EQ(t, sfsLookupNoFollow("empty.txt"), -1);
KT_EQ(t, sfsLookupNoFollow("huge.txt"), -1);
}
KTEST(sfsRemoveSymlinkDoesNotAffectTarget)
{
if (!KT_TRUE(t, sfsFormat()))
return;
int32 f = sfsCreateFile("real.txt");
KT_TRUE(t, f > 0);
KT_EQ(t, sfsWriteAt(f, 0, "untouched", 9), 9);
KT_TRUE(t, sfsCreateSymlink("link.txt", "real.txt") > 0);
KT_TRUE(t, sfsRemove("link.txt"));
KT_EQ(t, sfsLookupNoFollow("link.txt"), -1);
/* The target must survive with its data intact - unlink of a symlink
must never recurse into what it points at. */
int32 stillThere = sfsLookup("real.txt");
KT_EQ(t, stillThere, f);
char back[16];
memset(back, 0, sizeof(back));
KT_EQ(t, sfsReadAt(stillThere, 0, back, 9), 9);
KT_EQ(t, memcmp(back, "untouched", 9), 0);
}