Repository navigation
Expand file tree
/
Copy pathprocess.cpp
More file actions
1416 lines (1333 loc) · 67.9 KB
/
Copy pathprocess.cpp
File metadata and controls
1416 lines (1333 loc) · 67.9 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
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include "win.h"
#include "handle.h"
#include "rights.h"
#include <openkal/env.h>
#include <openkal/process.h>
#include <openkal/memory.h>
#include <openkal/random.h>
// A program image that has been started.
//
// openkal names the program relative to a directory the caller holds, and this
// environment's operation for starting one takes a path. The directory's own
// path is therefore asked for. That is not a namespace being reconstructed ---
// it is this environment's name for a directory the caller already holds, and
// asking the environment what it calls something is the opposite of inventing a
// name for it.
//
// Duplication of the calling image does not appear, and this environment is the
// reason the omission is not a Unix preference: it has no such operation at
// all, and an interface that offered one would have obliged this implementation
// to construct it out of nothing.
namespace {
bool observable_units();
constexpr okw_uptr kCommandLine = 32768; // this environment's own bound
// THE NAMES A STARTED PROGRAM IS GIVEN, IN THE FORM EVERY PROGRAM READS.
//
// GetFinalPathNameByHandleW answers with the `\\?\' prefix, which tells this
// system to take the rest verbatim. CreateProcessW accepts it, and the started
// program inherits it as its current directory --- where the command interpreter
// refuses it ("UNC paths are not supported") and runs in the Windows directory
// instead. A batch file, which is how many tools are installed on this system,
// therefore ran somewhere other than where it was started. So the prefix is
// removed wherever the name means the same without it: a drive path within the
// classic bound, and a network share in its `\\server\share' form. A longer name
// keeps the prefix, because without it the name would not be accepted at all.
okw_uptr plain_name(wchar_t* s, okw_uptr n) {
// MAX_PATH, less the separator and the terminator a current directory takes.
constexpr okw_uptr kClassic = 258;
const bool verbatim = n >= 4 && s[0] == L'\\' && s[1] == L'\\' && s[2] == L'?' && s[3] == L'\\';
if (!verbatim) return n;
const bool drive = n >= 7 && ((s[4] >= L'A' && s[4] <= L'Z') || (s[4] >= L'a' && s[4] <= L'z'))
&& s[5] == L':' && s[6] == L'\\';
const bool share = n >= 8 && (s[4] == L'U' || s[4] == L'u') && (s[5] == L'N' || s[5] == L'n')
&& (s[6] == L'C' || s[6] == L'c') && s[7] == L'\\';
okw_uptr drop = 0;
if (drive && n - 4 <= kClassic) drop = 4; // \\?\C:\dir -> C:\dir
else if (share && n - 6 <= kClassic) drop = 6; // \\?\UNC\host\x -> \\host\x
if (drop == 0) return n;
for (okw_uptr i = drop; i <= n; ++i) s[i - drop] = s[i]; // the terminator too
if (drop == 6) s[0] = L'\\';
return n - drop;
}
// A SPAWN INHERITS ONLY THE HANDLES IT PLACED. Version 0.13.
//
// `CreateProcessW' with inheritance enabled gives the started program EVERY
// inheritable handle of this process, not only the three in its start-up
// record --- and that was once the whole of what this implementation relied
// upon: a caller-inheritable handle the caller had not placed here crossed the
// spawn anyway, because bInheritHandles does not discriminate. A detached
// child then kept a starter's own standard output open long after the starter
// had gone, and whoever waited for the end of that pipe waited for the wrong
// program to end. `PROC_THREAD_ATTRIBUTE_HANDLE_LIST' is the position this
// system offers for exactly that: it narrows what `CreateProcessW' inherits to
// the array named there, and a handle outside it is not inherited even when it
// is itself marked inheritable for some reason of the caller's own.
//
// A handle in that array must still be marked inheritable, which is the half
// this file already did: each placed handle is marked, the program is
// started, and each is put back as it was --- a borrowed standard stream is
// the caller's and not this operation's to change --- with a lock around the
// whole of it, because marking is a property of the handle and not of the
// call.
SRWLOCK_ g_starting{};
// The granted directories and the records of recorded ones (0.23).
constexpr kal_uintptr kMaxGrants = 16;
struct inheritance {
HANDLE handle[3]{};
DWORD before[3]{};
bool marked[3]{};
bool held = false;
// The handles this start actually places, deduplicated: what goes into
// PROC_THREAD_ATTRIBUTE_HANDLE_LIST. The system refuses a list holding one
// handle twice, which stdout and stderr placed upon the same pipe would
// otherwise be. After the streams, the duplicates a start makes for its
// grants and their records, which are made inheritable when they are made
// and released after the start (`granting').
HANDLE list[3 + 2 * kMaxGrants]{};
DWORD count = 0;
void include(HANDLE h) { if (count < sizeof list / sizeof list[0]) list[count++] = h; }
inheritance(bool active, HANDLE in, HANDLE out, HANDLE err) {
if (!active) return;
AcquireSRWLockExclusive(&g_starting);
held = true;
const HANDLE given[3] = { in, out, err };
for (int i = 0; i < 3; ++i) {
handle[i] = given[i];
if (given[i] == nullptr || given[i] == INVALID_HANDLE_VALUE) continue;
// One stream placed twice --- output and error, typically --- is
// marked and put back once, and named once in the list below.
bool again = false;
for (int j = 0; j < i; ++j) again = again || (marked[j] && handle[j] == given[i]);
if (again) continue;
DWORD flags = 0;
if (!GetHandleInformation(given[i], &flags)) continue;
before[i] = flags & HANDLE_FLAG_INHERIT;
marked[i] = SetHandleInformation(given[i], HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) != 0;
// A handle this system would not make inheritable --- a console
// pseudo-handle is the ordinary case --- is not named here either:
// naming an uninheritable handle in the explicit list is refused by
// the call that builds it, where the old, unrestricted inheritance
// simply left such a handle uninherited.
if (marked[i]) list[count++] = given[i];
}
}
~inheritance() {
for (int i = 2; i >= 0; --i)
if (marked[i]) SetHandleInformation(handle[i], HANDLE_FLAG_INHERIT, before[i]);
if (held) ReleaseSRWLockExclusive(&g_starting);
}
};
// The attribute list naming `window.list' to `CreateProcessW'. Obtained and
// released around one call, the way `scratch' below is: this system sizes the
// buffer for a caller, so there is a first call that only measures.
struct attribute_list {
void* buffer = nullptr;
unsigned long long size = 0;
LPPROC_THREAD_ATTRIBUTE_LIST list = nullptr;
bool build(const inheritance& window) {
if (window.count == 0) return false;
InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
buffer = kal_alloc(static_cast<kal_uintptr>(size), alignof(void*));
if (buffer == nullptr) return false;
list = static_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buffer);
if (!InitializeProcThreadAttributeList(list, 1, 0, &size)) { list = nullptr; return false; }
// `window.list' outlives this call --- it is the caller's local, held
// until CreateProcessW returns --- so nothing here copies it again.
if (!UpdateProcThreadAttribute(list, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
const_cast<HANDLE*>(window.list),
static_cast<unsigned long long>(window.count) * sizeof(HANDLE),
nullptr, nullptr)) {
DeleteProcThreadAttributeList(list);
list = nullptr;
return false;
}
return true;
}
~attribute_list() {
if (list) DeleteProcThreadAttributeList(list);
if (buffer) kal_free(buffer, static_cast<kal_uintptr>(size), alignof(void*));
}
};
bool append_wide(wchar_t* out, okw_uptr cap, okw_uptr& at, const wchar_t* s, okw_uptr n) {
if (at + n + 1 >= cap) return false;
for (okw_uptr i = 0; i < n; ++i) out[at++] = s[i];
return true;
}
// One element of the vector, quoted so that the started program recovers
// exactly what the caller supplied.
//
// Clause 7.6 requires the vector to be passed unaltered, and this environment
// passes one string and lets the started program split it. The quoting below is
// the inverse of the splitting this environment defines, so that the two agree;
// getting it wrong would alter the vector while appearing to pass it.
// One string of the caller's encoding, converted straight into the buffer it is
// destined for.
//
// Not through okw::wide_name, which is for names: a name is bounded by what a
// file system accepts and an argument or a named value is not. An environment's
// search path is routinely longer than any name, and converting it through a
// buffer sized for names refused it --- which reached the caller as "the
// argument is not valid", four operations away from the length that caused it.
bool append_utf8(wchar_t* out, okw_uptr cap, okw_uptr& at, const char* s, okw_uptr n) {
if (n == 0) return true;
if (at + n + 1 >= cap) return false;
const int produced = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s,
static_cast<int>(n), out + at,
static_cast<int>(cap - at - 1));
if (produced <= 0) return false;
at += static_cast<okw_uptr>(produced);
return true;
}
// UNTIL 0.7.3 EVERY SEPARATOR WAS DROPPED, AND EVERY ELEMENT WAS QUOTED.
//
// A run of backslashes was counted and never written, so `C:\dir\file' arrived as
// `C:dirfile' and a trailing one ended the quoting early and joined the elements
// after it. And an element that needs no quoting was quoted anyway, which the
// splitting accepts and the command interpreter does not: it reads `"/c"' as a
// command rather than as its switch, and a batch file --- run through the
// interpreter --- received a line it called incorrect. Now an element is written
// as it is unless the splitting would alter it, and otherwise quoted by the rule
// the splitting inverts: backslashes before a quote are doubled and the quote
// escaped, backslashes before the closing quote are doubled, and every other
// backslash is literal.
bool append_quoted(wchar_t* out, okw_uptr cap, okw_uptr& at, const wchar_t* s, okw_uptr n) {
bool needs = n == 0;
for (okw_uptr i = 0; i < n && !needs; ++i)
needs = s[i] == L' ' || s[i] == L'\t' || s[i] == L'\n' || s[i] == L'\v' || s[i] == L'"';
if (!needs) return append_wide(out, cap, at, s, n);
if (!append_wide(out, cap, at, L"\"", 1)) return false;
okw_uptr backslashes = 0;
for (okw_uptr i = 0; i < n; ++i) {
if (s[i] == L'\\') { ++backslashes; continue; }
const okw_uptr written = s[i] == L'"' ? 2 * backslashes + 1 : backslashes;
for (okw_uptr k = 0; k < written; ++k)
if (!append_wide(out, cap, at, L"\\", 1)) return false;
backslashes = 0;
if (!append_wide(out, cap, at, s + i, 1)) return false;
}
for (okw_uptr k = 0; k < 2 * backslashes; ++k)
if (!append_wide(out, cap, at, L"\\", 1)) return false;
return append_wide(out, cap, at, L"\"", 1);
}
// --- the handles of this interface (0.17) ------------------------------------
//
// The top byte of a process word says which of four things it names; handle.h
// leaves that byte free. A started program is its process handle, as it always
// was. A started program whose lifetime is bound to this one carries a record
// beside it (below). This program itself names no handle at all. A program
// found by its identifier is the handle `OpenProcess' gave, which keeps the
// identifier from being given to another program while it is held.
constexpr okw_uptr kind_child = 0, kind_self = 1, kind_opened = 2, kind_bound = 3;
inline okw_uptr kind_of(kal_process p) { return p.h >> 56; }
inline okw_uptr with_kind(okw_uptr w, okw_uptr kind) {
return (w & 0x00ffffffffffffffull) | (kind << 56);
}
// A STARTED PROGRAM WHOSE LIFETIME IS BOUND TO THIS ONE (K5).
//
// The primitive is a job that ends its members when its last handle closes
// (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE): this program holds the only handle, and
// however this program ends, the system closes it. One job per started
// program, and not one for all of them, because a program that is also placed
// in a unit of the caller's (`kal_spawn.job') is already in that job, and this
// system nests a second job beneath the first only while the second is empty.
//
// The job's handle must outlive every caller-visible operation on the program,
// so the word names a record of the two handles --- a pointer to a structure of
// this implementation's own, which clause 7.2 lists among what a handle may be.
// The record is released with the process handle; the job's handle is released
// only once the program has ended, since releasing it earlier would end the
// program. A record is never reused, so a released word is refused (`gone').
struct bound_record {
void* process;
void* job;
int gone;
};
inline bound_record* bound_of(kal_process p) {
return reinterpret_cast<bound_record*>(p.h & 0x00ffffffffffffffull);
}
// The process handle a word names, whatever its kind; null for none.
inline void* process_of(kal_process p) {
switch (kind_of(p)) {
case kind_child: return okw::unpack(p.h);
case kind_self: return GetCurrentProcess();
case kind_opened: return okw::unpack(with_kind(p.h, 0));
case kind_bound: {
bound_record* r = bound_of(p);
return (r && !r->gone) ? r->process : nullptr;
}
default: return nullptr;
}
}
// --- what a program and a unit use, and a limit on a unit (openkal 0.20) ----
//
// A PROGRAM'S MEMORY is its commit charge (`K32GetProcessMemoryInfo':
// `PagefileUsage' now, `PeakPagefileUsage' at most), which is what a job counts
// and limits. A process object keeps its counters after the program ends, for as
// long as a handle to it is held.
//
// A UNIT IS A JOB OBJECT, which counts its own members (`ActiveProcesses') and
// its own commit (`JobMemory').
//
// A LIMIT IS HELD BY THE JOB (HARD): JOB_OBJECT_LIMIT_JOB_MEMORY refuses an
// allocation that would take the job over its limit. What the specification
// states is that the WHOLE unit ends, and the job alone would only refuse: so
// each limited job reports to one completion port, a context started with the
// first limit waits on it, and on the job's report that it reached its limit it
// ends the job. A program of a limited job that ended because an allocation was
// refused, before that context ended the rest, is told apart by the job's own
// record of the violation.
// WINE KEEPS NO MEMORY FOR A JOB AND HOLDS NO LIMIT ON ONE, and answers the
// queries as though it did (a job's memory reads zero, a limit is accepted and
// never reached). Measured under the wine of this repository's CI. Where the
// environment is wine --- it exports `wine_get_version' from ntdll --- neither
// position is claimed and both operations report kal_err_not_supported, so
// that what is claimed is what the environment does.
bool keeps_job_memory() {
static int answer = -1; // -1 not asked
if (answer < 0) {
static const wchar_t ntdll[] = L"ntdll.dll";
HANDLE n = LoadLibraryW(ntdll);
answer = (n && GetProcAddress(n, "wine_get_version")) ? 0 : 1;
}
return answer == 1;
}
struct limited { HANDLE job; int ended; };
// The number a job ended for its limit gives its programs: one with the
// customer bit set, which no status of this system's own has.
constexpr UINT kLimitCode = 0xE0DA1001u;
constexpr int kLimited = 64;
limited g_limited[kLimited];
SRWLOCK_ g_limited_lock{};
HANDLE g_port = nullptr;
int g_watching = 0;
DWORD OKW_API watch(LPVOID) {
for (;;) {
DWORD message = 0; unsigned long long key = 0; OVERLAPPED* detail = nullptr;
if (!GetQueuedCompletionStatus(g_port, &message, &key, &detail, INFINITE)) continue;
if (message != JOB_OBJECT_MSG_JOB_MEMORY_LIMIT_) continue;
AcquireSRWLockExclusive(&g_limited_lock);
for (auto& r : g_limited)
if (r.job && reinterpret_cast<unsigned long long>(r.job) == key) {
r.ended = 1;
TerminateJobObject(r.job, kLimitCode);
}
ReleaseSRWLockExclusive(&g_limited_lock);
}
}
// Whether `process' ended because a limited job it belongs to reached its limit.
bool ended_by_limit(HANDLE process) {
bool limit = false;
AcquireSRWLockExclusive(&g_limited_lock);
for (const auto& r : g_limited) {
if (!r.job) continue;
BOOL in = FALSE;
if (!IsProcessInJob(process, r.job, &in) || !in) continue;
JOB_VIOLATION_ v{};
if (r.ended || (QueryInformationJobObject(r.job, JobObjectLimitViolationInformation_, &v, sizeof v, nullptr)
&& (v.ViolationLimitFlags & JOB_OBJECT_LIMIT_JOB_MEMORY_)))
limit = true;
}
ReleaseSRWLockExclusive(&g_limited_lock);
return limit;
}
} // namespace
namespace {
} // namespace
namespace okw {
// The process handle a word names, for the operations outside this file that
// wait on a started program (timeout.cpp): a word of a program started with a
// bound lifetime names its record, not its handle (0.13.2).
void* process_handle(kal_process p) { return process_of(p); }
} // namespace okw
namespace {
// 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;
}
// --- what a start conveys beside its arguments (0.23) -------------------------
//
// THE GRANTS. This system has no numbering a preopen could arrive under, and
// needs none: a handle a program inherits has the value it had in the program
// that started it. So each grant is a duplicate of the caller's handle, made
// inheritable for this start and named in its explicit list, and the started
// program learns the values and the names from one variable,
//
// KAL_PREOPENS=<pid>{;<handle>,<len>,<name>}
//
// and what each carries (rights.h) from a second,
//
// KAL_GRANT_RIGHTS=<pid>{;<index>,<flags>,<record handle or -1>,<len>,<path>}
//
// where `<pid>' is this program: the started program reads them only when the
// program that started it is that one (fs.cpp), so a value that reaches a
// program through one that did not read it --- a shell --- names no one. The
// arrangement is the other two systems', with the starter's number in place of
// the started program's, which this system gives only once the program exists.
// THE PREFERENCE travels as KAL_RESOURCES, read by env.cpp.
bool named(const char* s, okw_uptr n, const char* key) {
okw_uptr k = 0;
for (; key[k]; ++k) {
if (k >= n) return false;
char a = s[k];
if (a >= 'a' && a <= 'z') a = static_cast<char>(a - 32);
if (a != key[k]) return false;
}
return k < n && s[k] == '=';
}
bool named(const wchar_t* s, okw_uptr n, const char* key) {
okw_uptr k = 0;
for (; key[k]; ++k) {
if (k >= n) return false;
wchar_t a = s[k];
if (a >= L'a' && a <= L'z') a = static_cast<wchar_t>(a - 32);
if (a != static_cast<wchar_t>(key[k])) return false;
}
return k < n && s[k] == L'=';
}
template <class C>
bool conveyed_here(const C* s, okw_uptr n) {
return named(s, n, "KAL_PREOPENS") || named(s, n, "KAL_GRANT_RIGHTS") || named(s, n, "KAL_RESOURCES");
}
char* put_decimal(char* o, unsigned long long 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;
}
struct granting {
HANDLE dup[kMaxGrants]{};
HANDLE rec[kMaxGrants]{};
kal_uintptr count = 0;
const kal_preopen* grants = nullptr;
char rights[8192];
okw_uptr rights_len = 0;
int make(const kal_preopen* g, kal_uintptr n, inheritance& window) {
grants = g;
const HANDLE self = GetCurrentProcess();
for (kal_uintptr i = 0; i < n; ++i) {
void* src = okw::unpack(g[i].dir.h);
if (!DuplicateHandle(self, src, self, &dup[i], 0, TRUE, DUPLICATE_SAME_ACCESS))
return okw::translate_win32(GetLastError());
count = i + 1;
window.include(dup[i]);
okw_u32 flags = 0; void* record = nullptr; const char* prefix = ""; okw_uptr plen = 0;
if (!okw::rights::conveyed(src, &flags, &record, &prefix, &plen)) continue;
if (record != nullptr) {
if (!DuplicateHandle(self, record, self, &rec[i], 0, TRUE, DUPLICATE_SAME_ACCESS))
return okw::translate_win32(GetLastError());
window.include(rec[i]);
}
char head[96];
char* o = head;
*o++ = ';'; o = put_decimal(o, i);
*o++ = ','; o = put_decimal(o, flags);
*o++ = ',';
if (rec[i] == nullptr) { *o++ = '-'; *o++ = '1'; }
else o = put_decimal(o, reinterpret_cast<okw_uptr>(rec[i]));
*o++ = ','; o = put_decimal(o, plen);
*o++ = ',';
const okw_uptr hn = static_cast<okw_uptr>(o - head);
if (rights_len + hn + plen >= sizeof rights) return kal_err_invalid;
for (okw_uptr k = 0; k < hn; ++k) rights[rights_len++] = head[k];
for (okw_uptr k = 0; k < plen; ++k) rights[rights_len++] = prefix[k];
}
return kal_ok;
}
~granting() {
for (kal_uintptr i = 0; i < count; ++i) {
if (dup[i]) CloseHandle(dup[i]);
if (rec[i]) CloseHandle(rec[i]);
}
}
};
// The block of named values a start passes, made of the caller's entries ---
// or this program's own where the caller gave none --- less any of the names
// a start conveys, and this start's.
struct environment {
wchar_t* w = nullptr;
okw_uptr cap = 0, at = 0;
char* extra = nullptr;
okw_uptr extra_cap = 0, extra_len = 0;
~environment() {
if (w) kal_free(w, cap * sizeof(wchar_t), alignof(wchar_t));
if (extra) kal_free(extra, extra_cap, 1);
}
bool put_utf8(const char* s, okw_uptr n) {
if (n == 0) return true;
const int produced = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s, static_cast<int>(n),
w + at, static_cast<int>(cap - at - 2));
if (produced <= 0) return false;
at += static_cast<okw_uptr>(produced);
w[at++] = 0;
return true;
}
int build(const char** envp, const kal_uintptr* lens, kal_uintptr envc, const granting* g,
const char* resources) {
// This start's own, first, as text of the caller's encoding.
okw_uptr need = 64;
if (g) {
need += 32;
for (kal_uintptr i = 0; i < g->count; ++i) need += 48 + g->grants[i].len;
need += 48 + g->rights_len;
}
extra_cap = need;
extra = static_cast<char*>(kal_alloc(extra_cap, 1));
if (!extra) return kal_err_no_memory;
const unsigned long long pid = GetCurrentProcessId();
char* o = extra;
auto text = [&](const char* t) { while (*t) *o++ = *t++; };
if (g) {
text("KAL_PREOPENS="); o = put_decimal(o, pid);
for (kal_uintptr i = 0; i < g->count; ++i) {
*o++ = ';'; o = put_decimal(o, reinterpret_cast<okw_uptr>(g->dup[i]));
*o++ = ','; o = put_decimal(o, g->grants[i].len);
*o++ = ',';
for (kal_uintptr k = 0; k < g->grants[i].len; ++k) *o++ = g->grants[i].name[k];
}
*o++ = '\0';
if (g->rights_len) {
text("KAL_GRANT_RIGHTS="); o = put_decimal(o, pid);
for (okw_uptr k = 0; k < g->rights_len; ++k) *o++ = g->rights[k];
*o++ = '\0';
}
}
if (resources) { text("KAL_RESOURCES="); text(resources); *o++ = '\0'; }
extra_len = static_cast<okw_uptr>(o - extra);
// The rest, measured: no unit of this system's encoding is made of
// fewer than one byte of the caller's.
wchar_t* own = envc == 0 ? GetEnvironmentStringsW() : nullptr;
okw_uptr total = extra_len + 2;
if (envc > 0) for (kal_uintptr i = 0; i < envc; ++i) total += lens[i] + 1;
else if (own) { const wchar_t* p = own; while (*p) { while (*p) { ++p; ++total; } ++p; ++total; } }
cap = total;
w = static_cast<wchar_t*>(kal_alloc(cap * sizeof(wchar_t), alignof(wchar_t)));
if (!w) { if (own) FreeEnvironmentStringsW(own); return kal_err_no_memory; }
bool ok = true;
if (envc > 0) {
for (kal_uintptr i = 0; i < envc && ok; ++i)
if (!conveyed_here(envp[i], lens[i])) ok = put_utf8(envp[i], lens[i]);
} else if (own) {
for (const wchar_t* p = own; *p;) {
okw_uptr n = 0;
while (p[n]) ++n;
if (!conveyed_here(p, n)) { for (okw_uptr k = 0; k <= n; ++k) w[at++] = p[k]; }
p += n + 1;
}
FreeEnvironmentStringsW(own);
}
for (const char* e = extra; ok && e < extra + extra_len;) {
const okw_uptr n = okw::length(e);
ok = put_utf8(e, n);
e += n + 1;
}
if (!ok) return kal_err_invalid;
// An empty block is one terminator before the last.
if (at == 0) w[at++] = 0;
w[at++] = 0;
return kal_ok;
}
};
} // namespace
extern "C" {
// Starting a program. One function since openkal 0.11.
//
// `grants' WAS REFUSED HERE UNTIL 0.19.0 (openkal 0.23), on the ground that this
// environment has no numbering a preopen could arrive under. It needs none: an
// inherited handle keeps its value, and the value travels with the name
// (`granting' above). A count of zero starts a program with no preopens.
//
// `KAL_SPAWN_BOUND_LIFETIME' WAS REFUSED HERE UNTIL 0.13, on the ground that no
// primitive arms it from inside the started image. None needs to: a job that
// ends its members when its last handle closes is armed from OUTSIDE, by the
// caller, and the system closes the caller's handle however the caller ends.
// See `bound_record'.
//
// AND THE UNIT IS IMPLEMENTED HERE, WHICH AN EARLIER SHAPE OF IT WAS NOT.
//
// 0.11 first spelled this as a flag: make the started program a unit, and let
// `kal_process_terminate' reach the unit afterwards. That shape could not be
// satisfied here. This system can FORM the unit --- a job object is exactly it ---
// but it cannot RECOVER one from a process handle, and `kal_process' is one word
// already holding the process. The other two implementations needed no storage
// because `getpgid(pid) == pid' recovers it from the kernel; this one would have
// needed a registry.
//
// Clause 7.1 states mechanically what needing a registry means: the
// specification "has taken a shape borrowed from one environment, and THE SHAPE
// IS AT FAULT rather than the implementation". handle.h says the same one level
// down --- its array "holds generations and nothing else", and a lookup deciding
// what a word referred to "would be a defect here".
//
// ⇒ So the shape changed rather than this file acquiring a table. The unit is now
// a handle the CALLER holds, whose identity is established at the first start, and
// both kinds of system perform that without remembering anything: here a job
// object is created and its handle reported; where the unit is a process group
// the first member's identifier is reported instead.
//
// JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE IS DELIBERATELY NOT SET. It would make
// `kal_process_job_close' end every member --- and closing means only releasing
// where the unit is a number, so one operation would mean two things. Ending is
// `kal_process_job_terminate' and nothing else is.
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;
void* dir = okw::unpack(how->base.h);
void* run = okw::unpack(how->work.h);
if (!dir || !run) return kal_err_invalid;
if (!okw::acceptable(path, path_len)) return kal_err_invalid;
constexpr kal_uintptr nulls = KAL_SPAWN_NULL_IN | KAL_SPAWN_NULL_OUT | KAL_SPAWN_NULL_ERR;
constexpr kal_uintptr preferences = KAL_SPAWN_RESOURCES_BALANCED | KAL_SPAWN_RESOURCES_PERFORMANCE
| KAL_SPAWN_RESOURCES_ECONOMY;
// KAL_SPAWN_CONFINE is not among them: this system holds a program to
// directories only by changing who may reach them for everyone (a
// security descriptor of each file), which a start does not get to do.
if ((how->flags & ~(KAL_SPAWN_BOUND_LIFETIME | KAL_SPAWN_ONE_STRING | nulls | preferences)) != 0)
return kal_err_not_supported;
const kal_uintptr preference = how->flags & preferences;
if (preference & (preference - 1)) return kal_err_invalid;
// THE GRANTS (0.23): each a directory this program holds, and a name that
// travels in a variable, which cannot carry a zero.
if (how->grants == nullptr && how->grant_count != 0) return kal_err_invalid;
if (how->grant_count > kMaxGrants) return kal_err_invalid;
for (kal_uintptr i = 0; i < how->grant_count; ++i) {
const kal_preopen& g = how->grants[i];
if (!okw::unpack(g.dir.h)) return kal_err_invalid;
if (g.len >= okw::kMaxName || (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;
}
// THE NULL DEVICE BESIDE A STREAM AT ONE POSITION is two answers to one
// question (openkal 0.22, clause 7.15).
if (streams != nullptr
&& (((how->flags & KAL_SPAWN_NULL_IN) && streams->in.h != 0)
|| ((how->flags & KAL_SPAWN_NULL_OUT) && streams->out.h != 0)
|| ((how->flags & KAL_SPAWN_NULL_ERR) && streams->err.h != 0)))
return kal_err_invalid;
// Clause 7.6, 0.17.1: the elements after the first joined as they are.
const bool one_string = (how->flags & KAL_SPAWN_ONE_STRING) != 0;
if (envc > 0 && (envp == nullptr || envp_lens == nullptr)) return kal_err_invalid;
if (!entries_well_formed(envp, envp_lens, envc)) return kal_err_invalid;
const bool bound = (how->flags & KAL_SPAWN_BOUND_LIFETIME) != 0;
// The unit, created before its first member and reported to the caller. A
// later member is assigned to the one the caller already holds.
HANDLE unit = nullptr;
bool unit_is_new = false;
if (how->job) {
if (how->job->h != 0) {
unit = okw::unpack(how->job->h);
if (!unit) return kal_err_invalid;
} else {
unit = CreateJobObjectW(nullptr, nullptr);
if (!unit) return okw::translate_win32(GetLastError());
unit_is_new = true;
}
}
struct unit_guard {
HANDLE h; bool own;
~unit_guard() { if (own && h) CloseHandle(h); }
} ug{ unit, unit_is_new };
// The directory's own name, and the program's beneath it.
// Obtained rather than kept in static storage: static storage shared
// between execution contexts would make two concurrent spawns one.
struct scratch {
wchar_t image[okw::kMaxName];
// THE DIRECTORY THE PROGRAM RUNS IN, WHICH IS NOT THE ONE IT IS NAMED
// FROM. `CreateProcessW' has taken a current directory all along; what
// was missing until 0.11 was a caller able to say which.
wchar_t cwd[okw::kMaxName];
wchar_t line[kCommandLine];
// One element, converted before it is quoted into `line'. Its own
// buffer: quoting lengthens an element, so converting it in place just
// beyond what `line' holds had the quoting overwrite what it had not
// yet read.
wchar_t one[kCommandLine];
};
auto* work = static_cast<scratch*>(kal_alloc(sizeof(scratch), alignof(scratch)));
if (!work) return kal_err_no_memory;
struct releaser {
scratch* p;
~releaser() { if (p) kal_free(p, sizeof(scratch), alignof(scratch)); }
} guard{ work };
wchar_t* image = work->image;
const DWORD n = GetFinalPathNameByHandleW(dir, image, okw::kMaxName - 2,
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
if (n == 0 || n >= okw::kMaxName - 2) return okw::translate_win32(GetLastError());
okw_uptr at = n;
image[at++] = L'\\';
okw::wide_name relative(path, path_len);
if (!relative.ok) return kal_err_invalid;
for (okw_uptr i = 0; i < relative.string.length / 2u; ++i) {
if (at + 2 >= okw::kMaxName) return kal_err_invalid;
image[at++] = relative.buffer[i];
}
image[at] = 0;
(void)plain_name(image, at);
// The same enquiry the image path comes from, upon the other directory.
wchar_t* cwd = work->cwd;
const DWORD cn = GetFinalPathNameByHandleW(run, cwd, okw::kMaxName - 1,
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
if (cn == 0 || cn >= okw::kMaxName - 1) return okw::translate_win32(GetLastError());
cwd[cn] = 0;
(void)plain_name(cwd, cn);
// The vector, unaltered, including its first element.
wchar_t* line = work->line;
okw_uptr used = 0;
for (kal_uintptr i = 0; i < argc; ++i) {
if (i && !append_wide(line, kCommandLine, used, L" ", 1)) return kal_err_no_space;
// Converted into a scratch of its own so that it can be quoted, and
// quoted because this environment passes one string and lets the
// started program split it. Clause 7.6 requires the vector to arrive
// unaltered, and the quoting is the inverse of that splitting.
okw_uptr produced = 0;
wchar_t* one = work->one;
const okw_uptr room = kCommandLine - 1;
if (!append_utf8(one, room, produced, argv[i], argv_lens[i]))
return argv_lens[i] >= room ? kal_err_no_space : kal_err_invalid;
if (one_string && i > 0) {
// KAL_SPAWN_ONE_STRING: the program parses this string by rules of
// its own (the command interpreter's /c), so it is passed as it is.
if (!append_wide(line, kCommandLine, used, one, produced)) return kal_err_no_space;
} else if (!append_quoted(line, kCommandLine, used, one, produced)) {
return kal_err_no_space;
}
}
line[used] = 0;
if (argc == 0) line[0] = 0;
// THE NAMED VALUES. An empty set means the started program receives the
// caller's, which is what this environment does when none is supplied ---
// unless this start conveys something of its own (0.23: the grants, what
// they carry, the preference), and then the block is the caller's, less
// any value of those names, and this start's. Made inside the start's
// window below, because the grants' handles are made there.
const int own_preference = kal_env_resources();
const int asked_preference = preference == KAL_SPAWN_RESOURCES_PERFORMANCE ? KAL_RESOURCES_PERFORMANCE
: preference == KAL_SPAWN_RESOURCES_ECONOMY ? KAL_RESOURCES_ECONOMY
: preference == KAL_SPAWN_RESOURCES_BALANCED ? KAL_RESOURCES_BALANCED
: own_preference;
const char* resources = asked_preference == KAL_RESOURCES_PERFORMANCE ? "performance"
: asked_preference == KAL_RESOURCES_ECONOMY ? "economy"
: preference != 0 ? "balanced" : nullptr;
// THE NULL DEVICE (openkal 0.22): this system's `NUL', opened for this
// start and released after it; the started program holds its own handle.
// It is placed and made inheritable as a caller's stream is, through
// `inheritance' below.
struct device_guard {
HANDLE h = INVALID_HANDLE_VALUE;
~device_guard() { if (h != INVALID_HANDLE_VALUE) CloseHandle(h); }
} device;
const kal_uintptr null_at = how->flags & nulls;
if (null_at != 0) {
device.h = CreateFileW(L"NUL", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, 0, nullptr);
if (device.h == INVALID_HANDLE_VALUE) return okw::translate_win32(GetLastError());
}
STARTUPINFOEXW ex{};
STARTUPINFOW& startup = ex.startup;
startup.cb = sizeof(STARTUPINFOW);
bool inherit = false;
const bool grants = how->grants != nullptr;
// A start that grants places the streams as one that is given them does:
// what it places is named in the list, and nothing else crosses.
if (grants || null_at != 0 || (streams && (streams->in.h || streams->out.h || streams->err.h))) {
auto placed = [&](kal_uintptr flag, kal_uintptr stream, DWORD standard) -> HANDLE {
if (null_at & flag) return device.h;
return stream ? reinterpret_cast<void*>(stream) : GetStdHandle(standard);
};
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdInput = placed(KAL_SPAWN_NULL_IN, streams ? streams->in.h : 0, STD_INPUT_HANDLE);
startup.hStdOutput = placed(KAL_SPAWN_NULL_OUT, streams ? streams->out.h : 0, STD_OUTPUT_HANDLE);
startup.hStdError = placed(KAL_SPAWN_NULL_ERR, streams ? streams->err.h : 0, STD_ERROR_HANDLE);
inherit = true;
}
PROCESS_INFORMATION info{};
BOOL started = FALSE;
DWORD refusal = 0;
int failed = kal_ok;
{
inheritance window(inherit, startup.hStdInput, startup.hStdOutput, startup.hStdError);
// THE GRANTS AND THEIR RECORDS, as duplicates made inheritable for this
// start: the started program receives each at the value it has here,
// and its name and what it carries in the variables below.
granting given;
environment block;
if (grants || resources != nullptr) {
failed = given.make(how->grants, how->grant_count, window);
if (failed == kal_ok) failed = block.build(envp, envp_lens, envc, grants ? &given : nullptr, resources);
} else if (envc > 0) {
failed = block.build(envp, envp_lens, envc, nullptr, nullptr);
}
// ONLY THE HANDLES `window' NAMED ARE ASKED TO CROSS. Without an
// explicit list, bInheritHandles=TRUE would still hand the started
// program every inheritable handle of this process, so it is set only
// together with the list that narrows what that means; a request with
// nothing markable inherits nothing rather than everything.
attribute_list attrs;
// SUSPENDED WHEN IT IS TO BE PLACED, so that nothing it starts can
// escape the job before the job holds it.
DWORD creation = CREATE_UNICODE_ENVIRONMENT | (bound || unit ? 0x4u /* CREATE_SUSPENDED */ : 0u);
BOOL inherit_handles = FALSE;
if (window.count > 0 && attrs.build(window)) {
startup.cb = sizeof(STARTUPINFOEXW);
ex.lpAttributeList = attrs.list;
creation |= EXTENDED_STARTUPINFO_PRESENT;
inherit_handles = TRUE;
}
if (failed == kal_ok) {
started = CreateProcessW(image, argc ? line : nullptr, nullptr, nullptr,
inherit_handles, creation,
block.w, cwd, &startup, &info);
// Read before the handles are put back, which may set it again.
if (!started) refusal = GetLastError();
}
}
if (failed != kal_ok) return failed;
if (!started) return okw::translate_win32(refusal);
// ECONOMY (0.23): the started program's pages are the first this system
// takes back when memory is short. Set by the starter, so that a program
// of this system's own --- one that does not read the preference --- is
// held to it as well; a program of this implementation also sets it on
// itself where it was started otherwise (env.cpp).
if (asked_preference == KAL_RESOURCES_ECONOMY) okw::lower_memory_priority(info.hProcess);
// ASSIGNED BEFORE THE CALLER IS TOLD ANYTHING. A program that could not be
// put into the unit is not the program that was asked for --- it would outlive
// a termination of the unit --- so it is ended rather than handed back.
if (unit && !AssignProcessToJobObject(unit, info.hProcess)) {
const DWORD why = GetLastError();
TerminateProcess(info.hProcess, 127);
CloseHandle(info.hThread);
CloseHandle(info.hProcess);
return okw::translate_win32(why);
}
// THE BOUND LIFETIME (K5), nested beneath the unit where there is one.
bound_record* record = nullptr;
if (bound) {
void* job = CreateJobObjectW(nullptr, nullptr);
JOB_LIMITS limits{};
limits.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE_;
record = static_cast<bound_record*>(kal_alloc(sizeof(bound_record), alignof(bound_record)));
if (!job || !record
|| !SetInformationJobObject(job, JobObjectExtendedLimitInformation_, &limits, sizeof limits)
|| !AssignProcessToJobObject(job, info.hProcess)) {
const DWORD why = GetLastError();
TerminateProcess(info.hProcess, 127);
CloseHandle(info.hThread);
CloseHandle(info.hProcess);
if (job) CloseHandle(job);
if (record) kal_free(record, sizeof(bound_record), alignof(bound_record));
return record ? okw::translate_win32(why) : kal_err_no_memory;
}
record->process = info.hProcess;
record->job = job;
record->gone = 0;
}
if (bound || unit) ResumeThread(info.hThread);
CloseHandle(info.hThread);
// The caller's word, written only now, and only for a unit this start made.
if (unit_is_new) { how->job->h = okw::pack(unit); ug.own = false; }
*out = record ? kal_process{ with_kind(reinterpret_cast<okw_uptr>(record), kind_bound) }
: kal_process{ okw::pack(info.hProcess) };
return kal_ok;
}
int kal_process_self(kal_process* out) {
if (out == nullptr) return kal_err_invalid;
*out = kal_process{ with_kind(GetCurrentProcessId(), kind_self) };
return kal_ok;
}
// A program found by its identifier. The handle keeps the identifier from being
// given to another program while it is held, which is what makes `started_ns'
// and `ended' answers about this program and not a later one.
int kal_process_open_id(kal_u64 id, kal_process* out) {
if (out == nullptr || id == 0) return kal_err_invalid;
if (id > 0xffffffffull) return kal_err_not_found;
void* h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE,
static_cast<DWORD>(id));
if (!h) {
const DWORD e = GetLastError();
return e == 87 /* ERROR_INVALID_PARAMETER: no such program */ ? kal_err_not_found
: okw::translate_win32(e);
}
const okw_uptr w = okw::pack(h);
if (w == 0 || (w >> 56) != 0) { CloseHandle(h); return kal_err_no_memory; }
*out = kal_process{ with_kind(w, kind_opened) };
return kal_ok;
}
int kal_process_info(kal_process p, kal_u32 wanted, kal_program_info* out) {
if (out == nullptr || out->self_size < 8) return kal_err_invalid;
void* h = process_of(p);
if (!h) return kal_err_invalid;
const kal_u32 self = out->self_size;
kal_program_info v{};
v.self_size = self;
v.id = kind_of(p) == kind_self ? GetCurrentProcessId() : GetProcessId(h);
v.present = KAL_PINFO_ID | KAL_PINFO_ENDED;
v.ended = kind_of(p) != kind_self && WaitForSingleObject(h, 0) == 0 /* WAIT_OBJECT_0 */;
FILETIME created{}, exited{}, kernel{}, user{};
if ((wanted & (KAL_PINFO_STARTED | KAL_PINFO_CPU)) && GetProcessTimes(h, &created, &exited, &kernel, &user)) {
auto ticks = [](const FILETIME& f) {
return (static_cast<unsigned long long>(f.dwHighDateTime) << 32) | f.dwLowDateTime;
};
if (wanted & KAL_PINFO_STARTED) { v.started_ns = ticks(created) * 100ull; v.present |= KAL_PINFO_STARTED; }
if (wanted & KAL_PINFO_CPU) { v.cpu_ns = (ticks(kernel) + ticks(user)) * 100ull; v.present |= KAL_PINFO_CPU; }
}
// MEMORY (openkal 0.20): the commit charge now and at most; the process
// object keeps both after the program ends, while the handle is held.
// PAGE FAULTS (0.23): this system counts them as one number, which is
// `faults_minor' with KAL_PINFO_MAJOR absent. Each is written where the
// caller's structure reaches it.
constexpr kal_u32 memory_end = 56, faults_end = 72;
PROCESS_MEMORY_COUNTERS_EX_ m{};
m.cb = sizeof m;
const bool counted = (wanted & (KAL_PINFO_MEMORY | KAL_PINFO_FAULTS)) && self >= memory_end
&& K32GetProcessMemoryInfo(kind_of(p) == kind_self ? GetCurrentProcess() : h, &m, sizeof m);
// An ended program whose counters read zero has no record of them left
// (wine keeps none), which is not a peak of zero: nothing is reported.
if (counted && (wanted & KAL_PINFO_MEMORY) && (!v.ended || m.PeakPagefileUsage != 0)) {
v.memory_bytes = v.ended ? 0 : m.PagefileUsage;
v.memory_peak_bytes = m.PeakPagefileUsage > m.PagefileUsage ? m.PeakPagefileUsage : m.PagefileUsage;
v.present |= KAL_PINFO_MEMORY;
}
// A count of zero is no count: every program has taken a fault by the time
// it can ask, and an emulator of this system that keeps none answers zero.
if (counted && (wanted & KAL_PINFO_FAULTS) && self >= faults_end && m.PageFaultCount != 0) {
v.faults_minor = m.PageFaultCount;
v.present |= KAL_PINFO_FAULTS;
}
const kal_u32 n = self < sizeof v ? self : static_cast<kal_u32>(sizeof v);
auto* d = reinterpret_cast<unsigned char*>(out);
const auto* s = reinterpret_cast<const unsigned char*>(&v);
for (kal_u32 i = 0; i < n; ++i) d[i] = s[i];
return kal_ok;
}
// A WORD THIS ENVIRONMENT SETS WHEN SOMEBODY HAS ASKED THIS PROGRAM TO END.
//
// AND THIS IS WHY THE INTERFACE IS A WORD RATHER THAN A HANDLER. The
// notification here arrives ON A CONTEXT OF ITS OWN --- the environment starts one
// to run the routine --- which is nothing like a disposition interrupting whatever
// was running. An interface shaped like the other system's signals would have
// had to pretend one was the other; a word both can set needs no pretending.
//
// The routine stores and wakes, which is all `kal_task_wait' needs on the other
// side. Returning false lets the default handling proceed, so a program that
// never reads the word behaves as it always did.
namespace {
kal_u32 g_stop_word = 0;
int g_stop_armed = 0;
// HANDLED, AND THE PROGRAM CONTINUES (K3). Returning false passed the event to
// the next routine, whose default is to end the program at once --- so the
// word was set and the program was gone before anything could read it.
// Measured: a console interrupt ended the program the word was armed in.
//
// Ctrl+C and Ctrl+Break are handled and the program continues. A console that
// is closing, a user logging off and a system shutting down are recorded the
// same way; for those this system ends the program shortly after the routine
// returns, whatever it returns, which process.h states as a request that is
// not a veto.
//
// THE TAKE-OVER RULE (0.17) HOLDS WITHOUT ANYTHING HERE: a program started
// with Ctrl+C disabled (CREATE_NEW_PROCESS_GROUP) keeps that flag, which
// installing a routine does not clear, so a request its starter declined
// stays declined.