added osproc.poInteractive and osproc.hasData; both experimental
This commit is contained in:
parent
6268bf33a2
commit
5f7ad9131f
1 changed files with 105 additions and 25 deletions
|
|
@ -24,6 +24,20 @@ when defined(linux):
|
||||||
import linux
|
import linux
|
||||||
|
|
||||||
type
|
type
|
||||||
|
ProcessOption* = enum ## options that can be passed `startProcess`
|
||||||
|
poEchoCmd, ## echo the command before execution
|
||||||
|
poUsePath, ## Asks system to search for executable using PATH environment
|
||||||
|
## variable.
|
||||||
|
## On Windows, this is the default.
|
||||||
|
poEvalCommand, ## Pass `command` directly to the shell, without quoting.
|
||||||
|
## Use it only if `command` comes from trused source.
|
||||||
|
poStdErrToStdOut, ## merge stdout and stderr to the stdout stream
|
||||||
|
poParentStreams, ## use the parent's streams
|
||||||
|
poInteractive ## optimize the buffer handling for responsiveness for
|
||||||
|
## UI applications. Currently this only affects
|
||||||
|
## Windows: Named pipes are used so that you can peek
|
||||||
|
## at the process' output streams.
|
||||||
|
|
||||||
ProcessObj = object of RootObj
|
ProcessObj = object of RootObj
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
fProcessHandle: Handle
|
fProcessHandle: Handle
|
||||||
|
|
@ -34,18 +48,10 @@ type
|
||||||
inStream, outStream, errStream: Stream
|
inStream, outStream, errStream: Stream
|
||||||
id: Pid
|
id: Pid
|
||||||
exitCode: cint
|
exitCode: cint
|
||||||
|
options: set[ProcessOption]
|
||||||
|
|
||||||
Process* = ref ProcessObj ## represents an operating system process
|
Process* = ref ProcessObj ## represents an operating system process
|
||||||
|
|
||||||
ProcessOption* = enum ## options that can be passed `startProcess`
|
|
||||||
poEchoCmd, ## echo the command before execution
|
|
||||||
poUsePath, ## Asks system to search for executable using PATH environment
|
|
||||||
## variable.
|
|
||||||
## On Windows, this is the default.
|
|
||||||
poEvalCommand, ## Pass `command` directly to the shell, without quoting.
|
|
||||||
## Use it only if `command` comes from trused source.
|
|
||||||
poStdErrToStdOut, ## merge stdout and stderr to the stdout stream
|
|
||||||
poParentStreams ## use the parent's streams
|
|
||||||
|
|
||||||
{.deprecated: [TProcess: ProcessObj, PProcess: Process,
|
{.deprecated: [TProcess: ProcessObj, PProcess: Process,
|
||||||
TProcessOption: ProcessOption].}
|
TProcessOption: ProcessOption].}
|
||||||
|
|
@ -302,7 +308,7 @@ proc execProcesses*(cmds: openArray[string],
|
||||||
result = max(waitForExit(p), result)
|
result = max(waitForExit(p), result)
|
||||||
close(p)
|
close(p)
|
||||||
|
|
||||||
proc select*(readfds: var seq[Process], timeout = 500): int
|
proc select*(readfds: var seq[Process], timeout = 500): int {.benign.}
|
||||||
## `select` with a sensible Nim interface. `timeout` is in milliseconds.
|
## `select` with a sensible Nim interface. `timeout` is in milliseconds.
|
||||||
## Specify -1 for no timeout. Returns the number of processes that are
|
## Specify -1 for no timeout. Returns the number of processes that are
|
||||||
## ready to read from. The processes that are ready to be read from are
|
## ready to read from. The processes that are ready to be read from are
|
||||||
|
|
@ -394,13 +400,68 @@ when defined(Windows) and not defined(useNimRtl):
|
||||||
#var
|
#var
|
||||||
# O_WRONLY {.importc: "_O_WRONLY", header: "<fcntl.h>".}: int
|
# O_WRONLY {.importc: "_O_WRONLY", header: "<fcntl.h>".}: int
|
||||||
# O_RDONLY {.importc: "_O_RDONLY", header: "<fcntl.h>".}: int
|
# O_RDONLY {.importc: "_O_RDONLY", header: "<fcntl.h>".}: int
|
||||||
|
proc myDup(h: Handle; inherit: WinBool=1): Handle =
|
||||||
|
let thisProc = getCurrentProcess()
|
||||||
|
if duplicateHandle(thisProc, h,
|
||||||
|
thisProc, addr result,0,inherit,
|
||||||
|
DUPLICATE_SAME_ACCESS) == 0:
|
||||||
|
raiseOSError(osLastError())
|
||||||
|
|
||||||
|
proc createAllPipeHandles(si: var STARTUPINFO;
|
||||||
|
stdin, stdout, stderr: var Handle) =
|
||||||
|
var sa: SECURITY_ATTRIBUTES
|
||||||
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint
|
||||||
|
sa.lpSecurityDescriptor = nil
|
||||||
|
sa.bInheritHandle = 1
|
||||||
|
let pipeOutName = newWideCString(r"\\.\pipe\stdout")
|
||||||
|
let pipeInName = newWideCString(r"\\.\pipe\stdin")
|
||||||
|
let pipeOut = createNamedPipe(pipeOutName,
|
||||||
|
dwOpenMode=PIPE_ACCESS_INBOUND or FILE_FLAG_WRITE_THROUGH,
|
||||||
|
dwPipeMode=PIPE_NOWAIT,
|
||||||
|
nMaxInstances=1,
|
||||||
|
nOutBufferSize=1024, nInBufferSize=1024,
|
||||||
|
nDefaultTimeOut=0,addr sa)
|
||||||
|
if pipeOut == INVALID_HANDLE_VALUE:
|
||||||
|
raiseOSError(osLastError())
|
||||||
|
let pipeIn = createNamedPipe(pipeInName,
|
||||||
|
dwOpenMode=PIPE_ACCESS_OUTBOUND or FILE_FLAG_WRITE_THROUGH,
|
||||||
|
dwPipeMode=PIPE_NOWAIT,
|
||||||
|
nMaxInstances=1,
|
||||||
|
nOutBufferSize=1024, nInBufferSize=1024,
|
||||||
|
nDefaultTimeOut=0,addr sa)
|
||||||
|
if pipeIn == INVALID_HANDLE_VALUE:
|
||||||
|
raiseOSError(osLastError())
|
||||||
|
|
||||||
|
si.hStdOutput = createFileW(pipeOutName,
|
||||||
|
FILE_WRITE_DATA or SYNCHRONIZE, 0, addr sa,
|
||||||
|
OPEN_EXISTING, # very important flag!
|
||||||
|
FILE_ATTRIBUTE_NORMAL,
|
||||||
|
0 # no template file for OPEN_EXISTING
|
||||||
|
)
|
||||||
|
if si.hStdOutput == INVALID_HANDLE_VALUE:
|
||||||
|
raiseOSError(osLastError())
|
||||||
|
si.hStdError = myDup(si.hStdOutput)
|
||||||
|
si.hStdInput = createFileW(pipeInName,
|
||||||
|
FILE_READ_DATA or SYNCHRONIZE, 0, addr sa,
|
||||||
|
OPEN_EXISTING, # very important flag!
|
||||||
|
FILE_ATTRIBUTE_NORMAL,
|
||||||
|
0 # no template file for OPEN_EXISTING
|
||||||
|
)
|
||||||
|
if si.hStdOutput == INVALID_HANDLE_VALUE:
|
||||||
|
raiseOSError(osLastError())
|
||||||
|
|
||||||
|
stdin = myDup(pipeIn, 0)
|
||||||
|
stdout = myDup(pipeOut, 0)
|
||||||
|
discard closeHandle(pipeIn)
|
||||||
|
discard closeHandle(pipeOut)
|
||||||
|
stderr = stdout
|
||||||
|
|
||||||
proc createPipeHandles(rdHandle, wrHandle: var Handle) =
|
proc createPipeHandles(rdHandle, wrHandle: var Handle) =
|
||||||
var piInheritablePipe: SECURITY_ATTRIBUTES
|
var sa: SECURITY_ATTRIBUTES
|
||||||
piInheritablePipe.nLength = sizeof(SECURITY_ATTRIBUTES).cint
|
sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint
|
||||||
piInheritablePipe.lpSecurityDescriptor = nil
|
sa.lpSecurityDescriptor = nil
|
||||||
piInheritablePipe.bInheritHandle = 1
|
sa.bInheritHandle = 1
|
||||||
if createPipe(rdHandle, wrHandle, piInheritablePipe, 1024) == 0'i32:
|
if createPipe(rdHandle, wrHandle, sa, 1024) == 0'i32:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
|
|
||||||
proc fileClose(h: Handle) {.inline.} =
|
proc fileClose(h: Handle) {.inline.} =
|
||||||
|
|
@ -417,16 +478,20 @@ when defined(Windows) and not defined(useNimRtl):
|
||||||
success: int
|
success: int
|
||||||
hi, ho, he: Handle
|
hi, ho, he: Handle
|
||||||
new(result)
|
new(result)
|
||||||
|
result.options = options
|
||||||
si.cb = sizeof(si).cint
|
si.cb = sizeof(si).cint
|
||||||
if poParentStreams notin options:
|
if poParentStreams notin options:
|
||||||
si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or
|
si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or
|
||||||
createPipeHandles(si.hStdInput, hi)
|
if poInteractive notin options:
|
||||||
createPipeHandles(ho, si.hStdOutput)
|
createPipeHandles(si.hStdInput, hi)
|
||||||
if poStdErrToStdOut in options:
|
createPipeHandles(ho, si.hStdOutput)
|
||||||
si.hStdError = si.hStdOutput
|
if poStdErrToStdOut in options:
|
||||||
he = ho
|
si.hStdError = si.hStdOutput
|
||||||
|
he = ho
|
||||||
|
else:
|
||||||
|
createPipeHandles(he, si.hStdError)
|
||||||
else:
|
else:
|
||||||
createPipeHandles(he, si.hStdError)
|
createAllPipeHandles(si, hi, ho, he)
|
||||||
result.inHandle = FileHandle(hi)
|
result.inHandle = FileHandle(hi)
|
||||||
result.outHandle = FileHandle(ho)
|
result.outHandle = FileHandle(ho)
|
||||||
result.errHandle = FileHandle(he)
|
result.errHandle = FileHandle(he)
|
||||||
|
|
@ -482,12 +547,12 @@ when defined(Windows) and not defined(useNimRtl):
|
||||||
result.id = procInfo.dwProcessId
|
result.id = procInfo.dwProcessId
|
||||||
|
|
||||||
proc close(p: Process) =
|
proc close(p: Process) =
|
||||||
when false:
|
if poInteractive in p.options:
|
||||||
# somehow this does not work on Windows:
|
# somehow this is not always required on Windows:
|
||||||
discard closeHandle(p.inHandle)
|
discard closeHandle(p.inHandle)
|
||||||
discard closeHandle(p.outHandle)
|
discard closeHandle(p.outHandle)
|
||||||
discard closeHandle(p.errHandle)
|
discard closeHandle(p.errHandle)
|
||||||
discard closeHandle(p.FProcessHandle)
|
#discard closeHandle(p.FProcessHandle)
|
||||||
|
|
||||||
proc suspend(p: Process) =
|
proc suspend(p: Process) =
|
||||||
discard suspendThread(p.fProcessHandle)
|
discard suspendThread(p.fProcessHandle)
|
||||||
|
|
@ -564,7 +629,7 @@ when defined(Windows) and not defined(useNimRtl):
|
||||||
assert readfds.len <= MAXIMUM_WAIT_OBJECTS
|
assert readfds.len <= MAXIMUM_WAIT_OBJECTS
|
||||||
var rfds: WOHandleArray
|
var rfds: WOHandleArray
|
||||||
for i in 0..readfds.len()-1:
|
for i in 0..readfds.len()-1:
|
||||||
rfds[i] = readfds[i].fProcessHandle
|
rfds[i] = readfds[i].outHandle #fProcessHandle
|
||||||
|
|
||||||
var ret = waitForMultipleObjects(readfds.len.int32,
|
var ret = waitForMultipleObjects(readfds.len.int32,
|
||||||
addr(rfds), 0'i32, timeout.int32)
|
addr(rfds), 0'i32, timeout.int32)
|
||||||
|
|
@ -578,6 +643,11 @@ when defined(Windows) and not defined(useNimRtl):
|
||||||
readfds.del(i)
|
readfds.del(i)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
proc hasData*(p: Process): bool =
|
||||||
|
var x: int32
|
||||||
|
if peekNamedPipe(p.outHandle, lpTotalBytesAvail=addr x):
|
||||||
|
result = x > 0
|
||||||
|
|
||||||
elif not defined(useNimRtl):
|
elif not defined(useNimRtl):
|
||||||
const
|
const
|
||||||
readIdx = 0
|
readIdx = 0
|
||||||
|
|
@ -635,6 +705,7 @@ elif not defined(useNimRtl):
|
||||||
var
|
var
|
||||||
pStdin, pStdout, pStderr: array [0..1, cint]
|
pStdin, pStdout, pStderr: array [0..1, cint]
|
||||||
new(result)
|
new(result)
|
||||||
|
result.options = options
|
||||||
result.exitCode = -3 # for ``waitForExit``
|
result.exitCode = -3 # for ``waitForExit``
|
||||||
if poParentStreams notin options:
|
if poParentStreams notin options:
|
||||||
if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or
|
if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or
|
||||||
|
|
@ -960,6 +1031,15 @@ elif not defined(useNimRtl):
|
||||||
|
|
||||||
pruneProcessSet(readfds, (rd))
|
pruneProcessSet(readfds, (rd))
|
||||||
|
|
||||||
|
proc hasData*(p: Process): bool =
|
||||||
|
var rd: TFdSet
|
||||||
|
|
||||||
|
FD_ZERO(rd)
|
||||||
|
let m = max(0, int(p.outHandle))
|
||||||
|
FD_SET(cint(p.outHandle), rd)
|
||||||
|
|
||||||
|
result = int(select(cint(m+1), addr(rd), nil, nil, nil)) == 1
|
||||||
|
|
||||||
|
|
||||||
proc execCmdEx*(command: string, options: set[ProcessOption] = {
|
proc execCmdEx*(command: string, options: set[ProcessOption] = {
|
||||||
poStdErrToStdOut, poUsePath}): tuple[
|
poStdErrToStdOut, poUsePath}): tuple[
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue