osproc: make failed execv an exception (when using fork or clone)
startProcessAuxFork creates a pipe, which is used by a child to pass an error code if execv fails.
This commit is contained in:
parent
cd2bd7fa7b
commit
4c09fc110f
2 changed files with 64 additions and 23 deletions
|
|
@ -2066,6 +2066,7 @@ proc pthread_spin_unlock*(a1: ptr Tpthread_spinlock): cint {.
|
||||||
proc pthread_testcancel*() {.importc, header: "<pthread.h>".}
|
proc pthread_testcancel*() {.importc, header: "<pthread.h>".}
|
||||||
|
|
||||||
|
|
||||||
|
proc exitnow*(code: int): void {.importc: "_exit", header: "<unistd.h>".}
|
||||||
proc access*(a1: cstring, a2: cint): cint {.importc, header: "<unistd.h>".}
|
proc access*(a1: cstring, a2: cint): cint {.importc, header: "<unistd.h>".}
|
||||||
proc alarm*(a1: cint): cint {.importc, header: "<unistd.h>".}
|
proc alarm*(a1: cint): cint {.importc, header: "<unistd.h>".}
|
||||||
proc chdir*(a1: cstring): cint {.importc, header: "<unistd.h>".}
|
proc chdir*(a1: cstring): cint {.importc, header: "<unistd.h>".}
|
||||||
|
|
|
||||||
|
|
@ -600,13 +600,16 @@ elif not defined(useNimRtl):
|
||||||
sysArgs: cstringArray
|
sysArgs: cstringArray
|
||||||
sysEnv: cstringArray
|
sysEnv: cstringArray
|
||||||
workingDir: cstring
|
workingDir: cstring
|
||||||
pStdin, pStdout, pStderr: array[0..1, cint]
|
pStdin, pStdout, pStderr, pErrorPipe: array[0..1, cint]
|
||||||
optionPoUsePath: bool
|
optionPoUsePath: bool
|
||||||
optionPoParentStreams: bool
|
optionPoParentStreams: bool
|
||||||
optionPoStdErrToStdOut: bool
|
optionPoStdErrToStdOut: bool
|
||||||
|
|
||||||
proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].}
|
proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].}
|
||||||
proc startProcessAfterFork(data: ptr TStartProcessData) {.tags: [FExecIO, FReadEnv], cdecl.}
|
|
||||||
|
proc startProcessAuxFork(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].}
|
||||||
|
proc startProcessAfterFork(data: ptr TStartProcessData) {.
|
||||||
|
tags: [FExecIO, FReadEnv], noStackFrame, cdecl.}
|
||||||
|
|
||||||
proc startProcess(command: string,
|
proc startProcess(command: string,
|
||||||
workingDir: string = "",
|
workingDir: string = "",
|
||||||
|
|
@ -658,23 +661,11 @@ elif not defined(useNimRtl):
|
||||||
data.optionPoStdErrToStdOut = poStdErrToStdOut in options
|
data.optionPoStdErrToStdOut = poStdErrToStdOut in options
|
||||||
data.workingDir = workingDir
|
data.workingDir = workingDir
|
||||||
|
|
||||||
when defined(useClone):
|
|
||||||
const stackSize = 8096
|
|
||||||
let stackEnd = cast[clong](alloc(stackSize))
|
|
||||||
let stack = cast[pointer](stackEnd + stackSize)
|
|
||||||
let fn: pointer = startProcessAfterFork
|
|
||||||
pid = clone(fn, stack,
|
|
||||||
cint(CLONE_VM or CLONE_VFORK or SIGCHLD),
|
|
||||||
pointer(addr data), nil, nil, nil)
|
|
||||||
|
|
||||||
if pid < 0: osError(osLastError())
|
when defined(posix_spawn) and not defined(useFork) and not defined(useClone):
|
||||||
elif defined(posix_spawn) and not defined(useFork):
|
|
||||||
pid = startProcessAuxSpawn(data)
|
pid = startProcessAuxSpawn(data)
|
||||||
else:
|
else:
|
||||||
pid = fork()
|
pid = startProcessAuxFork(data)
|
||||||
if pid < 0: osError(osLastError())
|
|
||||||
if pid == 0:
|
|
||||||
startProcessAfterFork(addr(data))
|
|
||||||
|
|
||||||
# Parent process. Copy process information.
|
# Parent process. Copy process information.
|
||||||
if poEchoCmd in options:
|
if poEchoCmd in options:
|
||||||
|
|
@ -747,30 +738,79 @@ elif not defined(useNimRtl):
|
||||||
chck res
|
chck res
|
||||||
return pid
|
return pid
|
||||||
|
|
||||||
|
proc startProcessAuxFork(data: TStartProcessData): TPid =
|
||||||
|
if pipe(data.pErrorPipe) != 0:
|
||||||
|
osError(osLastError())
|
||||||
|
|
||||||
|
finally:
|
||||||
|
discard close(data.pErrorPipe[readIdx])
|
||||||
|
|
||||||
|
var pid: TPid
|
||||||
|
var dataCopy = data
|
||||||
|
|
||||||
|
if defined(useClone):
|
||||||
|
const stackSize = 8096
|
||||||
|
let stackEnd = cast[clong](alloc(stackSize))
|
||||||
|
let stack = cast[pointer](stackEnd + stackSize)
|
||||||
|
let fn: pointer = startProcessAfterFork
|
||||||
|
pid = clone(fn, stack,
|
||||||
|
cint(CLONE_VM or CLONE_VFORK or SIGCHLD),
|
||||||
|
pointer(addr dataCopy), nil, nil, nil)
|
||||||
|
discard close(data.pErrorPipe[writeIdx])
|
||||||
|
dealloc(stack)
|
||||||
|
else:
|
||||||
|
pid = fork()
|
||||||
|
if pid == 0:
|
||||||
|
startProcessAfterFork(addr(dataCopy))
|
||||||
|
exitnow(1)
|
||||||
|
|
||||||
|
discard close(data.pErrorPipe[writeIdx])
|
||||||
|
if pid < 0: osError(osLastError())
|
||||||
|
|
||||||
|
var error: cint
|
||||||
|
let sizeRead = read(data.pErrorPipe[readIdx], addr error, sizeof(error))
|
||||||
|
if sizeRead == sizeof(error):
|
||||||
|
osError($strerror(error))
|
||||||
|
|
||||||
|
return pid
|
||||||
|
|
||||||
|
proc startProcessFail(data: ptr TStartProcessData) {.noStackFrame.} =
|
||||||
|
var error: cint = errno
|
||||||
|
discard write(data.pErrorPipe[writeIdx], addr error, sizeof(error))
|
||||||
|
exitnow(1)
|
||||||
|
|
||||||
proc startProcessAfterFork(data: ptr TStartProcessData) =
|
proc startProcessAfterFork(data: ptr TStartProcessData) =
|
||||||
# Warning: no GC here!
|
# Warning: no GC here!
|
||||||
|
# Or anythink that touches global structures - all called nimrod procs
|
||||||
|
# must be marked with noStackFrame. Inspect C code after making changes.
|
||||||
if not data.optionPoParentStreams:
|
if not data.optionPoParentStreams:
|
||||||
discard close(data.pStdin[writeIdx])
|
discard close(data.pStdin[writeIdx])
|
||||||
if dup2(data.pStdin[readIdx], readIdx) < 0: osError(osLastError())
|
if dup2(data.pStdin[readIdx], readIdx) < 0:
|
||||||
|
startProcessFail(data)
|
||||||
discard close(data.pStdout[readIdx])
|
discard close(data.pStdout[readIdx])
|
||||||
if dup2(data.pStdout[writeIdx], writeIdx) < 0: osError(osLastError())
|
if dup2(data.pStdout[writeIdx], writeIdx) < 0:
|
||||||
|
startProcessFail(data)
|
||||||
discard close(data.pStderr[readIdx])
|
discard close(data.pStderr[readIdx])
|
||||||
if data.optionPoStdErrToStdOut:
|
if data.optionPoStdErrToStdOut:
|
||||||
if dup2(data.pStdout[writeIdx], 2) < 0: osError(osLastError())
|
if dup2(data.pStdout[writeIdx], 2) < 0:
|
||||||
|
startProcessFail(data)
|
||||||
else:
|
else:
|
||||||
if dup2(data.pStderr[writeIdx], 2) < 0: osError(osLastError())
|
if dup2(data.pStderr[writeIdx], 2) < 0:
|
||||||
|
startProcessFail(data)
|
||||||
|
|
||||||
if data.workingDir.len > 0:
|
if data.workingDir.len > 0:
|
||||||
if chdir(data.workingDir) < 0:
|
if chdir(data.workingDir) < 0:
|
||||||
quit("chdir failed")
|
startProcessFail(data)
|
||||||
|
|
||||||
|
discard close(data.pErrorPipe[readIdx])
|
||||||
|
discard fcntl(data.pErrorPipe[writeIdx], F_SETFD, FD_CLOEXEC)
|
||||||
|
|
||||||
if data.optionPoUsePath:
|
if data.optionPoUsePath:
|
||||||
discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv)
|
discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv)
|
||||||
else:
|
else:
|
||||||
discard execve(data.sysCommand, data.sysArgs, data.sysEnv)
|
discard execve(data.sysCommand, data.sysArgs, data.sysEnv)
|
||||||
|
|
||||||
# too risky to raise an exception here:
|
startProcessFail(data)
|
||||||
quit("execve call failed: " & $strerror(errno))
|
|
||||||
|
|
||||||
proc close(p: PProcess) =
|
proc close(p: PProcess) =
|
||||||
if p.inStream != nil: close(p.inStream)
|
if p.inStream != nil: close(p.inStream)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue