The Pedigree Project 0.1
file-lock-contract-test/main.c
1#define _GNU_SOURCE
2#include <poll.h>
3#include <signal.h>
4#include <string.h>
5#include <unistd.h>
6
7#include "contract.h"
8#include <sys/file.h>
9#include <sys/socket.h>
10#include <sys/wait.h>
11
12int64_t fl_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 fl_pause(int milliseconds) {
17 struct timespec pause = {milliseconds / 1000, (milliseconds % 1000) * 1000000L};
18 while (nanosleep(&pause, &pause) && errno == EINTR) {
19 }
20}
21int fl_wait(volatile int* flag, int milliseconds) {
22 const int64_t until = fl_now() + (int64_t)milliseconds * 1000000;
23 while (!__atomic_load_n(flag, __ATOMIC_ACQUIRE)) {
24 if (fl_now() >= until)
25 return -1;
26 fl_pause(2);
27 }
28 return 0;
29}
30int fl_reap(pid_t child, int milliseconds) {
31 const int64_t until = fl_now() + (int64_t)milliseconds * 1000000;
32 while (fl_now() < until) {
33 int status;
34 pid_t result = waitpid(child, &status, WNOHANG);
35 if (result == child)
36 return WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
37 if (result < 0 && errno != EINTR)
38 return -1;
39 fl_pause(5);
40 }
41 fprintf(stderr, "FILE-LOCK-CONTRACT: child=%ld timeout\n", (long)child);
42 kill(child, SIGKILL);
43 while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
44 }
45 return -1;
46}
47int fl_byte(int socket, char expected) {
48 struct pollfd entry = {.fd = socket, .events = POLLIN};
49 int result;
50 do
51 result = poll(&entry, 1, 5000);
52 while (result < 0 && errno == EINTR);
53 char byte;
54 return result > 0 && read(socket, &byte, 1) == 1 && byte == expected ? 0 : -1;
55}
56int fl_send_fd(int socket, int fd) {
57 char byte = 'f';
58 struct iovec vector = {&byte, 1};
59 union {
60 struct cmsghdr align;
61 char bytes[CMSG_SPACE(sizeof(int))];
62 } control = {0};
63 struct msghdr message = {.msg_iov = &vector,
64 .msg_iovlen = 1,
65 .msg_control = control.bytes,
66 .msg_controllen = sizeof(control.bytes)};
67 struct cmsghdr* item = CMSG_FIRSTHDR(&message);
68 item->cmsg_level = SOL_SOCKET;
69 item->cmsg_type = SCM_RIGHTS;
70 item->cmsg_len = CMSG_LEN(sizeof(int));
71 memcpy(CMSG_DATA(item), &fd, sizeof(fd));
72 return sendmsg(socket, &message, 0) == 1 ? 0 : -1;
73}
74int fl_receive_fd(int socket) {
75 char byte;
76 struct iovec vector = {&byte, 1};
77 union {
78 struct cmsghdr align;
79 char bytes[CMSG_SPACE(sizeof(int))];
80 } control = {0};
81 struct msghdr message = {.msg_iov = &vector,
82 .msg_iovlen = 1,
83 .msg_control = control.bytes,
84 .msg_controllen = sizeof(control.bytes)};
85 if (recvmsg(socket, &message, MSG_CMSG_CLOEXEC) != 1 || (message.msg_flags & MSG_CTRUNC))
86 return -1;
87 struct cmsghdr* item = CMSG_FIRSTHDR(&message);
88 if (!item || item->cmsg_level != SOL_SOCKET || item->cmsg_type != SCM_RIGHTS ||
89 item->cmsg_len != CMSG_LEN(sizeof(int)))
90 return -1;
91 int fd;
92 memcpy(&fd, CMSG_DATA(item), sizeof(fd));
93 return fd;
94}
95int fl_file(char path[128], const char* directory) {
96 static unsigned sequence;
97 snprintf(path, 128, "%s/file-lock-%ld-%u", directory, (long)getpid(), ++sequence);
98 int fd = open(path, O_CREAT | O_EXCL | O_RDWR, 0600);
99 if (fd >= 0 && ftruncate(fd, 128)) {
100 close(fd);
101 unlink(path);
102 return -1;
103 }
104 return fd;
105}
106int fl_record(int fd, int command, short type, short whence, off_t start, off_t length) {
107 struct flock lock = {.l_type = type, .l_whence = whence, .l_start = start, .l_len = length};
108 return fcntl(fd, command, &lock);
109}
110int fl_lock(int fd, int kind, short type, int blocking) {
111 if (kind == FL_FLOCK) {
112 int operation = type == F_UNLCK ? LOCK_UN : type == F_RDLCK ? LOCK_SH : LOCK_EX;
113 return flock(fd, operation | (blocking ? 0 : LOCK_NB));
114 }
115 return fl_record(fd,
116 kind == FL_CLASSIC ? (blocking ? F_SETLKW : F_SETLK)
117 : (blocking ? F_OFD_SETLKW : F_OFD_SETLK),
118 type, SEEK_SET, 0, 0);
119}
120int fl_query(int fd, int command, off_t start, off_t length, short type, off_t expected_start,
121 off_t expected_length, pid_t owner) {
122 struct flock lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = start, .l_len = length};
123 if (fcntl(fd, command, &lock))
124 return -1;
125 if (lock.l_type == type &&
126 (type == F_UNLCK || (lock.l_whence == SEEK_SET && lock.l_start == expected_start &&
127 lock.l_len == expected_length && lock.l_pid == owner)))
128 return 0;
129 fprintf(stderr, "FILE-LOCK-CONTRACT: GETLK type=%d whence=%d range=%lld/%lld pid=%ld\n",
130 lock.l_type, lock.l_whence, (long long)lock.l_start, (long long)lock.l_len,
131 (long)lock.l_pid);
132 return -1;
133}
134static int run(const char* name, int (*test)(void)) {
135 printf("FILE-LOCK-CONTRACT: BEGIN %s\n", name);
136 fflush(stdout);
137 pid_t child = fork();
138 if (child < 0)
139 return -1;
140 if (!child) {
141 alarm(40);
142 int result = test();
143 fflush(stdout);
144 fflush(stderr);
145 _exit(result ? 1 : 0);
146 }
147 const int status = fl_reap(child, 45000);
148 printf("FILE-LOCK-CONTRACT: %s %s status=%d\n", status ? "FAIL" : "PASS", name, status);
149 fflush(stdout);
150 return status;
151}
152int main(int argc, char** argv) {
153 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
154 return 1;
155 if (argc > 1 && !strcmp(argv[1], "lock-exec"))
156 return file_lock_exec(argc, argv);
157 const struct {
158 const char* name;
159 int (*test)(void);
160 } suites[] = {{"flock", file_lock_flock},
161 {"records", file_lock_records},
162 {"lifetime", file_lock_lifetime},
163 {"blocking", file_lock_blocking},
164 {"creation", file_lock_creation}};
165 int selected = 0;
166 for (unsigned n = 0; n < sizeof(suites) / sizeof(suites[0]); ++n) {
167 if (argc > 1 && strcmp(argv[1], suites[n].name))
168 continue;
169 selected = 1;
170 if (run(suites[n].name, suites[n].test))
171 return 1;
172 }
173 if (!selected)
174 return 2;
175 puts("FILE-LOCK-CONTRACT: END PASS");
176 return 0;
177}