mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
os: add Process (#6786)
This commit is contained in:
44
vlib/os/fd.v
Normal file
44
vlib/os/fd.v
Normal file
@@ -0,0 +1,44 @@
|
||||
module os
|
||||
|
||||
// file descriptor based operations:
|
||||
pub fn fd_close(fd int) int {
|
||||
return C.close(fd)
|
||||
}
|
||||
|
||||
pub fn fd_write(fd int, s string) {
|
||||
mut sp := s.str
|
||||
mut remaining := s.len
|
||||
for remaining > 0 {
|
||||
written := C.write(fd, sp, remaining)
|
||||
if written < 0 {
|
||||
return
|
||||
}
|
||||
remaining = remaining - written
|
||||
sp = unsafe {sp + written}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fd_slurp(fd int) []string {
|
||||
mut res := []string{}
|
||||
for {
|
||||
s, b := fd_read(fd, 4096)
|
||||
if b <= 0 {
|
||||
break
|
||||
}
|
||||
res << s
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
pub fn fd_read(fd int, maxbytes int) (string, int) {
|
||||
mut buf := malloc(maxbytes)
|
||||
nbytes := C.read(fd, buf, maxbytes)
|
||||
if nbytes < 0 {
|
||||
free(buf)
|
||||
return '', nbytes
|
||||
}
|
||||
unsafe {
|
||||
buf[nbytes] = 0
|
||||
}
|
||||
return tos(buf, nbytes), nbytes
|
||||
}
|
||||
@@ -8,8 +8,6 @@ struct C.dirent {
|
||||
|
||||
fn C.readdir(voidptr) &C.dirent
|
||||
|
||||
fn C.getpid() int
|
||||
|
||||
fn C.readlink() int
|
||||
|
||||
fn C.getline(voidptr, voidptr, voidptr) int
|
||||
@@ -24,10 +22,6 @@ fn C.fdopen(int, string) voidptr
|
||||
|
||||
fn C.CopyFile(&u32, &u32, int) int
|
||||
|
||||
fn C.fork() int
|
||||
|
||||
fn C.wait() int
|
||||
|
||||
// fn C.proc_pidpath(int, byteptr, int) int
|
||||
struct C.stat {
|
||||
st_size int
|
||||
|
||||
240
vlib/os/process.v
Normal file
240
vlib/os/process.v
Normal file
@@ -0,0 +1,240 @@
|
||||
module os
|
||||
|
||||
// ProcessState.not_started - the process has not yet started
|
||||
// ProcessState.running - the process is currently running
|
||||
// ProcessState.stopped - the process was running, but was stopped temporarily
|
||||
// ProcessState.exited - the process has finished/exited
|
||||
// ProcessState.aborted - the process was terminated by a signal
|
||||
pub enum ProcessState {
|
||||
not_started
|
||||
running
|
||||
stopped
|
||||
exited
|
||||
aborted
|
||||
}
|
||||
|
||||
[ref_only]
|
||||
pub struct Process {
|
||||
pub:
|
||||
filename string // the process's command file path
|
||||
pub mut:
|
||||
pid int // the PID of the process
|
||||
code int = -1
|
||||
// the exit code of the process, != -1 *only* when status is .exited *and* the process was not aborted
|
||||
status ProcessState = .not_started
|
||||
// the current status of the process
|
||||
err string // if the process fails, contains the reason why
|
||||
args []string // the arguments that the command takes
|
||||
env_is_custom bool // true, when the environment was customized with .set_environment
|
||||
env []string // the environment with which the process was started
|
||||
use_stdio_ctl bool // when true, then you can use p.stdin_write(), p.stdout_slurp() and p.stderr_slurp()
|
||||
stdio_fd [3]int
|
||||
}
|
||||
|
||||
// new_process - create a new process descriptor
|
||||
// NB: new does NOT start the new process.
|
||||
// That is done because you may want to customize it first,
|
||||
// by calling different set_ methods on it.
|
||||
// In order to start it, call p.run() or p.wait()
|
||||
pub fn new_process(filename string) &Process {
|
||||
return &Process{
|
||||
filename: filename
|
||||
}
|
||||
}
|
||||
|
||||
// set_args - set the arguments for the new process
|
||||
pub fn (mut p Process) set_args(pargs []string) &Process {
|
||||
if p.status != .not_started {
|
||||
return p
|
||||
}
|
||||
p.args = pargs
|
||||
return p
|
||||
}
|
||||
|
||||
// set_environment - set a custom environment variable mapping for the new process
|
||||
pub fn (mut p Process) set_environment(envs map[string]string) &Process {
|
||||
if p.status != .not_started {
|
||||
return p
|
||||
}
|
||||
p.env_is_custom = true
|
||||
p.env = []string{}
|
||||
for k, v in envs {
|
||||
p.env << '$k=$v'
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// run - starts the new process
|
||||
pub fn (mut p Process) run() &Process {
|
||||
if p.status != .not_started {
|
||||
return p
|
||||
}
|
||||
p._spawn()
|
||||
return p
|
||||
}
|
||||
|
||||
// signal_kill - kills the process, after that it is no longer running
|
||||
pub fn (mut p Process) signal_kill() &Process {
|
||||
if p.status !in [.running, .stopped] {
|
||||
return p
|
||||
}
|
||||
p._signal_kill()
|
||||
p.status = .aborted
|
||||
return p
|
||||
}
|
||||
|
||||
// signal_stop - stops the process, you can resume it with p.signal_continue()
|
||||
pub fn (mut p Process) signal_stop() &Process {
|
||||
if p.status != .running {
|
||||
return p
|
||||
}
|
||||
p._signal_stop()
|
||||
p.status = .stopped
|
||||
return p
|
||||
}
|
||||
|
||||
// signal_continue - tell a stopped process to continue/resume its work
|
||||
pub fn (mut p Process) signal_continue() &Process {
|
||||
if p.status != .stopped {
|
||||
return p
|
||||
}
|
||||
p._signal_continue()
|
||||
p.status = .running
|
||||
return p
|
||||
}
|
||||
|
||||
// wait - wait for a process to finish.
|
||||
// NB: You have to call p.wait(), otherwise a finished process
|
||||
// would get to a zombie state, and its resources will not get
|
||||
// released fully, until its parent process exits.
|
||||
// NB: This call will block the calling process until the child
|
||||
// process is finished.
|
||||
pub fn (mut p Process) wait() &Process {
|
||||
if p.status == .not_started {
|
||||
p._spawn()
|
||||
}
|
||||
if p.status !in [.running, .stopped] {
|
||||
return p
|
||||
}
|
||||
p._wait()
|
||||
return p
|
||||
}
|
||||
|
||||
//
|
||||
// _spawn - should not be called directly, but only by p.run()/p.wait() .
|
||||
// It encapsulates the fork/execve mechanism that allows the
|
||||
// asynchronous starting of the new child process.
|
||||
fn (mut p Process) _spawn() int {
|
||||
if !p.env_is_custom {
|
||||
p.env = []string{}
|
||||
current_environment := environ()
|
||||
for k, v in current_environment {
|
||||
p.env << '$k=$v'
|
||||
}
|
||||
}
|
||||
mut pid := 0
|
||||
$if windows {
|
||||
pid = p.win_spawn_process()
|
||||
} $else {
|
||||
pid = p.unix_spawn_process()
|
||||
}
|
||||
p.pid = pid
|
||||
p.status = .running
|
||||
return 0
|
||||
}
|
||||
|
||||
// is_alive - query whether the process p.pid is still alive
|
||||
pub fn (mut p Process) is_alive() bool {
|
||||
if p.status in [.running, .stopped] {
|
||||
return p._is_alive()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//
|
||||
pub fn (mut p Process) set_redirect_stdio() &Process {
|
||||
p.use_stdio_ctl = true
|
||||
return p
|
||||
}
|
||||
|
||||
pub fn (mut p Process) stdin_write(s string) {
|
||||
p._check_redirection_call('stdin_write')
|
||||
fd_write(p.stdio_fd[0], s)
|
||||
}
|
||||
|
||||
pub fn (mut p Process) stdout_slurp() string {
|
||||
p._check_redirection_call('stdout_slurp')
|
||||
return fd_slurp(p.stdio_fd[1]).join('')
|
||||
}
|
||||
|
||||
pub fn (mut p Process) stderr_slurp() string {
|
||||
p._check_redirection_call('stderr_slurp')
|
||||
return fd_slurp(p.stdio_fd[2]).join('')
|
||||
}
|
||||
|
||||
pub fn (mut p Process) stdout_read() string {
|
||||
p._check_redirection_call('stdout_read')
|
||||
s, _ := fd_read(p.stdio_fd[1], 4096)
|
||||
return s
|
||||
}
|
||||
|
||||
pub fn (mut p Process) stderr_read() string {
|
||||
p._check_redirection_call('stderr_read')
|
||||
s, _ := fd_read(p.stdio_fd[2], 4096)
|
||||
return s
|
||||
}
|
||||
|
||||
// _check_redirection_call - should be called just by stdxxx methods
|
||||
fn (mut p Process) _check_redirection_call(fn_name string) {
|
||||
if !p.use_stdio_ctl {
|
||||
panic('Call p.set_redirect_stdio() before calling p.$fn_name')
|
||||
}
|
||||
if p.status == .not_started {
|
||||
panic('Call p.${fn_name}() after you have called p.run()')
|
||||
}
|
||||
}
|
||||
|
||||
// _signal_stop - should not be called directly, except by p.signal_stop
|
||||
fn (mut p Process) _signal_stop() {
|
||||
$if windows {
|
||||
p.win_stop_process()
|
||||
} $else {
|
||||
p.unix_stop_process()
|
||||
}
|
||||
}
|
||||
|
||||
// _signal_continue - should not be called directly, just by p.signal_continue
|
||||
fn (mut p Process) _signal_continue() {
|
||||
$if windows {
|
||||
p.win_resume_process()
|
||||
} $else {
|
||||
p.unix_resume_process()
|
||||
}
|
||||
}
|
||||
|
||||
// _signal_kill - should not be called directly, except by p.signal_kill
|
||||
fn (mut p Process) _signal_kill() {
|
||||
$if windows {
|
||||
p.win_kill_process()
|
||||
} $else {
|
||||
p.unix_kill_process()
|
||||
}
|
||||
}
|
||||
|
||||
// _wait - should not be called directly, except by p.wait()
|
||||
fn (mut p Process) _wait() {
|
||||
$if windows {
|
||||
p.win_wait()
|
||||
} $else {
|
||||
p.unix_wait()
|
||||
}
|
||||
}
|
||||
|
||||
// _is_alive - should not be called directly, except by p.is_alive()
|
||||
fn (mut p Process) _is_alive() bool {
|
||||
$if windows {
|
||||
return p.win_is_alive()
|
||||
} $else {
|
||||
return p.unix_is_alive()
|
||||
}
|
||||
}
|
||||
134
vlib/os/process_nix.c.v
Normal file
134
vlib/os/process_nix.c.v
Normal file
@@ -0,0 +1,134 @@
|
||||
module os
|
||||
|
||||
fn (mut p Process) unix_spawn_process() int {
|
||||
mut pipeset := [6]int{}
|
||||
if p.use_stdio_ctl {
|
||||
C.pipe(&pipeset[0]) // pipe read end 0 <- 1 pipe write end
|
||||
C.pipe(&pipeset[2]) // pipe read end 2 <- 3 pipe write end
|
||||
C.pipe(&pipeset[4]) // pipe read end 4 <- 5 pipe write end
|
||||
}
|
||||
pid := fork()
|
||||
if pid != 0 {
|
||||
// This is the parent process after the fork.
|
||||
// NB: pid contains the process ID of the child process
|
||||
if p.use_stdio_ctl {
|
||||
p.stdio_fd[0] = pipeset[1] // store the write end of child's in
|
||||
p.stdio_fd[1] = pipeset[2] // store the read end of child's out
|
||||
p.stdio_fd[2] = pipeset[4] // store the read end of child's err
|
||||
// close the rest of the pipe fds, the parent does not need them
|
||||
fd_close(pipeset[0])
|
||||
fd_close(pipeset[3])
|
||||
fd_close(pipeset[5])
|
||||
}
|
||||
return pid
|
||||
}
|
||||
//
|
||||
// Here, we are in the child process.
|
||||
// It still shares file descriptors with the parent process,
|
||||
// but it is otherwise independant and can do stuff *without*
|
||||
// affecting the parent process.
|
||||
if p.use_stdio_ctl {
|
||||
// Redirect the child standart in/out/err to the pipes that
|
||||
// were created in the parent.
|
||||
// Close the parent's pipe fds, the child do not need them:
|
||||
fd_close(pipeset[1])
|
||||
fd_close(pipeset[2])
|
||||
fd_close(pipeset[4])
|
||||
// redirect the pipe fds to the child's in/out/err fds:
|
||||
C.dup2(pipeset[0], 0)
|
||||
C.dup2(pipeset[3], 1)
|
||||
C.dup2(pipeset[5], 2)
|
||||
// close the pipe fdsx after the redirection
|
||||
fd_close(pipeset[0])
|
||||
fd_close(pipeset[3])
|
||||
fd_close(pipeset[5])
|
||||
}
|
||||
mut cargv := []charptr{}
|
||||
mut cenvs := []charptr{}
|
||||
cargv << p.filename.str
|
||||
for i in 0 .. p.args.len {
|
||||
cargv << p.args[i].str
|
||||
}
|
||||
for i in 0 .. p.env.len {
|
||||
cenvs << p.env[i].str
|
||||
}
|
||||
cargv << charptr(0)
|
||||
cenvs << charptr(0)
|
||||
C.execve(p.filename.str, cargv.data, cenvs.data)
|
||||
// NB: normally execve does not return at all.
|
||||
// If it returns, then something went wrong...
|
||||
eprintln(posix_get_error_msg(C.errno))
|
||||
exit(1)
|
||||
return 0
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_stop_process() {
|
||||
C.kill(p.pid, C.SIGSTOP)
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_resume_process() {
|
||||
C.kill(p.pid, C.SIGCONT)
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_kill_process() {
|
||||
C.kill(p.pid, C.SIGKILL)
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_wait() {
|
||||
cstatus := 0
|
||||
ret := C.waitpid(p.pid, &cstatus, 0)
|
||||
if ret == -1 {
|
||||
p.err = posix_get_error_msg(C.errno)
|
||||
return
|
||||
}
|
||||
pret, is_signaled := posix_wait4_to_exit_status(cstatus)
|
||||
if is_signaled {
|
||||
p.status = .aborted
|
||||
p.err = 'Terminated by signal ${ret:2d} (${sigint_to_signal_name(pret)})'
|
||||
} else {
|
||||
p.status = .exited
|
||||
}
|
||||
p.code = pret
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_is_alive() bool {
|
||||
cstatus := 0
|
||||
ret := C.waitpid(p.pid, &cstatus, C.WNOHANG)
|
||||
if ret == -1 {
|
||||
p.err = posix_get_error_msg(C.errno)
|
||||
return false
|
||||
}
|
||||
if ret == 0 {
|
||||
return true
|
||||
}
|
||||
pret, is_signaled := posix_wait4_to_exit_status(cstatus)
|
||||
if is_signaled {
|
||||
p.status = .aborted
|
||||
p.err = 'Terminated by signal ${ret:2d} (${sigint_to_signal_name(pret)})'
|
||||
} else {
|
||||
p.status = .exited
|
||||
}
|
||||
p.code = pret
|
||||
return false
|
||||
}
|
||||
|
||||
// these are here to make v_win.c/v.c generation work in all cases:
|
||||
fn (mut p Process) win_spawn_process() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
fn (mut p Process) win_stop_process() {
|
||||
}
|
||||
|
||||
fn (mut p Process) win_resume_process() {
|
||||
}
|
||||
|
||||
fn (mut p Process) win_kill_process() {
|
||||
}
|
||||
|
||||
fn (mut p Process) win_wait() {
|
||||
}
|
||||
|
||||
fn (mut p Process) win_is_alive() bool {
|
||||
return false
|
||||
}
|
||||
63
vlib/os/process_test.v
Normal file
63
vlib/os/process_test.v
Normal file
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
fn test_getpid() {
|
||||
pid := os.getpid()
|
||||
eprintln('current pid: $pid')
|
||||
assert pid != 0
|
||||
}
|
||||
|
||||
fn test_run() {
|
||||
if os.user_os() == 'windows' {
|
||||
return
|
||||
}
|
||||
//
|
||||
mut p := os.new_process('/bin/sleep')
|
||||
p.set_args(['0.2'])
|
||||
p.run()
|
||||
assert p.status == .running
|
||||
assert p.pid > 0
|
||||
assert p.pid != os.getpid()
|
||||
mut i := 0
|
||||
for {
|
||||
if !p.is_alive() {
|
||||
break
|
||||
}
|
||||
os.system('ps -opid= -oppid= -ouser= -onice= -of= -ovsz= -orss= -otime= -oargs= -p $p.pid')
|
||||
time.sleep_ms(50)
|
||||
i++
|
||||
}
|
||||
p.wait()
|
||||
assert p.code == 0
|
||||
assert p.status == .exited
|
||||
//
|
||||
eprintln('polling iterations: $i')
|
||||
assert i > 1
|
||||
assert i < 20
|
||||
}
|
||||
|
||||
fn test_wait() {
|
||||
if os.user_os() == 'windows' {
|
||||
return
|
||||
}
|
||||
mut p := os.new_process('/bin/date')
|
||||
p.wait()
|
||||
assert p.pid != os.getpid()
|
||||
assert p.code == 0
|
||||
assert p.status == .exited
|
||||
}
|
||||
|
||||
fn test_slurping_output() {
|
||||
if os.user_os() == 'windows' {
|
||||
return
|
||||
}
|
||||
mut p := os.new_process('/bin/date')
|
||||
p.set_redirect_stdio()
|
||||
p.wait()
|
||||
assert p.code == 0
|
||||
assert p.status == .exited
|
||||
output := p.stdout_slurp().trim_space()
|
||||
errors := p.stderr_slurp().trim_space()
|
||||
eprintln('p output: "$output"')
|
||||
eprintln('p errors: "$errors"')
|
||||
}
|
||||
51
vlib/os/process_windows.c.v
Normal file
51
vlib/os/process_windows.c.v
Normal file
@@ -0,0 +1,51 @@
|
||||
module os
|
||||
|
||||
fn (mut p Process) win_spawn_process() int {
|
||||
eprintln('TODO implement waiting for a process on windows')
|
||||
return 12345
|
||||
}
|
||||
|
||||
fn (mut p Process) win_stop_process() {
|
||||
eprintln('TODO implement stopping a process on windows')
|
||||
}
|
||||
|
||||
fn (mut p Process) win_resume_process() {
|
||||
eprintln('TODO implement resuming a process on windows')
|
||||
}
|
||||
|
||||
fn (mut p Process) win_kill_process() {
|
||||
eprintln('TODO implement killing a process on windows')
|
||||
}
|
||||
|
||||
fn (mut p Process) win_wait() {
|
||||
eprintln('TODO implement waiting for a process on windows')
|
||||
p.status = .exited
|
||||
p.code = 0
|
||||
}
|
||||
|
||||
fn (mut p Process) win_is_alive() bool {
|
||||
eprintln('TODO implement checking whether the process is still alive on windows')
|
||||
return false
|
||||
}
|
||||
|
||||
//
|
||||
// these are here to make v_win.c/v.c generation work in all cases:
|
||||
fn (mut p Process) unix_spawn_process() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_stop_process() {
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_resume_process() {
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_kill_process() {
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_wait() {
|
||||
}
|
||||
|
||||
fn (mut p Process) unix_is_alive() bool {
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user