19static void require(
int condition,
const char* operation) {
21 printf(
"FS-PERSISTENCE-CONTRACT: FAIL %s errno=%d\n", operation, errno);
26static int open_directory(
const char* path) {
27 int fd = open(path, O_RDONLY | O_DIRECTORY);
28 require(fd >= 0,
"open directory");
32static void require_absent(
const char* path) {
35 require(lstat(path, &status) == -1 && errno == ENOENT,
"retired name remains absent");
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);
44 require(mkdir(base, 0700) == 0,
"create isolated persistence directory");
46 require(chdir(base) == 0,
"enter persistence directory");
47 int directory = open_directory(
".");
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),
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");
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");
78 unsigned char observed[
sizeof(contents)];
79 int file = open(
"right/result", O_RDONLY);
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");
97 while ((entry = readdir(moved))) {
98 if (!strcmp(entry->d_name,
"..")) {
99 require(entry->d_ino == status.st_ino,
"reboot preserves moved parent entry");
103 require(found_parent && closedir(moved) == 0,
"read moved parent entry");
104 file = open(
"complete", O_RDONLY);
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");
110 require(close(directory) == 0,
"close persistence directory");
111 printf(
"FS-PERSISTENCE-CONTRACT: PASS %s\n", write_phase ?
"write" :
"read");