From d633261a99f0e289f122ade4b25aa9d9d6467ac8 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Mon, 16 Nov 2020 18:32:50 +0200 Subject: [PATCH] os: add Process (#6786) --- cmd/tools/test_os_process.v | 81 +++++++++++ vlib/builtin/cfns.c.v | 215 ++++++++++++--------------- vlib/os/fd.v | 44 ++++++ vlib/os/os_c.v | 6 - vlib/os/process.v | 240 +++++++++++++++++++++++++++++++ vlib/os/process_nix.c.v | 134 +++++++++++++++++ vlib/os/process_test.v | 63 ++++++++ vlib/os/process_windows.c.v | 51 +++++++ vlib/readline/readline_linux.c.v | 2 - 9 files changed, 701 insertions(+), 135 deletions(-) create mode 100644 cmd/tools/test_os_process.v create mode 100644 vlib/os/fd.v create mode 100644 vlib/os/process.v create mode 100644 vlib/os/process_nix.c.v create mode 100644 vlib/os/process_test.v create mode 100644 vlib/os/process_windows.c.v diff --git a/cmd/tools/test_os_process.v b/cmd/tools/test_os_process.v new file mode 100644 index 0000000000..fb01cbbe98 --- /dev/null +++ b/cmd/tools/test_os_process.v @@ -0,0 +1,81 @@ +module main + +import os +import time +import os.cmdline + +enum Target { + both + stderr + stdout + alternate +} + +fn s2target(s string) Target { + return match s { + 'both' { Target.both } + 'stderr' { Target.stderr } + 'alternate' { Target.alternate } + else { Target.stdout } + } +} + +struct Context { +mut: + timeout_ms int + period_ms int + exitcode int + target Target + omode Target + is_verbose bool +} + +fn (mut ctx Context) println(s string) { + if ctx.target == .alternate { + ctx.omode = if ctx.omode == .stderr { Target.stdout } else { Target.stderr } + } + if ctx.target in [.both, .stdout] || ctx.omode == .stdout { + println('stdout, $s') + } + if ctx.target in [.both, .stderr] || ctx.omode == .stderr { + eprintln('stderr, $s') + } +} + +fn do_timeout(c &Context) { + mut ctx := c + time.sleep_ms(ctx.timeout_ms) + exit(ctx.exitcode) +} + +fn main() { + mut ctx := Context{} + args := os.args[1..] + if '-h' in args || '--help' in args { + println("Usage: + test_os_process [-v] [-h] [-target stderr/stdout/both/alternate] [-exitcode 0] [-timeout_ms 1000] [-period_ms 100] + Prints lines periodically (-period_ms), to stdout/stderr (-target). + After a while (-timeout_ms), exit with (-exitcode). + This program is useful for platform independent testing + of child process/standart input/output control. + It is used in V\'s `os` module tests. +") + } + ctx.is_verbose = '-v' in args + ctx.target = s2target(cmdline.option(args, '-target', 'both')) + ctx.exitcode = cmdline.option(args, '-exitcode', '0').int() + ctx.timeout_ms = cmdline.option(args, '-timeout_ms', '1000').int() + ctx.period_ms = cmdline.option(args, '-period_ms', '100').int() + if ctx.target == .alternate { + ctx.omode = .stdout + } + if ctx.is_verbose { + eprintln('> args: $args | context: $ctx') + } + go do_timeout(&ctx) + for i := 1; true; i++ { + ctx.println('$i') + time.sleep_ms(ctx.period_ms) + } + time.sleep(100000) +} diff --git a/vlib/builtin/cfns.c.v b/vlib/builtin/cfns.c.v index 214189a48a..82dd3292aa 100644 --- a/vlib/builtin/cfns.c.v +++ b/vlib/builtin/cfns.c.v @@ -4,388 +4,307 @@ module builtin fn C.memcpy(byteptr, byteptr, int) voidptr fn C.memcmp(byteptr, byteptr, int) int -fn C.memmove(byteptr, byteptr, int) voidptr -fn C.calloc(int) byteptr -fn C.malloc(int) byteptr -fn C.realloc(a byteptr, b int) byteptr -fn C.free(ptr voidptr) -fn C.exit(code int) +fn C.memmove(byteptr, byteptr, int) voidptr + +fn C.calloc(int) byteptr + +fn C.malloc(int) byteptr + +fn C.realloc(a byteptr, b int) byteptr + +fn C.free(ptr voidptr) + +fn C.exit(code int) fn C.qsort(voidptr, int, int, qsort_callback_func) - fn C.sprintf(a ...voidptr) int - fn C.strlen(s charptr) int -fn C.sscanf(byteptr, byteptr,...byteptr) int +fn C.sscanf(byteptr, byteptr, ...byteptr) int fn C.isdigit(s byteptr) bool + // stdio.h fn C.popen(c charptr, t charptr) voidptr // fn C.backtrace(a &voidptr, size int) int -fn C.backtrace_symbols(a &voidptr, size int) &charptr + +fn C.backtrace_symbols(a &voidptr, size int) &charptr + fn C.backtrace_symbols_fd(a &voidptr, size int, fd int) // pub fn proc_pidpath(int, voidptr, int) int - fn C.realpath(charptr, charptr) &char - fn C.chmod(byteptr, int) int - fn C.printf(byteptr, ...byteptr) int fn C.puts(byteptr) int fn C.fputs(byteptr) int - fn C.fflush(byteptr) int + // TODO define args in these functions fn C.fseek() int - fn C.fopen() voidptr - fn C.fileno(voidptr) int - fn C.fwrite() int - fn C.fclose() int - fn C.pclose() int +// process execution, os.process: +fn C.getpid() int + fn C.system() int -fn C.posix_spawn(&int, charptr, voidptr, voidptr, &charptr, voidptr) int -fn C.waitpid(int, voidptr, int) int + +fn C.posix_spawn(child_pid &int, path charptr, file_actions voidptr, attrp voidptr, argv &charptr, envp &charptr) int + +fn C.posix_spawnp(child_pid &int, exefile charptr, file_actions voidptr, attrp voidptr, argv &charptr, envp &charptr) int + +fn C.execve(cmd_path charptr, args voidptr, envs voidptr) int + +fn C.fork() int + +fn C.wait(status &int) int + +fn C.waitpid(pid int, status &int, options int) int + +fn C.kill(pid int, sig int) int fn C.setenv(charptr) int - fn C.unsetenv(charptr) int - fn C.access() int - fn C.remove() int - fn C.rmdir() int - fn C.chdir() int - fn C.fread() int - fn C.rewind() int - fn C.stat(charptr) int - fn C.lstat() int - fn C.rename() int - fn C.fgets() int - fn C.memset() int - fn C.sigemptyset() int - fn C.getcwd() int - -fn C.signal() int - +fn C.signal(signal int, handlercb voidptr) voidptr fn C.mktime() int - fn C.gettimeofday() int - [trusted] fn C.sleep(int) int - fn C.usleep() int - fn C.opendir() voidptr - fn C.closedir() int - fn C.mkdir() int - fn C.srand() int - fn C.atof() int - fn C.tolower() int - fn C.toupper() int - [trusted] fn C.getchar() int - [trusted] fn C.strerror(int) charptr - fn C.snprintf() int - fn C.fprintf(byteptr, ...byteptr) - fn C.WIFEXITED() bool - fn C.WEXITSTATUS() int - fn C.WIFSIGNALED() bool - fn C.WTERMSIG() int - fn C.isatty() int - fn C.syscall() int - fn C.sysctl() int - fn C._fileno(int) int - fn C._get_osfhandle(fd int) C.intptr_t - fn C.GetModuleFileName() int + fn C.GetModuleFileNameW(hModule voidptr, lpFilename &u16, nSize u32) u32 - fn C.CreateFile() voidptr -fn C.CreateFileW(lpFilename &u16, dwDesiredAccess u32, dwShareMode u32, lpSecurityAttributes &u16, dwCreationDisposition u32, dwFlagsAndAttributes u32, hTemplateFile voidptr) u32 +fn C.CreateFileW(lpFilename &u16, dwDesiredAccess u32, dwShareMode u32, lpSecurityAttributes &u16, dwCreationDisposition u32, dwFlagsAndAttributes u32, hTemplateFile voidptr) u32 fn C.GetFinalPathNameByHandleW(hFile voidptr, lpFilePath &u16, nSize u32, dwFlags u32) int - fn C.CreatePipe(hReadPipe &voidptr, hWritePipe &voidptr, lpPipeAttributes voidptr, nSize u32) bool - fn C.SetHandleInformation(hObject voidptr, dwMask u32, dw_flags u32) bool - fn C.ExpandEnvironmentStringsW(lpSrc &u16, lpDst &u16, nSize u32) u32 - fn C.SendMessageTimeout() u32 -fn C.SendMessageTimeoutW(hWnd voidptr, Msg u32, wParam &u16, lParam &u32, fuFlags u32, uTimeout u32, lpdwResult &u64) u32 +fn C.SendMessageTimeoutW(hWnd voidptr, Msg u32, wParam &u16, lParam &u32, fuFlags u32, uTimeout u32, lpdwResult &u64) u32 fn C.CreateProcessW(lpApplicationName &u16, lpCommandLine &u16, lpProcessAttributes voidptr, lpThreadAttributes voidptr, bInheritHandles bool, dwCreationFlags u32, lpEnvironment voidptr, lpCurrentDirectory &u16, lpStartupInfo voidptr, lpProcessInformation voidptr) bool - fn C.ReadFile(hFile voidptr, lpBuffer voidptr, nNumberOfBytesToRead u32, lpNumberOfBytesRead C.LPDWORD, lpOverlapped voidptr) bool - fn C.GetFileAttributesW(lpFileName byteptr) u32 - fn C.RegQueryValueEx() voidptr + fn C.RegQueryValueExW(hKey voidptr, lpValueName &u16, lp_reserved &u32, lpType &u32, lpData byteptr, lpcbData &u32) int - fn C.RegOpenKeyEx() voidptr + fn C.RegOpenKeyExW(hKey voidptr, lpSubKey &u16, ulOptions u32, samDesired u32, phkResult voidptr) int - fn C.RegSetValueEx() voidptr -fn C.RegSetValueExW(hKey voidptr, lpValueName &u16, Reserved u32, dwType u32, lpData byteptr, lpcbData u32) int +fn C.RegSetValueExW(hKey voidptr, lpValueName &u16, Reserved u32, dwType u32, lpData byteptr, lpcbData u32) int fn C.RegCloseKey() - fn C.RemoveDirectory() int - -//fn C.GetStdHandle() voidptr +// fn C.GetStdHandle() voidptr fn C.GetStdHandle(u32) voidptr - -//fn C.SetConsoleMode() +// fn C.SetConsoleMode() fn C.SetConsoleMode(voidptr, u32) - -//fn C.GetConsoleMode() int +// fn C.GetConsoleMode() int fn C.GetConsoleMode(voidptr, &u32) int +fn C.GetCurrentProcessId() int fn C.wprintf() - -//fn C.setbuf() +// fn C.setbuf() fn C.setbuf(voidptr, charptr) - fn C.SymCleanup() - fn C.MultiByteToWideChar() int - fn C.wcslen() int - fn C.WideCharToMultiByte() int - fn C._wstat() - fn C._wrename() - fn C._wfopen() voidptr - fn C._wpopen() voidptr - fn C._pclose() int - fn C._wsystem() int - fn C._wgetenv() voidptr - fn C._putenv() int - fn C._waccess() int - fn C._wremove() int - fn C.ReadConsole() voidptr - fn C.WriteConsole() voidptr - fn C.WriteFile() voidptr - fn C._wchdir() - fn C._wgetcwd() int - fn C._fullpath() int - fn C.GetCommandLine() voidptr - fn C.LocalFree() - fn C.FindFirstFileW() voidptr - fn C.FindFirstFile() voidptr - fn C.FindNextFile() int - fn C.FindClose() - fn C.MAKELANGID() int - fn C.FormatMessage() voidptr - fn C.CloseHandle(voidptr) int - fn C.GetExitCodeProcess() - - - fn C.GetTickCount() i64 - fn C.Sleep() - fn C.WSAStartup(u16, &voidptr) int - fn C.WSAGetLastError() int - fn C.closesocket(int) int - fn C.vschannel_init(&C.TlsContext) - fn C.request(&C.TlsContext, int, &u16, byteptr, &byteptr) - fn C.vschannel_cleanup(&C.TlsContext) - fn C.URLDownloadToFile(int, &u16, &u16, int, int) - fn C.GetLastError() u32 - fn C.CreateDirectory(byteptr, int) bool // win crypto @@ -393,60 +312,102 @@ fn C.BCryptGenRandom(int, voidptr, int, int) int // win synchronization fn C.CreateMutex(int, bool, byteptr) voidptr + fn C.WaitForSingleObject(voidptr, int) int + fn C.ReleaseMutex(voidptr) bool + fn C.CreateEvent(int, bool, bool, byteptr) voidptr + fn C.SetEvent(voidptr) int fn C.CreateSemaphore(voidptr, int, int, voidptr) voidptr + fn C.ReleaseSemaphore(voidptr, int, voidptr) voidptr fn C.InitializeSRWLock(voidptr) + fn C.AcquireSRWLockShared(voidptr) + fn C.AcquireSRWLockExclusive(voidptr) + fn C.ReleaseSRWLockShared(voidptr) + fn C.ReleaseSRWLockExclusive(voidptr) // pthread.h fn C.pthread_mutex_init(voidptr, voidptr) int + fn C.pthread_mutex_lock(voidptr) int + fn C.pthread_mutex_unlock(voidptr) int + fn C.pthread_mutex_destroy(voidptr) int fn C.pthread_rwlockattr_init(voidptr) int + fn C.pthread_rwlockattr_setkind_np(voidptr, int) int + fn C.pthread_rwlockattr_setpshared(voidptr, int) int + fn C.pthread_rwlock_init(voidptr, voidptr) int + fn C.pthread_rwlock_rdlock(voidptr) int + fn C.pthread_rwlock_wrlock(voidptr) int + fn C.pthread_rwlock_unlock(voidptr) int fn C.pthread_condattr_init(voidptr) int + fn C.pthread_condattr_setpshared(voidptr, int) int + fn C.pthread_condattr_destroy(voidptr) int + fn C.pthread_cond_init(voidptr, voidptr) int + fn C.pthread_cond_signal(voidptr) int + fn C.pthread_cond_wait(voidptr, voidptr) int + fn C.pthread_cond_timedwait(voidptr, voidptr, voidptr) int + fn C.pthread_cond_destroy(voidptr) int fn C.sem_init(voidptr, int, u32) int + fn C.sem_post(voidptr) int + fn C.sem_wait(voidptr) int + fn C.sem_trywait(voidptr) int + fn C.sem_timedwait(voidptr, voidptr) int + fn C.sem_destroy(voidptr) int // MacOS semaphore functions fn C.dispatch_semaphore_create(i64) voidptr + fn C.dispatch_semaphore_signal(voidptr) i64 + fn C.dispatch_semaphore_wait(voidptr, u64) i64 + fn C.dispatch_time(u64, i64) u64 + fn C.dispatch_release(voidptr) +// file descriptor based reading/writing fn C.read(fd int, buf voidptr, count size_t) int + fn C.write(fd int, buf voidptr, count size_t) int + fn C.close(fd int) int +// pipes +fn C.pipe(pipefds &int) int + +fn C.dup2(oldfd int, newfd int) int + // used by gl, stbi, freetype fn C.glTexImage2D() diff --git a/vlib/os/fd.v b/vlib/os/fd.v new file mode 100644 index 0000000000..e3326c9dab --- /dev/null +++ b/vlib/os/fd.v @@ -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 +} diff --git a/vlib/os/os_c.v b/vlib/os/os_c.v index 8f76470148..d77912261f 100644 --- a/vlib/os/os_c.v +++ b/vlib/os/os_c.v @@ -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 diff --git a/vlib/os/process.v b/vlib/os/process.v new file mode 100644 index 0000000000..ed5e607e29 --- /dev/null +++ b/vlib/os/process.v @@ -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() + } +} diff --git a/vlib/os/process_nix.c.v b/vlib/os/process_nix.c.v new file mode 100644 index 0000000000..104ab12add --- /dev/null +++ b/vlib/os/process_nix.c.v @@ -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 +} diff --git a/vlib/os/process_test.v b/vlib/os/process_test.v new file mode 100644 index 0000000000..6cd75010d9 --- /dev/null +++ b/vlib/os/process_test.v @@ -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"') +} diff --git a/vlib/os/process_windows.c.v b/vlib/os/process_windows.c.v new file mode 100644 index 0000000000..55d2a8c1f1 --- /dev/null +++ b/vlib/os/process_windows.c.v @@ -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 +} diff --git a/vlib/readline/readline_linux.c.v b/vlib/readline/readline_linux.c.v index eb5e70fcd2..7b71b6ec75 100644 --- a/vlib/readline/readline_linux.c.v +++ b/vlib/readline/readline_linux.c.v @@ -38,8 +38,6 @@ fn C.tcsetattr() int fn C.raise() -fn C.kill(int, int) int - fn C.getppid() int // Enable the raw mode of the terminal