The Pedigree Project 0.1
fanotify-handle-contract-test/fixture.c
1#define _GNU_SOURCE
2#include <poll.h>
3#include <signal.h>
4#include <string.h>
5#include <time.h>
6#include <unistd.h>
7
8#include "contract.h"
9#include <sys/socket.h>
10#include <sys/wait.h>
11
12int64_t fh_now(void) {
13 struct timespec now;
14 return clock_gettime(CLOCK_MONOTONIC, &now) ? -1 : (int64_t)now.tv_sec * 1000000000 + now.tv_nsec;
15}
16void fh_pause(int milliseconds) {
17 struct timespec pause = {milliseconds / 1000, (milliseconds % 1000) * 1000000};
18 while (nanosleep(&pause, &pause) && errno == EINTR) {
19 }
20}
21int fh_wait(volatile int* flag, int milliseconds) {
22 int64_t start = fh_now(), deadline = start + (int64_t)milliseconds * 1000000;
23 while (!__atomic_load_n(flag, __ATOMIC_ACQUIRE)) {
24 int64_t now = fh_now();
25 if (start < 0 || now < 0 || now >= deadline)
26 return -1;
27 fh_pause(2);
28 }
29 return 0;
30}
31int fh_reap(pid_t child, int milliseconds) {
32 int64_t start = fh_now(), deadline = start + (int64_t)milliseconds * 1000000;
33 int64_t now;
34 while (start >= 0 && (now = fh_now()) >= 0 && now < deadline) {
35 int status;
36 pid_t result = waitpid(child, &status, WNOHANG);
37 if (result == child)
38 return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
39 if (result < 0 && errno != EINTR)
40 return -1;
41 fh_pause(5);
42 }
43 kill(child, SIGKILL);
44 while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
45 }
46 return -1;
47}
48int fh_read_all(int fd, void* buffer, size_t length) {
49 unsigned char* bytes = buffer;
50 while (length) {
51 ssize_t amount = read(fd, bytes, length);
52 if (amount < 0 && errno == EINTR)
53 continue;
54 if (amount <= 0)
55 return -1;
56 bytes += amount;
57 length -= amount;
58 }
59 return 0;
60}
61int fh_write_all(int fd, const void* buffer, size_t length) {
62 const unsigned char* bytes = buffer;
63 while (length) {
64 ssize_t amount = write(fd, bytes, length);
65 if (amount < 0 && errno == EINTR)
66 continue;
67 if (amount <= 0)
68 return -1;
69 bytes += amount;
70 length -= amount;
71 }
72 return 0;
73}
74int fh_send(int fd, char byte) {
75 return fh_write_all(fd, &byte, 1);
76}
77int fh_readable(int fd, int milliseconds) {
78 struct pollfd entry = {.fd = fd, .events = POLLIN};
79 int result = poll(&entry, 1, milliseconds);
80 return result > 0 ? !!(entry.revents & POLLIN) : result;
81}
82int fh_receive(int fd, char byte) {
83 if (fh_readable(fd, 5000) != 1)
84 return -1;
85 char received;
86 return !fh_read_all(fd, &received, 1) && received == byte ? 0 : -1;
87}
88int fh_send_fd(int socket, int fd) {
89 char byte = 'f';
90 struct iovec iov = {&byte, 1};
91 union {
92 struct cmsghdr align;
93 char bytes[CMSG_SPACE(sizeof(int))];
94 } control = {0};
95 struct msghdr message = {.msg_iov = &iov,
96 .msg_iovlen = 1,
97 .msg_control = control.bytes,
98 .msg_controllen = sizeof(control.bytes)};
99 struct cmsghdr* item = CMSG_FIRSTHDR(&message);
100 item->cmsg_level = SOL_SOCKET;
101 item->cmsg_type = SCM_RIGHTS;
102 item->cmsg_len = CMSG_LEN(sizeof(int));
103 memcpy(CMSG_DATA(item), &fd, sizeof(fd));
104 return sendmsg(socket, &message, 0) == 1 ? 0 : -1;
105}
106int fh_receive_fd(int socket) {
107 char byte;
108 struct iovec iov = {&byte, 1};
109 union {
110 struct cmsghdr align;
111 char bytes[CMSG_SPACE(sizeof(int))];
112 } control = {0};
113 struct msghdr message = {.msg_iov = &iov,
114 .msg_iovlen = 1,
115 .msg_control = control.bytes,
116 .msg_controllen = sizeof(control.bytes)};
117 if (recvmsg(socket, &message, MSG_CMSG_CLOEXEC) != 1 || message.msg_flags & MSG_CTRUNC)
118 return -1;
119 struct cmsghdr* item = CMSG_FIRSTHDR(&message);
120 if (!item || item->cmsg_level != SOL_SOCKET || item->cmsg_type != SCM_RIGHTS ||
121 item->cmsg_len != CMSG_LEN(sizeof(int)))
122 return -1;
123 int fd;
124 memcpy(&fd, CMSG_DATA(item), sizeof(fd));
125 return fd;
126}
127int fh_export(int fd, struct fh_handle* handle, int* mount_id) {
128 memset(handle, 0, sizeof(*handle));
129 handle->handle_bytes = sizeof(handle->bytes);
130 return name_to_handle_at(fd, "", (struct file_handle*)handle, mount_id, AT_EMPTY_PATH);
131}
132int fh_equal(const struct fh_handle* first, const struct fh_handle* second) {
133 return first->handle_bytes <= sizeof(first->bytes) &&
134 first->handle_bytes == second->handle_bytes && first->handle_type == second->handle_type &&
135 !memcmp(first->bytes, second->bytes, first->handle_bytes);
136}
137int fh_create(struct fh_file* file) {
138 static unsigned sequence;
139 memset(file, 0, sizeof(*file));
140 file->fd = -1;
141 snprintf(file->path, sizeof(file->path), "/fanotify-handle-%ld-%u", (long)getpid(), ++sequence);
142 file->fd = open(file->path, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
143 if (file->fd < 0 || fh_write_all(file->fd, "0123456789abcdef", 16) ||
144 fh_export(file->fd, &file->handle, &file->mount_id)) {
145 int saved = errno;
146 fh_close(file);
147 errno = saved;
148 return -1;
149 }
150 return 0;
151}
152void fh_close(struct fh_file* file) {
153 if (file->fd >= 0)
154 close(file->fd);
155 if (file->path[0])
156 unlink(file->path);
157 file->fd = -1;
158 file->path[0] = 0;
159}
160int fh_group(int nonblock) {
161 return fanotify_init(FAN_REPORT_FID | FAN_CLOEXEC | (nonblock ? FAN_NONBLOCK : 0), O_RDONLY);
162}
163int fh_mark(int group, const struct fh_file* file, unsigned flags, uint64_t mask) {
164 return fanotify_mark(group, flags, mask, file->fd, NULL);
165}
166int fh_modify(const struct fh_file* file) {
167 return pwrite(file->fd, "!", 1, 0) == 1 ? 0 : -1;
168}
169size_t fh_record_size(const struct fh_handle* handle) {
170 return sizeof(struct fanotify_event_metadata) +
171 ((sizeof(struct fanotify_event_info_fid) + 8 + handle->handle_bytes + 3) & ~(size_t)3);
172}
173int fh_parse(const void* bytes, size_t length, struct fh_record* record) {
174 struct fanotify_event_metadata metadata;
175 if (length < sizeof(metadata))
176 return -1;
177 memcpy(&metadata, bytes, sizeof(metadata));
178 if (metadata.vers != FANOTIFY_METADATA_VERSION || metadata.event_len != length ||
179 metadata.metadata_len < sizeof(metadata) || metadata.metadata_len > length ||
180 metadata.fd != FAN_NOFD)
181 return -1;
182 memset(record, 0, sizeof(*record));
183 record->mask = metadata.mask;
184 record->pid = metadata.pid;
185 if (metadata.mask & FAN_Q_OVERFLOW)
186 return length == metadata.metadata_len ? 0 : -1;
187 const unsigned char* cursor = (const unsigned char*)bytes + metadata.metadata_len;
188 size_t remaining = length - metadata.metadata_len;
189 if (remaining < sizeof(struct fanotify_event_info_fid) + 8)
190 return -1;
191 struct fanotify_event_info_header info;
192 memcpy(&info, cursor, sizeof(info));
193 if (info.info_type != FAN_EVENT_INFO_TYPE_FID || info.len != remaining)
194 return -1;
195 memcpy(record->fsid, cursor + sizeof(info), sizeof(record->fsid));
196 memcpy(&record->handle, cursor + sizeof(struct fanotify_event_info_fid), 8);
197 size_t count = record->handle.handle_bytes;
198 if (!count || count > sizeof(record->handle.bytes) ||
199 sizeof(struct fanotify_event_info_fid) + 8 + count > remaining)
200 return -1;
201 memcpy(record->handle.bytes, cursor + sizeof(struct fanotify_event_info_fid) + 8, count);
202 unsigned present = 0;
203 for (size_t n = 0; n < sizeof(record->fsid); ++n)
204 present |= record->fsid[n];
205 return present ? 0 : -1;
206}
207int fh_take(int group, const struct fh_handle* shape, struct fh_record* record) {
208 unsigned char bytes[256];
209 size_t length = fh_record_size(shape);
210 if (length > sizeof(bytes))
211 return -1;
212 ssize_t result = read(group, bytes, length);
213 return result > 0 ? fh_parse(bytes, (size_t)result, record) : -1;
214}
215int fh_event(int group, const struct fh_file* file, uint64_t mask, pid_t pid) {
216 struct fh_record record = {0};
217 int result = fh_take(group, &file->handle, &record);
218 if (result || !fh_equal(&record.handle, &file->handle) || (record.mask & mask) != mask ||
219 record.pid != pid) {
220 fprintf(stderr,
221 "FANOTIFY-HANDLE-CONTRACT: event result=%d expected=%llx/%ld actual=%llx/%d errno=%d\n",
222 result, (unsigned long long)mask, (long)pid, (unsigned long long)record.mask,
223 record.pid, errno);
224 return -1;
225 }
226 return 0;
227}
228int fh_drain(int group) {
229 unsigned char bytes[4096];
230 for (unsigned attempt = 0; attempt < FH_QUEUE_LIMIT + 2; ++attempt) {
231 ssize_t count = read(group, bytes, sizeof(bytes));
232 if (count < 0 && errno == EAGAIN)
233 return 0;
234 if (count <= 0)
235 return -1;
236 }
237 return -1;
238}