The Pedigree Project 0.1
xattr-contract-test/persistence.c
1#define _GNU_SOURCE
2#include <fcntl.h>
3#include <string.h>
4#include <unistd.h>
5
6#include "contract.h"
7#include <sys/stat.h>
8#include <sys/xattr.h>
9
10struct paths {
11 char base[192], parent[192], original[224], renamed[224], alias[224];
12 char directory[224], empty[224], completion[224];
13};
14
15static const unsigned char binary[] = {0, 0xff, 0x17, 0, 0x93, 4, 0x80};
16static const unsigned char replacement[] = {0x51, 0, 0xe7, 0x22};
17static const char* const file_names[] = {"user.binary", "user.empty", "user.replaced"};
18
19static int path_init(struct paths* paths, const char* base) {
20 const size_t length = strlen(base);
21 if (length < 2 || length >= sizeof(paths->base) || base[0] != '/' || base[length - 1] == '/')
22 return -1;
23 strcpy(paths->base, base);
24 strcpy(paths->parent, base);
25 char* end = strrchr(paths->parent, '/');
26 if (end == paths->parent)
27 end[1] = 0;
28 else
29 *end = 0;
30 snprintf(paths->original, sizeof(paths->original), "%s/payload-original", base);
31 snprintf(paths->renamed, sizeof(paths->renamed), "%s/payload-renamed", base);
32 snprintf(paths->alias, sizeof(paths->alias), "%s/payload-link", base);
33 snprintf(paths->directory, sizeof(paths->directory), "%s/directory", base);
34 snprintf(paths->empty, sizeof(paths->empty), "%s/ea-only", base);
35 snprintf(paths->completion, sizeof(paths->completion), "%s/complete", base);
36 return 0;
37}
38
39static void payload(unsigned char bytes[513]) {
40 for (size_t n = 0; n < 513; ++n)
41 bytes[n] = (unsigned char)(n * 29 + 3);
42}
43
44static int sync_directory(const char* path) {
45 int fd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
46 if (fd < 0)
47 return -1;
48 int result = fsync(fd), saved = errno;
49 if (close(fd) && !result) {
50 saved = errno;
51 result = -1;
52 }
53 errno = saved;
54 return result;
55}
56
57static int create_fixtures(const struct paths* paths) {
58 int failed = 0, fd = -1, directory = -1, empty = -1;
59 struct stat status;
60 unsigned char bytes[513];
61 payload(bytes);
62 errno = 0;
63 CHECK(lstat(paths->base, &status) == -1 && errno == ENOENT);
64 CHECK(!mkdir(paths->base, 0700));
65 CHECK(!mkdir(paths->directory, 0700));
66 directory = open(paths->directory, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
67 CHECK(directory >= 0);
68 CHECK(!fsetxattr(directory, "user.directory", binary, sizeof(binary), XATTR_CREATE));
69 fd = open(paths->original, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
70 CHECK(fd >= 0);
71 CHECK(!xa_write_all(fd, bytes, sizeof(bytes)));
72 CHECK(!fsetxattr(fd, "user.binary", binary, sizeof(binary), XATTR_CREATE));
73 CHECK(!fsetxattr(fd, "user.empty", NULL, 0, XATTR_CREATE));
74 CHECK(!fsetxattr(fd, "user.replaced", "old", 3, XATTR_CREATE));
75 CHECK(!fsetxattr(fd, "user.replaced", replacement, sizeof(replacement), XATTR_REPLACE));
76 CHECK(!fsetxattr(fd, "user.removed", "gone", 4, XATTR_CREATE));
77 CHECK(!fremovexattr(fd, "user.removed"));
78 CHECK(!link(paths->original, paths->alias));
79 CHECK(!rename(paths->original, paths->renamed));
80 empty = open(paths->empty, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
81 CHECK(empty >= 0);
82 CHECK(!fstat(empty, &status) && !status.st_size && !status.st_blocks);
83 CHECK(!fsetxattr(empty, "user.only", binary, sizeof(binary), XATTR_CREATE));
84 CHECK(!fstat(empty, &status) && !status.st_size &&
85 status.st_blocks == (blkcnt_t)(xa_block / 512));
86 CHECK(!fsync(fd) && !fsync(empty) && !fsync(directory));
87 CHECK(!sync_directory(paths->base) && !sync_directory(paths->parent));
88out:
89 if (empty >= 0 && close(empty))
90 failed = 1;
91 if (directory >= 0 && close(directory))
92 failed = 1;
93 if (fd >= 0 && close(fd))
94 failed = 1;
95 // Leave a failed fixture in place for disk and serial inspection.
96 return failed;
97}
98
99static int verify_fixtures(const struct paths* paths) {
100 int failed = 0, renamed = -1, directory = -1, empty = -1;
101 struct xa_file file = {.fd = -1, .backend = XA_EXT2};
102 struct stat alias_status, renamed_status, status;
103 unsigned char expected[513], actual[513], extra;
104 payload(expected);
105 CHECK(!stat(paths->base, &status) && S_ISDIR(status.st_mode));
106 errno = 0;
107 CHECK(lstat(paths->original, &status) == -1 && errno == ENOENT);
108 file.fd = open(paths->alias, O_RDONLY | O_CLOEXEC);
109 renamed = open(paths->renamed, O_RDONLY | O_CLOEXEC);
110 CHECK(file.fd >= 0 && renamed >= 0);
111 CHECK(!fstat(file.fd, &alias_status) && !fstat(renamed, &renamed_status));
112 CHECK(S_ISREG(alias_status.st_mode) && S_ISREG(renamed_status.st_mode) &&
113 alias_status.st_dev == renamed_status.st_dev &&
114 alias_status.st_ino == renamed_status.st_ino && alias_status.st_nlink == 2 &&
115 renamed_status.st_nlink == 2 && alias_status.st_size == sizeof(expected));
116 CHECK(!xa_read_all(file.fd, actual, sizeof(actual)) && !memcmp(actual, expected, sizeof(actual)));
117 CHECK(read(file.fd, &extra, 1) == 0);
118 CHECK(!xa_value(&file, XA_FD, "user.binary", binary, sizeof(binary)));
119 CHECK(!xa_value(&file, XA_FD, "user.empty", NULL, 0));
120 CHECK(!xa_value(&file, XA_FD, "user.replaced", replacement, sizeof(replacement)));
121 errno = 0;
122 CHECK(fgetxattr(file.fd, "user.removed", NULL, 0) == -1 && errno == ENODATA);
123 CHECK(!xa_names(&file, XA_FD, file_names, sizeof(file_names) / sizeof(file_names[0])));
124 struct xa_file second = {.fd = renamed, .backend = XA_EXT2};
125 CHECK(!xa_value(&second, XA_FD, "user.binary", binary, sizeof(binary)));
126 CHECK(!xa_names(&second, XA_FD, file_names, sizeof(file_names) / sizeof(file_names[0])));
127 directory = open(paths->directory, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
128 CHECK(directory >= 0 && !fstat(directory, &status) && S_ISDIR(status.st_mode));
129 struct xa_file dir = {.fd = directory, .backend = XA_EXT2, .directory = 1};
130 const char* directory_names[] = {"user.directory"};
131 CHECK(!xa_value(&dir, XA_FD, "user.directory", binary, sizeof(binary)));
132 CHECK(!xa_names(&dir, XA_FD, directory_names, 1));
133 empty = open(paths->empty, O_RDONLY | O_CLOEXEC);
134 CHECK(empty >= 0 && !fstat(empty, &status));
135 CHECK(S_ISREG(status.st_mode) && !status.st_size &&
136 status.st_blocks == (blkcnt_t)(xa_block / 512));
137 struct xa_file ea_only = {.fd = empty, .backend = XA_EXT2};
138 const char* empty_names[] = {"user.only"};
139 CHECK(!xa_value(&ea_only, XA_FD, "user.only", binary, sizeof(binary)));
140 CHECK(!xa_names(&ea_only, XA_FD, empty_names, 1));
141out:
142 if (empty >= 0)
143 close(empty);
144 if (directory >= 0)
145 close(directory);
146 if (renamed >= 0)
147 close(renamed);
148 if (file.fd >= 0)
149 close(file.fd);
150 return failed;
151}
152
153static int completion(const struct paths* paths, const char* expected, int write_stage) {
154 int failed = 0, fd = -1;
155 const size_t length = strlen(expected);
156 char actual[256], extra;
157 struct stat status;
158 CHECK(length < sizeof(actual));
159 if (write_stage) {
160 fd = open(paths->completion, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
161 CHECK(fd >= 0);
162 CHECK(!xa_write_all(fd, expected, length) && !fsync(fd));
163 CHECK(!close(fd));
164 fd = -1;
165 CHECK(!sync_directory(paths->base) && !sync_directory(paths->parent));
166 } else {
167 fd = open(paths->completion, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
168 CHECK(fd >= 0 && !fstat(fd, &status) && S_ISREG(status.st_mode) &&
169 status.st_size == (off_t)length);
170 CHECK(!xa_read_all(fd, actual, length) && !memcmp(actual, expected, length));
171 CHECK(read(fd, &extra, 1) == 0);
172 }
173out:
174 if (fd >= 0)
175 close(fd);
176 return failed;
177}
178
179int xa_persistence(const char* base, const char* token, int write_stage) {
180 struct paths paths;
181 char manifest[256];
182 const size_t token_length = strlen(token);
183 if (path_init(&paths, base) || !token_length || token_length > 96)
184 return 1;
185 for (size_t n = 0; n < token_length; ++n) {
186 const unsigned char byte = token[n];
187 if (!((byte >= 'a' && byte <= 'z') || (byte >= 'A' && byte <= 'Z') ||
188 (byte >= '0' && byte <= '9') || byte == '-' || byte == '_' || byte == '.'))
189 return 1;
190 }
191 int length =
192 snprintf(manifest, sizeof(manifest),
193 "PEDIGREE-XATTR-PERSIST 1\nfixture=1\ntoken=%s\nblock=%zu\n", token, xa_block);
194 if (length < 0 || (size_t)length >= sizeof(manifest))
195 return 1;
196 if (write_stage) {
197 if (create_fixtures(&paths) || verify_fixtures(&paths))
198 return 1;
199 return completion(&paths, manifest, 1);
200 }
201 // The completion record is checked before any fixture is opened. The read
202 // stage never creates, repairs, removes, or mutates an attribute.
203 return completion(&paths, manifest, 0) || verify_fixtures(&paths);
204}