feat: add jobctlRunBackground and jobctlApplyRedirects to jobctl
This commit is contained in:
1 parent
83421e7dba
commit
39528a6ce6
1 file changed
+156
+156
@@ -159,3 +159,159 @@ int64 jobctlWaitPipelineStages(const uint32* pids, int count)
|
|||||||
already does for a single job. */
|
already does for a single job. */
|
||||||
keyboardSetForegroundPgid(pids[0]);
|
keyboardSetForegroundPgid(pids[0]);
|
||||||
|
|
||||||
|
/* threadJoin blocks on a SPECIFIC pid regardless of which stage
|
||||||
|
actually exits first -- joining every one, in this fixed order,
|
||||||
|
reaps all of them correctly no matter the real completion order, the
|
||||||
|
same way a real shell's own wait() for each of a pipeline's known
|
||||||
|
children would. */
|
||||||
|
int64 lastCode = 0;
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
lastCode = threadJoin(pids[i]);
|
||||||
|
|
||||||
|
keyboardSetForegroundPgid(callerPgid);
|
||||||
|
|
||||||
|
return lastCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool jobctlRunBackground(const char* path, const char* const* argv, int argc,
|
||||||
|
uint32* outPid)
|
||||||
|
{
|
||||||
|
uint32 pid = jobctlSpawnPiped(path, argv, argc, -1, -1, threadInvalidId);
|
||||||
|
if (pid == threadInvalidId)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Deliberately no keyboardSetForegroundPgid call at all -- a
|
||||||
|
backgrounded job never owns the terminal, so a Ctrl-C typed while it
|
||||||
|
runs reaches whatever WAS already in the foreground instead (the
|
||||||
|
shell itself, if nothing else is running in the foreground), never
|
||||||
|
this job. */
|
||||||
|
*outPid = pid;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool jobctlApplyRedirects(uint32 pid, const char* inPath, const char* outPath, bool outAppend,
|
||||||
|
const char* errPath, bool errAppend)
|
||||||
|
{
|
||||||
|
FileDescriptor* fds = threadAt(pid)->fds;
|
||||||
|
|
||||||
|
if (inPath) {
|
||||||
|
/* Same "release the default console reader before overwriting fd 0"
|
||||||
|
step jobctlSpawnPiped already does for a piped stdin above --
|
||||||
|
keyboardCloseReader is a safe no-op on an already-closed/invalid
|
||||||
|
handle (readerAt's own bounds+used check, keyboard.c), so this
|
||||||
|
is correct even if fd 0 already holds something else entirely
|
||||||
|
(e.g. a pipe end from an earlier jobctlSpawnPiped call on this
|
||||||
|
same pid, in runPipeline's left-side case below). */
|
||||||
|
keyboardCloseReader(fds[0].pipeHandle);
|
||||||
|
if (vfsOpenInto(fds, 0, inPath, oReadOnly) < 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outPath) {
|
||||||
|
uint32 flags = oCreate | oWriteOnly | (outAppend ? oAppend : oTrunc);
|
||||||
|
if (vfsOpenInto(fds, 1, outPath, flags) < 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errPath) {
|
||||||
|
uint32 flags = oCreate | oWriteOnly | (errAppend ? oAppend : oTrunc);
|
||||||
|
if (vfsOpenInto(fds, 2, errPath, flags) < 0)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 jobctlSpawnForegroundRedirected(const char* path, const char* const* argv, int argc,
|
||||||
|
const char* inPath, const char* outPath, bool outAppend,
|
||||||
|
const char* errPath, bool errAppend)
|
||||||
|
{
|
||||||
|
uint32 pid = jobctlSpawnPiped(path, argv, argc, -1, -1, threadInvalidId);
|
||||||
|
if (pid == threadInvalidId)
|
||||||
|
return threadInvalidId;
|
||||||
|
|
||||||
|
if (!jobctlApplyRedirects(pid, inPath, outPath, outAppend, errPath, errAppend)) {
|
||||||
|
threadKill(pid);
|
||||||
|
threadJoin(pid);
|
||||||
|
return threadInvalidId;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboardSetForegroundPgid(pid);
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
uint32 pid = jobctlSpawnPiped(path, argv, argc, -1, -1, threadInvalidId);
|
||||||
|
if (pid == threadInvalidId)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!jobctlApplyRedirects(pid, inPath, outPath, outAppend, errPath, errAppend)) {
|
||||||
|
threadKill(pid);
|
||||||
|
threadJoin(pid);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outPid = pid;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generalizes jobctlSpawnPiped's own fd-baking-before-first-instruction
|
||||||
|
machinery to also here-string-feed stdin: spawns `path` with fd 0 wired
|
||||||
|
to a brand-new pipe AND fd 1/pgid wired exactly as `stdoutHandle`/
|
||||||
|
`joinPgid` ask (passed straight through to jobctlSpawnPiped, unlike the
|
||||||
|
fixed -1/threadInvalidId every earlier here-string caller in this file
|
||||||
|
used), then feeds `text` plus a trailing newline into the new pipe's
|
||||||
|
write end and closes it -- all BEFORE returning, so the child can never
|
||||||
|
observe the write end still open. Ordering matters: the write end is
|
||||||
|
only added/used/closed AFTER jobctlSpawnPiped has already attached the
|
||||||
|
read end (pipeAddReader, via vfsPipeAttachInto in vfs.c) -- pipe.c's
|
||||||
|
pipeReleaseIfUnused only frees a pipe's slot once BOTH readers and
|
||||||
|
writers reach 0, so closing the write end while the read end is still
|
||||||
|
attached (readers==1) leaves the pipe, and the data already sitting in
|
||||||
|
its ring buffer, alive for the child to actually read; closing it
|
||||||
|
BEFORE the read end attached (readers==0) would instead free the whole
|
||||||
|
slot out from under the still-buffered payload the instant this
|
||||||
|
function's own pipeCloseWrite ran, handing the child a dangling handle.
|
||||||
|
`text`'s own length plus one newline byte is always far under
|
||||||
|
pipeBufferSize (256, pipe.h) for any line this shell's own lineCapacity
|
||||||
|
(128, shell.c) could ever have typed it from, so the non-blocking
|
||||||
|
pipeWrite below can never see "buffer full" and needs no retry loop the
|
||||||
|
way a general-purpose writer would. Returns threadInvalidId, having
|
||||||
|
spawned and written nothing, on the same load/spawn failures as
|
||||||
|
jobctlSpawnPiped, or if the pipe table itself is exhausted
|
||||||
|
(vfsPipeCreateHandle). */
|
||||||
|
uint32 jobctlSpawnPipedHereString(const char* path, const char* const* argv, int argc,
|
||||||
|
const char* text, int stdoutHandle, uint32 joinPgid)
|
||||||
|
{
|
||||||
|
int handle = vfsPipeCreateHandle();
|
||||||
|
if (handle < 0)
|
||||||
|
return threadInvalidId;
|
||||||
|
|
||||||
|
uint32 pid = jobctlSpawnPiped(path, argv, argc, handle, stdoutHandle, joinPgid);
|
||||||
|
if (pid == threadInvalidId) {
|
||||||
|
pipeDestroyIfUnattached(handle);
|
||||||
|
return threadInvalidId;
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeAddWriter(handle);
|
||||||
|
uint64 length = strlen(text);
|
||||||
|
if (length > 0)
|
||||||
|
pipeWrite(handle, text, length);
|
||||||
|
pipeWrite(handle, "\n", 1);
|
||||||
|
pipeCloseWrite(handle);
|
||||||
|
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 jobctlSpawnForegroundHereString(const char* path, const char* const* argv, int argc,
|
||||||
|
const char* text, const char* outPath, bool outAppend,
|
||||||
|
const char* errPath, bool errAppend)
|
||||||
|
{
|
||||||
|
uint32 pid = jobctlSpawnPipedHereString(path, argv, argc, text, -1, threadInvalidId);
|
||||||
|
if (pid == threadInvalidId)
|
||||||
|
return threadInvalidId;
|
||||||
|
|
||||||
Reference in new issue
Block a user