-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprocess.cpp
More file actions
448 lines (395 loc) · 20.1 KB
/
Copy pathprocess.cpp
File metadata and controls
448 lines (395 loc) · 20.1 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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#include "sys.h"
#include "handle.h"
#include <openkal/process.h>
#include <openkal/memory.h>
// A program image that has been started.
//
// This system has no operation that starts a program relative to a directory,
// so the directory is entered by the duplicate before it replaces itself --- a
// duplicate that exists for the length of two calls, is not a resource the
// caller receives, and is exactly what openkal declines to offer for that
// reason. The caller's own working directory is not touched, which is the
// property the interface requires and which an implementation that entered the
// directory before duplicating would not have.
namespace {
constexpr kal_uintptr kMaxEntries = 512;
// The counted arrays the interface takes become the terminated arrays this
// kernel takes. Every allocation happens before the program is duplicated, so
// that the duplicate performs nothing but a few calls: a duplicate of a program
// that has more than one execution context may hold a lock no context in it
// will release.
struct vector {
char** slots = nullptr;
char* bytes = nullptr;
okm_uptr slots_bytes = 0;
okm_uptr bytes_bytes = 0;
bool ok = true;
bool build(const char** items, const kal_uintptr* lens, kal_uintptr n) {
if (n > kMaxEntries) { ok = false; return false; }
okm_uptr total = 0;
for (kal_uintptr i = 0; i < n; ++i) total += lens[i] + 1;
slots_bytes = (n + 1) * sizeof(char*);
bytes_bytes = total == 0 ? 1 : total;
slots = static_cast<char**>(kal_alloc(slots_bytes, alignof(char*)));
bytes = static_cast<char*>(kal_alloc(bytes_bytes, 1));
if (!slots || !bytes) { ok = false; return false; }
okm_uptr at = 0;
for (kal_uintptr i = 0; i < n; ++i) {
okm::copy(bytes + at, items[i], lens[i]);
bytes[at + lens[i]] = '\0';
slots[i] = bytes + at;
at += lens[i] + 1;
}
slots[n] = nullptr;
return true;
}
~vector() {
if (slots) kal_free(slots, slots_bytes, alignof(char*));
if (bytes) kal_free(bytes, bytes_bytes, 1);
}
};
constexpr okm_long nr_fchdir = 13;
// openkal 0.11: the unit a started program joins.
constexpr okm_long nr_setpgid = 82;
} // namespace
extern "C" {
// Starting a program. One function since openkal 0.11, where three declarations
// became one and their modifiers became positions in `kal_spawn'.
//
// ⚠️⚠️ AND THIS IMPLEMENTATION IS WHERE THE MISSING MODIFIER WAS ALREADY VISIBLE,
// which is worth recording rather than quietly fixing.
//
// This kernel has no `execveat', so a program named relative to a directory has
// always been started by entering that directory first --- the `fchdir(base)'
// below used to be the whole story. So a started program's working directory WAS
// `base' here, and on the other kernel it was whatever that implementation
// happened to be in. ⭐ Same openkal calls, two different observable answers,
// and neither was wrong because the specification said nothing.
//
// ⇒ 0.11 gives the caller a second directory, and both implementations now enter
// the one the caller named. The divergence is gone because the thing that caused
// it --- a property nobody had declared --- is declared.
int kal_process_spawn(const kal_spawn* how,
const char* path, kal_uintptr path_len,
const char** argv, const kal_uintptr* argv_lens, kal_uintptr argc,
const char** envp, const kal_uintptr* envp_lens, kal_uintptr envc,
const kal_spawn_streams* streams,
kal_process* out) {
if (how == nullptr || out == nullptr) return kal_err_invalid;
const int b = okm::unpack(how->base.h);
const int w = okm::unpack(how->work.h);
if (b < 0 || w < 0) return kal_err_invalid;
if (!okm::acceptable(path, path_len)) return kal_err_invalid;
if (how->grant_count > 0 && how->grants == nullptr) return kal_err_invalid;
// ⚠️ A LIFETIME THIS KERNEL CANNOT BIND IS REFUSED BEFORE ANYTHING STARTS.
// The reason is unchanged from 0.10 and is stated at kal_process_props: the
// binding must hold however the caller ends, including when it is killed
// outright, and what this system offers instead is a WATCH, which needs a
// live context to notice. Composing it would move the defect from "refused"
// to "works except when it matters".
if (how->flags != 0) return kal_err_not_supported;
okm::terminated p(path, path_len);
if (!p.ok) return kal_err_invalid;
vector args, envs;
if (!args.build(argv, argv_lens, argc)) return kal_err_no_memory;
if (!envs.build(envp, envp_lens, envc)) return kal_err_no_memory;
constexpr kal_uintptr max_grants = 16;
if (how->grant_count > max_grants) return kal_err_invalid;
int granted[max_grants];
for (kal_uintptr i = 0; i < how->grant_count; ++i) {
granted[i] = okm::unpack(how->grants[i].dir.h);
if (granted[i] < 0) return kal_err_invalid;
}
// ⭐ THE PROGRAM'S NAME IS MADE ABSOLUTE BEFORE THE DIRECTORY MOVES, because
// with no `execveat' the two things `base' and `work' now mean cannot both be
// served by one `fchdir'. `F_GETPATH' answers the path of an open directory,
// which src/fs.cpp already relies on for the same reason: this kernel has no
// call that reports a working directory, so a path is obtained from the
// descriptor that names it.
char whole[1024];
kal_uintptr n = 0;
if (p.buf[0] == '/') {
while (p.buf[n] && n < sizeof whole - 1) { whole[n] = p.buf[n]; ++n; }
} else {
const okm_long r = okm::sys(okm::nr_fcntl, b, okm::f_getpath,
reinterpret_cast<okm_long>(whole));
if (okm::failed(r)) return okm::translate(r);
while (whole[n] && n < sizeof whole - 1) ++n;
if (n && whole[n - 1] != '/' && n < sizeof whole - 1) whole[n++] = '/';
for (kal_uintptr i = 0; p.buf[i] && n < sizeof whole - 1; ++i) whole[n++] = p.buf[i];
}
if (n >= sizeof whole - 1) return kal_err_invalid;
whole[n] = '\0';
const okm_long in = streams ? static_cast<okm_long>(streams->in.h) : 0;
const okm_long ou = streams ? static_cast<okm_long>(streams->out.h) : 0;
const okm_long er = streams ? static_cast<okm_long>(streams->err.h) : 0;
// ⭐ The unit, named here by a process group --- which is to say by whichever
// program formed it first. Zero for the first member; a later one is given
// the number to join.
const okm_long join = how->job ? static_cast<okm_long>(how->job->h) : 0;
const bool unit = how->job != nullptr;
bool is_duplicate = false;
const okm_long child = okm::duplicate(is_duplicate);
if (okm::failed(child)) return okm::translate(child);
// The duplicate is distinguished by the second value the call returns and
// not by the first: both images receive the same first value here. The
// reason, and what happens to an implementation that tests the first alone,
// are in src/sys.h beside the call.
if (is_duplicate) {
if (in != 0) okm::sys(okm::nr_dup2, in, 0);
if (ou != 0) okm::sys(okm::nr_dup2, ou, 1);
if (er != 0) okm::sys(okm::nr_dup2, er, 2);
for (kal_uintptr i = 0; i < how->grant_count; ++i) {
const okm_long want = static_cast<okm_long>(3 + i);
if (granted[i] != want) okm::sys(okm::nr_dup2, granted[i], want);
}
// The directory the program RUNS in --- `whole' already carries where it
// is named from, so this no longer has to serve both.
okm::sys(nr_fchdir, w);
if (unit) okm::sys(nr_setpgid, 0, join);
okm::sys(okm::nr_execve, reinterpret_cast<okm_long>(whole),
reinterpret_cast<okm_long>(args.slots),
reinterpret_cast<okm_long>(envs.slots));
for (;;) okm::sys(okm::nr_exit, 127);
}
// Written only after the start succeeded, and only when the unit was new:
// the first member's identifier IS the group's.
if (unit && join == 0) how->job->h = static_cast<kal_uintptr>(child);
*out = kal_process{ static_cast<kal_uintptr>(child) };
return kal_ok;
}
// A channel: a pair of streams of which one end is meant to cross a spawn.
//
// This kernel's `pipe' reports BOTH descriptors as return values rather than
// through a buffer, which is a property of its calling convention and not of the
// call: the second value comes back in the second register. src/sys.h says the
// same thing about the duplication primitive, and for the same reason.
//
// THERE IS NO pipe2 HERE, so close-on-exec is set afterwards with fcntl. Doing
// it in two steps is not equivalent under a concurrent spawn --- another context
// starting a program between the two would inherit the descriptors --- and this
// implementation states that rather than concealing it. A caller that spawns
// from one context, which is what a program using this operation does, is not
// affected.
int kal_process_channel(kal_stream* mine, kal_stream* theirs) {
if (mine == nullptr || theirs == nullptr) return kal_err_invalid;
okm_long second = 0;
const okm_long first = okm::pipe_pair(second);
if (okm::failed(first)) return okm::translate(first);
constexpr okm_long f_setfd = 2, fd_cloexec = 1;
okm::sys(okm::nr_fcntl, first, f_setfd, fd_cloexec);
okm::sys(okm::nr_fcntl, second, f_setfd, fd_cloexec);
// Bare descriptors, because openkal.stream's transfer operations take what
// this kernel takes. kal_fs_stream reports a file's stream the same way.
*mine = kal_stream{ static_cast<kal_uintptr>(first) }; // the reading end
*theirs = kal_stream{ static_cast<kal_uintptr>(second) }; // the writing end
return kal_ok;
}
void kal_process_channel_close(kal_stream s) {
// The standard streams are borrowed and are numbered 0, 1 and 2; closing one
// of those through this operation would take a stream away from the whole
// program.
const okm_long fd = static_cast<okm_long>(s.h);
if (fd < 3) return;
okm::sys(okm::nr_close, fd);
}
// Starting a program that receives exactly the directories named.
// One program, whatever unit it is in --- the unit has its own operation below,
int kal_process_wait(kal_process h, int* status, int* terminated_by_environment) {
if (h.h == 0) return kal_err_invalid;
int st = 0;
for (;;) {
const okm_long r = okm::sys(okm::nr_wait4, static_cast<okm_long>(h.h),
reinterpret_cast<okm_long>(&st), 0, 0);
if (okm::interrupted(r)) continue;
if (okm::failed(r)) return okm::translate(r);
break;
}
// The encoding is the kernel's: the low seven bits name the signal that
// ended the program and are zero when it ended by returning, in which case
// the next eight bits are what it returned.
const int signalled = st & 0x7f;
if (signalled == 0) {
if (status) *status = (st >> 8) & 0xff;
if (terminated_by_environment) *terminated_by_environment = 0;
} else {
if (status) *status = signalled;
if (terminated_by_environment) *terminated_by_environment = 1;
}
return kal_ok;
}
// so this one's meaning never turns on how the program was started.
int kal_process_terminate(kal_process h) {
if (h.h == 0) return kal_err_invalid;
const okm_long r = okm::sys(okm::nr_kill, static_cast<okm_long>(h.h), 15 /* SIGTERM */);
return okm::failed(r) ? okm::translate(r) : kal_ok;
}
// ⚠️⚠️ NOT CLAIMED HERE, FOR THE SAME REASON THE SIGPIPE NOTE IN src/env.cpp
// GIVES. Observing a request to end means installing a disposition, and this
// kernel's `sigaction' takes a structure carrying a TRAMPOLINE its C library
// supplies. A disposition installed with the wrong shape shows up as a program
// dying in a way nobody can trace --- and a facility this repository has not
// MEASURED is exactly what it refuses to claim elsewhere.
//
// ⇒ Null, and KAL_PROCESS_PROP_STOP_REQUESTED unclaimed, so a caller that asks
// first is told. The other implementation answers it; this one will when it can
// be exercised here.
// ⭐⭐ A WORD THIS PROGRAM'S ENVIRONMENT SETS WHEN SOMEBODY HAS ASKED IT TO END.
//
// ⚠️⚠️ THE TRAMPOLINE IS THIS IMPLEMENTATION'S OWN, WHICH IS WHY THIS ARRIVED A
// VERSION LATE. The raw `sigaction' of this kernel takes a structure whose
// SECOND field is `sa_tramp': the kernel enters that address, not the handler,
// and the handler is passed to it as an argument. A C library ordinarily
// supplies it (`_sigtramp' in libsystem) and there is no C library beneath this
// implementation. A structure installed with a null or wrong `sa_tramp' does not
// fail at installation --- it fails on DELIVERY, inside the handler, at an
// address that belongs to nobody, which is the least attributable failure this
// implementation could ship. So the order was: a conformance check that raises
// the signal first, this second.
//
// ⚠️ ARMED ON THE FIRST ENQUIRY AND NOT AT STARTUP, exactly as openkal-linux
// argues: a program that never asks keeps the default action, and adding this
// operation therefore changes nothing for anyone who does not use it.
namespace {
kal_u32 g_stop_word = 0;
int g_stop_armed = 0;
#if defined(__aarch64__)
constexpr okm_long nr_sigaction = 46;
// What the kernel enters. Its arguments are (handler, infostyle, sig, siginfo,
// ucontext) in x0..x4; it calls the handler with the last three and then asks
// the kernel to restore the interrupted context.
//
// ⚠️ x19 AND x20 ARE USED WITHOUT BEING SAVED, and that is correct here rather
// than sloppy: this function does not return to its caller. `sigreturn' restores
// the whole of the interrupted context, callee-saved registers included, so the
// values these two held belong to a frame the kernel is about to reinstate.
extern "C" void okm_sigtramp(void);
asm(".globl _okm_sigtramp\n"
".p2align 2\n"
"_okm_sigtramp:\n"
" mov x19, x1\n" // infostyle
" mov x20, x4\n" // ucontext
" mov x8, x0\n" // handler
" mov x0, x2\n" // sig
" mov x1, x3\n" // siginfo
" mov x2, x20\n" // ucontext
" blr x8\n"
" mov x0, x20\n" // ucontext
" mov x1, x19\n" // infostyle
" mov x16, #184\n" // SYS_sigreturn
" svc #0x80\n"
" brk #1\n"); // sigreturn does not come back
void stop_handler(int) {
__atomic_store_n(&g_stop_word, 1u, __ATOMIC_RELEASE);
// Woken through the same operation `kal_task_wake' performs, issued as the
// raw call because a handler may not enter code that takes a lock. Waking
// ALL of them: any number of contexts may be waiting upon this one word, and
// the handler has no way to learn how many.
okm::sys(okm::nr_ulock_wake,
okm::ul_compare_and_wait | okm::ulf_no_errno | okm::ulf_wake_all,
reinterpret_cast<okm_long>(&g_stop_word), 0);
}
// The structure this kernel's `sigaction' takes. `sa_tramp' is the second field
// and is the whole reason this is spelled out rather than borrowed.
struct macos_sigaction {
void (*handler)(int);
void (*tramp)(void*, int, int, void*, void*);
unsigned int mask;
int flags;
};
// ⚠️ THE RESULT IS EXAMINED, AND THE FUNCTION EXISTS TO RETURN IT. An
// installation that failed would leave a word that can never change, and
// answering the caller with one is `nothing here reports success having done
// nothing' in its exact form: the program would ask whether its end had been
// requested, be told no, and go on being told no after it had been.
bool arm_one(int signo) {
macos_sigaction act{};
act.handler = &stop_handler;
act.tramp = reinterpret_cast<void (*)(void*, int, int, void*, void*)>(&okm_sigtramp);
return !okm::failed(okm::sys(nr_sigaction, signo,
reinterpret_cast<okm_long>(&act), 0));
}
#endif // __aarch64__
} // namespace
const kal_u32* kal_process_stop_requested(void) {
#if defined(__aarch64__)
// ⚠️ THREE STATES AND NOT TWO: not yet tried, armed, refused. A second
// caller must be told what the first found rather than arming again --- and
// must not be told `not yet tried' while the first is still inside the
// installation.
int state = __atomic_load_n(&g_stop_armed, __ATOMIC_ACQUIRE);
if (state == 0) {
// SIGTERM is the one `kal_process_terminate' sends here; SIGINT is what
// an interactive stream delivers. Both are requests to end, which is the
// whole of what this word reports.
const bool ok = arm_one(15) && arm_one(2);
state = ok ? 1 : -1;
__atomic_store_n(&g_stop_armed, state, __ATOMIC_RELEASE);
}
return state == 1 ? &g_stop_word : nullptr;
#else
// ⚠️ DECLINED ON THE OTHER ARCHITECTURE, AND NOT BECAUSE IT CANNOT BE
// WRITTEN. The trampoline above has an x86_64 counterpart of the same
// length. What it does not have is a way to be RUN: the build tool has no
// release for x86_64 on this system, so continuous integration compiles the
// sources there and executes nothing --- and a trampoline that has never
// been entered is the one thing this operation must not ship. Clause 6.2
// makes the absence a fact a caller reads, and `kal_process_props' below
// does not claim the position.
return 0;
#endif
}
// This program itself joins or forms a unit --- what `kal_spawn.job' cannot say,
// because that places a program the caller STARTS and a copy wishing to lead a
// unit must say so about ITSELF before it replaces itself.
int kal_process_job_enter(kal_job* j) {
if (j == nullptr) return kal_err_invalid;
const okm_long join = static_cast<okm_long>(j->h);
const okm_long r = okm::sys(nr_setpgid, 0, join);
if (okm::failed(r)) return okm::translate(r);
if (join == 0) j->h = static_cast<kal_uintptr>(okm::sys(okm::nr_getpid));
return kal_ok;
}
// Every program in the unit, including ones never held as a handle.
//
// ⚠️ A group is named by a process identifier, and those are reused: once the
// program that formed it has ended and the numbers have wrapped, this can reach
// a different group. That is what this system does, and it is recorded rather
// than hidden.
int kal_process_job_terminate(kal_job j) {
if (j.h == 0) return kal_err_invalid;
// The signal that cannot be declined --- see openkal-linux for the reasoning:
// a unit contains programs the caller never held a handle to, so a request any
// member may ignore does not terminate the unit.
const okm_long r = okm::sys(okm::nr_kill, -static_cast<okm_long>(j.h), 9 /* SIGKILL */);
return okm::failed(r) ? okm::translate(r) : kal_ok;
}
// A group here is a number and not a resource, so there is nothing to release.
void kal_process_job_close(kal_job) { }
// Releasing the handle does not affect the program. A program that has not been
// waited for continues, and this environment collects it when the caller exits.
void kal_process_close(kal_process) { }
// Starting a program whose lifetime is bound to this one's. Version 0.10.
//
// ⚠️ EVERY POSITION THE SPECIFICATION HAS ASSIGNED IS ACCOUNTED FOR HERE, either
// by being claimed or by being deliberately absent. BOUND_LIFETIME is absent
// because `kal_process_spawn' above refuses every flag; STOP_REQUESTED is
// claimed only where the trampoline it needs has been entered by a running
// program, which is the architecture continuous integration executes.
kal_uintptr kal_process_props(void) { return
KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING
| KAL_PROCESS_PROP_EXIT_STATUS
| KAL_PROCESS_PROP_CHANNEL | KAL_PROCESS_PROP_GRANT_DIR
| KAL_PROCESS_PROP_JOB
#if defined(__aarch64__)
// ⚠️ AND IT AGREES WITH `kal_process_stop_requested', WHICH IS A REQUIREMENT
// AND NOT A COURTESY: the header defines null there as the absence this
// position reports, so the two cannot disagree. It is read and never armed
// --- asking what an implementation can do must not install a disposition ---
// so this claims the position until an installation has actually been refused,
// and stops claiming it afterwards.
| (__atomic_load_n(&g_stop_armed, __ATOMIC_ACQUIRE) == -1
? 0u : KAL_PROCESS_PROP_STOP_REQUESTED)
#endif
; }
}