Repository navigation
Expand file tree
/
Copy pathprocess.cpp
More file actions
969 lines (880 loc) · 43.8 KB
/
Copy pathprocess.cpp
File metadata and controls
969 lines (880 loc) · 43.8 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
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
#include "sys.h"
#include "handle.h"
#include <openkal/process.h>
#include <openkal/memory.h>
namespace okm { extern bool g_pipe_ignored_here; }
// 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;
constexpr char kPreopens[] = "KAL_PREOPENS=";
constexpr okm_uptr kPreopensLen = sizeof kPreopens - 1;
constexpr okm_uptr kPidDigits = 10;
bool names_preopens(const char* s, kal_uintptr n) {
if (n < kPreopensLen) return false;
for (okm_uptr i = 0; i < kPreopensLen; ++i)
if (s[i] != kPreopens[i]) return false;
return true;
}
okm_uptr digits(okm_uptr v) {
okm_uptr n = 1;
while (v >= 10) { v /= 10; ++n; }
return n;
}
char* put_decimal(char* o, okm_uptr v) {
char b[24]; int i = 0;
do { b[i++] = static_cast<char>('0' + v % 10); v /= 10; } while (v);
while (i) *o++ = b[--i];
return o;
}
// Every descriptor from `from' upward is marked to close when the image is
// replaced. This kernel has no `close_range'. What it has is `/dev/fd', which
// lists the descriptors of the process reading it, so exactly those are marked;
// where it cannot be read, every number below the descriptor limit is tried,
// and an unlimited limit is taken as OPEN_MAX, which is the bound this system's
// own C library applies in the same case.
void close_on_exec_from(okm_long from) {
const okm_long dir = okm::sys(okm::nr_openat, okm::at_fdcwd,
reinterpret_cast<okm_long>("/dev/fd"),
okm::o_rdonly | okm::o_directory | okm::o_cloexec, 0);
if (!okm::failed(dir)) {
alignas(8) char buf[4096];
okm_i64 position = 0;
for (;;) {
const okm_long n = okm::sys(okm::nr_getdirentries64, dir,
reinterpret_cast<okm_long>(buf),
static_cast<okm_long>(sizeof buf),
reinterpret_cast<okm_long>(&position));
if (okm::interrupted(n)) continue;
if (okm::failed(n) || n == 0) break;
for (okm_long at = 0; at < n; ) {
const auto* d = reinterpret_cast<const okm::kdirent64*>(buf + at);
okm_long fd = 0; bool number = d->namlen > 0;
for (unsigned short c = 0; c < d->namlen; ++c) {
if (d->name[c] < '0' || d->name[c] > '9') { number = false; break; }
fd = fd * 10 + (d->name[c] - '0');
}
if (number && fd >= from && fd != dir)
okm::sys(okm::nr_fcntl, fd, okm::f_setfd, okm::fd_cloexec);
if (d->reclen == 0) break;
at += d->reclen;
}
}
okm::sys(okm::nr_close, dir);
return;
}
okm_u64 lim[2] = { 256, 256 };
okm::sys(okm::nr_getrlimit, okm::rlimit_nofile, reinterpret_cast<okm_long>(lim));
const okm_u64 bound = lim[0] > static_cast<okm_u64>(okm::open_max)
? static_cast<okm_u64>(okm::open_max) : lim[0];
for (okm_long fd = from; static_cast<okm_u64>(fd) < bound; ++fd)
okm::sys(okm::nr_fcntl, fd, okm::f_setfd, okm::fd_cloexec);
}
// 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;
}
// THE ENVIRONMENT, WHICH CARRIES THE NAMES OF THE GRANTED DIRECTORIES.
//
// A granted directory reaches the started program as a descriptor, and a
// descriptor carries no name. The names travel in one variable,
//
// KAL_PREOPENS=<pid>{;<fd>,<len>,<name>}
//
// which the started program reads when it first enumerates its preopens
// (`table' in fs.cpp). The arrangement and its spelling are openkal-linux's,
// which takes them from systemd's LISTEN_FDS, LISTEN_FDNAMES and LISTEN_PID;
// one spelling on both kernels is what lets a program read its grants
// without knowing which implementation started it. It is bound to the
// process it was written for in the same way: `<pid>' is ten digits written
// by the duplicate once it knows its own number, so a value inherited
// through a program that does not read it, a shell for example, names no
// one when it arrives one generation further down.
//
// Every variable of that name the caller supplied is left out, so a value
// is never forwarded; one is added only when the caller asked for grants,
// and asking for none (a count of zero) is a value with no entries.
char* pid_digits = nullptr;
bool build_env(const char** items, const kal_uintptr* lens, kal_uintptr n,
const kal_preopen* grants, kal_uintptr g) {
if (n > kMaxEntries) { ok = false; return false; }
kal_uintptr kept = 0;
okm_uptr total = 0;
for (kal_uintptr i = 0; i < n; ++i) {
if (names_preopens(items[i], lens[i])) continue;
++kept; total += lens[i] + 1;
}
okm_uptr extra = 0;
if (grants) {
extra = kPreopensLen + kPidDigits + 1;
for (kal_uintptr i = 0; i < g; ++i)
extra += 3 + digits(3 + i) + digits(grants[i].len) + grants[i].len;
}
slots_bytes = (kept + (grants ? 1 : 0) + 1) * sizeof(char*);
bytes_bytes = total + extra == 0 ? 1 : total + extra;
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, k = 0;
for (kal_uintptr i = 0; i < n; ++i) {
if (names_preopens(items[i], lens[i])) continue;
okm::copy(bytes + at, items[i], lens[i]);
bytes[at + lens[i]] = '\0';
slots[k++] = bytes + at;
at += lens[i] + 1;
}
if (grants) {
char* o = bytes + at;
slots[k++] = o;
okm::copy(o, kPreopens, kPreopensLen); o += kPreopensLen;
pid_digits = o;
okm::fill(o, '0', kPidDigits); o += kPidDigits;
for (kal_uintptr i = 0; i < g; ++i) {
*o++ = ';'; o = put_decimal(o, 3 + i);
*o++ = ','; o = put_decimal(o, grants[i].len);
*o++ = ',';
okm::copy(o, grants[i].name, grants[i].len); o += grants[i].len;
}
*o = '\0';
}
slots[k] = nullptr;
return true;
}
// In the duplicate, which is the first point at which the number is known.
void stamp(okm_long pid) const {
if (!pid_digits) return;
for (int i = static_cast<int>(kPidDigits) - 1; i >= 0; --i) {
pid_digits[i] = static_cast<char>('0' + pid % 10);
pid /= 10;
}
}
~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;
// --- reporting a replacement that failed -----------------------------------
//
// THE REPLACEMENT HAPPENS IN THE DUPLICATE, SO ITS FAILURE WAS REPORTED TO
// NOBODY. Version 0.13.
//
// A program is started here by duplicating this image and replacing the
// duplicate. The replacement is the part that can fail --- the name is absent,
// or is a directory, or is not a program, or may not be executed --- and it
// failed inside an image the caller does not have. This implementation ended
// that image with 127 and answered kal_ok with a handle, so a caller learned
// something was wrong only by waiting and reading 127, which is exactly what a
// program that RAN and exited 127 reports. openkal SPEC.md clause 11 entry 16.
//
// The arrangement is openkal-linux's: a pipe whose ends close when the image
// is replaced. Nothing arrives, the replacement happened; a value arrives, it
// did not, and the value says why.
struct exec_report {
int fd[2] = { -1, -1 };
bool armed = false;
// THE PIPE MUST NOT SIT WHERE THE DUPLICATE IS ABOUT TO PLACE SOMETHING.
// The duplicate places streams at 0, 1 and 2 and granted directories at 3
// and upwards, so a pipe that happened to hold one of those numbers would
// be closed by the very placement whose failure it exists to report --- and
// the parent would then read end-of-input and call that success.
//
// F_DUPFD_CLOEXEC answers the lowest FREE descriptor at or above a bound,
// atomically. There is no pipe2 on this kernel, so each end is marked
// close-on-exec separately before being lifted; a caller that spawns from
// one context, which is what this operation is used from, is not affected
// by the two steps not being atomic with each other.
bool open(kal_uintptr placements) {
okm_long w = -1;
const okm_long r = okm::pipe_pair(w);
if (okm::failed(r)) return false;
fd[0] = static_cast<int>(r);
fd[1] = static_cast<int>(w);
okm::sys(okm::nr_fcntl, fd[0], okm::f_setfd, okm::fd_cloexec);
okm::sys(okm::nr_fcntl, fd[1], okm::f_setfd, okm::fd_cloexec);
const okm_long floor = 3 + static_cast<okm_long>(placements);
armed = lift(fd[0], floor) && lift(fd[1], floor);
if (!armed) close_both();
return armed;
}
void close_both() {
if (fd[0] >= 0) okm::sys(okm::nr_close, fd[0]);
if (fd[1] >= 0) okm::sys(okm::nr_close, fd[1]);
fd[0] = fd[1] = -1;
}
// In the duplicate, once the replacement has returned --- which it does
// only when it did not happen.
void say(okm_long failure) const {
if (!armed) return;
okm_long value = failure;
okm::sys(okm::nr_write, fd[1],
reinterpret_cast<okm_long>(&value), sizeof value);
}
// In this image. Zero when the replacement happened, otherwise the
// kernel's own negative value for why it did not.
okm_long heard() {
if (!armed) return 0;
okm::sys(okm::nr_close, fd[1]);
fd[1] = -1;
okm_long value = 0;
okm_long n;
// A transfer this short is not divided, but it can be interrupted.
do {
n = okm::sys(okm::nr_read, fd[0],
reinterpret_cast<okm_long>(&value), sizeof value);
} while (n == -okm::e_intr);
okm::sys(okm::nr_close, fd[0]);
fd[0] = -1;
return (n == static_cast<okm_long>(sizeof value)) ? value : 0;
}
private:
static bool lift(int& f, okm_long floor) {
const okm_long n = okm::sys(okm::nr_fcntl, f, okm::f_dupfd_cloexec, floor);
if (okm::failed(n)) return false;
okm::sys(okm::nr_close, f);
f = static_cast<int>(n);
return true;
}
};
// A duplicate that could not be replaced is ended, and this image waits for it
// so that nothing is left for a caller to meet later. It is the one wait this
// implementation performs that a caller did not ask for, and it is bounded:
// the duplicate has already reached `exit'.
inline void reap(okm_long child) {
int status = 0;
okm_long r;
do {
r = okm::sys(okm::nr_wait4, child,
reinterpret_cast<okm_long>(&status), 0, 0);
} while (r == -okm::e_intr);
}
} // 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.
} // extern "C" (reopened below)
// --- the handles of this interface (0.17) ------------------------------------
//
// A started program's handle is its identifier. Since 0.17 two more kinds are
// told apart by the top byte: this program itself, and a program found by its
// identifier. This kernel has no descriptor for a program, so the second
// carries, above the identifier, twenty-four bits of the moment the program
// started --- what tells the program found apart from a later one given the
// same number (process.h: "records started_ns at the open").
namespace {
constexpr unsigned kind_child = 0, kind_self = 1, kind_opened = 2;
inline unsigned handle_kind(kal_process h) { return static_cast<unsigned>(h.h >> 56); }
inline okm_long handle_pid(kal_process h) { return static_cast<okm_long>(h.h & 0xffffffffu); }
// `proc_bsdinfo', as `proc_info' (PROC_PIDTBSDINFO) writes it.
struct bsdinfo {
okm_u32 flags, status, xstatus, pid, ppid, uid, gid, ruid, rgid, svuid, svgid, rfu;
char comm[16]; char name[32];
okm_u32 nfiles, pgid, pjobc, tdev, tpgid; int nice;
okm_u64 start_sec, start_usec;
};
static_assert(sizeof(bsdinfo) == 136, "proc_bsdinfo");
bool bsd(okm_long pid, bsdinfo& out) {
const okm_long r = okm::sys(okm::nr_proc_info, 2 /* PROC_INFO_CALL_PIDINFO */, pid,
3 /* PROC_PIDTBSDINFO */, 0, reinterpret_cast<okm_long>(&out),
sizeof out);
return !okm::failed(r) && r == static_cast<okm_long>(sizeof out);
}
okm_u32 start_mark(const bsdinfo& b) {
const okm_u64 us = b.start_sec * 1000000ull + b.start_usec;
return static_cast<okm_u32>((us ^ (us >> 24) ^ (us >> 48)) & 0xffffffu);
}
// The kernel reports processor time in its own ticks, and this is the ratio to
// nanoseconds (`mach_timebase_info', a Mach trap): 125/3 on the processors this
// system now runs on, 1/1 on the earlier ones.
void timebase(okm_u32& numer, okm_u32& denom) {
struct { okm_u32 numer, denom; } tb{1, 1};
#if defined(__aarch64__)
register okm_long x0 __asm__("x0") = reinterpret_cast<okm_long>(&tb);
register okm_long x16 __asm__("x16") = -89;
__asm__ __volatile__("svc #0x80" : "+r"(x0) : "r"(x16) : "memory", "cc", "x1");
#else
okm_long r;
__asm__ __volatile__("syscall" : "=a"(r) : "a"(0x1000000 + 89), "D"(&tb) : "rcx", "r11", "memory");
#endif
numer = tb.numer ? tb.numer : 1; denom = tb.denom ? tb.denom : 1;
}
bool cpu_ns(okm_long pid, okm_u64& out) {
alignas(8) unsigned char ri[400] = {}; // rusage_info_v0 and some
const okm_long r = okm::sys(okm::nr_proc_info, 9 /* PROC_INFO_CALL_PIDRUSAGE */, pid,
0 /* RUSAGE_INFO_V0 */, 0, reinterpret_cast<okm_long>(ri), 0);
if (okm::failed(r)) return false;
okm_u64 user = 0, sys = 0;
okm::copy(reinterpret_cast<char*>(&user), reinterpret_cast<const char*>(ri + 16), 8);
okm::copy(reinterpret_cast<char*>(&sys), reinterpret_cast<const char*>(ri + 24), 8);
okm_u32 n = 1, d = 1;
timebase(n, d);
out = (user + sys) / d * n + (user + sys) % d * n / d;
return true;
}
bool has_ended(kal_process h) {
if (handle_kind(h) == kind_self) return false;
bsdinfo b{};
if (handle_kind(h) == kind_opened) {
if (!bsd(handle_pid(h), b)) return true;
if (b.status == 5 /* SZOMB */) return true;
return start_mark(b) != static_cast<okm_u32>((h.h >> 32) & 0xffffffu);
}
alignas(8) unsigned char info[128] = {};
const okm_long r = okm::sys(okm::nr_waitid, 1 /* P_PID */, handle_pid(h),
reinterpret_cast<okm_long>(info),
0x4 | 0x1 | 0x20 /* WEXITED | WNOHANG | WNOWAIT */);
if (r == -10 /* ECHILD */) return true;
if (okm::failed(r)) return false;
int si_pid = 0;
okm::copy(reinterpret_cast<char*>(&si_pid), reinterpret_cast<const char*>(info + 8), sizeof si_pid);
return si_pid != 0;
}
// EVERY ENVIRONMENT ENTRY IS `NAME=value' (0.17, process.h).
bool entries_well_formed(const char** envp, const kal_uintptr* lens, kal_uintptr n) {
for (kal_uintptr i = 0; i < n; ++i) {
if (envp[i] == nullptr) return false;
kal_uintptr eq = 0;
while (eq < lens[i] && envp[i][eq] != '=') ++eq;
if (eq == 0 || eq == lens[i]) return false;
}
return true;
}
} // namespace
extern "C" {
int kal_process_self(kal_process* out) {
if (out == nullptr) return kal_err_invalid;
*out = kal_process{ (static_cast<kal_uintptr>(kind_self) << 56)
| static_cast<kal_uintptr>(okm::sys(okm::nr_getpid)) };
return kal_ok;
}
int kal_process_open_id(kal_u64 id, kal_process* out) {
if (out == nullptr || id == 0) return kal_err_invalid;
if (id > 0x7fffffffu) return kal_err_not_found;
bsdinfo b{};
if (!bsd(static_cast<okm_long>(id), b)) {
const okm_long probe = okm::sys(okm::nr_kill, static_cast<okm_long>(id), 0);
return probe == -1 /* EPERM */ ? kal_err_permission : kal_err_not_found;
}
if (b.status == 5 /* SZOMB */) return kal_err_not_found;
*out = kal_process{ (static_cast<kal_uintptr>(kind_opened) << 56)
| (static_cast<kal_uintptr>(start_mark(b)) << 32)
| static_cast<kal_uintptr>(id) };
return kal_ok;
}
int kal_process_info(kal_process p, kal_u32 wanted, kal_program_info* out) {
if (out == nullptr || p.h == 0 || out->self_size < 8) return kal_err_invalid;
const kal_u32 self = out->self_size;
kal_program_info v{};
v.self_size = self;
const okm_long pid = handle_pid(p);
v.id = static_cast<kal_u64>(pid);
v.present = KAL_PINFO_ID | KAL_PINFO_ENDED;
const bool ended = has_ended(p);
v.ended = ended ? 1u : 0u;
bsdinfo b{};
if (!ended && (wanted & KAL_PINFO_STARTED) && bsd(pid, b)) {
v.started_ns = b.start_sec * 1000000000ull + b.start_usec * 1000ull;
v.present |= KAL_PINFO_STARTED;
}
okm_u64 c = 0;
if (!ended && (wanted & KAL_PINFO_CPU) && cpu_ns(pid, c)) { v.cpu_ns = c; v.present |= KAL_PINFO_CPU; }
const kal_u32 n = self < sizeof v ? self : static_cast<kal_u32>(sizeof v);
okm::copy(reinterpret_cast<char*>(out), reinterpret_cast<const char*>(&v), n);
return kal_ok;
}
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;
if (envc > 0 && (envp == nullptr || envp_lens == nullptr)) return kal_err_invalid;
if (!entries_well_formed(envp, envp_lens, envc)) return kal_err_invalid;
okm::terminated p(path, path_len);
if (!p.ok) return kal_err_invalid;
// Resolved before the duplication, because a failure after it would leave a
// duplicate to be reaped and a caller with an error it cannot act upon. A
// name travels in the environment, which cannot carry a zero byte.
constexpr kal_uintptr max_grants = 16;
if (how->grant_count > max_grants) return kal_err_invalid;
okm_long granted[max_grants];
for (kal_uintptr i = 0; i < how->grant_count; ++i) {
const kal_preopen& g = how->grants[i];
const int fd = okm::unpack(g.dir.h);
if (fd < 0) return kal_err_invalid;
if (g.len > okm::max_name || (g.len > 0 && g.name == nullptr)) return kal_err_invalid;
for (kal_uintptr c = 0; c < g.len; ++c)
if (g.name[c] == '\0') return kal_err_invalid;
granted[i] = fd;
}
vector args, envs;
if (!args.build(argv, argv_lens, argc)) return kal_err_no_memory;
if (!envs.build_env(envp, envp_lens, envc, how->grants, how->grant_count))
return kal_err_no_memory;
// 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;
// The bound is 3 + grant_count, because the placements below reach that
// far. A caller that cannot arm the report is not refused for it: `heard'
// then answers zero and this operation behaves as it did before 0.13,
// which is the same fallback openkal-linux takes.
exec_report report;
report.open(how->grant_count);
bool is_duplicate = false;
const okm_long child = okm::duplicate(is_duplicate);
if (okm::failed(child)) { report.close_both(); 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) {
// WHAT THE STARTED PROGRAM RECEIVES IS THE THREE STREAMS AND THE GRANTED
// DIRECTORIES, AND NOTHING ELSE (SPEC.md clause 7.13).
//
// Every source is first moved above the positions being filled, because
// placing one source on its position must not overwrite another that
// still has to be read: a granted directory, a stream or the working
// directory may each occupy a number between 0 and 3+n. Placing them one
// at a time where they stood let one grant arrive as a copy of another,
// and let a placement overwrite the directory the program was to run
// in. A source that is moved has its own descriptor flag; `dup2' onto a
// different number clears it on the copy it places, including where the
// source happened to be the position itself --- which `dup2' left
// untouched, still marked to close, so that the grant never arrived.
const okm_long top = static_cast<okm_long>(3 + how->grant_count);
auto lift = [&](okm_long fd) -> okm_long {
const okm_long r = okm::sys(okm::nr_fcntl, fd, okm::f_dupfd_cloexec, top);
if (okm::failed(r)) {
report.say(r);
okm::sys(okm::nr_exit, 127);
for (;;) { }
}
return r;
};
const okm_long sin = in != 0 ? lift(in) : 0;
const okm_long sou = ou != 0 ? lift(ou) : 0;
const okm_long ser = er != 0 ? lift(er) : 0;
for (kal_uintptr i = 0; i < how->grant_count; ++i) granted[i] = lift(granted[i]);
const okm_long work = lift(w);
if (sin != 0) okm::sys(okm::nr_dup2, sin, 0);
if (sou != 0) okm::sys(okm::nr_dup2, sou, 1);
if (ser != 0) okm::sys(okm::nr_dup2, ser, 2);
for (kal_uintptr i = 0; i < how->grant_count; ++i)
okm::sys(okm::nr_dup2, granted[i], static_cast<okm_long>(3 + i));
// What the CALLER itself inherited without the flag is not a handle it
// granted. A program that starts others inside a sandbox passes every
// descriptor it holds to them, and one of those reaching beyond the
// sandbox is exactly what the sandbox was for.
close_on_exec_from(top);
envs.stamp(okm::sys(okm::nr_getpid));
// The directory the program RUNS in --- `whole' already carries where it
// is named from, so this no longer has to serve both.
//
// A FAILURE HERE MUST NOT REACH `execve'. Running the right program in
// the wrong directory is the silent wrongness this pipe exists to
// remove, so it is reported through the same channel an exec failure
// uses.
if (const okm_long e = okm::sys(nr_fchdir, work); okm::failed(e)) {
report.say(e);
okm::sys(okm::nr_exit, 127);
for (;;) { }
}
if (unit) okm::sys(nr_setpgid, 0, join);
// SIGPIPE AS THIS PROGRAM WAS STARTED WITH, not as this implementation
// set it for its own writes (env.cpp): an ignore would outlive `execve'.
if (okm::g_pipe_ignored_here) {
struct { void (*handler)(int); void* tramp; unsigned int mask; int flags; } standard{};
okm::sys(46 /* sigaction */, 13 /* SIGPIPE */, reinterpret_cast<okm_long>(&standard), 0);
}
const okm_long why = okm::sys(okm::nr_execve, reinterpret_cast<okm_long>(whole),
reinterpret_cast<okm_long>(args.slots),
reinterpret_cast<okm_long>(envs.slots));
// Reached only when the replacement did not happen, because when it
// does there is nothing here to reach.
report.say(why);
okm::sys(okm::nr_exit, 127);
for (;;) { }
}
// A START THAT DID NOT HAPPEN IS REPORTED, AND BY ITS REASON. Version
// 0.13. Before this the duplicate's own failure reached nobody: this image
// read nothing from a channel that did not exist and answered kal_ok with
// a handle, whatever the name given it was.
if (const okm_long why = report.heard()) {
reap(child);
return okm::translate(why);
}
// 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;
if (handle_kind(h) == kind_self) return kal_err_invalid;
if (handle_kind(h) == kind_opened) return kal_err_not_supported;
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;
if (handle_kind(h) == kind_self) return kal_err_invalid;
if (handle_kind(h) == kind_opened) return kal_err_not_supported;
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, token) in x0..x5; it calls the handler with sig, siginfo and
// ucontext and then asks the kernel to restore the interrupted context.
//
// THE TOKEN IS PASSED BACK (0.15). The kernel validates `sigreturn' against a
// per-thread token it hands the trampoline in x5, and validation is switched
// on for the process the first time anything installs a disposition through
// the C library. Until then a trampoline that passed nothing worked --- which
// is how this one shipped --- and afterwards `sigreturn' refused, and the
// `brk' below ended the program with SIGTRAP: measured, a program that called
// `signal(SIGINT, SIG_IGN)' before arming the word died on its first SIGTERM.
//
// x19, x20 AND x21 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 x21, x5\n" // token
" 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 x2, x21\n" // token
" mov x16, #184\n" // SYS_sigreturn
" svc #0x80\n"
" brk #1\n"); // sigreturn does not come back
int g_stop_signal = 0; // which request came first, for kal_process_end_as_requested
void stop_handler(int signo) {
int none = 0;
__atomic_compare_exchange_n(&g_stop_signal, &none, signo, false,
__ATOMIC_RELAXED, __ATOMIC_RELAXED);
__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) {
// A REQUEST THE STARTER ARRANGED NOT TO DELIVER STAYS UNDELIVERED (0.17,
// the take-over rule): an inherited SIG_IGN is left as it is.
{
struct { void (*handler)(int); unsigned int mask; int flags; } old{};
if (!okm::failed(okm::sys(nr_sigaction, signo, 0, reinterpret_cast<okm_long>(&old)))
&& old.handler == reinterpret_cast<void (*)(int)>(1))
return true;
}
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; SIGHUP is the terminal that started
// the program closing (0.15.1). All are requests to end, which is the
// whole of what this word reports.
const bool ok = arm_one(15) && arm_one(2) && arm_one(1);
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
}
// ENDS AS THE REQUEST WOULD HAVE ENDED THE PROGRAM (0.17): the request's
// default treatment restored, the request unblocked and sent again; a program
// still here a moment later ends with the status a shell gives one so ended.
int kal_process_end_as_requested(void) {
if (__atomic_load_n(&g_stop_word, __ATOMIC_ACQUIRE) == 0) return kal_err_invalid;
#if defined(__aarch64__)
int signo = __atomic_load_n(&g_stop_signal, __ATOMIC_RELAXED);
if (signo == 0) signo = 15;
macos_sigaction act{}; // SIG_DFL, no trampoline needed
okm::sys(nr_sigaction, signo, reinterpret_cast<okm_long>(&act), 0);
unsigned int set = 1u << (signo - 1);
okm::sys(okm::nr_sigprocmask, 2 /* SIG_UNBLOCK */, reinterpret_cast<okm_long>(&set), 0);
// A signal a program sends itself, unblocked, is delivered before the call
// returns; the exit below is reached only if it was not.
okm::sys(okm::nr_kill, okm::sys(okm::nr_getpid), signo);
okm::sys(okm::nr_exit, 128 + signo);
#endif
for (;;) { }
}
// 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) { } // no kind of handle here holds a resource
// 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
| KAL_PROCESS_PROP_OPEN_ID
#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
; }
}