16#define FILES_PER_WORKER 32
17#define CHECK(expression) \
19 if (!(expression)) { \
20 fprintf(stderr, "RAMFS-INODE-CONTRACT: FAIL line=%d errno=%d\n", __LINE__, errno); \
26static int same_inode(
const struct stat* a,
const struct stat* b) {
27 return a->st_dev == b->st_dev && a->st_ino == b->st_ino;
30static int inspect_node(
const char* directory,
const char* name, mode_t kind,
31 const struct stat* target,
struct stat* result) {
33 int length = snprintf(path,
sizeof(path),
"%s/%s", directory, name);
34 if (length < 0 || (
size_t)length >=
sizeof(path)) {
38 struct stat descriptor, followed;
39 if (lstat(path, result) || stat(path, &followed))
41 int fd = open(path, O_PATH | O_NOFOLLOW);
44 int ok = fstat(fd, &descriptor) == 0;
47 return ok && result->st_ino != 0 && (result->st_mode & S_IFMT) == kind &&
48 same_inode(result, &descriptor) && same_inode(target ? target : result, &followed);
53 pthread_mutex_t* gate;
54 struct stat files[FILES_PER_WORKER];
58static void* create_files(
void* argument) {
66 for (
int i = 0; i < FILES_PER_WORKER; ++i) {
68 snprintf(name,
sizeof(name),
"parallel-%d", i);
69 int fd = openat(
worker->directory, name, O_CREAT | O_EXCL | O_RDWR, 0600);
76 fstat(fd, &
worker->files[i]) == 0 && fstatat(
worker->directory, name, &by_name, 0) == 0;
77 int saved_error = errno;
82 if (!ok || !
worker->files[i].st_ino || !same_inode(&
worker->files[i], &by_name)) {
83 worker->error = ok ? EINVAL : saved_error;
90int main(
int argc,
char** argv) {
91 const char* base = argc > 1 ? argv[1] :
"/tmp";
92 char directory[PATH_MAX];
93 int length = snprintf(directory,
sizeof(directory),
"%s/ramfs-inode-contract.XXXXXX", base);
94 int failed = 0, created = 0, root = -1, retained = -1, reopened = -1, replacement = -1;
95 int gate_locked = 0, started = 0, joined = 0;
96 pthread_mutex_t gate = PTHREAD_MUTEX_INITIALIZER;
97 pthread_t threads[WORKERS];
98 struct worker workers[WORKERS];
99 memset(workers, 0,
sizeof(workers));
100 for (
int i = 0; i < WORKERS; ++i)
101 workers[i].directory = -1;
102 const char* names[] = {
"first",
"second",
"dir-0",
"dir-1",
"dir-2",
"dir-3",
"link-0",
"link-1"};
103 struct stat nodes[11], observed;
104 CHECK(argc <= 2 && length >= 0 && (
size_t)length <
sizeof(directory));
105 CHECK(mkdtemp(directory) != NULL);
107 root = open(directory, O_RDONLY | O_DIRECTORY);
108 CHECK(root >= 0 && stat(base, &nodes[0]) == 0 && fstat(root, &nodes[1]) == 0);
109 printf(
"RAMFS-INODE-CONTRACT: base dev=%ju ino=%ju directory dev=%ju ino=%ju\n",
110 (uintmax_t)nodes[0].st_dev, (uintmax_t)nodes[0].st_ino, (uintmax_t)nodes[1].st_dev,
111 (uintmax_t)nodes[1].st_ino);
112 CHECK(nodes[0].st_ino != 0 && nodes[1].st_ino != 0 && !same_inode(&nodes[0], &nodes[1]));
113 retained = openat(root,
"first", O_CREAT | O_EXCL | O_RDWR, 0600);
114 reopened = openat(root,
"second", O_CREAT | O_EXCL | O_RDWR, 0600);
115 CHECK(retained >= 0 && reopened >= 0);
116 CHECK(write(retained,
"old", 3) == 3 && close(reopened) == 0);
118 for (
int i = 0; i < WORKERS; ++i)
119 CHECK(mkdirat(root, names[2 + i], 0700) == 0);
120 CHECK(symlinkat(
"first", root,
"link-0") == 0 && symlinkat(
"second", root,
"link-1") == 0);
121 for (
int i = 0; i < 8; ++i) {
122 mode_t kind = i < 2 ? S_IFREG : i < 6 ? S_IFDIR : S_IFLNK;
124 inspect_node(directory, names[i], kind, i >= 6 ? &nodes[2 + i - 6] : NULL, &nodes[2 + i]));
126 for (
int i = 1; i < 10; ++i) {
127 CHECK(nodes[i].st_dev == nodes[0].st_dev);
128 for (
int j = 0; j < i; ++j)
129 CHECK(!same_inode(&nodes[i], &nodes[j]));
131 puts(
"RAMFS-INODE-CONTRACT: PASS distinct-kinds");
133 reopened = openat(root,
"first", O_RDONLY);
134 CHECK(reopened >= 0 && fstat(reopened, &observed) == 0 && same_inode(&nodes[2], &observed));
135 CHECK(close(reopened) == 0);
137 CHECK(renameat(root,
"first", root,
"renamed") == 0);
138 CHECK(inspect_node(directory,
"renamed", S_IFREG, NULL, &observed) &&
139 same_inode(&nodes[2], &observed));
140 reopened = openat(root,
"renamed", O_RDONLY);
141 CHECK(reopened >= 0 && fstat(reopened, &observed) == 0 && same_inode(&nodes[2], &observed));
142 CHECK(close(reopened) == 0 && fstat(retained, &observed) == 0 &&
143 same_inode(&nodes[2], &observed));
145 CHECK(renameat(root,
"dir-3", root,
"dir-renamed") == 0);
146 CHECK(inspect_node(directory,
"dir-renamed", S_IFDIR, NULL, &observed) &&
147 same_inode(&nodes[7], &observed));
148 CHECK(renameat(root,
"dir-renamed", root,
"dir-3") == 0);
149 CHECK(renameat(root,
"link-1", root,
"link-renamed") == 0);
150 CHECK(inspect_node(directory,
"link-renamed", S_IFLNK, &nodes[3], &observed) &&
151 same_inode(&nodes[9], &observed));
152 CHECK(renameat(root,
"link-renamed", root,
"link-1") == 0);
153 CHECK(unlinkat(root,
"renamed", 0) == 0);
154 replacement = openat(root,
"renamed", O_CREAT | O_EXCL | O_RDWR, 0600);
155 CHECK(replacement >= 0 && fstat(replacement, &observed) == 0 && observed.st_ino != 0);
156 nodes[10] = observed;
157 CHECK(nodes[10].st_dev == nodes[2].st_dev);
158 for (
int i = 0; i < 10; ++i)
159 CHECK(!same_inode(&nodes[10], &nodes[i]));
160 CHECK(write(replacement,
"new", 3) == 3 && fstat(retained, &observed) == 0 &&
161 same_inode(&nodes[2], &observed));
163 CHECK(pread(retained, contents,
sizeof(contents), 0) ==
sizeof(contents) &&
164 memcmp(contents,
"old",
sizeof(contents)) == 0);
165 CHECK(pread(replacement, contents,
sizeof(contents), 0) ==
sizeof(contents) &&
166 memcmp(contents,
"new",
sizeof(contents)) == 0);
167 puts(
"RAMFS-INODE-CONTRACT: PASS stable-and-unlinked");
169 CHECK(pthread_mutex_lock(&gate) == 0);
171 for (
int i = 0; i < WORKERS; ++i) {
172 workers[i].directory = openat(root, names[2 + i], O_RDONLY | O_DIRECTORY);
173 workers[i].gate = &gate;
174 CHECK(workers[i].directory >= 0);
175 int error = pthread_create(&threads[i], NULL, create_files, &workers[i]);
181 CHECK(pthread_mutex_unlock(&gate) == 0);
183 for (; joined < started; ++joined) {
184 int error = pthread_join(threads[joined], NULL);
186 fprintf(stderr,
"RAMFS-INODE-CONTRACT: FAIL join=%d\n", error);
190 for (
int i = 0; i < WORKERS; ++i) {
191 if (workers[i].error)
192 errno = workers[i].error;
193 CHECK(workers[i].error == 0);
194 for (
int j = 0; j < FILES_PER_WORKER; ++j) {
195 struct stat* current = &workers[i].files[j];
196 CHECK(current->st_ino != 0 && current->st_dev == nodes[0].st_dev);
197 for (
int k = 0; k < 11; ++k)
198 CHECK(!same_inode(current, &nodes[k]));
199 for (
int k = 0; k <= i; ++k)
200 for (
int n = 0; n < (k == i ? j : FILES_PER_WORKER); ++n)
201 CHECK(!same_inode(current, &workers[k].files[n]));
204 puts(
"RAMFS-INODE-CONTRACT: PASS concurrent-create");
208 pthread_mutex_unlock(&gate);
209 for (; joined < started; ++joined) {
210 int error = pthread_join(threads[joined], NULL);
212 fprintf(stderr,
"RAMFS-INODE-CONTRACT: FAIL cleanup-join=%d\n", error);
216 pthread_mutex_destroy(&gate);
221 if (replacement >= 0)
223 for (
int i = 0; i < WORKERS; ++i) {
224 if (workers[i].directory >= 0) {
225 for (
int j = 0; j < FILES_PER_WORKER; ++j) {
227 snprintf(name,
sizeof(name),
"parallel-%d", j);
228 unlinkat(workers[i].directory, name, 0);
230 close(workers[i].directory);
234 unlinkat(root,
"first", 0);
235 unlinkat(root,
"second", 0);
236 unlinkat(root,
"renamed", 0);
237 unlinkat(root,
"link-0", 0);
238 unlinkat(root,
"link-1", 0);
239 unlinkat(root,
"link-renamed", 0);
240 unlinkat(root,
"dir-renamed", AT_REMOVEDIR);
241 for (
int i = 0; i < WORKERS; ++i)
242 unlinkat(root, names[2 + i], AT_REMOVEDIR);
245 if (created && rmdir(directory)) {
246 fprintf(stderr,
"RAMFS-INODE-CONTRACT: FAIL cleanup errno=%d\n", errno);
249 printf(
"RAMFS-INODE-CONTRACT: END %s base=%s\n", failed ?
"FAIL" :
"PASS", base);
250 return failed ? 1 : 0;