diff --git a/src/sfsTest.c b/src/sfsTest.c index 7917679..0b531e0 100644 --- a/src/sfsTest.c +++ b/src/sfsTest.c @@ -468,3 +468,159 @@ KTEST(sfsPersistsMtimeAndCtimeAcrossUnmountRemount) return; KT_EQ(t, sfsWriteAt(f, 0, "persisted", 9), 9); + uint64 mtimeBefore = sfsMtimeOf(f); + uint64 ctimeBefore = sfsCtimeOf(f); + KT_GT(t, mtimeBefore, 0); + + sfsUnmount(); + KT_TRUE(t, sfsMount()); + + int32 reloaded = sfsLookup("timepersist.txt"); + KT_EQ(t, reloaded, f); + KT_EQ(t, sfsMtimeOf(reloaded), mtimeBefore); + KT_EQ(t, sfsCtimeOf(reloaded), ctimeBefore); +} + +/* Real chmod(2) owner rwx bits (fs.h's fsModeOwner*): createNode stamps a + type-based default (a file gets fsModeDefaultFile, a directory gets + fsModeDefaultDir), sfsChmod overwrites it and bumps ctime like any other + metadata-only mutation (sfsLink's linkCount bump does the same, see + above), and sfsModeOf/sfsChmod on an invalid inode number fail the same + way sfsSizeOf/sfsTypeOf already do for one. */ +KTEST(sfsModeDefaultsByTypeAndChmodBumpsCtime) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("modefile.txt"); + if (!KT_TRUE(t, f > 0)) + return; + KT_EQ(t, sfsModeOf(f), fsModeDefaultFile); + + int32 d = sfsCreateDir("modedir"); + if (!KT_TRUE(t, d > 0)) + return; + KT_EQ(t, sfsModeOf(d), fsModeDefaultDir); + + uint64 ctimeBefore = sfsCtimeOf(f); + int64 before = rtcNow(); + KT_TRUE(t, sfsChmod(f, 0400)); + int64 after = rtcNow(); + + KT_EQ(t, sfsModeOf(f), 0400u); + KT_GE(t, sfsCtimeOf(f), ctimeBefore); + KT_GE(t, sfsCtimeOf(f), before); + KT_LE(t, sfsCtimeOf(f), after); + + KT_EQ(t, sfsModeOf(-1), 0u); + KT_EQ(t, sfsModeOf(9999), 0u); + KT_FALSE(t, sfsChmod(-1, 0644)); + KT_FALSE(t, sfsChmod(9999, 0644)); +} + +/* Same real point as sfsPersistsMtimeAndCtimeAcrossUnmountRemount above, + applied to mode: sfsUnmount() drops every in-memory structure, so an + exact match after sfsMount() proves the chmod'd value genuinely lives on + disk inside the inode, not in some cache that happens to survive. */ +KTEST(sfsPersistsModeAcrossUnmountRemount) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("modepersist.txt"); + if (!KT_TRUE(t, f > 0)) + return; + KT_TRUE(t, sfsChmod(f, 0400)); + + sfsUnmount(); + KT_TRUE(t, sfsMount()); + + int32 reloaded = sfsLookup("modepersist.txt"); + KT_EQ(t, reloaded, f); + KT_EQ(t, sfsModeOf(reloaded), 0400u); +} + +/* Real owner identity (fs.h's FbStat.uid, sched.h's currentThreadUid()): + createNode stamps every new node -- file, dir, symlink, fifo all go + through it, exactly like mtime/ctime already verified sufficient for + (FALA 39's own note) -- with whoever is CURRENT at creation time. + ktestRunAll always runs as the permanent idle thread (id 0, uid 0), the + same context schedTest.c's own uid-default tests already document + relying on, so both a freshly created file and the root inode itself + (sfsFormat, this test's own sfsFormat() call included) come back + root-owned. sfsUidOf on an invalid inode number fails the same way + sfsModeOf/sfsSizeOf/sfsTypeOf already do for one. */ +KTEST(sfsCreateNodeStampsCreatorUidAsOwner) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + KT_EQU(t, sfsUidOf(sfsRootInode), 0u); + + int32 f = sfsCreateFile("uidfile.txt"); + if (!KT_TRUE(t, f > 0)) + return; + KT_EQU(t, sfsUidOf(f), 0u); + + int32 d = sfsCreateDir("uiddir"); + if (!KT_TRUE(t, d > 0)) + return; + KT_EQU(t, sfsUidOf(d), 0u); + + KT_EQU(t, sfsUidOf(-1), 0u); + KT_EQU(t, sfsUidOf(9999), 0u); +} + +/* Same real point as sfsPersistsModeAcrossUnmountRemount above, applied to + uid: sfsUnmount() drops every in-memory structure, so an exact match + after sfsMount() proves ownership genuinely lives on disk inside the + inode, not in some cache that happens to survive. */ +KTEST(sfsPersistsUidAcrossUnmountRemount) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("uidpersist.txt"); + if (!KT_TRUE(t, f > 0)) + return; + KT_EQU(t, sfsUidOf(f), 0u); + + sfsUnmount(); + KT_TRUE(t, sfsMount()); + + int32 reloaded = sfsLookup("uidpersist.txt"); + KT_EQ(t, reloaded, f); + KT_EQU(t, sfsUidOf(reloaded), 0u); +} + +/* Real chown(2)'s on-disk half (sfsChown): overwrites the inode's owner + uid outright and bumps ctime like sfsChmod's own metadata-only mutation + does -- same "bump ctime, invalid inode fails the same way" shape as + sfsModeDefaultsByTypeAndChmodBumpsCtime above. sfsChown itself has no + privilege check (vfsChown, vfs.c, is the real choke point that enforces + chown(2)'s root-only rule), so calling it directly here at any uid is + deliberately not restricted. */ +KTEST(sfsChownReassignsOwnerAndBumpsCtime) +{ + if (!KT_TRUE(t, sfsFormat())) + return; + + int32 f = sfsCreateFile("chownfile.txt"); + if (!KT_TRUE(t, f > 0)) + return; + KT_EQU(t, sfsUidOf(f), 0u); + + uint64 ctimeBefore = sfsCtimeOf(f); + int64 before = rtcNow(); + KT_TRUE(t, sfsChown(f, 9001)); + int64 after = rtcNow(); + + KT_EQU(t, sfsUidOf(f), 9001u); + KT_GE(t, sfsCtimeOf(f), ctimeBefore); + KT_GE(t, sfsCtimeOf(f), before); + KT_LE(t, sfsCtimeOf(f), after); + + KT_FALSE(t, sfsChown(-1, 1000)); + KT_FALSE(t, sfsChown(9999, 1000)); +} +