test: cover sfsTest
This commit is contained in:
1 parent
28a39f4234
commit
fe18aab0a9
1 file changed
+162
+162
@@ -781,3 +781,165 @@ KTEST(sfsSymlinkResolvesToTargetFile)
|
||||
if (!KT_TRUE(t, sfsFormat()))
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user