Files
frostbyte/src/jobctl.c
T

162 lines
7.0 KiB
C

#include "jobctl.h"
#include "sched.h"
#include "vfs.h"
#include "fs.h"
#include "elf.h"
#include "paging.h"
#include "keyboard.h"
#include "pipe.h"
#include "string.h"
/* Matches kmain.c's userStackSize and syscall.c's execStackSize -- all
three name the exact same "one slot's worth of stack" constant (elf.h's
elfSlotStackOffset leaves 0x8000000 bytes below the stack top before the
next thing in the slot, brk, starts; nothing in this kernel has ever
needed anywhere near that much real stack). Kept as jobctl.c's own copy
rather than sharing a header-level define with the other two call sites,
the same "each caller of this pattern owns its own named copy" choice
kmain.c and syscall.c already made independently of each other. */
#define jobctlStackSize 0x4000
/* Shared by both functions below: resolves `path` through the VFS, loads
it at its own fixed ELF-linked slot (elfLoad picks the address, same as
syscall.c's loadImageByPath), and spawns it as a brand new process group
leader -- the one behavioral difference from doSpawn (syscall.c), which
leaves a freshly spawned child in its parent's existing group instead.
Returns threadInvalidId, having created nothing, on any failure (missing
binary, no execute permission, bad ELF, stack mapping failure, or a full
thread table). */
static uint32 jobctlSpawn(const char* path, const char* const* argv, int argc)
{
VfsNode* node = vfsLookup(path);
if (!node)
return threadInvalidId;
/* Real EACCES-shaped refusal, same check and same reasoning as
syscall.c's loadImageByPath (sysExec/sysSpawn's own parallel loading
path) -- a file the CALLING thread's identity is not permitted to
exec (vfsPermitted, owner-vs-other-vs-root) can never be spawned this
way either. Collapsed into the same undifferentiated threadInvalidId
failure as every other reason jobctlSpawn already refuses (missing
binary, bad ELF, ...), matching this function's own existing doc
comment. */
if (!vfsPermitted(vfsMode(node), vfsUid(node), vfsGid(node), currentThreadUid(), currentThreadGid(), fsModeOwnerExec))
return threadInvalidId;
const void* image = vfsFileData(node);
uint64 size = vfsSize(node);
uint64 entry = elfLoad(image, size);
if (entry == 0)
return threadInvalidId;
uint64 stackTop = elfSlotStackTop(entry);
if (!pagingMapUserRangeFlags(stackTop - jobctlStackSize, jobctlStackSize, pageNoExecute))
return threadInvalidId;
uint64 initialRsp;
if (!elfBuildInitialStack(stackTop, jobctlStackSize, argv, argc, &initialRsp))
return threadInvalidId;
uint32 pid = threadCreateUser(path, entry, initialRsp);
if (pid == threadInvalidId)
return threadInvalidId;
threadSetBrk(pid, elfSlotBrkStart(entry));
/* A real shell's fork()+setpgid(child, child): every job started
through jobctl gets its own fresh group regardless of the caller's
own pgid, so a later Ctrl-C aimed at THIS job (via
keyboardSetForegroundPgid below) can never also reach some earlier,
unrelated job that happened to share the caller's group. */
threadSetPgid(pid, pid);
return pid;
}
uint32 jobctlSpawnPiped(const char* path, const char* const* argv, int argc,
int stdinHandle, int stdoutHandle, uint32 joinPgid)
{
uint32 pid = jobctlSpawn(path, argv, argc);
if (pid == threadInvalidId)
return threadInvalidId;
/* jobctlSpawn above already made this its own new group leader --
overridden here, after the fact, only for a pipeline's second-and-
later stage, which needs to join the FIRST stage's group instead so
the two are scoped together for signals (threadKillGroup/
sigRaiseGroup, sched.h/signal.h) exactly like a real shell's
fork()+setpgid(child, leaderPid) for every stage after the first. */
if (joinPgid != threadInvalidId)
threadSetPgid(pid, joinPgid);
/* Bakes the requested pipe ends into fd 0/fd 1 of a thread that has not
run a single instruction yet (threadCreateUser, inside jobctlSpawn
above, already set its state Runnable -- see that function's own
field-by-field ordering -- but nothing yields between here and
jobctlSpawn's return, the same timing this file's own
threadSetBrk/threadSetPgid calls already rely on). fd 0's default
console reader (vfsFdInit, vfs.c) is released first so redirecting it
never leaks that reader slot -- exactly the resource reclaimOpenFds
(sched.c) already frees for a thread that dies still holding it,
just done here up front instead of at exit. fd 1 has no such
resource to release: its own default (fdConsole, no reader) owns
nothing. */
FileDescriptor* fds = threadAt(pid)->fds;
if (stdinHandle >= 0) {
keyboardCloseReader(fds[0].pipeHandle);
vfsPipeAttachInto(fds, 0, stdinHandle, false);
}
if (stdoutHandle >= 0)
vfsPipeAttachInto(fds, 1, stdoutHandle, true);
return pid;
}
uint32 jobctlSpawnForeground(const char* path, const char* const* argv, int argc)
{
uint32 pid = jobctlSpawnPiped(path, argv, argc, -1, -1, threadInvalidId);
if (pid == threadInvalidId)
return threadInvalidId;
keyboardSetForegroundPgid(pid);
return pid;
}
int64 jobctlWaitForeground(uint32 pid)
{
/* Read BEFORE threadJoin, not after: this is the WAITER's own group
(the shell, still very much alive), not the job's -- reading it
after threadJoin would still be correct today since nothing else
changes the calling thread's own pgid in between, but reading it
first keeps this function correct even if that ever changes, and
costs nothing. */
uint32 callerPgid = threadPgid(currentThreadId());
int64 code = threadJoin(pid);
/* Reclaim the terminal for the CALLER's own group the instant the job
exits -- deliberately not "whatever pgid happened to own the
terminal right before jobctlSpawnForeground ran" (which could itself
be some earlier, now-dead job's stale id if this is the very first
foreground job the caller has ever run): 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. */
keyboardSetForegroundPgid(callerPgid);
return code;
}
int64 jobctlWaitPipelineStages(const uint32* pids, int count)
{
/* Same "read before threadJoin" reasoning as jobctlWaitForeground
above. */
uint32 callerPgid = threadPgid(currentThreadId());
/* Grants the terminal to the pipeline's shared group (pids[0] IS that
group's id -- jobctlSpawnPiped's own joinPgid contract already made
every other pid a member of it) once, for the whole chain together,
rather than per-spawn -- a Ctrl-C typed while the pipeline runs
reaches every stage at once, exactly like jobctlSpawnForeground
already does for a single job. */
keyboardSetForegroundPgid(pids[0]);