The Pedigree Project 0.1
fs-persistence-contracts.c
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include <dirent.h>
9#include <errno.h>
10#include <fcntl.h>
11#include <stdio.h>
12#include <string.h>
13#include <unistd.h>
14
15#include <sys/stat.h>
16
17extern void fail(void) __attribute__((noreturn));
18
19static void require(int condition, const char* operation) {
20 if (!condition) {
21 printf("FS-PERSISTENCE-CONTRACT: FAIL %s errno=%d\n", operation, errno);
22 fail();
23 }
24}
25
26static int open_directory(const char* path) {
27 int fd = open(path, O_RDONLY | O_DIRECTORY);
28 require(fd >= 0, "open directory");
29 return fd;
30}
31
32static void require_absent(const char* path) {
33 struct stat status;
34 errno = 0;
35 require(lstat(path, &status) == -1 && errno == ENOENT, "retired name remains absent");
36}
37
38void test_fs_persistence_contracts(const char* base, int write_phase) {
39 unsigned char contents[32768 + 113];
40 for (size_t i = 0; i < sizeof(contents); ++i) {
41 contents[i] = (unsigned char)(i * 29 + 7);
42 }
43 if (write_phase) {
44 require(mkdir(base, 0700) == 0, "create isolated persistence directory");
45 }
46 require(chdir(base) == 0, "enter persistence directory");
47 int directory = open_directory(".");
48 if (write_phase) {
49 require(mkdir("left", 0700) == 0 && mkdir("right", 0700) == 0, "create rename parents");
50 int left = open_directory("left");
51 int right = open_directory("right");
52 int file = open("left/staged", O_CREAT | O_EXCL | O_RDWR, 0600);
53 require(file >= 0 && write(file, contents, sizeof(contents)) == sizeof(contents),
54 "write staged file");
55 require(link("left/staged", "left/alias") == 0, "create temporary hardlink");
56 require(fsync(file) == 0, "flush staged file");
57 require(close(file) == 0, "close staged file");
58 file = open("right/result", O_CREAT | O_EXCL | O_RDWR, 0600);
59 require(file >= 0 && write(file, "old", 3) == 3 && fsync(file) == 0 && close(file) == 0,
60 "persist replacement target");
61 require(mkdir("left/moved", 0700) == 0 && mkdir("left/removed", 0700) == 0,
62 "create directory mutations");
63 require(rename("left/staged", "right/result") == 0 && unlink("left/alias") == 0 &&
64 rename("left/moved", "right/moved") == 0 && rmdir("left/removed") == 0,
65 "commit namespace mutations");
66 // Parent sync must include the namespace metadata changed by rename and
67 // unlink, including the moved child's on-disk parent entry.
68 require(fsync(left) == 0 && fsync(right) == 0 && fsync(directory) == 0,
69 "flush mutated directories");
70 require(close(left) == 0 && close(right) == 0, "close rename parents");
71 file = open("complete", O_CREAT | O_EXCL | O_WRONLY, 0600);
72 require(file >= 0 && write(file, "ready", 5) == 5 && fsync(file) == 0 && close(file) == 0 &&
73 fsync(directory) == 0,
74 "persist completion marker");
75 int parent = open_directory("..");
76 require(fsync(parent) == 0 && close(parent) == 0, "flush containing directory");
77 } else {
78 unsigned char observed[sizeof(contents)];
79 int file = open("right/result", O_RDONLY);
80 struct stat status;
81 require(file >= 0 && fstat(file, &status) == 0 && status.st_size == sizeof(contents) &&
82 status.st_nlink == 1 && status.st_blocks > 0,
83 "reboot preserves file metadata");
84 require(read(file, observed, sizeof(observed)) == sizeof(observed) &&
85 !memcmp(contents, observed, sizeof(contents)),
86 "reboot preserves every saved byte");
87 require(close(file) == 0, "close verified file");
88 require_absent("left/staged");
89 require_absent("left/alias");
90 require_absent("left/moved");
91 require_absent("left/removed");
92 require(stat("right", &status) == 0, "read new parent inode");
93 DIR* moved = opendir("right/moved");
94 require(moved != NULL, "open moved directory");
95 int found_parent = 0;
96 struct dirent* entry;
97 while ((entry = readdir(moved))) {
98 if (!strcmp(entry->d_name, "..")) {
99 require(entry->d_ino == status.st_ino, "reboot preserves moved parent entry");
100 found_parent = 1;
101 }
102 }
103 require(found_parent && closedir(moved) == 0, "read moved parent entry");
104 file = open("complete", O_RDONLY);
105 char marker[5];
106 require(file >= 0 && read(file, marker, sizeof(marker)) == sizeof(marker) &&
107 !memcmp(marker, "ready", sizeof(marker)) && close(file) == 0,
108 "reboot preserves completion marker");
109 }
110 require(close(directory) == 0, "close persistence directory");
111 printf("FS-PERSISTENCE-CONTRACT: PASS %s\n", write_phase ? "write" : "read");
112 fflush(stdout);
113}