The Pedigree Project 0.1
transfer-contract-test/main.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/sendfile.h>
10#include <sys/socket.h>
11#include <sys/wait.h>
12
13int64_t tf_now(void) {
14 struct timespec now;
15 return clock_gettime(CLOCK_MONOTONIC, &now) ? -1 : (int64_t)now.tv_sec * 1000000000 + now.tv_nsec;
16}
17void tf_pause(int milliseconds) {
18 struct timespec delay = {milliseconds / 1000, (milliseconds % 1000) * 1000000L};
19 while (nanosleep(&delay, &delay) && errno == EINTR) {
20 }
21}
22int tf_wait(volatile int* flag, int milliseconds) {
23 const int64_t until = tf_now() + (int64_t)milliseconds * 1000000;
24 while (!__atomic_load_n(flag, __ATOMIC_ACQUIRE)) {
25 if (tf_now() >= until)
26 return -1;
27 tf_pause(2);
28 }
29 return 0;
30}
31int tf_reap(pid_t child, int milliseconds) {
32 const int64_t until = tf_now() + (int64_t)milliseconds * 1000000;
33 while (tf_now() < until) {
34 int status;
35 pid_t result = waitpid(child, &status, WNOHANG);
36 if (result == child) {
37 int code = WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status);
38 if (code)
39 fprintf(stderr, "TRANSFER-CONTRACT: child=%ld status=%d\n", (long)child, code);
40 return code;
41 }
42 if (result < 0 && errno != EINTR)
43 return -1;
44 tf_pause(5);
45 }
46 fprintf(stderr, "TRANSFER-CONTRACT: child=%ld timeout\n", (long)child);
47 kill(child, SIGKILL);
48 while (waitpid(child, NULL, 0) < 0 && errno == EINTR) {
49 }
50 return -1;
51}
52unsigned char tf_pattern(size_t offset, int seed) {
53 return seed == TF_FILLER ? 0x7a : ((offset * 17) ^ (offset >> 9) ^ seed) & 255;
54}
55int tf_create(struct tf_file* file, const char* directory, size_t bytes, int seed) {
56 static unsigned sequence;
57 snprintf(file->path, sizeof(file->path), "%s/transfer-%ld-%u", directory, (long)getpid(),
58 ++sequence);
59 file->fd = open(file->path, O_CREAT | O_EXCL | O_RDWR, 0600);
60 if (file->fd < 0)
61 return -1;
62 unsigned char buffer[4096];
63 for (size_t offset = 0; offset < bytes;) {
64 size_t count = bytes - offset < sizeof(buffer) ? bytes - offset : sizeof(buffer);
65 for (size_t n = 0; n < count; ++n)
66 buffer[n] = tf_pattern(offset + n, seed);
67 if (pwrite(file->fd, buffer, count, offset) != (ssize_t)count)
68 return -1;
69 offset += count;
70 }
71 return 0;
72}
73void tf_destroy(struct tf_file* file) {
74 if (file->fd >= 0)
75 close(file->fd);
76 if (*file->path)
77 unlink(file->path);
78 file->fd = -1;
79}
80int tf_verify(int fd, off_t offset, size_t length, size_t source_offset, int seed) {
81 unsigned char buffer[4096];
82 for (size_t done = 0; done < length;) {
83 size_t count = length - done < sizeof(buffer) ? length - done : sizeof(buffer);
84 if (pread(fd, buffer, count, offset + done) != (ssize_t)count)
85 return -1;
86 for (size_t n = 0; n < count; ++n)
87 if (buffer[n] != tf_pattern(source_offset + done + n, seed))
88 return -1;
89 done += count;
90 }
91 return 0;
92}
93int tf_socket_read(int fd, size_t length, size_t source_offset, int seed) {
94 unsigned char buffer[4096];
95 const int64_t until = tf_now() + 5000000000;
96 for (size_t done = 0; done < length;) {
97 struct pollfd entry = {.fd = fd, .events = POLLIN};
98 if (tf_now() >= until)
99 return -1;
100 int ready = poll(&entry, 1, 100);
101 if (ready < 0 && errno == EINTR)
102 continue;
103 if (ready < 0)
104 return -1;
105 if (!ready)
106 continue;
107 size_t count = length - done < sizeof(buffer) ? length - done : sizeof(buffer);
108 ssize_t received = read(fd, buffer, count);
109 if (received <= 0)
110 return -1;
111 for (ssize_t n = 0; n < received; ++n)
112 if (buffer[n] != tf_pattern(source_offset + done + n, seed))
113 return -1;
114 done += received;
115 }
116 return 0;
117}
118ssize_t tf_saturate(int fd) {
119 int flags = fcntl(fd, F_GETFL);
120 if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK))
121 return -1;
122 char bytes[4096];
123 memset(bytes, 0x7a, sizeof(bytes));
124 ssize_t total = 0;
125 while (total < 1024 * 1024) {
126 ssize_t result = write(fd, bytes, sizeof(bytes));
127 if (result < 0 && errno == EAGAIN) {
128 if (fcntl(fd, F_SETFL, flags))
129 return -1;
130 return total;
131 }
132 if (result <= 0)
133 break;
134 total += result;
135 }
136 fcntl(fd, F_SETFL, flags);
137 return -1;
138}
139ssize_t tf_copy(int kind, int input, off_t* input_offset, int output, off_t* output_offset,
140 size_t count) {
141 return kind == TF_SENDFILE
142 ? sendfile(output, input, input_offset, count)
143 : copy_file_range(input, input_offset, output, output_offset, count, 0);
144}
145void* tf_worker(void* argument) {
146 struct tf_transfer* transfer = argument;
147 __atomic_store_n(&transfer->ready, 1, __ATOMIC_RELEASE);
148 if (!tf_wait(&transfer->gate, 5000)) {
149 transfer->started = tf_now();
150 errno = 0;
151 transfer->result = tf_copy(transfer->kind, transfer->input, transfer->input_offset,
152 transfer->output, transfer->output_offset, transfer->count);
153 transfer->error = errno;
154 transfer->finished = tf_now();
155 } else
156 transfer->error = ETIMEDOUT;
157 __atomic_store_n(&transfer->done, 1, __ATOMIC_RELEASE);
158 return NULL;
159}
160void tf_diagnostic(const struct tf_transfer* transfer) {
161 int done = __atomic_load_n(&transfer->done, __ATOMIC_ACQUIRE);
162 fprintf(stderr,
163 "TRANSFER-CONTRACT: kind=%d count=%zu done=%d result=%ld/%d start=%lld end=%lld\n",
164 transfer->kind, transfer->count, done, done ? (long)transfer->result : -2,
165 done ? transfer->error : -2, done ? (long long)transfer->started : -1,
166 done ? (long long)transfer->finished : -1);
167}
168int tf_join(pthread_t thread, struct tf_transfer* transfer) {
169 __atomic_store_n(&transfer->gate, 1, __ATOMIC_RELEASE);
170 if (tf_wait(&transfer->done, 5000)) {
171 tf_diagnostic(transfer);
172 _exit(60);
173 }
174 return pthread_join(thread, NULL);
175}
176static int run(const char* name, int (*test)(void)) {
177 printf("TRANSFER-CONTRACT: BEGIN %s\n", name);
178 fflush(stdout);
179 pid_t child = fork();
180 if (child < 0)
181 return -1;
182 if (!child) {
183 alarm(40);
184 int result = test();
185 fflush(stdout);
186 fflush(stderr);
187 _exit(result ? 1 : 0);
188 }
189 const int status = tf_reap(child, 45000);
190 printf("TRANSFER-CONTRACT: %s %s status=%d\n", status ? "FAIL" : "PASS", name, status);
191 fflush(stdout);
192 return status;
193}
194int main(int argc, char** argv) {
195 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
196 return 1;
197 const struct {
198 const char* name;
199 int (*test)(void);
200 } suites[] = {{"file-copy", transfer_file_copy},
201 {"offsets", transfer_offsets},
202 {"stream", transfer_stream},
203 {"lifetime", transfer_lifetime},
204 {"races", transfer_races}};
205 int selected = 0;
206 for (unsigned n = 0; n < sizeof(suites) / sizeof(suites[0]); ++n) {
207 if (argc > 1 && strcmp(argv[1], suites[n].name))
208 continue;
209 selected = 1;
210 if (run(suites[n].name, suites[n].test))
211 return 1;
212 }
213 if (!selected)
214 return 2;
215 puts("TRANSFER-CONTRACT: END PASS");
216 return 0;
217}