The Pedigree Project 0.1
fanotify-handle-contract-test/persistence.c
1#define _GNU_SOURCE
2#include <string.h>
3#include <unistd.h>
4
5#include "contract.h"
6#include <sys/stat.h>
7
8struct manifest {
9 char magic[16];
10 uint32_t version, payload_size;
11 uint64_t inode;
12 int32_t writer_mount;
13 char token[64];
14 unsigned char payload[64];
15 struct fh_handle linked, stale;
16};
17static const char magic[] = "FH-PERSIST-1";
18static const char payload[] = "persistent fanotify handle\n";
19
20static int valid_token(const char* token) {
21 size_t count = strlen(token);
22 if (!count || count >= sizeof(((struct manifest*)0)->token))
23 return 0;
24 for (size_t n = 0; n < count; ++n) {
25 char ch = token[n];
26 if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') ||
27 ch == '-' || ch == '_'))
28 return 0;
29 }
30 return 1;
31}
32static int save(int directory, const char* name, const void* bytes, size_t size) {
33 int fd = openat(directory, name, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
34 if (fd < 0)
35 return -1;
36 int failed = fh_write_all(fd, bytes, size) || fsync(fd);
37 if (close(fd))
38 failed = 1;
39 return failed ? -1 : 0;
40}
41static int load(int directory, const char* name, void* bytes, size_t size) {
42 int fd = openat(directory, name, O_RDONLY | O_CLOEXEC);
43 if (fd < 0)
44 return -1;
45 char extra;
46 int failed = fh_read_all(fd, bytes, size) || read(fd, &extra, 1) != 0;
47 if (close(fd))
48 failed = 1;
49 return failed ? -1 : 0;
50}
51
52int fh_persistence(const char* base, const char* token, int write_stage) {
53 int failed = 0, directory = -1, parent = -1, target = -1, stale = -1, decoded = -1, alias = -1;
54 struct manifest manifest = {0};
55 struct stat state, alias_state;
56 char completion[128], actual_completion[128], parent_path[192];
57 char original_path[224], renamed_path[224], alias_path[224], stale_path[224];
58 CHECK(base[0] == '/' && strlen(base) < sizeof(parent_path) - 1 && valid_token(token));
59 CHECK(base[strlen(base) - 1] != '/');
60 const char* slash = strrchr(base, '/');
61 size_t parent_length = slash == base ? 1 : (size_t)(slash - base);
62 memcpy(parent_path, base, parent_length);
63 parent_path[parent_length] = 0;
64 snprintf(original_path, sizeof(original_path), "%s/target", base);
65 snprintf(renamed_path, sizeof(renamed_path), "%s/renamed", base);
66 snprintf(alias_path, sizeof(alias_path), "%s/alias", base);
67 snprintf(stale_path, sizeof(stale_path), "%s/stale", base);
68 int completion_length =
69 snprintf(completion, sizeof(completion), "FH-HANDLE-COMPLETE 1 %s\n", token);
70 CHECK(completion_length > 0 && (size_t)completion_length < sizeof(completion));
71 if (write_stage) {
72 CHECK(!mkdir(base, 0700));
73 parent = open(parent_path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
74 CHECK(parent >= 0);
75 }
76 directory = open(base, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
77 CHECK(directory >= 0);
78 if (write_stage) {
79 memcpy(manifest.magic, magic, sizeof(magic));
80 manifest.version = 1;
81 manifest.payload_size = sizeof(payload) - 1;
82 memcpy(manifest.payload, payload, manifest.payload_size);
83 memcpy(manifest.token, token, strlen(token) + 1);
84 target = open(original_path, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
85 CHECK(target >= 0 && !fh_write_all(target, manifest.payload, manifest.payload_size));
86 CHECK(!fh_export(target, &manifest.linked, &manifest.writer_mount));
87 CHECK(!fstat(target, &state));
88 manifest.inode = state.st_ino;
89 CHECK(!link(original_path, alias_path) && !rename(original_path, renamed_path));
90 CHECK(!fsync(target) && !close(target));
91 target = -1;
92 stale = open(stale_path, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
93 int stale_mount;
94 CHECK(stale >= 0 && !fh_export(stale, &manifest.stale, &stale_mount));
95 CHECK(stale_mount == manifest.writer_mount && !fsync(stale));
96 CHECK(!unlink(stale_path) && !close(stale));
97 stale = -1;
98 CHECK(!save(directory, "manifest", &manifest, sizeof(manifest)));
99 CHECK(!fsync(directory) && !fsync(parent));
100 CHECK(!save(directory, "complete", completion, (size_t)completion_length));
101 CHECK(!fsync(directory) && !fsync(parent));
102 } else {
103 CHECK(!load(directory, "complete", actual_completion, (size_t)completion_length));
104 CHECK(!memcmp(actual_completion, completion, (size_t)completion_length));
105 CHECK(!load(directory, "manifest", &manifest, sizeof(manifest)));
106 CHECK(!memcmp(manifest.magic, magic, sizeof(magic)) && manifest.version == 1);
107 CHECK(!memcmp(manifest.token, token, strlen(token) + 1));
108 CHECK(manifest.payload_size == sizeof(payload) - 1 &&
109 !memcmp(manifest.payload, payload, sizeof(payload) - 1));
110 CHECK(manifest.linked.handle_bytes && manifest.linked.handle_bytes <= 128 &&
111 manifest.stale.handle_bytes && manifest.stale.handle_bytes <= 128);
112 decoded = open_by_handle_at(directory, (struct file_handle*)&manifest.linked, O_RDONLY);
113 CHECK(decoded >= 0 && !fstat(decoded, &state) && (uint64_t)state.st_ino == manifest.inode);
114 unsigned char actual[sizeof(manifest.payload)];
115 CHECK(!fh_read_all(decoded, actual, manifest.payload_size));
116 CHECK(!memcmp(actual, manifest.payload, manifest.payload_size));
117 CHECK(read(decoded, actual, 1) == 0);
118 target = open(renamed_path, O_RDONLY | O_CLOEXEC);
119 alias = open(alias_path, O_RDONLY | O_CLOEXEC);
120 CHECK(target >= 0 && alias >= 0 && !fstat(alias, &alias_state));
121 CHECK(alias_state.st_ino == state.st_ino && alias_state.st_nlink == 2);
122 struct fh_handle fresh;
123 int mount_id;
124 CHECK(!fh_export(target, &fresh, &mount_id) && fh_equal(&fresh, &manifest.linked));
125 CHECK(!fh_export(alias, &fresh, &mount_id) && fh_equal(&fresh, &manifest.linked));
126 errno = 0;
127 CHECK(open_by_handle_at(directory, (struct file_handle*)&manifest.stale, O_RDONLY) == -1 &&
128 errno == ESTALE);
129 printf("FANOTIFY-HANDLE-PERSIST: verified inode=%llu writer_mount=%d reader_mount=%d\n",
130 (unsigned long long)manifest.inode, manifest.writer_mount, mount_id);
131 }
132out:
133 if (alias >= 0 && close(alias))
134 failed = 1;
135 if (decoded >= 0 && close(decoded))
136 failed = 1;
137 if (stale >= 0 && close(stale))
138 failed = 1;
139 if (target >= 0 && close(target))
140 failed = 1;
141 if (directory >= 0 && close(directory))
142 failed = 1;
143 if (parent >= 0 && close(parent))
144 failed = 1;
145 return failed;
146}