@@ -56,35 +56,95 @@ struct vector {
5656};
5757
5858constexpr okm_long nr_fchdir = 13 ;
59+ // openkal 0.11: a started program that forms a job of its own.
60+ constexpr okm_long nr_setpgid = 82 ;
61+ constexpr okm_long nr_getpgid = 151 ;
5962
6063} // namespace
6164
6265extern " C" {
6366
64- int kal_process_spawn (kal_dir base,
67+ // Starting a program. One function since openkal 0.11, where three declarations
68+ // became one and their modifiers became positions in `kal_spawn'.
69+ //
70+ // ⚠️⚠️ AND THIS IMPLEMENTATION IS WHERE THE MISSING MODIFIER WAS ALREADY VISIBLE,
71+ // which is worth recording rather than quietly fixing.
72+ //
73+ // This kernel has no `execveat', so a program named relative to a directory has
74+ // always been started by entering that directory first --- the `fchdir(base)'
75+ // below used to be the whole story. So a started program's working directory WAS
76+ // `base' here, and on the other kernel it was whatever that implementation
77+ // happened to be in. ⭐ Same openkal calls, two different observable answers,
78+ // and neither was wrong because the specification said nothing.
79+ //
80+ // ⇒ 0.11 gives the caller a second directory, and both implementations now enter
81+ // the one the caller named. The divergence is gone because the thing that caused
82+ // it --- a property nobody had declared --- is declared.
83+ int kal_process_spawn (const kal_spawn* how,
6584 const char * path, kal_uintptr path_len,
6685 const char ** argv, const kal_uintptr* argv_lens, kal_uintptr argc,
6786 const char ** envp, const kal_uintptr* envp_lens, kal_uintptr envc,
6887 const kal_spawn_streams* streams,
6988 kal_process* out) {
70- const int b = okm::unpack (base.h );
71- if (b < 0 || out == nullptr ) return kal_err_invalid;
89+ if (how == nullptr || out == nullptr ) return kal_err_invalid;
90+
91+ const int b = okm::unpack (how->base .h );
92+ const int w = okm::unpack (how->work .h );
93+ if (b < 0 || w < 0 ) return kal_err_invalid;
7294 if (!okm::acceptable (path, path_len)) return kal_err_invalid;
95+ if (how->grant_count > 0 && how->grants == nullptr ) return kal_err_invalid;
96+
97+ // ⚠️ A LIFETIME THIS KERNEL CANNOT BIND IS REFUSED BEFORE ANYTHING STARTS.
98+ // The reason is unchanged from 0.10 and is stated at kal_process_props: the
99+ // binding must hold however the caller ends, including when it is killed
100+ // outright, and what this system offers instead is a WATCH, which needs a
101+ // live context to notice. Composing it would move the defect from "refused"
102+ // to "works except when it matters".
103+ constexpr kal_uintptr can = KAL_SPAWN_OWN_JOB ;
104+ if (how->flags & ~can) return kal_err_not_supported;
105+
73106 okm::terminated p (path, path_len);
74107 if (!p.ok ) return kal_err_invalid;
75108
76- // The vector is passed unaltered. Clause 7.6: argv[0] is the name the
77- // started program observes as its own, and it is the caller's to choose ---
78- // the started program reads it through kal_env_arg(0), so a caller that did
79- // not supply it could not predict what the program would read.
80109 vector args, envs;
81110 if (!args.build (argv, argv_lens, argc)) return kal_err_no_memory;
82111 if (!envs.build (envp, envp_lens, envc)) return kal_err_no_memory;
83112
113+ constexpr kal_uintptr max_grants = 16 ;
114+ if (how->grant_count > max_grants) return kal_err_invalid;
115+ int granted[max_grants];
116+ for (kal_uintptr i = 0 ; i < how->grant_count ; ++i) {
117+ granted[i] = okm::unpack (how->grants [i].dir .h );
118+ if (granted[i] < 0 ) return kal_err_invalid;
119+ }
120+
121+ // ⭐ THE PROGRAM'S NAME IS MADE ABSOLUTE BEFORE THE DIRECTORY MOVES, because
122+ // with no `execveat' the two things `base' and `work' now mean cannot both be
123+ // served by one `fchdir'. `F_GETPATH' answers the path of an open directory,
124+ // which src/fs.cpp already relies on for the same reason: this kernel has no
125+ // call that reports a working directory, so a path is obtained from the
126+ // descriptor that names it.
127+ char whole[1024 ];
128+ kal_uintptr n = 0 ;
129+ if (p.buf [0 ] == ' /' ) {
130+ while (p.buf [n] && n < sizeof whole - 1 ) { whole[n] = p.buf [n]; ++n; }
131+ } else {
132+ const okm_long r = okm::sys (okm::nr_fcntl, b, okm::f_getpath,
133+ reinterpret_cast <okm_long>(whole));
134+ if (okm::failed (r)) return okm::translate (r);
135+ while (whole[n] && n < sizeof whole - 1 ) ++n;
136+ if (n && whole[n - 1 ] != ' /' && n < sizeof whole - 1 ) whole[n++] = ' /' ;
137+ for (kal_uintptr i = 0 ; p.buf [i] && n < sizeof whole - 1 ; ++i) whole[n++] = p.buf [i];
138+ }
139+ if (n >= sizeof whole - 1 ) return kal_err_invalid;
140+ whole[n] = ' \0 ' ;
141+
84142 const okm_long in = streams ? static_cast <okm_long>(streams->in .h ) : 0 ;
85143 const okm_long ou = streams ? static_cast <okm_long>(streams->out .h ) : 0 ;
86144 const okm_long er = streams ? static_cast <okm_long>(streams->err .h ) : 0 ;
87145
146+ const bool job = (how->flags & KAL_SPAWN_OWN_JOB ) != 0 ;
147+
88148 bool is_duplicate = false ;
89149 const okm_long child = okm::duplicate (is_duplicate);
90150 if (okm::failed (child)) return okm::translate (child);
@@ -97,8 +157,19 @@ int kal_process_spawn(kal_dir base,
97157 if (in != 0 ) okm::sys (okm::nr_dup2, in, 0 );
98158 if (ou != 0 ) okm::sys (okm::nr_dup2, ou, 1 );
99159 if (er != 0 ) okm::sys (okm::nr_dup2, er, 2 );
100- okm::sys (nr_fchdir, b);
101- okm::sys (okm::nr_execve, reinterpret_cast <okm_long>(p.buf ),
160+
161+ for (kal_uintptr i = 0 ; i < how->grant_count ; ++i) {
162+ const okm_long want = static_cast <okm_long>(3 + i);
163+ if (granted[i] != want) okm::sys (okm::nr_dup2, granted[i], want);
164+ }
165+
166+ // The directory the program RUNS in --- `whole' already carries where it
167+ // is named from, so this no longer has to serve both.
168+ okm::sys (nr_fchdir, w);
169+
170+ if (job) okm::sys (nr_setpgid, 0 , 0 );
171+
172+ okm::sys (okm::nr_execve, reinterpret_cast <okm_long>(whole),
102173 reinterpret_cast <okm_long>(args.slots ),
103174 reinterpret_cast <okm_long>(envs.slots ));
104175 for (;;) okm::sys (okm::nr_exit, 127 );
@@ -149,97 +220,22 @@ void kal_process_channel_close(kal_stream s) {
149220}
150221
151222// Starting a program that receives exactly the directories named.
152- //
153- // The grants are placed as descriptors three and upward, which is where
154- // kal_fs_preopen reads them back from. The inverse relationship clause 7.11
155- // describes is between those two operations, which is why they must agree about
156- // the numbering rather than each choosing one.
157- int kal_process_spawn_with (kal_dir base,
158- const char * path, kal_uintptr path_len,
159- const char ** argv, const kal_uintptr* argv_lens, kal_uintptr argc,
160- const char ** envp, const kal_uintptr* envp_lens, kal_uintptr envc,
161- const kal_spawn_streams* streams,
162- const kal_preopen* grants, kal_uintptr grant_count,
163- kal_process* out) {
164- const int b = okm::unpack (base.h );
165- if (b < 0 || out == nullptr ) return kal_err_invalid;
166- if (!okm::acceptable (path, path_len)) return kal_err_invalid;
167- if (grant_count > 0 && grants == nullptr ) return kal_err_invalid;
168- okm::terminated p (path, path_len);
169- if (!p.ok ) return kal_err_invalid;
170-
171- vector args, envs;
172- if (!args.build (argv, argv_lens, argc)) return kal_err_no_memory;
173- if (!envs.build (envp, envp_lens, envc)) return kal_err_no_memory;
174-
175- // Resolved before the duplication, because a failure after it would leave a
176- // child to be reaped and a caller holding an error it cannot act upon.
177- constexpr kal_uintptr max_grants = 16 ;
178- if (grant_count > max_grants) return kal_err_invalid;
179- int granted[max_grants];
180- for (kal_uintptr i = 0 ; i < grant_count; ++i) {
181- granted[i] = okm::unpack (grants[i].dir .h );
182- if (granted[i] < 0 ) return kal_err_invalid;
183- }
184-
185- const okm_long in = streams ? static_cast <okm_long>(streams->in .h ) : 0 ;
186- const okm_long ou = streams ? static_cast <okm_long>(streams->out .h ) : 0 ;
187- const okm_long er = streams ? static_cast <okm_long>(streams->err .h ) : 0 ;
188-
189- bool is_duplicate = false ;
190- const okm_long child = okm::duplicate (is_duplicate);
191- if (okm::failed (child)) return okm::translate (child);
192-
193- if (is_duplicate) {
194- if (in != 0 ) okm::sys (okm::nr_dup2, in, 0 );
195- if (ou != 0 ) okm::sys (okm::nr_dup2, ou, 1 );
196- if (er != 0 ) okm::sys (okm::nr_dup2, er, 2 );
197-
198- // dup2 onto the same number succeeds and does nothing, unlike dup3,
199- // which refuses. Either behaviour is right for this loop; only the
200- // reason differs, and it is stated so that a reader comparing the two
201- // implementations does not take one of them for an oversight.
202- for (kal_uintptr i = 0 ; i < grant_count; ++i)
203- okm::sys (okm::nr_dup2, granted[i], static_cast <okm_long>(3 + i));
204-
205- okm::sys (nr_fchdir, b);
206- okm::sys (okm::nr_execve, reinterpret_cast <okm_long>(p.buf ),
207- reinterpret_cast <okm_long>(args.slots ),
208- reinterpret_cast <okm_long>(envs.slots ));
209- for (;;) okm::sys (okm::nr_exit, 127 );
210- }
211-
212- *out = kal_process{ static_cast <kal_uintptr>(child) };
213- return kal_ok;
214- }
215-
216- int kal_process_wait (kal_process h, int * status, int * terminated_by_environment) {
217- if (h.h == 0 ) return kal_err_invalid;
218- int st = 0 ;
219- for (;;) {
220- const okm_long r = okm::sys (okm::nr_wait4, static_cast <okm_long>(h.h ),
221- reinterpret_cast <okm_long>(&st), 0 , 0 );
222- if (okm::interrupted (r)) continue ;
223- if (okm::failed (r)) return okm::translate (r);
224- break ;
225- }
226- // The encoding is the kernel's: the low seven bits name the signal that
227- // ended the program and are zero when it ended by returning, in which case
228- // the next eight bits are what it returned.
229- const int signalled = st & 0x7f ;
230- if (signalled == 0 ) {
231- if (status) *status = (st >> 8 ) & 0xff ;
232- if (terminated_by_environment) *terminated_by_environment = 0 ;
233- } else {
234- if (status) *status = signalled;
235- if (terminated_by_environment) *terminated_by_environment = 1 ;
236- }
237- return kal_ok;
238- }
239223
224+ // ⭐ REACHES THE WHOLE JOB WHEN THERE IS ONE, AND THE HANDLE CARRIES NOTHING TO
225+ // SAY SO. A program started with KAL_SPAWN_OWN_JOB called `setpgid(0, 0)', so its
226+ // group identifier is its own; one started without it inherited this
227+ // implementation's, which is some other process. `getpgid(pid) == pid'
228+ // distinguishes them exactly.
229+ //
230+ // ⚠️ Without this the flag would do nothing a caller could see: forming the job
231+ // matters only because terminating then reaches what the started program itself
232+ // started.
240233int kal_process_terminate (kal_process h) {
241234 if (h.h == 0 ) return kal_err_invalid;
242- const okm_long r = okm::sys (okm::nr_kill, static_cast <okm_long>(h.h ), 15 /* SIGTERM */ );
235+ const okm_long pid = static_cast <okm_long>(h.h );
236+ const okm_long pgid = okm::sys (nr_getpgid, pid);
237+ const okm_long target = (!okm::failed (pgid) && pgid == pid) ? -pid : pid;
238+ const okm_long r = okm::sys (okm::nr_kill, target, 15 /* SIGTERM */ );
243239 return okm::failed (r) ? okm::translate (r) : kal_ok;
244240}
245241
@@ -249,32 +245,11 @@ void kal_process_close(kal_process) { }
249245
250246// Starting a program whose lifetime is bound to this one's. Version 0.10.
251247//
252- // ⚠️⚠️ REFUSED HERE, AND THE REFUSAL IS THE HONEST ANSWER RATHER THAN A GAP TO
253- // FILL LATER WITH SOMETHING THAT LOOKS LIKE IT.
254- //
255- // The binding openkal describes has to hold however the caller ends, including
256- // when it is killed outright --- and this system has no primitive that arms it
257- // from inside the started image. The other kernel does, in one call.
258- //
259- // ⚠️ What this system offers instead is a WATCH: a context here can be told when
260- // another ends and can then act. That is not the same thing and must not be
261- // offered as it. A watch needs a live context to notice, so a caller that is
262- // killed outright notices nothing and the started program survives --- which is
263- // precisely the failure the operation exists to remove. Composing it would move
264- // the defect from "refused" to "works except when it matters".
265- //
266- // ⇒ `KAL_PROCESS_PROP_BOUND_LIFETIME' is not claimed, and a caller that asks
267- // first is told before it depends on it.
268- int kal_process_spawn_bound (kal_dir, const char *, kal_uintptr,
269- const char **, const kal_uintptr*, kal_uintptr,
270- const char **, const kal_uintptr*, kal_uintptr,
271- const kal_spawn_streams*, kal_process*) {
272- return kal_err_not_supported;
273- }
274248
275249kal_uintptr kal_process_props (void ) { return
276250 KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING
277251 | KAL_PROCESS_PROP_EXIT_STATUS
278- | KAL_PROCESS_PROP_CHANNEL | KAL_PROCESS_PROP_GRANT_DIR ; }
252+ | KAL_PROCESS_PROP_CHANNEL | KAL_PROCESS_PROP_GRANT_DIR
253+ | KAL_PROCESS_PROP_OWN_JOB ; }
279254
280255}
0 commit comments