mirror of
https://github.com/gechandesu/runcmd.git
synced 2026-07-15 00:50:01 +03:00
Compare commits
10
Commits
v0.3.0
...
604b9e2d42
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
604b9e2d42 | ||
|
|
ac516c7b6c | ||
|
|
5636f29acd | ||
|
|
6c3f40179b | ||
|
|
7390af4699 | ||
|
|
8286af90eb | ||
|
|
14864ba992 | ||
|
|
c62722cba9 | ||
|
|
7acab2165e | ||
|
|
8e7d757b3b |
@@ -13,9 +13,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup V
|
- name: Setup V
|
||||||
run: |
|
run: |
|
||||||
wget -qO /tmp/v.zip https://github.com/vlang/v/releases/latest/download/v_linux.zip
|
git clone --depth=1 https://github.com/vlang/v /tmp/v && cd /tmp/v && make
|
||||||
unzip -q /tmp/v.zip -d /tmp
|
/tmp/v/v symlink
|
||||||
echo /tmp/v >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Build docs
|
- name: Build docs
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup V
|
- name: Setup V
|
||||||
run: |
|
run: |
|
||||||
wget -qO /tmp/v.zip https://github.com/vlang/v/releases/latest/download/v_linux.zip
|
git clone --depth=1 https://github.com/vlang/v /tmp/v && cd /tmp/v && make
|
||||||
unzip -q /tmp/v.zip -d /tmp
|
/tmp/v/v symlink
|
||||||
echo /tmp/v >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ but I don't like any of them. So let's overview.
|
|||||||
|
|
||||||
* `os.execvp()`, `os.execve()` — cross-platform versions of the C functions of the same name.
|
* `os.execvp()`, `os.execve()` — cross-platform versions of the C functions of the same name.
|
||||||
|
|
||||||
* `os.execute()`, `os.execute_opt()`, `os.execute_or_exit()`, `os.execute_or_panic()` — starts and waits for a command to completed. Under the hood, they perform a dirty hack by calling shell with stream redirection `'exec 2>&1;${cmd}'`.
|
* `os.execute()`, `os.execute_opt()`, `os.execute_or_exit()`, `os.execute_or_panic()`, `os.raw_execute()` — starts and waits for a command to completed. Under the hood, they perform a dirty hack by calling shell with stream redirection `'exec 2>&1;${cmd}'`.
|
||||||
|
|
||||||
* `util.execute_with_timeout()` (from `os.util`) — just an `os.execute()` wrapper.
|
* `util.execute_with_timeout()` (from `os.util`) — just an `os.execute()` wrapper.
|
||||||
|
|
||||||
@@ -55,7 +55,6 @@ directory. **Examples are very important**.
|
|||||||
|
|
||||||
- [x] Basic implementation.
|
- [x] Basic implementation.
|
||||||
- [x] Contexts support for creating cancelable commands, commands with timeouts, etc.
|
- [x] Contexts support for creating cancelable commands, commands with timeouts, etc.
|
||||||
- [ ] Process groups support, pgkill().
|
|
||||||
- [ ] Better error handling and more tests...
|
- [ ] Better error handling and more tests...
|
||||||
|
|
||||||
Send pull requests for additional features/bugfixes.
|
Send pull requests for additional features/bugfixes.
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
module runcmd
|
|
||||||
|
|
||||||
import io
|
|
||||||
|
|
||||||
// buffer creates simple bytes buffer that can be read through `io.Reader` interface.
|
|
||||||
pub fn buffer(data []u8) ByteBuffer {
|
|
||||||
return ByteBuffer{
|
|
||||||
bytes: data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ByteBuffer {
|
|
||||||
bytes []u8
|
|
||||||
mut:
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
|
|
||||||
// read reads `buf.len` bytes from internal bytes buffer and returns number of bytes read.
|
|
||||||
pub fn (mut b ByteBuffer) read(mut buf []u8) !int {
|
|
||||||
if b.pos >= b.bytes.len {
|
|
||||||
return io.Eof{}
|
|
||||||
}
|
|
||||||
n := copy(mut buf, b.bytes[b.pos..])
|
|
||||||
b.pos += n
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
@@ -207,7 +207,7 @@ pub fn (mut c Command) start() !int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
child_pipes_hook := fn [mut c, pipes] (mut p Process) ! {
|
child_pipes_hook := fn [mut c, pipes] (mut p Process) ! {
|
||||||
printdbg('child pipes hook!')
|
printdbg('Command.start: executing child pipes hook...')
|
||||||
if !c.redirect_stdio {
|
if !c.redirect_stdio {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -228,21 +228,21 @@ pub fn (mut c Command) start() !int {
|
|||||||
if c.redirect_stdio {
|
if c.redirect_stdio {
|
||||||
if c.stdin != none {
|
if c.stdin != none {
|
||||||
c.stdio_copy_fns << fn [mut c] () ! {
|
c.stdio_copy_fns << fn [mut c] () ! {
|
||||||
printdbg('Command.start: stdin copy callback called')
|
printdbg('Command.start: stdin copy closure: started')
|
||||||
mut fd := c.stdin()!
|
mut fd := c.stdin()!
|
||||||
printdbg('Command.start: stdin copy callback: child stdin fd=${fd.fd}')
|
printdbg('Command.start: stdin copy closure: child stdin fd=${fd.fd}')
|
||||||
if c.stdin != none {
|
if c.stdin != none {
|
||||||
// FIXME: V bug?: without `if` guard acessing
|
// FIXME: V bug?: without `if` guard acessing
|
||||||
// to c.stdin causes SIGSEGV.
|
// to c.stdin causes SIGSEGV.
|
||||||
io_copy(mut c.stdin, mut fd, 'copy stdin')!
|
io_copy(mut c.stdin, mut fd, 'copy stdin')!
|
||||||
printdbg('Command.start: stdin copy callback: close child stdin fd after copy')
|
printdbg('Command.start: stdin copy closure: close child stdin fd after copy')
|
||||||
fd_close(fd.fd)!
|
fd_close(fd.fd)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.stdout != none {
|
if c.stdout != none {
|
||||||
c.stdio_copy_fns << fn [mut c] () ! {
|
c.stdio_copy_fns << fn [mut c] () ! {
|
||||||
printdbg('Command.start: stdout copy callback called')
|
printdbg('Command.start: stdout copy closure: started')
|
||||||
mut fd := c.stdout()!
|
mut fd := c.stdout()!
|
||||||
if c.stdout != none {
|
if c.stdout != none {
|
||||||
io_copy(mut fd, mut c.stdout, 'copy stdout')!
|
io_copy(mut fd, mut c.stdout, 'copy stdout')!
|
||||||
@@ -251,7 +251,7 @@ pub fn (mut c Command) start() !int {
|
|||||||
}
|
}
|
||||||
if c.stderr != none {
|
if c.stderr != none {
|
||||||
c.stdio_copy_fns << fn [mut c] () ! {
|
c.stdio_copy_fns << fn [mut c] () ! {
|
||||||
printdbg('Command.start: stderr copy callback called')
|
printdbg('Command.start: stderr copy closure: started')
|
||||||
mut fd := c.stderr()!
|
mut fd := c.stderr()!
|
||||||
if c.stderr != none {
|
if c.stderr != none {
|
||||||
io_copy(mut fd, mut c.stderr, 'copy stderr')!
|
io_copy(mut fd, mut c.stderr, 'copy stderr')!
|
||||||
@@ -268,7 +268,7 @@ pub fn (mut c Command) start() !int {
|
|||||||
path: path
|
path: path
|
||||||
argv: c.args
|
argv: c.args
|
||||||
env: if c.env.len == 0 { os.environ() } else { c.env }
|
env: if c.env.len == 0 { os.environ() } else { c.env }
|
||||||
dir: c.dir
|
dir: os.abs_path(c.dir)
|
||||||
post_fork: [parent_pipes_hook]
|
post_fork: [parent_pipes_hook]
|
||||||
pre_exec: pre_exec_hooks
|
pre_exec: pre_exec_hooks
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,9 @@ pub fn (mut c Command) start() !int {
|
|||||||
for f in c.stdio_copy_fns {
|
for f in c.stdio_copy_fns {
|
||||||
go fn (func IOCopyFn) {
|
go fn (func IOCopyFn) {
|
||||||
printdbg('Command.start: starting I/O copy closure in coroutine')
|
printdbg('Command.start: starting I/O copy closure in coroutine')
|
||||||
func() or { eprintln('error in I/O copy coroutine: ${err}') }
|
func() or {
|
||||||
|
eprintln('error in I/O copy coroutine: msg=${err.msg()}; code=${err.code()}')
|
||||||
|
}
|
||||||
}(f)
|
}(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,7 +360,7 @@ pub fn (c Command) stdin() !WriteFd {
|
|||||||
WriteFd{c.stdio[0]}
|
WriteFd{c.stdio[0]}
|
||||||
} else {
|
} else {
|
||||||
printdbg('${@METHOD}: invalid fd -1')
|
printdbg('${@METHOD}: invalid fd -1')
|
||||||
error_with_code('Bad file descriptor', 9)
|
error_with_code('File descriptor is not set', 9)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +372,7 @@ pub fn (c Command) stdout() !ReadFd {
|
|||||||
ReadFd{c.stdio[1]}
|
ReadFd{c.stdio[1]}
|
||||||
} else {
|
} else {
|
||||||
printdbg('${@METHOD}: invalid fd -1')
|
printdbg('${@METHOD}: invalid fd -1')
|
||||||
error_with_code('Bad file descriptor', 9)
|
error_with_code('File descriptor is not set', 9)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,7 +384,7 @@ pub fn (c Command) stderr() !ReadFd {
|
|||||||
ReadFd{c.stdio[2]}
|
ReadFd{c.stdio[2]}
|
||||||
} else {
|
} else {
|
||||||
printdbg('${@METHOD}: invalid fd -1')
|
printdbg('${@METHOD}: invalid fd -1')
|
||||||
error_with_code('Bad file descriptor', 9)
|
error_with_code('File descriptor is not set', 9)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,11 +414,14 @@ fn io_copy(mut src io.Reader, mut dst io.Writer, msg string) ! {
|
|||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
nr := src.read(mut buf) or {
|
nr := src.read(mut buf) or {
|
||||||
printdbg('${@FN}: (${msg}) got error from reader, breaking loop: ${err}')
|
printdbg("${@FN}: (${msg}) got error from reader, breaking loop: msg='${err.msg()}'; code=${err.code()}")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
printdbg('${@FN}: (${msg}) ${nr} bytes read from src to buf')
|
printdbg('${@FN}: (${msg}) ${nr} bytes read from src to buf')
|
||||||
nw := dst.write(buf[..nr]) or { return err }
|
nw := dst.write(buf[..nr]) or {
|
||||||
|
printdbg("${@FN}: (${msg}) got error from writer, exiting: msg='${err.msg()}'; code=${err.code()}")
|
||||||
|
return err
|
||||||
|
}
|
||||||
printdbg('${@FN}: (${msg}) ${nw} bytes written to dst')
|
printdbg('${@FN}: (${msg}) ${nw} bytes written to dst')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
module runcmd
|
module runcmd
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
@[if runcmd_trace ?]
|
@[if runcmd_trace ?]
|
||||||
fn printdbg(s string) {
|
fn printdbg(s string) {
|
||||||
eprintln('runcmd[pid=${v_getpid()}]: ${s}')
|
text := 'runcmd[pid=${v_getpid()}]: ${s}'
|
||||||
|
eprintln(text)
|
||||||
|
trace_file := $d('runcmd_trace_file', '')
|
||||||
|
if trace_file != '' {
|
||||||
|
file := os.open_append(trace_file) or { return }
|
||||||
|
file.write_string(text + '\n') or {}
|
||||||
|
file.flush()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import io.string_reader
|
import io
|
||||||
import rand
|
import rand
|
||||||
import runcmd
|
import runcmd
|
||||||
import time
|
import time
|
||||||
@@ -20,7 +20,7 @@ fn main() {
|
|||||||
mut child_stdout := cmd.stdout()!
|
mut child_stdout := cmd.stdout()!
|
||||||
|
|
||||||
// Prepare reader to store command output.
|
// Prepare reader to store command output.
|
||||||
mut output := string_reader.StringReader.new(reader: child_stdout)
|
mut output := io.new_buffered_reader(reader: child_stdout)
|
||||||
|
|
||||||
// Start stdout reading in a coroutine.
|
// Start stdout reading in a coroutine.
|
||||||
//
|
//
|
||||||
@@ -56,4 +56,6 @@ fn main() {
|
|||||||
|
|
||||||
// wait() will close the child stdout file desciptor by itself.
|
// wait() will close the child stdout file desciptor by itself.
|
||||||
cmd.wait()!
|
cmd.wait()!
|
||||||
|
|
||||||
|
println('Child state: ${cmd.state}')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import io.string_reader
|
import io
|
||||||
import runcmd
|
import runcmd
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// Prepare command.
|
// Prepare command.
|
||||||
mut cmd := runcmd.new('sh', '-c', r'for i in {1..5}; do echo line $i; sleep .5; done; echo finish!')
|
mut cmd := runcmd.new('sh', '-c',
|
||||||
|
r'for i in {1..5}; do echo line $i; sleep .5; done; echo finish!')
|
||||||
|
|
||||||
// This is required to captute standart I/O streams.
|
// This is required to captute standart I/O streams.
|
||||||
cmd.redirect_stdio = true
|
cmd.redirect_stdio = true
|
||||||
@@ -14,7 +15,7 @@ fn main() {
|
|||||||
|
|
||||||
// Setup StringReader with stdout input. Note the cmd.stdout()! call, it
|
// Setup StringReader with stdout input. Note the cmd.stdout()! call, it
|
||||||
// returns the io.Reader interface and reads child process stdout file descriptor.
|
// returns the io.Reader interface and reads child process stdout file descriptor.
|
||||||
mut reader := string_reader.StringReader.new(reader: cmd.stdout()!)
|
mut reader := io.new_buffered_reader(reader: cmd.stdout()!)
|
||||||
|
|
||||||
// Read sdtout line by line until EOF.
|
// Read sdtout line by line until EOF.
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
|
import io
|
||||||
import strings
|
import strings
|
||||||
import runcmd
|
import runcmd
|
||||||
|
|
||||||
|
struct ByteBuffer {
|
||||||
|
bytes []u8
|
||||||
|
mut:
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
// read reads `buf.len` bytes from internal bytes buffer and returns number of bytes read.
|
||||||
|
pub fn (mut b ByteBuffer) read(mut buf []u8) !int {
|
||||||
|
if b.pos >= b.bytes.len {
|
||||||
|
return io.Eof{}
|
||||||
|
}
|
||||||
|
n := copy(mut buf, b.bytes[b.pos..])
|
||||||
|
b.pos += n
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
input := 'Hello from parent process!'
|
input := 'Hello from parent process!'
|
||||||
|
|
||||||
@@ -8,8 +25,10 @@ fn main() {
|
|||||||
// * `reader` reads input from the parent process; it will be copied to the
|
// * `reader` reads input from the parent process; it will be copied to the
|
||||||
// standard input of the child process.
|
// standard input of the child process.
|
||||||
// * `writer` accepts data from the child process; it will be copied from the
|
// * `writer` accepts data from the child process; it will be copied from the
|
||||||
// standard output of the child process.
|
// standard output of the child process. This is optinal.
|
||||||
mut reader := runcmd.buffer(input.bytes())
|
mut reader := ByteBuffer{
|
||||||
|
bytes: input.bytes()
|
||||||
|
}
|
||||||
mut writer := strings.new_builder(4096)
|
mut writer := strings.new_builder(4096)
|
||||||
|
|
||||||
// Prepare the command.
|
// Prepare the command.
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ pub fn (s ProcessState) str() string {
|
|||||||
str = 'unknown'
|
str = 'unknown'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.status.coredump() {
|
if s.status.coredump() {
|
||||||
str += ' (core dumped)'
|
str += ' (core dumped)'
|
||||||
}
|
}
|
||||||
@@ -145,6 +146,7 @@ pub fn (mut p Process) start() !int {
|
|||||||
os.chdir(p.dir)!
|
os.chdir(p.dir)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printdbg('${@METHOD}: calling execve() with executable `${p.path}`')
|
||||||
os.execve(p.path, p.argv, env)!
|
os.execve(p.path, p.argv, env)!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Module {
|
Module {
|
||||||
name: 'runcmd'
|
name: 'runcmd'
|
||||||
description: 'Run external commands'
|
description: 'Run external commands'
|
||||||
version: '0.3.0'
|
version: '0.4.0'
|
||||||
license: 'Unlicense'
|
license: 'Unlicense'
|
||||||
repo_url: 'https://github.com/gechandesu/runcmd'
|
repo_url: 'https://github.com/gechandesu/runcmd'
|
||||||
dependencies: []
|
dependencies: []
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
module runcmd
|
module runcmd
|
||||||
|
|
||||||
@[trusted]
|
|
||||||
fn C.WIFSTOPPED(int) bool
|
fn C.WIFSTOPPED(int) bool
|
||||||
|
|
||||||
@[trusted]
|
|
||||||
fn C.WCOREDUMP(int) bool
|
fn C.WCOREDUMP(int) bool
|
||||||
|
|
||||||
@[trusted]
|
|
||||||
fn C.WIFCONTINUED(int) bool
|
fn C.WIFCONTINUED(int) bool
|
||||||
|
|
||||||
@[trusted]
|
|
||||||
fn C.WSTOPSIG(int) int
|
fn C.WSTOPSIG(int) int
|
||||||
|
|
||||||
// WaitStatus stores the result value of [wait(2)](https://www.man7.org/linux/man-pages/man2/wait.2.html) syscall.
|
// WaitStatus stores the result value of [wait(2)](https://www.man7.org/linux/man-pages/man2/wait.2.html) syscall.
|
||||||
|
|||||||
Reference in New Issue
Block a user