mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-08 08:38:51 +00:00
Merge pull request #4290 from flysand7/pipe-has-data
[os2/process]: Implement `process_exec`, and `pipe_has_data`
This commit is contained in:
@@ -55,13 +55,15 @@ _get_platform_error :: proc() -> Error {
|
||||
case win32.ERROR_NEGATIVE_SEEK:
|
||||
return .Invalid_Offset
|
||||
|
||||
case win32.ERROR_BROKEN_PIPE:
|
||||
return .Broken_Pipe
|
||||
|
||||
case
|
||||
win32.ERROR_BAD_ARGUMENTS,
|
||||
win32.ERROR_INVALID_PARAMETER,
|
||||
win32.ERROR_NOT_ENOUGH_MEMORY,
|
||||
win32.ERROR_NO_MORE_FILES,
|
||||
win32.ERROR_LOCK_VIOLATION,
|
||||
win32.ERROR_BROKEN_PIPE,
|
||||
win32.ERROR_CALL_NOT_IMPLEMENTED,
|
||||
win32.ERROR_INSUFFICIENT_BUFFER,
|
||||
win32.ERROR_INVALID_NAME,
|
||||
|
||||
@@ -1,6 +1,43 @@
|
||||
package os2
|
||||
|
||||
/*
|
||||
Create an anonymous pipe.
|
||||
|
||||
This procedure creates an anonymous pipe, returning two ends of the pipe, `r`
|
||||
and `w`. The file `r` is the readable end of the pipe. The file `w` is a
|
||||
writeable end of the pipe.
|
||||
|
||||
Pipes are used as an inter-process communication mechanism, to communicate
|
||||
between a parent and a child process. The child uses one end of the pipe to
|
||||
write data, and the parent uses the other end to read from the pipe
|
||||
(or vice-versa). When a parent passes one of the ends of the pipe to the child
|
||||
process, that end of the pipe needs to be closed by the parent, before any data
|
||||
is attempted to be read.
|
||||
|
||||
Although pipes look like files and is compatible with most file APIs in package
|
||||
os2, the way it's meant to be read is different. Due to asynchronous nature of
|
||||
the communication channel, the data may not be present at the time of a read
|
||||
request. The other scenario is when a pipe has no data because the other end
|
||||
of the pipe was closed by the child process.
|
||||
*/
|
||||
@(require_results)
|
||||
pipe :: proc() -> (r, w: ^File, err: Error) {
|
||||
return _pipe()
|
||||
}
|
||||
|
||||
/*
|
||||
Check if the pipe has any data.
|
||||
|
||||
This procedure checks whether a read-end of the pipe has data that can be
|
||||
read, and returns `true`, if the pipe has readable data, and `false` if the
|
||||
pipe is empty. This procedure does not block the execution of the current
|
||||
thread.
|
||||
|
||||
**Note**: If the other end of the pipe was closed by the child process, the
|
||||
`.Broken_Pipe`
|
||||
can be returned by this procedure. Handle these errors accordingly.
|
||||
*/
|
||||
@(require_results)
|
||||
pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) {
|
||||
return _pipe_has_data(r)
|
||||
}
|
||||
|
||||
@@ -15,3 +15,29 @@ _pipe :: proc() -> (r, w: ^File, err: Error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) {
|
||||
if r == nil || r.impl == nil {
|
||||
return false, nil
|
||||
}
|
||||
fd := linux.Fd((^File_Impl)(r.impl).fd)
|
||||
poll_fds := []linux.Poll_Fd {
|
||||
linux.Poll_Fd {
|
||||
fd = fd,
|
||||
events = {.IN, .HUP},
|
||||
},
|
||||
}
|
||||
n, errno := linux.poll(poll_fds, 0)
|
||||
if n != 1 || errno != nil {
|
||||
return false, _get_platform_error(errno)
|
||||
}
|
||||
pipe_events := poll_fds[0].revents
|
||||
if pipe_events >= {.IN} {
|
||||
return true, nil
|
||||
}
|
||||
if pipe_events >= {.HUP} {
|
||||
return false, .Broken_Pipe
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -44,3 +44,28 @@ _pipe :: proc() -> (r, w: ^File, err: Error) {
|
||||
return
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) {
|
||||
if r == nil || r.impl == nil {
|
||||
return false, nil
|
||||
}
|
||||
fd := posix.FD((^File_Impl)(r.impl).fd)
|
||||
poll_fds := []posix.pollfd {
|
||||
posix.pollfd {
|
||||
fd = fd,
|
||||
events = {.IN, .HUP},
|
||||
},
|
||||
}
|
||||
n := posix.poll(raw_data(poll_fds), u32(len(poll_fds)), 0)
|
||||
if n != 1 {
|
||||
return false, _get_platform_error()
|
||||
}
|
||||
pipe_events := poll_fds[0].revents
|
||||
if pipe_events >= {.IN} {
|
||||
return true, nil
|
||||
}
|
||||
if pipe_events >= {.HUP} {
|
||||
return false, .Broken_Pipe
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
@@ -15,3 +15,15 @@ _pipe :: proc() -> (r, w: ^File, err: Error) {
|
||||
return new_file(uintptr(p[0]), ""), new_file(uintptr(p[1]), ""), nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) {
|
||||
if r == nil || r.impl == nil {
|
||||
return false, nil
|
||||
}
|
||||
handle := win32.HANDLE((^File_Impl)(r.impl).fd)
|
||||
bytes_available: u32
|
||||
if !win32.PeekNamedPipe(handle, nil, 0, nil, &bytes_available, nil) {
|
||||
return false, _get_platform_error()
|
||||
}
|
||||
return bytes_available > 0, nil
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package os2
|
||||
|
||||
import "base:runtime"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
|
||||
/*
|
||||
@@ -343,10 +344,96 @@ Process_Desc :: struct {
|
||||
handle inheritance properties, make sure to serialize all those calls.
|
||||
*/
|
||||
@(require_results)
|
||||
process_start :: proc(desc := Process_Desc {}) -> (Process, Error) {
|
||||
process_start :: proc(desc: Process_Desc) -> (Process, Error) {
|
||||
return _process_start(desc)
|
||||
}
|
||||
|
||||
/*
|
||||
Execute the process and capture stdout and stderr streams.
|
||||
|
||||
This procedure creates a new process, with a given command and environment
|
||||
strings as parameters, and waits until the process finishes execution. While
|
||||
the process is running, this procedure accumulates the output of its stdout
|
||||
and stderr streams and returns byte slices containing the captured data from
|
||||
the streams.
|
||||
|
||||
This procedure expects that `stdout` and `stderr` fields of the `desc` parameter
|
||||
are left at default, i.e. a `nil` value. You can not capture stdout/stderr and
|
||||
redirect it to a file at the same time.
|
||||
|
||||
This procedure does not free `stdout` and `stderr` slices before an error is
|
||||
returned. Make sure to call `delete` on these slices.
|
||||
*/
|
||||
@(require_results)
|
||||
process_exec :: proc(
|
||||
desc: Process_Desc,
|
||||
allocator: runtime.Allocator,
|
||||
loc := #caller_location,
|
||||
) -> (
|
||||
state: Process_State,
|
||||
stdout: []u8,
|
||||
stderr: []u8,
|
||||
err: Error,
|
||||
) {
|
||||
assert(desc.stdout == nil, "Cannot redirect stdout when it's being captured", loc)
|
||||
assert(desc.stderr == nil, "Cannot redirect stderr when it's being captured", loc)
|
||||
stdout_r, stdout_w := pipe() or_return
|
||||
defer close(stdout_r)
|
||||
stderr_r, stderr_w := pipe() or_return
|
||||
defer close(stdout_w)
|
||||
process: Process
|
||||
{
|
||||
// NOTE(flysand): Make sure the write-ends are closed, regardless
|
||||
// of the outcome. This makes read-ends readable on our side.
|
||||
defer close(stdout_w)
|
||||
defer close(stderr_w)
|
||||
desc := desc
|
||||
desc.stdout = stdout_w
|
||||
desc.stderr = stderr_w
|
||||
process = process_start(desc) or_return
|
||||
}
|
||||
stdout_builder := strings.builder_make(allocator) or_return
|
||||
stderr_builder := strings.builder_make(allocator) or_return
|
||||
read_data: for {
|
||||
buf: [1024]u8
|
||||
n: int
|
||||
has_data: bool
|
||||
hangup := false
|
||||
has_data, err = pipe_has_data(stdout_r)
|
||||
if has_data {
|
||||
n, err = read(stdout_r, buf[:])
|
||||
strings.write_bytes(&stdout_builder, buf[:n])
|
||||
}
|
||||
switch err {
|
||||
case nil: // nothing
|
||||
case .Broken_Pipe:
|
||||
hangup = true
|
||||
case:
|
||||
return
|
||||
}
|
||||
has_data, err = pipe_has_data(stderr_r)
|
||||
if has_data {
|
||||
n, err = read(stderr_r, buf[:])
|
||||
strings.write_bytes(&stderr_builder, buf[:n])
|
||||
}
|
||||
switch err {
|
||||
case nil: // nothing
|
||||
case .Broken_Pipe:
|
||||
hangup = true
|
||||
case:
|
||||
return
|
||||
}
|
||||
if hangup {
|
||||
break read_data
|
||||
}
|
||||
}
|
||||
err = nil
|
||||
stdout = transmute([]u8) strings.to_string(stdout_builder)
|
||||
stderr = transmute([]u8) strings.to_string(stderr_builder)
|
||||
state = process_wait(process) or_return
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
The state of the process after it has finished execution.
|
||||
*/
|
||||
@@ -405,7 +492,6 @@ process_close :: proc(process: Process) -> (Error) {
|
||||
Terminate a process.
|
||||
|
||||
This procedure terminates a process, specified by it's handle, `process`.
|
||||
|
||||
*/
|
||||
@(require_results)
|
||||
process_kill :: proc(process: Process) -> (Error) {
|
||||
|
||||
@@ -381,6 +381,14 @@ foreign kernel32 {
|
||||
nDefaultTimeOut: DWORD,
|
||||
lpSecurityAttributes: LPSECURITY_ATTRIBUTES,
|
||||
) -> HANDLE ---
|
||||
PeekNamedPipe :: proc(
|
||||
hNamedPipe: HANDLE,
|
||||
lpBuffer: rawptr,
|
||||
nBufferSize: u32,
|
||||
lpBytesRead: ^u32,
|
||||
lpTotalBytesAvail: ^u32,
|
||||
lpBytesLeftThisMessage: ^u32,
|
||||
) -> BOOL ---
|
||||
CancelIo :: proc(handle: HANDLE) -> BOOL ---
|
||||
GetOverlappedResult :: proc(
|
||||
hFile: HANDLE,
|
||||
|
||||
Reference in New Issue
Block a user