The Pedigree Project 0.1
ptrace-contract-test/fixture.c
1#define _GNU_SOURCE
2#include <fcntl.h>
3#include <poll.h>
4#include <sched.h>
5#include <string.h>
6#include <time.h>
7#include <unistd.h>
8
9#include "contract.h"
10
11size_t tc_page;
12
13int64_t tc_now(void) {
14 struct timespec value;
15 return clock_gettime(CLOCK_MONOTONIC, &value)
16 ? -1
17 : (int64_t)value.tv_sec * INT64_C(1000000000) + value.tv_nsec;
18}
19
20static int transfer(int fd, void* data, size_t size, int writing) {
21 unsigned char* bytes = data;
22 int64_t now = tc_now();
23 if (now < 0)
24 return -1;
25 const int64_t end = now + INT64_C(10000000000);
26 while (size) {
27 now = tc_now();
28 if (now < 0)
29 return -1;
30 if (now >= end) {
31 errno = ETIMEDOUT;
32 return -1;
33 }
34 struct pollfd ready = {.fd = fd, .events = writing ? POLLOUT : POLLIN};
35 int result = poll(&ready, 1, (int)((end - now) / 1000000 + 1));
36 if (result < 0 && errno == EINTR)
37 continue;
38 if (result <= 0) {
39 if (!result)
40 errno = ETIMEDOUT;
41 return -1;
42 }
43 ssize_t count = writing ? write(fd, bytes, size) : read(fd, bytes, size);
44 if (count < 0 && (errno == EINTR || errno == EAGAIN))
45 continue;
46 if (count <= 0) {
47 if (!count)
48 errno = EPIPE;
49 return -1;
50 }
51 bytes += count;
52 size -= (size_t)count;
53 }
54 return 0;
55}
56
57int tc_read(int fd, void* data, size_t size) {
58 return transfer(fd, data, size, 0);
59}
60
61int tc_write(int fd, const void* data, size_t size) {
62 return transfer(fd, (void*)data, size, 1);
63}
64
65int tc_wait(pid_t pid, int* status, int milliseconds) {
66 int64_t now = tc_now();
67 if (now < 0)
68 return -1;
69 const int64_t end = now + (int64_t)milliseconds * 1000000;
70 while (now < end) {
71 pid_t result = wait4(pid, status, WNOHANG, NULL);
72 if (result == pid)
73 return 0;
74 if (result < 0 && errno != EINTR)
75 return -1;
76 struct timespec delay = {0, 2000000};
77 nanosleep(&delay, NULL);
78 now = tc_now();
79 if (now < 0)
80 return -1;
81 }
82 errno = ETIMEDOUT;
83 return -1;
84}
85
86void tc_cleanup(pid_t* pid) {
87 if (*pid <= 0)
88 return;
89 kill(*pid, SIGKILL);
90 int64_t now = tc_now();
91 const int64_t end = now + INT64_C(2000000000);
92 while (now >= 0 && now < end) {
93 int status;
94 int result = tc_wait(*pid, &status, (int)((end - now) / 1000000 + 1));
95 if ((!result && (WIFEXITED(status) || WIFSIGNALED(status))) || (result && errno == ECHILD)) {
96 *pid = -1;
97 return;
98 }
99 if (result)
100 break;
101 now = tc_now();
102 }
103 fprintf(stderr, "child cleanup incomplete pid=%d errno=%d\n", *pid, errno);
104}
105
106int tc_atomic_wait(atomic_uint* value, unsigned expected) {
107 int64_t now = tc_now(), end = now + INT64_C(10000000000);
108 while (now >= 0 && now < end) {
109 if (atomic_load(value) == expected)
110 return 0;
111 sched_yield();
112 now = tc_now();
113 }
114 if (now >= 0)
115 errno = ETIMEDOUT;
116 return -1;
117}
118
119int tc_send(const struct tc_child* child, int operation, int signal) {
120 struct tc_command command = {operation, signal};
121 return tc_write(child->command, &command, sizeof(command));
122}
123
124int tc_receive(int fd, int kind, struct tc_message* message) {
125 struct tc_message local;
126 if (!message)
127 message = &local;
128 if (tc_read(fd, message, sizeof(*message)))
129 return -1;
130 if (message->kind == kind)
131 return 0;
132 fprintf(stderr, "report actual=%c expected=%c signal=%d code=%d result=%lld error=%d\n",
133 message->kind, kind, message->signal, message->code, (long long)message->result,
134 message->error);
135 errno = EIO;
136 return -1;
137}
138
139int tc_spawn(struct tc_child* child, int traced) {
140 int command[2], report[2];
141 if (pipe2(command, O_CLOEXEC | O_NONBLOCK))
142 return -1;
143 if (pipe2(report, O_CLOEXEC | O_NONBLOCK)) {
144 close(command[0]);
145 close(command[1]);
146 return -1;
147 }
148 pid_t pid = fork();
149 if (!pid) {
150 close(command[1]);
151 close(report[0]);
152 tc_tracee(command[0], report[1], traced);
153 _exit(121);
154 }
155 close(command[0]);
156 close(report[1]);
157 if (pid < 0) {
158 close(command[1]);
159 close(report[0]);
160 return -1;
161 }
162 *child = (struct tc_child){pid, command[1], report[0]};
163 struct tc_message ready = {0};
164 if (tc_receive(child->report, 'R', &ready) || ready.result) {
165 if (ready.result)
166 fprintf(stderr, "TRACEME startup result=%lld errno=%d\n", (long long)ready.result,
167 ready.error);
168 tc_close(child);
169 return -1;
170 }
171 return 0;
172}
173
174void tc_close(struct tc_child* child) {
175 tc_cleanup(&child->pid);
176 if (child->command >= 0)
177 close(child->command);
178 if (child->report >= 0)
179 close(child->report);
180 *child = (struct tc_child)TC_CHILD_INIT;
181}
182
183int tc_stop(struct tc_child* child, int signal) {
184 if (tc_send(child, 'S', signal))
185 return -1;
186 return tc_wait_stop(child, signal);
187}
188
189int tc_wait_stop(struct tc_child* child, int signal) {
190 int status;
191 if (tc_wait(child->pid, &status, 10000))
192 return -1;
193 if (WIFEXITED(status) || WIFSIGNALED(status))
194 child->pid = -1;
195 if (WIFSTOPPED(status) && WSTOPSIG(status) == signal)
196 return 0;
197 fprintf(stderr, "stop signal=%d actual status=%#x\n", signal, status);
198 return -1;
199}
200
201int tc_finish(struct tc_child* child) {
202 if (tc_send(child, 'E', 0) || tc_receive(child->report, 'E', NULL))
203 return -1;
204 int status;
205 if (tc_wait(child->pid, &status, 10000))
206 return -1;
207 if (WIFEXITED(status) || WIFSIGNALED(status))
208 child->pid = -1;
209 if (WIFEXITED(status) && WEXITSTATUS(status) == TC_EXIT_CODE)
210 return 0;
211 fprintf(stderr, "exit status=%#x\n", status);
212 return -1;
213}
214
215int tc_resume(pid_t child, int request, int signal) {
216 return (int)ptrace(request, child, (void*)0, (void*)(uintptr_t)(unsigned)signal);
217}
218
219int tc_signal_info(const siginfo_t* info, int signal, int code, pid_t pid, uid_t uid) {
220 if (info->si_signo == signal && !info->si_errno && info->si_code == code && info->si_pid == pid &&
221 info->si_uid == uid)
222 return 0;
223 fprintf(stderr, "siginfo actual=%d/%d/%d/%d/%u expected=%d/0/%d/%d/%u\n", info->si_signo,
224 info->si_errno, info->si_code, info->si_pid, info->si_uid, signal, code, pid, uid);
225 return -1;
226}