1
0
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:
Delyan Angelov
2020-11-16 18:32:50 +02:00
committed by GitHub
parent 8e473181ed
commit d633261a99
9 changed files with 701 additions and 135 deletions

44
vlib/os/fd.v Normal file
View 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
}

View File

@@ -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
View 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
View 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
View 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"')
}

View 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
}