Repository navigation
Expand file tree
/
Copy pathconformance_process_task.cpp
More file actions
220 lines (199 loc) · 10 KB
/
Copy pathconformance_process_task.cpp
File metadata and controls
220 lines (199 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// Conformance: openkal.process and openkal.task.
import openkal.process;
import openkal.task;
import openkal.fs;
import openkal.stream;
namespace {
int failures = 0;
void say(const char* s) {
kal_uintptr n = 0; while (s[n]) ++n;
kal::write(kal::err(), s, n);
}
void say_num(int v) {
if (v < 0) { kal::write(kal::err(), "-", 1); v = -v; }
char b[12]; int i = 12;
if (v == 0) b[--i] = '0';
while (v > 0) { b[--i] = static_cast<char>('0' + v % 10); v /= 10; }
kal::write(kal::err(), b + i, static_cast<kal_uintptr>(12 - i));
}
void check(bool ok, const char* what) {
if (ok) return;
++failures;
const char pre[] = "FAIL: ";
kal::write(kal::err(), pre, sizeof(pre) - 1);
kal_uintptr n = 0; while (what[n]) ++n;
kal::write(kal::err(), what, n);
kal::write(kal::err(), "\n", 1);
}
// Shared between contexts. The word is what openkal.task suspends upon, and it
// is ordinary memory: the interface adds no object of its own.
volatile __UINT32_TYPE__ g_word = 0;
int g_ran = 0;
void worker(void* arg) {
g_ran = 1;
*static_cast<int*>(arg) = 42;
__atomic_store_n(reinterpret_cast<__UINT32_TYPE__*>(const_cast<__UINT32_TYPE__*>(&g_word)),
1u, __ATOMIC_SEQ_CST);
kal_uintptr woken = 0;
kal_task_wake(const_cast<const __UINT32_TYPE__*>(&g_word), 1, &woken);
}
}
int main() {
// --- openkal.process -----------------------------------------------------
//
// The program to start is reached through a directory the environment
// supplied, which is the whole reason the set exists: a program and the
// program it starts are commonly not beneath one root.
kal_dir slash{}; char nm[512] = {}; kal_uintptr nl = 0;
bool have_root = false;
for (kal_uintptr i = 0; i < kal::fs::preopen_count(); ++i) {
kal_dir d{}; char n[512]; kal_uintptr l = 0;
if (kal_fs_preopen(i, &d, n, sizeof n, &l) != kal_ok) continue;
if (l == 1 && n[0] == '/') {
slash = d; nm[0] = '/'; nm[1] = '\0'; nl = l; have_root = true;
}
}
check(have_root, "a directory covering the file system is supplied");
if (have_root) {
// The program that succeeds and the program that fails are at
// different places on different systems. The test locates them rather
// than assuming, because assuming would make it a test of one system.
const char* true_paths[] = { "bin/true", "usr/bin/true" };
const kal_uintptr true_lens[] = { 8, 12 };
const char* false_paths[] = { "bin/false", "usr/bin/false" };
const kal_uintptr false_lens[] = { 9, 13 };
// THE CANDIDATE IS CHOSEN BY ASKING, AND IT USED TO BE CHOSEN BY
// SPAWNING AND RETRYING. That retry could not work and never ran:
// kal_process_spawn reports whether the DUPLICATE was made, and the
// program is replaced afterwards, inside a copy the caller no longer
// is. A path that does not exist therefore produces kal_ok and a
// duplicate that finishes with 127, so the first candidate was always
// taken and the second was unreachable code.
//
// On a system holding /usr/bin/true and no /bin/true the consequence
// was `status == 127' at the observation below, which is what this
// system reported the first time these suites were ever run. The
// observation after it -- that a non-zero status is reported as such --
// held throughout, upon a program that was never started.
auto locate = [&](const char* const* paths, const kal_uintptr* lens_) -> int {
for (int i = 0; i < 2; ++i) {
kal_node_info info{}; info.self_size = sizeof info;
if (kal_fs_info(slash, paths[i], lens_[i], 0, kal::fs::field::kind, &info) != kal_ok)
continue;
if (info.kind != kal_node_absent) return i;
}
return -1;
};
const int t = locate(true_paths, true_lens);
const int fpath = locate(false_paths, false_lens);
check(t >= 0, "a program that succeeds is found");
check(fpath >= 0, "a program that fails is found");
kal_process p{};
const char* argv[] = { "openkal" };
const kal_uintptr lens[] = { 7 };
int rc = kal_err_invalid;
if (t >= 0)
rc = kal_process_spawn(slash, true_paths[t], true_lens[t], argv, lens, 1,
nullptr, nullptr, 0, nullptr, &p);
check(rc == kal_ok, "a program is started");
if (rc == kal_ok) {
int status = -1, terminated = -1;
check(kal_process_wait(p, &status, &terminated) == kal_ok, "the program is waited for");
// The observed status is reported when it is wrong. `127' names an
// image that was not replaced and `0' names one that ran; without
// the number the two arrive as the same line.
if (!(status == 0 && terminated == 0)) {
say(" status="); say_num(status);
say(" terminated="); say_num(terminated); say("\n");
}
check(status == 0 && terminated == 0, "the status it finished with is reported");
kal_process_close(p);
}
// A program that finishes with a non-zero status is distinguished from
// one that succeeded. Without this, a harness that ignored the status
// would pass.
kal_process q{};
const char* qargv[] = { "openkal" };
int qrc = kal_err_invalid;
if (fpath >= 0)
qrc = kal_process_spawn(slash, false_paths[fpath], false_lens[fpath], qargv, lens, 1,
nullptr, nullptr, 0, nullptr, &q);
check(qrc == kal_ok, "the program that fails is started");
if (qrc == kal_ok) {
int status = -1, terminated = -1;
kal_process_wait(q, &status, &terminated);
// NOT MERELY NON-ZERO. 127 is what a duplicate reports when the
// image was never replaced, so `!= 0' is satisfied by a program
// that did not run -- which is precisely how this observation held
// while the one above did not.
check(status == 1 && terminated == 0,
"the status of a program that fails is its own and not 127");
kal_process_close(q);
}
// Clause 7.6: the vector is passed unaltered, and argv[0] is the name
// the started program observes as its own.
//
// The programs started above ignore their arguments, so they cannot
// distinguish an implementation that passes the vector from one that
// prepends the path — which is how that defect survived a suite that
// started programs and read their statuses. A shell does distinguish
// them: `sh -c <script>` takes $0 from its own argv[0] when no further
// argument is given, so the script observes the name the caller chose.
// An implementation that prepended the path would give the shell an
// extra argument, which it would read as a script file to open, and the
// status would be non-zero.
const char* sh_paths[] = { "bin/sh", "usr/bin/sh" };
const kal_uintptr sh_lens[] = { 6, 10 };
const char* script = "test \"$0\" = openkal-observed-argv0";
kal_uintptr script_len = 0; while (script[script_len]) ++script_len;
// Located rather than retried, for the reason recorded above: the retry
// this replaces could not distinguish a path that does not exist from
// one that does, so it always took the first.
const int sh = locate(sh_paths, sh_lens);
check(sh >= 0, "a shell is found");
kal_process r{};
const char* rargv[] = { "openkal-observed-argv0", "-c", script };
const kal_uintptr rlens[] = { 22, 2, script_len };
int rrc = kal_err_invalid;
if (sh >= 0)
rrc = kal_process_spawn(slash, sh_paths[sh], sh_lens[sh], rargv, rlens, 3,
nullptr, nullptr, 0, nullptr, &r);
check(rrc == kal_ok, "a shell is started");
if (rrc == kal_ok) {
int status = -1, terminated = -1;
kal_process_wait(r, &status, &terminated);
check(status == 0 && terminated == 0,
"the started program observes the argument vector the caller supplied, unaltered");
kal_process_close(r);
}
}
// A name that ascends is refused here as it is in the file system.
kal_process bad{};
check(kal_process_spawn(kal::fs::working(), "../bin/true", 11,
nullptr, nullptr, 0, nullptr, nullptr, 0, nullptr, &bad)
!= kal_ok, "an ascending program name is refused");
// --- openkal.task --------------------------------------------------------
int written = 0;
kal_task t{};
check(kal_task_start(worker, &written, &t) == kal_ok, "an execution context starts");
// Suspension upon the word, which the worker changes and then wakes. The
// loop re-examines the condition after waking, because waking is permitted
// to be spurious and the specification says so.
while (__atomic_load_n(reinterpret_cast<__UINT32_TYPE__*>(
const_cast<__UINT32_TYPE__*>(&g_word)), __ATOMIC_SEQ_CST) == 0u) {
kal_task_wait(const_cast<const __UINT32_TYPE__*>(&g_word), 0u,
1000ull * 1000 * 1000);
}
check(kal_task_join(t) == kal_ok, "the context is joined");
check(g_ran == 1 && written == 42, "the context ran and observed shared memory");
// A wait whose expected value does not match returns rather than suspending,
// which is what makes the primitive usable without losing a wake.
__UINT32_TYPE__ other = 5;
check(kal_task_wait(&other, 6u, 1000ull * 1000) == kal_ok,
"a wait on a value that already differs returns");
kal_task_yield();
check(kal_task_current() != 0, "the calling context has an identity");
const char ok[] = "openkal-macos: process and task conformance\n";
kal::write(kal::out(), ok, sizeof(ok) - 1);
return failures == 0 ? 0 : 1;
}