Files
2026-09-13 21:46:59 +02:00

113 lines
3.5 KiB
C

#include "ulib.h"
#include "fs.h"
/*
* Real ring-3 proof of unlink(2)/rmdir(2) as two DISTINCT syscalls
* (sysUnlink=13, pre-existing; sysRmdir=43, new this fala) through
* userUnlink/userRmdir (ulib.h), exercising both tmpfs (/tmp) and the
* persistent on-disk SFS mount (/mnt) to prove vfs.c's dispatch layer
* enforces the same real POSIX split -- unlink refuses a directory,
* rmdir refuses anything that is not an EMPTY directory -- on both
* backing stores, not just one. Fully synchronous like nicetest.c/
* date.c/timetest.c: no injected keystrokes, no shared shell channel, so
* there is no scheduling-timing race to flake on regardless of host load.
*/
static int createFile(const char* path)
{
int fd = (int)userOpen(path, oReadWrite | oCreate);
if (fd < 0)
return -1;
userClose(fd);
return 0;
}
int main(void)
{
print("rmtest: start, pid=");
printDec(userGetpid());
print("\n");
if (userMkdir("/tmp/rmtest") != 0) {
print("rmtest: FAIL mkdir /tmp/rmtest\n");
return 1;
}
if (createFile("/tmp/rmtest/f.txt") != 0) {
print("rmtest: FAIL create /tmp/rmtest/f.txt\n");
return 1;
}
if (userRmdir("/tmp/rmtest") == 0) {
print("rmtest: FAIL rmdir succeeded on a non-empty tmpfs dir\n");
return 1;
}
if (userUnlink("/tmp/rmtest") == 0) {
print("rmtest: FAIL unlink succeeded on a tmpfs directory\n");
return 1;
}
FbStat st;
if (userStat("/tmp/rmtest", &st) != 0 || st.type != fsTypeDir) {
print("rmtest: FAIL tmpfs dir did not survive the rejected attempts\n");
return 1;
}
print("rmtest: tmpfs-reject-ok\n");
if (userUnlink("/tmp/rmtest/f.txt") != 0) {
print("rmtest: FAIL unlink /tmp/rmtest/f.txt\n");
return 1;
}
if (userRmdir("/tmp/rmtest") != 0) {
print("rmtest: FAIL rmdir empty tmpfs dir\n");
return 1;
}
if (userStat("/tmp/rmtest", &st) == 0) {
print("rmtest: FAIL tmpfs dir still present after rmdir\n");
return 1;
}
print("rmtest: tmpfs-remove-ok\n");
if (userMkdir("/mnt/rmtest") != 0) {
print("rmtest: FAIL mkdir /mnt/rmtest\n");
return 1;
}
/* The exact previously-shipped gap this fala closed: unlinking an
EMPTY on-disk directory used to silently succeed (sfsRemove's own
dir-emptiness check let it through with no type gate above it) --
must now be refused, EISDIR-shaped, same as tmpfs above. */
if (userUnlink("/mnt/rmtest") == 0) {
print("rmtest: FAIL unlink succeeded on an empty SFS directory\n");
return 1;
}
if (userStat("/mnt/rmtest", &st) != 0 || st.type != fsTypeDir) {
print("rmtest: FAIL SFS dir did not survive the rejected unlink\n");
return 1;
}
print("rmtest: sfs-reject-ok\n");
if (createFile("/mnt/rmtest/g.txt") != 0) {
print("rmtest: FAIL create /mnt/rmtest/g.txt\n");
return 1;
}
if (userRmdir("/mnt/rmtest") == 0) {
print("rmtest: FAIL rmdir succeeded on a non-empty SFS dir\n");
return 1;
}
if (userUnlink("/mnt/rmtest/g.txt") != 0) {
print("rmtest: FAIL unlink /mnt/rmtest/g.txt\n");
return 1;
}
if (userRmdir("/mnt/rmtest") != 0) {
print("rmtest: FAIL rmdir empty SFS dir\n");
return 1;
}
if (userStat("/mnt/rmtest", &st) == 0) {
print("rmtest: FAIL SFS dir still present after rmdir\n");
return 1;
}
print("rmtest: sfs-remove-ok\n");
print("rmtest: PASS\n");
return 0;
}