-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprocess.cpp
More file actions
483 lines (443 loc) · 21.4 KB
/
Copy pathprocess.cpp
File metadata and controls
483 lines (443 loc) · 21.4 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
#include "win.h"
#include "handle.h"
#include <openkal/process.h>
#include <openkal/memory.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 {
constexpr okw_uptr kCommandLine = 32768; // this environment's own bound
// ⚠️ THE HANDLES A START PLACES ARE INHERITABLE FOR THE LENGTH OF THE START AND NO
// LONGER, AND ONE START AT A TIME DOES THIS.
//
// `CreateProcessW' with inheritance enabled gives the started program EVERY
// inheritable handle of this process, not only the three in its start-up record.
// A handle left marked after a start reaches the next program started, and a
// handle marked by a start on another context reaches this one; either way a
// program ends up holding a pipe that belongs to another, and whoever waits for
// the end of that pipe waits for the wrong program. So 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.
SRWLOCK_ g_starting{};
struct inheritance {
HANDLE handle[3]{};
DWORD before[3]{};
bool marked[3]{};
bool held = false;
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.
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;
}
}
~inheritance() {
for (int i = 2; i >= 0; --i)
if (marked[i]) SetHandleInformation(handle[i], HANDLE_FLAG_INHERIT, before[i]);
if (held) ReleaseSRWLockExclusive(&g_starting);
}
};
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;
}
bool append_quoted(wchar_t* out, okw_uptr cap, okw_uptr& at, const wchar_t* s, okw_uptr 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; }
if (s[i] == L'"') {
for (okw_uptr k = 0; k <= backslashes; ++k)
if (!append_wide(out, cap, at, L"\\", 1)) return false;
backslashes = 0;
} else {
backslashes = 0;
}
// The run of separators preceding this character is emitted with it.
if (at + 1 >= cap) return false;
if (s[i] == L'"') { out[at++] = L'"'; continue; }
out[at++] = s[i];
}
for (okw_uptr k = 0; k < backslashes; ++k)
if (!append_wide(out, cap, at, L"\\", 1)) return false;
return append_wide(out, cap, at, L"\"", 1);
}
} // namespace
extern "C" {
// Starting a program. One function since openkal 0.11.
//
// ⚠️ TWO POSITIONS IN `kal_spawn' ARE REFUSED HERE, AND EACH REFUSAL
// IS OLDER THAN 0.11 --- the record moved, the answers did not.
//
// `grants': this environment has no numbering a preopen could arrive under, so
// there is no correspondence to descriptor three. KAL_PROCESS_PROP_GRANT_DIR is
// not claimed. A count of zero asks for a program with no preopens, which is
// what a program here gets anyway, so that request IS answerable and is
// answered.
//
// `KAL_SPAWN_BOUND_LIFETIME': no primitive arms it from inside the started image.
//
// ⭐⭐ 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;
if (how->grant_count > 0) return kal_err_not_supported;
if (how->flags != 0) return kal_err_not_supported;
// 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];
wchar_t block[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;
// 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;
// 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 = line + used + 1; // beyond what is written
const okw_uptr room = kCommandLine - used - 2;
if (!append_utf8(one, room, produced, argv[i], argv_lens[i]))
return argv_lens[i] >= room ? kal_err_no_space : kal_err_invalid;
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.
wchar_t* block = work->block;
okw_uptr block_used = 0;
for (kal_uintptr i = 0; i < envc; ++i) {
if (!append_utf8(block, kCommandLine, block_used, envp[i], envp_lens[i]))
return kal_err_no_space;
block[block_used++] = 0;
}
block[block_used++] = 0;
STARTUPINFOW startup{};
startup.cb = sizeof startup;
bool inherit = false;
if (streams && (streams->in.h || streams->out.h || streams->err.h)) {
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdInput = streams->in.h ? reinterpret_cast<void*>(streams->in.h)
: GetStdHandle(STD_INPUT_HANDLE);
startup.hStdOutput = streams->out.h ? reinterpret_cast<void*>(streams->out.h)
: GetStdHandle(STD_OUTPUT_HANDLE);
startup.hStdError = streams->err.h ? reinterpret_cast<void*>(streams->err.h)
: GetStdHandle(STD_ERROR_HANDLE);
inherit = true;
}
PROCESS_INFORMATION info{};
BOOL started = FALSE;
DWORD refusal = 0;
{
inheritance window(inherit, startup.hStdInput, startup.hStdOutput, startup.hStdError);
started = CreateProcessW(image, argc ? line : nullptr, nullptr, nullptr,
inherit ? TRUE : FALSE,
CREATE_UNICODE_ENVIRONMENT,
envc ? block : nullptr, cwd, &startup, &info);
// Read before the handles are put back, which may set it again.
if (!started) refusal = GetLastError();
}
if (!started) return okw::translate_win32(refusal);
CloseHandle(info.hThread);
// ⚠️ 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.hProcess);
return okw::translate_win32(why);
}
// 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 = kal_process{ okw::pack(info.hProcess) };
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;
BOOL OKW_API stop_routine(DWORD) {
g_stop_word = 1;
WakeByAddressAll(&g_stop_word);
return FALSE;
}
} // namespace
// ⚠️ Armed on the first enquiry, so that adding this operation changes nothing
// for a program that does not use it.
const kal_u32* kal_process_stop_requested(void) {
if (!g_stop_armed) { g_stop_armed = 1; SetConsoleCtrlHandler(stop_routine, TRUE); }
return &g_stop_word;
}
// This program itself joins or forms a unit. ⭐ NATURAL HERE TOO, and by the
// route this environment already offers: a job object is created before it has
// members, so the caller simply becomes its first one.
int kal_process_job_enter(kal_job* j) {
if (j == nullptr) return kal_err_invalid;
HANDLE unit = nullptr;
bool made = false;
if (j->h != 0) {
unit = okw::unpack(j->h);
if (!unit) return kal_err_invalid;
} else {
unit = CreateJobObjectW(nullptr, nullptr);
if (!unit) return okw::translate_win32(GetLastError());
made = true;
}
if (!AssignProcessToJobObject(unit, GetCurrentProcess())) {
const DWORD why = GetLastError();
if (made) CloseHandle(unit);
return okw::translate_win32(why);
}
if (made) j->h = okw::pack(unit);
return kal_ok;
}
// Every program in the unit. A job ends its members as one, which is the whole
// reason this environment's job object is the right thing to build a unit from.
int kal_process_job_terminate(kal_job j) {
HANDLE h = okw::unpack(j.h);
if (!h) return kal_err_invalid;
if (!TerminateJobObject(h, 15)) return okw::translate_win32(GetLastError());
return kal_ok;
}
// ⚠️ RELEASES AND DOES NOT END. The limit that would have ended the members on
// the last close is deliberately not set --- see the note above kal_process_spawn.
void kal_process_job_close(kal_job j) {
HANDLE h = okw::unpack(j.h);
if (h) { okw::retire(j.h); CloseHandle(h); }
}
// A channel: a pair of streams of which one end is meant to cross a spawn.
//
// ⚠️⚠️ NEITHER END IS INHERITABLE, AND UNTIL 0.7.2 THE FAR ONE WAS FROM THE MOMENT
// IT WAS CREATED.
//
// This environment decides inheritance per handle, and a start with inheritance
// enabled gives the program every inheritable handle of this process. A far end
// created inheritable therefore reached every program started while it existed:
// the program at the other end of the channel, when the channel carried that
// program's input --- it then held the writing end of its own input and never
// observed the end of it, so a parent that wrote and closed waited for ever for a
// child still reading --- and any program another context started meanwhile,
// which kept the pipe open after the program it belonged to had ended.
//
// The two descriptor implementations create both ends close-on-exec for exactly
// that reason and let the spawn place the far one deliberately, and this one now
// does the same: `kal_process_spawn' marks the handles it places for the length of
// the start (see `inheritance' above).
int kal_process_channel(kal_stream* mine, kal_stream* theirs) {
if (mine == nullptr || theirs == nullptr) return kal_err_invalid;
SECURITY_ATTRIBUTES sa{};
sa.nLength = sizeof sa;
sa.bInheritHandle = FALSE;
HANDLE reading = nullptr, writing = nullptr;
if (!CreatePipe(&reading, &writing, &sa, 0))
return okw::translate_win32(GetLastError());
// Bare handles rather than packed ones, because openkal.stream's transfer
// operations take what this environment takes. kal_fs_stream reports a
// file's stream the same way and for the same reason.
*mine = kal_stream{ reinterpret_cast<kal_uintptr>(reading) };
*theirs = kal_stream{ reinterpret_cast<kal_uintptr>(writing) };
return kal_ok;
}
void kal_process_channel_close(kal_stream s) {
void* h = reinterpret_cast<void*>(s.h);
if (h == nullptr || h == INVALID_HANDLE_VALUE) return;
// The standard streams are borrowed. Closing one through this operation
// would take a stream away from the whole program.
if (h == GetStdHandle(STD_INPUT_HANDLE) ||
h == GetStdHandle(STD_OUTPUT_HANDLE) ||
h == GetStdHandle(STD_ERROR_HANDLE)) return;
CloseHandle(h);
}
int kal_process_wait(kal_process p, int* status, int* terminated) {
void* h = okw::unpack(p.h);
if (!h) return kal_err_invalid;
if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0)
return okw::translate_win32(GetLastError());
DWORD code = 0;
if (!GetExitCodeProcess(h, &code)) return okw::translate_win32(GetLastError());
// This environment reports one number and does not say whether the program
// chose it. A program terminated by the environment is given the number the
// terminating call supplied, and this implementation supplies one that is
// not an ordinary status --- so the two remain distinguishable, which is
// what the interface requires and all that it requires.
if (code == 0xC0000409u || code == 0xFFFFFFFFu) {
if (status) *status = static_cast<int>(code & 0x7fffffff);
if (terminated) *terminated = 1;
} else {
if (status) *status = static_cast<int>(code);
if (terminated) *terminated = 0;
}
return kal_ok;
}
int kal_process_terminate(kal_process p) {
void* h = okw::unpack(p.h);
if (!h) return kal_err_invalid;
return TerminateProcess(h, 0xFFFFFFFFu) ? kal_ok : okw::translate_win32(GetLastError());
}
// Releasing the handle does not affect the program: this environment keeps the
// program alive independently of who holds a handle to it.
void kal_process_close(kal_process p) {
void* h = okw::unpack(p.h);
if (h) { okw::retire(p.h); CloseHandle(h); }
}
// ⚠️ THREE POSITIONS ARE DELIBERATELY ABSENT, AND EACH IS ABSENT BECAUSE THE
// NEXT CALL REFUSES IT. A word claiming a facility the operation then declines is
// the disagreement clause 6.2 exists to prevent, so the two are written together
// and read together:
//
// GRANT_DIR kal_process_spawn refuses a non-empty `grants'
// BOUND_LIFETIME no primitive arms it from inside the started image
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_JOB; }
}