17 Commits
Author SHA1 Message Date
ge 604b9e2d42 chore,ci: Run v fmt -w .; Use latest V in CI 2026-06-01 00:04:58 +03:00
ge ac516c7b6c dbg: Better logging, -d runcmd_trace_file=FILEPATH support 2026-06-01 00:01:32 +03:00
ge 5636f29acd Remove unnecessary attributes 2026-05-31 22:41:12 +03:00
ge 6c3f40179b Revert "fix: Temporary disable WaitStatus core dump, continued and stopped states handling due https://github.com/vlang/v/issues/27098 bug"
This reverts commit 7390af4699.
2026-05-31 22:37:42 +03:00
ge 7390af4699 fix: Temporary disable WaitStatus core dump, continued and stopped states handling due https://github.com/vlang/v/issues/27098 bug 2026-05-12 20:06:50 +03:00
ge 8286af90eb mod: bump version 2026-01-29 04:10:09 +03:00
ge 14864ba992 examples: Remove io.string_reader 2026-01-29 04:09:33 +03:00
ge c62722cba9 readme: upd 2026-01-29 04:08:44 +03:00
ge 7acab2165e cmd: Use absolute Command.dir 2026-01-29 03:40:42 +03:00
ge 8e7d757b3b breaking: revome ByteBuffer from API 2026-01-29 03:34:10 +03:00
ge a43070aa95 mod: bump version 2026-01-08 09:58:26 +03:00
ge fdc57e846a examples: add command_with_chroot.c.v 2026-01-08 09:57:02 +03:00
ge e4c4c9ef87 feat,breaking: add pre_exec_hooks support, rework Process hooks 2026-01-08 09:56:47 +03:00
ge a3b5d4def5 readme: upd 2026-01-08 09:55:32 +03:00
ge 4ccc80f4a5 doc: fix docs for look_path/1 2026-01-08 09:55:04 +03:00
ge 21ebb3dbfa fix: improve error handling in Process 2026-01-08 05:39:19 +03:00
ge 04faf33542 examples: update command_with_timeout.v, remove io.string_reader from write_to_child_string.v 2026-01-08 05:12:41 +03:00
15 changed files with 226 additions and 115 deletions
+2 -3
View File
@@ -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: |
+2 -3
View File
@@ -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: |
+1 -2
View File
@@ -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}'`. Only stdout and exit_code are available in Result. * `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.
-26
View File
@@ -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
}
+47 -26
View File
@@ -27,9 +27,9 @@ pub mut:
// `maps` module: `maps.merge(os.environ(), {'MYENV': 'value'})` // `maps` module: `maps.merge(os.environ(), {'MYENV': 'value'})`
env map[string]string env map[string]string
// dir specifies the current working directory for the child // dir specifies the working directory for the child process.
// process. If not specified, the current working directory will // If not specified, the current working directory of parent
// be used. // will be used.
dir string dir string
// If true create pipes for standart in/out/err streams and // If true create pipes for standart in/out/err streams and
@@ -69,6 +69,11 @@ pub mut:
// if context is timed out or canceled. // if context is timed out or canceled.
cancel ?CommandCancelFn cancel ?CommandCancelFn
// pre_exec_hooks will be called before starting the command in
// the child process. Hooks can be used to modify a child's envi-
// ronment, for example to perform a chroot.
pre_exec_hooks []ProcessHookFn
// process holds the underlying Process once started. // process holds the underlying Process once started.
process ?&Process process ?&Process
@@ -94,7 +99,7 @@ mut:
stdio_copy_fns []IOCopyFn stdio_copy_fns []IOCopyFn
} }
// run starts a specified command and waits for it. After call see the .state // run starts a specified command and waits for it. After call see the `.state`
// value to get finished process identifier, exit status and other attributes. // value to get finished process identifier, exit status and other attributes.
// `run()` is shorthand for: // `run()` is shorthand for:
// ```v // ```v
@@ -110,12 +115,15 @@ pub fn (mut c Command) run() ! {
// status is non-zero `ExitError` error is returned. // status is non-zero `ExitError` error is returned.
// Example: // Example:
// ```v // ```v
// import runcmd
//
// mut okcmd := runcmd.new('sh', '-c', 'echo Hello, World!') // mut okcmd := runcmd.new('sh', '-c', 'echo Hello, World!')
// output := okcmd.output()! // ok_out := okcmd.output()!
// println(ok_out)
// // Hello, World! // // Hello, World!
// //
// mut badcmd := runcmd.new('sh', '-c', 'echo -n Error! >&2; false') // mut badcmd := runcmd.new('sh', '-c', 'echo -n Error! >&2; false')
// output := badcmd.output() or { // bad_out := badcmd.output() or {
// if err is runcmd.ExitError { // if err is runcmd.ExitError {
// eprintln(err) // eprintln(err)
// exit(err.code()) // exit(err.code())
@@ -124,14 +132,15 @@ pub fn (mut c Command) run() ! {
// panic(err) // panic(err)
// } // }
// } // }
// println(bad_out)
// // &runcmd.ExitError{ // // &runcmd.ExitError{
// // state: exit status 1 // // state: exit status 1
// // stderr: 'Error!' // // stderr: 'Error!'
// // } // // }
// ``` // ```
pub fn (mut c Command) output() !string { pub fn (mut c Command) output() !string {
mut out := strings.new_builder(4096) mut out := strings.new_builder(2048)
mut err := strings.new_builder(4096) mut err := strings.new_builder(2048)
c.redirect_stdio = true c.redirect_stdio = true
c.stdout = out c.stdout = out
c.stderr = err c.stderr = err
@@ -152,8 +161,10 @@ pub fn (mut c Command) output() !string {
// reading from the corresponding file descriptors is done concurrently. // reading from the corresponding file descriptors is done concurrently.
// Example: // Example:
// ```v // ```v
// import runcmd
// mut cmd := runcmd.new('sh', '-c', 'echo Hello, STDOUT!; echo Hello, STDERR! >&2') // mut cmd := runcmd.new('sh', '-c', 'echo Hello, STDOUT!; echo Hello, STDERR! >&2')
// output := cmd.combined_output()! // output := cmd.combined_output()!
// println(output)
// // Hello, STDOUT! // // Hello, STDOUT!
// // Hello, STDERR! // // Hello, STDERR!
// ``` // ```
@@ -183,7 +194,7 @@ pub fn (mut c Command) start() !int {
pipes[2] = pipe()! // stderr pipes[2] = pipe()! // stderr
} }
post_fork_parent_cb := fn [mut c, pipes] (mut p Process) ! { parent_pipes_hook := fn [mut c, pipes] (mut p Process) ! {
if !c.redirect_stdio { if !c.redirect_stdio {
return return
} }
@@ -195,7 +206,8 @@ pub fn (mut c Command) start() !int {
fd_close(pipes[2].w)! fd_close(pipes[2].w)!
} }
post_fork_child_cb := fn [mut c, pipes] (mut p Process) ! { child_pipes_hook := fn [mut c, pipes] (mut p Process) ! {
printdbg('Command.start: executing child pipes hook...')
if !c.redirect_stdio { if !c.redirect_stdio {
return return
} }
@@ -210,24 +222,27 @@ pub fn (mut c Command) start() !int {
fd_close(pipes[2].w)! fd_close(pipes[2].w)!
} }
mut pre_exec_hooks := [child_pipes_hook]
pre_exec_hooks << c.pre_exec_hooks
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')!
@@ -236,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')!
@@ -247,14 +262,15 @@ pub fn (mut c Command) start() !int {
// Prepare and start child process. // Prepare and start child process.
path := look_path(c.path)! path := look_path(c.path)!
printdbg('${@METHOD}: executable found: ${path}')
c.path = path c.path = path
c.process = &Process{ c.process = &Process{
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: if c.dir == '' { os.getwd() } else { c.dir } dir: os.abs_path(c.dir)
post_fork_parent_cb: post_fork_parent_cb post_fork: [parent_pipes_hook]
post_fork_child_cb: post_fork_child_cb pre_exec: pre_exec_hooks
} }
mut pid := -1 mut pid := -1
@@ -267,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)
} }
} }
@@ -335,14 +353,14 @@ pub fn (mut c Command) release() ! {
} }
// stdin returns an open file descriptor associated with the standard // stdin returns an open file descriptor associated with the standard
// input stream of the child process. This descriptor is writable only // input stream of the child process. This descriptor is write-only for
// by the parent process. // the parent process.
pub fn (c Command) stdin() !WriteFd { pub fn (c Command) stdin() !WriteFd {
return if c.stdio[0] != -1 { return if c.stdio[0] != -1 {
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)
} }
} }
@@ -354,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)
} }
} }
@@ -366,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)
} }
} }
@@ -396,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')
} }
} }
+10 -1
View File
@@ -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()
}
} }
+68
View File
@@ -0,0 +1,68 @@
/*
Note: chroot() call requires privilege escalation in the operating system.
Therefore, to run this example, run: `sudo v run command_with_chroot.c.v`
*/
import os
import runcmd
import term
fn C.chroot(&char) i32
fn main() {
// Create new root filesystem for demonstration.
new_root := '/tmp/new_root'
// Create dirtree and copy `ls` utility with shared objects...
paths := {
0: os.join_path(new_root, 'usr', 'bin')
1: os.join_path(new_root, 'usr', 'lib')
2: os.join_path(new_root, 'lib64')
}
for _, path in paths {
os.mkdir_all(path)!
}
os.cp('/usr/bin/ls', paths[0])!
os.cp('/usr/lib/libcap.so.2', paths[1])!
os.cp('/usr/lib/libc.so.6', paths[1])!
os.cp('/lib64/ld-linux-x86-64.so.2', paths[2])!
// Create a test file in the new root.
os.write_file(os.join_path_single(new_root, 'HELLO_FROM_CHROOT'), 'TEST')!
// Cleanup demo root filesystem at exit.
defer {
os.rmdir_all(new_root) or {}
}
// Prepare the command.
mut cmd := runcmd.new('ls', '-alFh', '/')
// Add pre-exec hook to perform chroot().
cmd.pre_exec_hooks << fn [new_root] (mut p runcmd.Process) ! {
if C.chroot(&char(new_root.str)) == -1 {
return os.last_error()
}
}
// Run command and read its output.
out := cmd.output() or {
if err is runcmd.ExitError {
eprintln(err)
exit(err.code())
} else {
panic(err)
}
}
// Expected output:
//
// total 4.0K
// drwxr-xr-x 4 0 0 100 Jan 8 06:39 ./
// drwxr-xr-x 4 0 0 100 Jan 8 06:39 ../
// -rw-r--r-- 1 0 0 4 Jan 8 06:39 HELLO_FROM_CHROOT
// drwxr-xr-x 2 0 0 60 Jan 8 06:39 lib64/
// drwxr-xr-x 4 0 0 80 Jan 8 06:39 usr/
println('Command output: ${term.yellow(out)}')
println('Child state: ${cmd.state}')
}
+4 -3
View File
@@ -11,16 +11,17 @@ fn main() {
mut cmd := runcmd.with_context(ctx, 'sleep', '120') mut cmd := runcmd.with_context(ctx, 'sleep', '120')
// Start a command. // Start a command.
started := time.now()
println('Start command at ${started}')
cmd.start()! cmd.start()!
started := time.now()
println('Command started at ${started}')
// Wait for command. // Wait for command.
cmd.wait()! cmd.wait()!
// The `sleep 120` command would run for two minutes without a timeout. // The `sleep 120` command would run for two minutes without a timeout.
// But in this example, it will time out after 10 seconds. // But in this example, it will time out after 10 seconds.
println('Command finished after ${time.now() - started}') finished := time.now()
println('Command finished at ${finished} after ${finished - started}')
// Since command has been terminated, the state would be: `signal: 15 (SIGTERM)` // Since command has been terminated, the state would be: `signal: 15 (SIGTERM)`
println('Child state: ${cmd.state}') println('Child state: ${cmd.state}')
+4 -2
View File
@@ -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}')
} }
+4 -3
View File
@@ -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 {
+21 -4
View File
@@ -1,17 +1,34 @@
import io.string_reader 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!'
// Prepare reader and writer. // Prepare reader and writer.
//
// * `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 := string_reader.StringReader.new(reader: runcmd.buffer(input.bytes()), source: input) mut reader := ByteBuffer{
bytes: input.bytes()
}
mut writer := strings.new_builder(4096) mut writer := strings.new_builder(4096)
// Prepare the command. // Prepare the command.
+46 -22
View File
@@ -2,7 +2,7 @@ module runcmd
import os import os
pub type ProcCallbackFn = fn (mut p Process) ! pub type ProcessHookFn = fn (mut p Process) !
pub struct Process { pub struct Process {
pub: pub:
@@ -18,14 +18,13 @@ pub:
// Working directory for the child process. // Working directory for the child process.
dir string dir string
// The *_cb fields stores callback functions that will be executed respectively: // The pre_* and post_* fields store the functions that will be executed, respectively:
// - before calling fork(); // - before fork() call in the parent process;
// - after calling fork() in the parent process, until the function exits; // - after fork() call in the parent process;
// - after calling fork() in the child process, until the working directory is // - after fork() call and before execve() call in the child process.
// changed and execve() is called. pre_fork []ProcessHookFn
pre_fork_cb ProcCallbackFn = fn (mut p Process) ! {} post_fork []ProcessHookFn
post_fork_parent_cb ProcCallbackFn = fn (mut p Process) ! {} pre_exec []ProcessHookFn
post_fork_child_cb ProcCallbackFn = fn (mut p Process) ! {}
mut: mut:
pid int = -1 pid int = -1
} }
@@ -87,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)'
} }
@@ -98,34 +98,58 @@ pub fn (s ProcessState) str() string {
// [execve(3p)](https://man7.org/linux/man-pages/man3/exec.3p.html) // [execve(3p)](https://man7.org/linux/man-pages/man3/exec.3p.html)
// calls. Return value is the child process identifier. // calls. Return value is the child process identifier.
pub fn (mut p Process) start() !int { pub fn (mut p Process) start() !int {
printdbg('${@METHOD}: current pid before fork() = ${v_getpid()}') if p.pid != -1 {
printdbg('${@METHOD}: executing pre-fork callback') return error('runcmd: process already started')
p.pre_fork_cb(mut p)! }
printdbg('${@METHOD}: executing pre-fork hooks (parent)')
for hook in p.pre_fork {
hook(mut p)!
}
pid := os.fork() pid := os.fork()
p.pid = pid p.pid = pid
printdbg('${@METHOD}: pid after fork() = ${pid}') if pid == -1 {
return os.last_error()
}
printdbg('${@METHOD}: child pid = ${pid}')
if pid != 0 { if pid != 0 {
// //
// This is the parent process after the fork // This is the parent process
// //
printdbg('${@METHOD}: executing post-fork parent callback')
p.post_fork_parent_cb(mut p)! printdbg('${@METHOD}: executing post-fork hooks (parent)')
return pid for hook in p.post_fork {
hook(mut p)!
} }
} else {
// //
// This is the child process // This is the child process
// //
printdbg('${@METHOD}: executing post-fork child callback')
p.post_fork_child_cb(mut p)! printdbg('${@METHOD}: executing pre-exec hooks (child)')
if p.dir != '' { for hook in p.pre_exec {
os.chdir(p.dir)! hook(mut p)!
} }
mut env := []string{} mut env := []string{}
for k, v in p.env { for k, v in p.env {
env << k + '=' + v env << k + '=' + v
} }
// If the working directory change was performed in a pre-exec hook,
// then calling os.chdir() again should be avoided. So current working
// directory could be checked here.
if p.dir != '' && os.getwd() != 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)!
}
return pid return pid
} }
@@ -154,7 +178,7 @@ pub fn (p &Process) signal(sig os.Signal) ! {
} }
} }
// kill send SIGKILL to the child process. // kill sends SIGKILL to the child process.
pub fn (p &Process) kill() ! { pub fn (p &Process) kill() ! {
p.signal(.kill)! p.signal(.kill)!
} }
+4 -3
View File
@@ -33,9 +33,10 @@ pub fn is_present(cmd string) bool {
} }
// look_path returns the absolute path to executable file. cmd may be a command // look_path returns the absolute path to executable file. cmd may be a command
// name or filepath (relative or absolute). If the name contains a slash, then the // name or filepath (relative or absolute). The command is searched in PATH. If
// PATH search is not performed, instead the path will be resolved and the file // the name contains a slash, then the PATH search is not performed, instead the
// existence and its permissions will be checked (execution must be allowed). // path will be resolved and the file existence will be checked. In both cases
// the file must have the execute permission bit enabled.
// Note: To use executables located in the current working directory use './file' // Note: To use executables located in the current working directory use './file'
// instead of just 'file'. Searching for executable files in the current directory // instead of just 'file'. Searching for executable files in the current directory
// is disabled for security reasons. See https://go.dev/blog/path-security. // is disabled for security reasons. See https://go.dev/blog/path-security.
+1 -1
View File
@@ -1,7 +1,7 @@
Module { Module {
name: 'runcmd' name: 'runcmd'
description: 'Run external commands' description: 'Run external commands'
version: '0.2.1' version: '0.4.0'
license: 'Unlicense' license: 'Unlicense'
repo_url: 'https://github.com/gechandesu/runcmd' repo_url: 'https://github.com/gechandesu/runcmd'
dependencies: [] dependencies: []
-4
View File
@@ -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.