added osproc.poInteractive and osproc.hasData; both experimental

This commit is contained in:
Araq
2015-09-29 19:29:45 +02:00
parent 6268bf33a2
commit 5f7ad9131f

View File

@@ -24,6 +24,20 @@ when defined(linux):
import linux
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
when defined(windows):
fProcessHandle: Handle
@@ -34,18 +48,10 @@ type
inStream, outStream, errStream: Stream
id: Pid
exitCode: cint
options: set[ProcessOption]
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,
TProcessOption: ProcessOption].}
@@ -302,7 +308,7 @@ proc execProcesses*(cmds: openArray[string],
result = max(waitForExit(p), result)
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.
## 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
@@ -394,13 +400,68 @@ when defined(Windows) and not defined(useNimRtl):
#var
# O_WRONLY {.importc: "_O_WRONLY", 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) =
var piInheritablePipe: SECURITY_ATTRIBUTES
piInheritablePipe.nLength = sizeof(SECURITY_ATTRIBUTES).cint
piInheritablePipe.lpSecurityDescriptor = nil
piInheritablePipe.bInheritHandle = 1
if createPipe(rdHandle, wrHandle, piInheritablePipe, 1024) == 0'i32:
var sa: SECURITY_ATTRIBUTES
sa.nLength = sizeof(SECURITY_ATTRIBUTES).cint
sa.lpSecurityDescriptor = nil
sa.bInheritHandle = 1
if createPipe(rdHandle, wrHandle, sa, 1024) == 0'i32:
raiseOSError(osLastError())
proc fileClose(h: Handle) {.inline.} =
@@ -417,16 +478,20 @@ when defined(Windows) and not defined(useNimRtl):
success: int
hi, ho, he: Handle
new(result)
result.options = options
si.cb = sizeof(si).cint
if poParentStreams notin options:
si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or
createPipeHandles(si.hStdInput, hi)
createPipeHandles(ho, si.hStdOutput)
if poStdErrToStdOut in options:
si.hStdError = si.hStdOutput
he = ho
if poInteractive notin options:
createPipeHandles(si.hStdInput, hi)
createPipeHandles(ho, si.hStdOutput)
if poStdErrToStdOut in options:
si.hStdError = si.hStdOutput
he = ho
else:
createPipeHandles(he, si.hStdError)
else:
createPipeHandles(he, si.hStdError)
createAllPipeHandles(si, hi, ho, he)
result.inHandle = FileHandle(hi)
result.outHandle = FileHandle(ho)
result.errHandle = FileHandle(he)
@@ -482,12 +547,12 @@ when defined(Windows) and not defined(useNimRtl):
result.id = procInfo.dwProcessId
proc close(p: Process) =
when false:
# somehow this does not work on Windows:
if poInteractive in p.options:
# somehow this is not always required on Windows:
discard closeHandle(p.inHandle)
discard closeHandle(p.outHandle)
discard closeHandle(p.errHandle)
discard closeHandle(p.FProcessHandle)
#discard closeHandle(p.FProcessHandle)
proc suspend(p: Process) =
discard suspendThread(p.fProcessHandle)
@@ -564,7 +629,7 @@ when defined(Windows) and not defined(useNimRtl):
assert readfds.len <= MAXIMUM_WAIT_OBJECTS
var rfds: WOHandleArray
for i in 0..readfds.len()-1:
rfds[i] = readfds[i].fProcessHandle
rfds[i] = readfds[i].outHandle #fProcessHandle
var ret = waitForMultipleObjects(readfds.len.int32,
addr(rfds), 0'i32, timeout.int32)
@@ -578,6 +643,11 @@ when defined(Windows) and not defined(useNimRtl):
readfds.del(i)
return 1
proc hasData*(p: Process): bool =
var x: int32
if peekNamedPipe(p.outHandle, lpTotalBytesAvail=addr x):
result = x > 0
elif not defined(useNimRtl):
const
readIdx = 0
@@ -635,6 +705,7 @@ elif not defined(useNimRtl):
var
pStdin, pStdout, pStderr: array [0..1, cint]
new(result)
result.options = options
result.exitCode = -3 # for ``waitForExit``
if poParentStreams notin options:
if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or
@@ -960,6 +1031,15 @@ elif not defined(useNimRtl):
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] = {
poStdErrToStdOut, poUsePath}): tuple[