diff options
Diffstat (limited to 'lib/pure/osproc.nim')
-rw-r--r-- | lib/pure/osproc.nim | 130 |
1 files changed, 105 insertions, 25 deletions
diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 7431be702..454f9eda0 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -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[ |