The Pedigree Project 0.1
child-wait-contract-test/lifetime.c
1#define _GNU_SOURCE
2#include <string.h>
3#include <unistd.h>
4
5#include "contract.h"
6
7static int64_t microseconds(struct timeval time) {
8 return (int64_t)time.tv_sec * 1000000 + time.tv_usec;
9}
10
11static int repeated_exit(void) {
12 int failed = 0, status;
13 struct cw_child child = CW_CHILD_INIT;
14 siginfo_t info;
15 struct rusage before, middle, after, first, usage;
16 memset(&before, 0xa5, sizeof(before));
17 memset(&middle, 0xa5, sizeof(middle));
18 memset(&after, 0xa5, sizeof(after));
19 memset(&first, 0xa5, sizeof(first));
20 CHECK(getrusage(RUSAGE_CHILDREN, &before) == 0);
21 CHECK(cw_spawn(&child, -1, (uid_t)-1, 83) == 0);
22 CHECK(cw_send(child.command, 'B') == 0 && cw_receive(child.report, 'B') == 0);
23 CHECK(cw_send(child.command, 'E') == 0);
24 CHECK(cw_raw_waitid(P_PID, child.pid, &info, WEXITED | WNOWAIT, &first) == 0);
25 CHECK(cw_info(&info, child.pid, getuid(), CLD_EXITED, 83) == 0 && !cw_usage(&first));
26 for (int i = 0; i < 4; ++i) {
27 memset(&usage, 0xa5, sizeof(usage));
28 CHECK(cw_raw_waitid(P_PID, child.pid, &info, WEXITED | WNOWAIT, &usage) == 0);
29 CHECK(cw_info(&info, child.pid, getuid(), CLD_EXITED, 83) == 0);
30 CHECK(memcmp(&usage, &first, CW_RUSAGE_BYTES) == 0 && !cw_usage(&usage));
31 }
32 CHECK(getrusage(RUSAGE_CHILDREN, &middle) == 0);
33 CHECK(microseconds(before.ru_utime) == microseconds(middle.ru_utime));
34 CHECK(microseconds(before.ru_stime) == microseconds(middle.ru_stime));
35 memset(&usage, 0xa5, sizeof(usage));
36 CHECK(wait4(child.pid, &status, 0, &usage) == child.pid);
37 child.pid = -1;
38 CHECK(WIFEXITED(status) && WEXITSTATUS(status) == 83);
39 CHECK(memcmp(&usage, &first, CW_RUSAGE_BYTES) == 0 && !cw_usage(&usage));
40 CHECK(getrusage(RUSAGE_CHILDREN, &after) == 0);
41 const int64_t user_delta = microseconds(after.ru_utime) - microseconds(before.ru_utime);
42 const int64_t system_delta = microseconds(after.ru_stime) - microseconds(before.ru_stime);
43 CHECK(user_delta >= microseconds(usage.ru_utime) &&
44 user_delta <= microseconds(usage.ru_utime) + 1);
45 CHECK(system_delta >= microseconds(usage.ru_stime) &&
46 system_delta <= microseconds(usage.ru_stime) + 1);
47out:
48 cw_cleanup(&child);
49 return failed;
50}
51
52struct contender {
53 pid_t child;
54 int use_wait4;
55 atomic_uint* start;
56 atomic_uint ready, done;
57 long result;
58 int error, status;
59 siginfo_t info;
60};
61
62static void* compete(void* argument) {
63 struct contender* waiter = argument;
64 atomic_store(&waiter->ready, 1);
65 if (cw_atomic_wait(waiter->start, 1)) {
66 waiter->result = -2;
67 } else {
68 errno = 0;
69 waiter->result = waiter->use_wait4 ? wait4(waiter->child, &waiter->status, 0, NULL)
70 : waitid(P_PID, waiter->child, &waiter->info, WEXITED);
71 waiter->error = errno;
72 }
73 atomic_store(&waiter->done, 1);
74 return NULL;
75}
76
77static int competing_reapers(void) {
78 int failed = 0, created = 0;
79 struct cw_child child = CW_CHILD_INIT;
80 pthread_t threads[2];
81 atomic_uint start = 0;
82 struct contender contenders[2] = {{.use_wait4 = 0, .start = &start},
83 {.use_wait4 = 1, .start = &start}};
84 CHECK(cw_spawn(&child, -1, (uid_t)-1, 87) == 0);
85 for (int i = 0; i < 2; ++i) {
86 contenders[i].child = child.pid;
87 CHECK(pthread_create(&threads[i], NULL, compete, &contenders[i]) == 0);
88 ++created;
89 CHECK(cw_atomic_wait(&contenders[i].ready, 1) == 0);
90 }
91 atomic_store(&start, 1);
92 CHECK(cw_send(child.command, 'E') == 0);
93 CHECK(cw_atomic_wait(&contenders[0].done, 1) == 0 && cw_atomic_wait(&contenders[1].done, 1) == 0);
94 pid_t pid = child.pid;
95 child.pid = -1;
96 CHECK((contenders[0].result == 0) + (contenders[1].result == pid) == 1);
97 for (int i = 0; i < 2; ++i) {
98 if (contenders[i].result < 0)
99 CHECK(contenders[i].result == -1 && contenders[i].error == ECHILD);
100 else if (i == 0)
101 CHECK(cw_info(&contenders[i].info, pid, getuid(), CLD_EXITED, 87) == 0);
102 else
103 CHECK(WIFEXITED(contenders[i].status) && WEXITSTATUS(contenders[i].status) == 87);
104 }
105 CHECK(waitpid(pid, NULL, WNOHANG) == -1 && errno == ECHILD);
106out:
107 atomic_store(&start, 1);
108 cw_cleanup(&child);
109 for (int i = 0; i < created; ++i)
110 if (pthread_join(threads[i], NULL))
111 failed = 1;
112 return failed;
113}
114
115struct sibling {
116 struct cw_child child;
117 int result;
118};
119
120static void* sibling_fork(void* argument) {
121 struct sibling* state = argument;
122 state->result = cw_spawn(&state->child, -1, (uid_t)-1, 91);
123 return NULL;
124}
125
126static int sibling_child(void) {
127 int failed = 0, started = 0;
128 pthread_t creator;
129 struct sibling state = {.child = CW_CHILD_INIT, .result = -1};
130 siginfo_t info;
131 CHECK(pthread_create(&creator, NULL, sibling_fork, &state) == 0);
132 started = 1;
133 CHECK(pthread_join(creator, NULL) == 0);
134 started = 0;
135 CHECK(state.result == 0 && cw_send(state.child.command, 'E') == 0);
136 CHECK(waitid(P_PID, state.child.pid, &info, WEXITED | WNOWAIT) == 0);
137 CHECK(cw_info(&info, state.child.pid, getuid(), CLD_EXITED, 91) == 0);
138 CHECK(waitid(P_ALL, 0, &info, WEXITED) == 0 && info.si_pid == state.child.pid);
139 state.child.pid = -1;
140out:
141 if (started)
142 pthread_join(creator, NULL);
143 cw_cleanup(&state.child);
144 return failed;
145}
146
147int cw_lifetime(void) {
148 return repeated_exit() || competing_reapers() || sibling_child();
149}