Skip to content

Commit 2db18a6

Browse files
committed
Provide the three operations openkal 0.8 adds to openkal.process
ADDING TO AN EXISTING INTERFACE OBLIGES EVERY IMPLEMENTATION OF IT, and adding a new interface obliges none. Clause 6.1 makes an interface a backend does not provide absent at the link and not a deviation; it makes one provided IN PART a deviation. The five interfaces version 0.8 adds are therefore free to decline, and the three names added to openkal.process are not. The specification's own surface checker said so before anything else noticed: openkal.process is provided in part: 3 of 8 names are not exported -- kal_process_channel kal_process_channel_close kal_process_spawn_with kal_process_channel is CreatePipe. THIS ENVIRONMENT DECIDES INHERITANCE PER HANDLE AND NOT PER EXEC, which is the opposite of the other two: there every handle is inherited unless marked otherwise, so those implementations mark both ends close-on-exec and let the spawn place the far one. Here the default is not to inherit, so the far end is created inheritable and the near end is withdrawn afterwards --- otherwise the started program would hold both ends and the writer would never observe the end of input. kal_process_spawn_with refuses a non-empty set of grants, and the refusal is the honest answer rather than a gap. A preopened directory is a handle a started program reads back by NUMBER, and this environment has no numbering: a handle crosses a spawn by being inheritable, and the started program learns of it through a mechanism the parent arranges. There is no correspondence to descriptor three. Clause 6.2 is what makes that conforming: the operation exists, reports kal_err_not_supported, and the property word does not claim KAL_PROCESS_PROP_GRANT_DIR --- so a caller learns from the word what it would otherwise learn from a failed call. A count of zero is still answered, by the ordinary spawn, because a program with no preopens is what this environment starts anyway.
1 parent 58b9e17 commit 2db18a6

2 files changed

Lines changed: 83 additions & 1 deletion

File tree

src/process.cpp

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,81 @@ int kal_process_spawn(kal_dir base,
177177
return kal_ok;
178178
}
179179

180+
// A channel: a pair of streams of which one end is meant to cross a spawn.
181+
//
182+
// THIS ENVIRONMENT DECIDES INHERITANCE PER HANDLE AND NOT PER EXEC, which is the
183+
// opposite of the other two and is why the far end is created inheritable while
184+
// the near end is not. On a descriptor system every handle is inherited unless
185+
// marked otherwise, so those implementations mark the ends close-on-exec and let
186+
// the spawn place the far one deliberately. Here the default is not to inherit,
187+
// so the far end must be marked to be inheritable and the near end must be left
188+
// alone --- otherwise the started program would hold both ends and the writer
189+
// would never observe the end of input.
190+
int kal_process_channel(kal_stream* mine, kal_stream* theirs) {
191+
if (mine == nullptr || theirs == nullptr) return kal_err_invalid;
192+
193+
SECURITY_ATTRIBUTES sa{};
194+
sa.nLength = sizeof sa;
195+
sa.bInheritHandle = TRUE;
196+
197+
HANDLE reading = nullptr, writing = nullptr;
198+
if (!CreatePipe(&reading, &writing, &sa, 0))
199+
return okw::translate_win32(GetLastError());
200+
201+
// The near end is withdrawn from inheritance after the fact, because
202+
// CreatePipe applies one set of attributes to both.
203+
SetHandleInformation(reading, HANDLE_FLAG_INHERIT, 0);
204+
205+
// Bare handles rather than packed ones, because openkal.stream's transfer
206+
// operations take what this environment takes. kal_fs_stream reports a
207+
// file's stream the same way and for the same reason.
208+
*mine = kal_stream{ reinterpret_cast<kal_uintptr>(reading) };
209+
*theirs = kal_stream{ reinterpret_cast<kal_uintptr>(writing) };
210+
return kal_ok;
211+
}
212+
213+
void kal_process_channel_close(kal_stream s) {
214+
void* h = reinterpret_cast<void*>(s.h);
215+
if (h == nullptr || h == INVALID_HANDLE_VALUE) return;
216+
// The standard streams are borrowed. Closing one through this operation
217+
// would take a stream away from the whole program.
218+
if (h == GetStdHandle(STD_INPUT_HANDLE) ||
219+
h == GetStdHandle(STD_OUTPUT_HANDLE) ||
220+
h == GetStdHandle(STD_ERROR_HANDLE)) return;
221+
CloseHandle(h);
222+
}
223+
224+
// Starting a program that receives exactly the directories named.
225+
//
226+
// ⚠️ NOT PROVIDED, AND THE REFUSAL IS THE HONEST ANSWER RATHER THAN A GAP. A
227+
// preopened directory is a handle a started program reads back through
228+
// kal_fs_preopen by NUMBER, and this environment has no numbering: a handle
229+
// crosses a spawn by being inheritable, and the started program learns of it
230+
// through a mechanism the parent has to arrange itself. There is no
231+
// correspondence here to descriptor three.
232+
//
233+
// Clause 6.2 is what makes the refusal conforming rather than a deviation: the
234+
// operation exists, reports kal_err_not_supported, and the property word does
235+
// not claim KAL_PROCESS_PROP_GRANT_DIR. A caller therefore learns from the word
236+
// what it would otherwise learn from a failed call.
237+
int kal_process_spawn_with(kal_dir base,
238+
const char* path, kal_uintptr path_len,
239+
const char** argv, const kal_uintptr* argv_lens, kal_uintptr argc,
240+
const char** envp, const kal_uintptr* envp_lens, kal_uintptr envc,
241+
const kal_spawn_streams* streams,
242+
const kal_preopen* grants, kal_uintptr grant_count,
243+
kal_process* out) {
244+
// A count of zero asks for a program with no preopens, which this
245+
// environment gives a started program anyway --- it has none to pass. That
246+
// request is therefore answerable, and is answered by the ordinary spawn.
247+
if (grant_count == 0)
248+
return kal_process_spawn(base, path, path_len,
249+
argv, argv_lens, argc,
250+
envp, envp_lens, envc, streams, out);
251+
(void)grants;
252+
return kal_err_not_supported;
253+
}
254+
180255
int kal_process_wait(kal_process p, int* status, int* terminated) {
181256
void* h = okw::unpack(p.h);
182257
if (!h) return kal_err_invalid;
@@ -212,8 +287,12 @@ void kal_process_close(kal_process p) {
212287
if (h) { okw::retire(p.h); CloseHandle(h); }
213288
}
214289

290+
// KAL_PROCESS_PROP_GRANT_DIR is deliberately absent: kal_process_spawn_with
291+
// refuses a non-empty set of grants here, and a word claiming a facility the
292+
// next call refuses is the disagreement clause 6.2 exists to prevent.
215293
const kal_uintptr kal_process_props =
216294
KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING
217-
| KAL_PROCESS_PROP_EXIT_STATUS;
295+
| KAL_PROCESS_PROP_EXIT_STATUS
296+
| KAL_PROCESS_PROP_CHANNEL;
218297

219298
}

src/win32.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ OKW_IMPORT BOOL OKW_API CloseHandle(HANDLE);
244244
OKW_IMPORT DWORD OKW_API GetLastError(void);
245245
OKW_IMPORT DWORD OKW_API GetFileType(HANDLE);
246246
OKW_IMPORT BOOL OKW_API SetHandleInformation(HANDLE, DWORD, DWORD);
247+
// For kal_process_channel. The security attributes decide whether the ends are
248+
// inheritable, which is what makes one of them able to cross a spawn.
249+
OKW_IMPORT BOOL OKW_API CreatePipe(HANDLE*, HANDLE*, SECURITY_ATTRIBUTES*, DWORD);
247250
OKW_IMPORT BOOL OKW_API GetConsoleMode(HANDLE, DWORD*);
248251
OKW_IMPORT BOOL OKW_API SetConsoleMode(HANDLE, DWORD);
249252

0 commit comments

Comments
 (0)