Files
frostbyte/include/jobctl.h
T
2026-09-01 19:11:57 +02:00

149 lines
9.1 KiB
C

#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);