feat: add jobctl
This commit is contained in:
1 parent
afbefed8da
commit
0bdc393f39
1 file changed
+148
@@ -0,0 +1,148 @@
|
||||
#ifndef FROSTBYTE_JOBCTL_H
|
||||
#define FROSTBYTE_JOBCTL_H
|
||||
|
||||
#include "types.h"
|
||||
|
||||
/*
|
||||
* Generic "run a binary by path, with real job control" helpers, callable
|
||||
* only from plain kernel-thread context (shell.c's shellRun, or a boot-time
|
||||
* demo thread in kmain.c) -- never from inside a syscall's interrupt-gate
|
||||
* handler, exactly like keyboardReaderReadBlocking/pipeReadBlocking's own
|
||||
* restriction, since jobctlWaitForeground below parks with threadJoin's
|
||||
* real halt() loop.
|
||||
*
|
||||
* Unlike syscall.c's doSpawn (sysSpawn's underlying implementation, used by
|
||||
* one ring-3 process to spawn another), a job started here always becomes
|
||||
* its OWN new process group leader (threadSetPgid(pid, pid)) regardless of
|
||||
* the caller's own pgid -- matching a real shell's fork()+setpgid(child,
|
||||
* child) convention for every new job/pipeline, not doSpawn's default of
|
||||
* inheriting the parent's group (right for a process spawning a plain
|
||||
* worker of its own, wrong for an interactive shell launching a new
|
||||
* foreground/background command). argv is already kernel memory here (the
|
||||
* caller IS the kernel, not a ring-3 process going through a syscall), so
|
||||
* neither function copies or range-checks it the way copyUserArgv/
|
||||
* userRangeOk (syscall.c) must for a user-supplied pointer.
|
||||
*/
|
||||
|
||||
/* Loads `path` and spawns it as a new process group leader, hands it "the
|
||||
terminal" (keyboardSetForegroundPgid, keyboard.h) immediately, and
|
||||
returns its pid without waiting -- split from the actual wait
|
||||
(jobctlWaitForeground below) so a caller can learn the pid the instant
|
||||
the job exists, not only after it has already exited (shell.c's own
|
||||
background job table needs the same immediacy from
|
||||
jobctlRunBackground below, so this mirrors it instead of hiding the pid
|
||||
inside a single combined call). Returns threadInvalidId, touching the
|
||||
terminal not at all, if `path` cannot be loaded (missing from the VFS,
|
||||
malformed ELF, or the thread table is full). */
|
||||
uint32 jobctlSpawnForeground(const char* path, const char* const* argv, int argc);
|
||||
|
||||
/* Blocks (threadJoin) until the pid jobctlSpawnForeground just returned
|
||||
exits, then reclaims the terminal for the CALLING thread's own group --
|
||||
deliberately not "whatever pgid owned the terminal right before
|
||||
jobctlSpawnForeground ran" (which could itself be some earlier, now-dead
|
||||
job's stale id): a real shell always gets its own prompt's terminal back
|
||||
when a foreground job quits, so a later Ctrl-C typed at the prompt
|
||||
reaches the shell's NEXT job, not a group nothing will ever answer for
|
||||
again. Returns the job's real exit code, same contract as threadJoin
|
||||
itself. */
|
||||
int64 jobctlWaitForeground(uint32 pid);
|
||||
|
||||
/* Loads `path` and spawns it as a new process group leader exactly like
|
||||
jobctlSpawnForeground, but never touches the foreground pgid at all and
|
||||
returns the new pid immediately without waiting -- a real shell's `&`
|
||||
suffix. The caller is responsible for eventually reaping it (e.g.
|
||||
shell.c's own background job table, reaped opportunistically via
|
||||
threadJoinAnyChild). Returns false (touching *outPid not at all) on the
|
||||
same load/spawn failures as jobctlSpawnForeground. */
|
||||
bool jobctlRunBackground(const char* path, const char* const* argv, int argc,
|
||||
uint32* outPid);
|
||||
|
||||
/* Generalizes jobctlSpawnForeground/jobctlRunBackground's shared "load and
|
||||
spawn" step for shell.c's new `cmd1 | cmd2` pipeline syntax: `stdinHandle`/
|
||||
`stdoutHandle` are raw pipe.h handles from vfsPipeCreateHandle (vfs.h),
|
||||
baked into the new process's own fd 0/fd 1 (vfsPipeAttachInto) BEFORE it
|
||||
ever runs a single instruction -- -1 for either one leaves that fd at its
|
||||
normal default (the real console), exactly like an unpiped command's own
|
||||
stdin/stdout. `joinPgid` is threadInvalidId for "become a new group
|
||||
leader" (jobctlSpawn's existing default, what every other caller here
|
||||
still wants) or an already-live pgid to join instead -- a pipeline's
|
||||
second-and-later stage joins its first stage's own new group so a single
|
||||
Ctrl-C reaches every stage at once, exactly like a real shell's pipeline
|
||||
job. Already fully general for a MIDDLE stage of a longer pipeline too:
|
||||
`stdinHandle`/`stdoutHandle` are independent, so a stage can have both a
|
||||
piped stdin AND a piped stdout at once (shell.c's runPipelineStages does
|
||||
exactly this for every interior stage of a 3+-stage pipeline) -- the only
|
||||
things that ever needed generalizing for that were shell.c's own
|
||||
parsing/orchestration and jobctlWaitPipelineStages below, never this
|
||||
function. Never touches the terminal itself: shell.c's own pipeline
|
||||
runner grants and reclaims it once, for the pipeline as a whole, via
|
||||
jobctlWaitPipelineStages below -- not per stage. Returns threadInvalidId
|
||||
on the same load/spawn failures as jobctlSpawnForeground. */
|
||||
uint32 jobctlSpawnPiped(const char* path, const char* const* argv, int argc,
|
||||
int stdinHandle, int stdoutHandle, uint32 joinPgid);
|
||||
|
||||
/* Blocks until every one of `pids[0..count-1]` (jobctlSpawnPiped's own
|
||||
return values for a pipeline's stages, `pids[1..count-1]` already joined
|
||||
to `pids[0]`'s group) has exited, then reclaims the terminal for the
|
||||
CALLING thread's own group -- the same two-step contract
|
||||
jobctlWaitForeground has for a single job, just for a whole N-stage
|
||||
pipeline at once (2 <= count <= shellMaxPipelineStages, shell.h). Grants
|
||||
the terminal to `pids[0]`'s group (which every other pid is already a
|
||||
member of) before waiting, exactly like jobctlSpawnForeground already
|
||||
does for a single job, so a Ctrl-C typed while the pipeline runs reaches
|
||||
every stage. threadJoin's own real blocking semantics mean joining every
|
||||
pid in this fixed order is correct regardless of which stage actually
|
||||
exits first. Returns the LAST stage's exit code, matching a real
|
||||
pipeline's own `$?` convention (the last command's status). */
|
||||
int64 jobctlWaitPipelineStages(const uint32* pids, int count);
|
||||
|
||||
/* Installs `inPath`/`outPath`/`errPath` file redirections (vfsOpenInto,
|
||||
vfs.h) into an ALREADY-SPAWNED child's fd 0/fd 1/fd 2 -- shell.c's new
|
||||
`cmd [< IN] [> OUT | >> OUT] [2> ERR | 2>> ERR]` syntax. Any path may be
|
||||
nullptr to leave that fd untouched (still whatever jobctlSpawn/
|
||||
jobctlSpawnPiped already gave it -- the real console by default, or a
|
||||
pipe end for a pipeline stage). `outAppend`/`errAppend` select `>>`/
|
||||
`2>>` (oAppend, never truncates) over `>`/`2>` (oTrunc) when the
|
||||
matching path is non-null; ignored otherwise. fd 0's default console
|
||||
reader is released first, exactly like jobctlSpawnPiped's own stdin
|
||||
override above, so redirecting it never leaks that reader slot; fd 1
|
||||
and fd 2 have no such resource (their own default, fdConsole, owns
|
||||
nothing -- neither is ever handed a reader by vfsFdInit). Exposed as
|
||||
its own function (rather than folded into jobctlSpawnPiped's already-
|
||||
long parameter list) so shell.c's `runPipeline` can layer a file
|
||||
redirect onto exactly one end of a pipeline stage -- the other end
|
||||
already wired to the shared pipe by jobctlSpawnPiped -- without a
|
||||
combinatorial explosion of pipe-handle vs. path parameters on one call.
|
||||
Returns false, leaving the child alive and running with whatever fds it
|
||||
already had, on a bad path (missing without oCreate's implicit
|
||||
create-on-write, unwritable parent, a fifo opened with the wrong mode,
|
||||
exhausted pipe/inode resources) -- the caller decides whether that
|
||||
warrants killing it (every caller in this kernel currently does, via
|
||||
threadKill+threadJoin, exactly like runPipeline already does for a
|
||||
failed second-stage spawn). */
|
||||
bool jobctlApplyRedirects(uint32 pid, const char* inPath, const char* outPath, bool outAppend,
|
||||
const char* errPath, bool errAppend);
|
||||
|
||||
/* Same as jobctlSpawnForeground, but installs `inPath`/`outPath`/`errPath`
|
||||
file redirections (jobctlApplyRedirects above) into the child's fd 0/
|
||||
fd 1/fd 2 BEFORE the terminal is ever granted to it -- shell.c's
|
||||
`NAME [ARGS] [< IN] [> OUT | >> OUT] [2> ERR | 2>> ERR]`. On a redirect
|
||||
failure the half-spawned child is killed and reaped before returning
|
||||
threadInvalidId, exactly as if the load/spawn itself had failed: the
|
||||
terminal must never be granted to a child whose own stdio could not be
|
||||
wired up as asked. */
|
||||
uint32 jobctlSpawnForegroundRedirected(const char* path, const char* const* argv, int argc,
|
||||
const char* inPath, const char* outPath, bool outAppend,
|
||||
const char* errPath, bool errAppend);
|
||||
|
||||
/* Same as jobctlRunBackground, but with the same file-redirection support
|
||||
as jobctlSpawnForegroundRedirected above -- shell.c's
|
||||
`NAME [ARGS] [< IN] [> OUT | >> OUT] [2> ERR | 2>> ERR] &`. Returns
|
||||
false (touching *outPid not at all) on the same load/spawn failures as
|
||||
jobctlRunBackground, or on a redirect failure (the half-spawned child is
|
||||
killed and reaped first, same as the foreground case). */
|
||||
bool jobctlRunBackgroundRedirected(const char* path, const char* const* argv, int argc,
|
||||
const char* inPath, const char* outPath, bool outAppend,
|
||||
const char* errPath, bool errAppend,
|
||||
uint32* outPid);
|
||||
|
||||
Reference in new issue
Block a user