Merge pull request #3009 from Skgland/process
adds predicates for spawning new processes without a shell
This commit is contained in:
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
# FIXME(issue #2138): run wasm tests, failing to run since https://github.com/mthom/scryer-prolog/pull/2137 removed wasm-pack
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'wasm32-unknown-unknown', publish: true, args: '--no-default-features' , test-args: '--no-run --no-default-features', use_swap: true }
|
||||
# Cargo.toml rust-version
|
||||
- { os: ubuntu-22.04, rust-version: "1.85", target: 'x86_64-unknown-linux-gnu'}
|
||||
- { os: ubuntu-22.04, rust-version: "1.87", target: 'x86_64-unknown-linux-gnu'}
|
||||
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu'}
|
||||
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri"}
|
||||
defaults:
|
||||
|
||||
@@ -11,7 +11,7 @@ keywords = ["prolog", "prolog-interpreter", "prolog-system"]
|
||||
categories = ["command-line-utilities"]
|
||||
build = "build/main.rs"
|
||||
# Remember to check CI
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.87"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
@@ -537,6 +537,16 @@ enum SystemClauseType {
|
||||
UnsetEnv,
|
||||
#[strum_discriminants(strum(props(Arity = "2", Name = "$shell")))]
|
||||
Shell,
|
||||
#[strum_discriminants(strum(props(Arity = "8", Name = "$process_create")))]
|
||||
ProcessCreate,
|
||||
#[strum_discriminants(strum(props(Arity = "2", Name = "$process_id")))]
|
||||
ProcessId,
|
||||
#[strum_discriminants(strum(props(Arity = "3", Name = "$process_wait")))]
|
||||
ProcessWait,
|
||||
#[strum_discriminants(strum(props(Arity = "1", Name = "$process_kill")))]
|
||||
ProcessKill,
|
||||
#[strum_discriminants(strum(props(Arity = "1", Name = "$process_release")))]
|
||||
ProcessRelease,
|
||||
#[strum_discriminants(strum(props(Arity = "1", Name = "$pid")))]
|
||||
Pid,
|
||||
#[strum_discriminants(strum(props(Arity = "4", Name = "$chars_base64")))]
|
||||
@@ -1825,6 +1835,11 @@ fn generate_instruction_preface() -> TokenStream {
|
||||
&Instruction::CallSetEnv |
|
||||
&Instruction::CallUnsetEnv |
|
||||
&Instruction::CallShell |
|
||||
&Instruction::CallProcessCreate |
|
||||
&Instruction::CallProcessId |
|
||||
&Instruction::CallProcessWait |
|
||||
&Instruction::CallProcessKill |
|
||||
&Instruction::CallProcessRelease |
|
||||
&Instruction::CallPid |
|
||||
&Instruction::CallCharsBase64 |
|
||||
&Instruction::CallDevourWhitespace |
|
||||
@@ -2063,6 +2078,11 @@ fn generate_instruction_preface() -> TokenStream {
|
||||
&Instruction::ExecuteSetEnv |
|
||||
&Instruction::ExecuteUnsetEnv |
|
||||
&Instruction::ExecuteShell |
|
||||
&Instruction::ExecuteProcessCreate |
|
||||
&Instruction::ExecuteProcessId |
|
||||
&Instruction::ExecuteProcessWait |
|
||||
&Instruction::ExecuteProcessKill |
|
||||
&Instruction::ExecuteProcessRelease |
|
||||
&Instruction::ExecutePid |
|
||||
&Instruction::ExecuteCharsBase64 |
|
||||
&Instruction::ExecuteDevourWhitespace |
|
||||
|
||||
@@ -194,6 +194,7 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
|
||||
|
||||
macro_rules! atom {
|
||||
#((#static_str_keys) => { Atom { index: #indices } };)*
|
||||
($name:literal) => {compile_error!(concat!("unknown static atom ", $name))};
|
||||
}
|
||||
|
||||
pub static STATIC_ATOMS_MAP: phf::Map<&'static str, Atom> = phf::phf_map! {
|
||||
|
||||
28
src/arena.rs
28
src/arena.rs
@@ -14,10 +14,13 @@ use ordered_float::OrderedFloat;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::PipeReader;
|
||||
use std::io::PipeWriter;
|
||||
use std::mem;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::net::TcpListener;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::process::Child;
|
||||
use std::ptr;
|
||||
use std::ptr::addr_of_mut;
|
||||
use std::ptr::NonNull;
|
||||
@@ -71,7 +74,10 @@ pub enum ArenaHeaderTag {
|
||||
TcpListener = 0b1000000,
|
||||
HttpListener = 0b1000001,
|
||||
HttpResponse = 0b1000010,
|
||||
PipeWriter = 0b1000011,
|
||||
Dropped = 0b1000100,
|
||||
PipeReader = 0b1001001,
|
||||
ChildProcess = 0b1001010,
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
@@ -387,6 +393,19 @@ impl ArenaAllocated for HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl ArenaAllocated for Child {
|
||||
type Payload = ManuallyDrop<Self>;
|
||||
#[inline]
|
||||
fn tag() -> ArenaHeaderTag {
|
||||
ArenaHeaderTag::ChildProcess
|
||||
}
|
||||
}
|
||||
impl AllocateInArena<Child> for Child {
|
||||
fn arena_allocate(self, arena: &mut Arena) -> TypedArenaPtr<Child> {
|
||||
Child::alloc(arena, ManuallyDrop::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct AllocSlab {
|
||||
@@ -546,6 +565,15 @@ unsafe fn drop_slab_in_place(value: NonNull<AllocSlab>, tag: ArenaHeaderTag) {
|
||||
ArenaHeaderTag::StandardErrorStream => {
|
||||
drop_typed_slab_in_place!(StandardErrorStream, value);
|
||||
}
|
||||
ArenaHeaderTag::PipeReader => {
|
||||
drop_typed_slab_in_place!(PipeReader, value);
|
||||
}
|
||||
ArenaHeaderTag::PipeWriter => {
|
||||
drop_typed_slab_in_place!(PipeWriter, value);
|
||||
}
|
||||
ArenaHeaderTag::ChildProcess => {
|
||||
drop_typed_slab_in_place!(Child, value);
|
||||
}
|
||||
ArenaHeaderTag::NullStream => {
|
||||
unreachable!("NullStream is never arena allocated!");
|
||||
}
|
||||
|
||||
@@ -1789,6 +1789,23 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> {
|
||||
(ArenaHeaderTag::Dropped, _value) => {
|
||||
self.print_impromptu_atom(atom!("$dropped_value"));
|
||||
}
|
||||
(ArenaHeaderTag::ChildProcess, process) => {
|
||||
|
||||
let process_atom = atom!("$process");
|
||||
|
||||
if self.format_struct(max_depth, 1, process_atom) {
|
||||
let atom = TokenOrRedirect::NumberFocus(max_depth, NumberFocus::Unfocused(Number::Fixnum(Fixnum::build_with(process.id()))), op);
|
||||
|
||||
let process_root = self.state_stack.pop().unwrap();
|
||||
|
||||
self.state_stack.pop();
|
||||
self.state_stack.pop();
|
||||
|
||||
self.state_stack.push(atom);
|
||||
self.state_stack.push(TokenOrRedirect::Open);
|
||||
self.state_stack.push(process_root);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
);
|
||||
|
||||
224
src/lib/process.pl
Normal file
224
src/lib/process.pl
Normal file
@@ -0,0 +1,224 @@
|
||||
:- module(process, [
|
||||
process_create/3,
|
||||
process_id/2,
|
||||
process_release/1,
|
||||
process_wait/2,
|
||||
process_wait/3,
|
||||
process_kill/1
|
||||
]).
|
||||
|
||||
:- use_module(library(error)).
|
||||
:- use_module(library(iso_ext)).
|
||||
:- use_module(library(lists), [member/2, maplist/2, maplist/3, append/2]).
|
||||
:- use_module(library(reif), [tfilter/3, memberd_t/3]).
|
||||
|
||||
|
||||
%% process_create(+Exe, +Args:list, +Options).
|
||||
%
|
||||
% Create a new process by executing the executable Exe and passing it the Arguments Args.
|
||||
%
|
||||
% Note: On windows please take note of [windows argument splitting](https://doc.rust-lang.org/std/process/index.html#windows-argument-splitting).
|
||||
%
|
||||
% Options is a list consisting of the following options:
|
||||
%
|
||||
% * `cwd(+Path)` Set the processes working directory to `Path`
|
||||
% * `process(-Process)` `Process` will be assigned a process handle for the spawned process
|
||||
% * `env(+List)` Don't inherit environment variables and set the variables defined in `List`
|
||||
% * `environment(+List)` Inherit environment variables and set/override the variables defined in `List`
|
||||
% * `stdin(Spec)`, `stdout(Spec)` or `stderr(Spec)` defines how to redirect the spawned processes io streams
|
||||
%
|
||||
% The elements of `List` in `env(List)`/`environment(List)` List must be string pairs using `=/2`.
|
||||
% `env/1` and `environment/1` may not be both specified.
|
||||
%
|
||||
% The following stdio `Spec` are available:
|
||||
%
|
||||
% * `std` inherit the current processes original stdio streams (does currently not account for stdio being changed by `set_input` or `set_output`)
|
||||
% * `file(+Path)` attach the strea to the file at `Path`
|
||||
% * `null` discards writes and behaves as eof for read. Equivalent to using `file(/dev/null)`
|
||||
% * `pipe(-Steam)` create a new pipe and assigne one end to the created process and the other end to `Stream`
|
||||
%
|
||||
% Specifying an option multiple times is an error, when an option is not specified the following defaults apply:
|
||||
%
|
||||
% - `cwd(".")`
|
||||
% - `environment([])`
|
||||
% - `stdin(std)`, `stdout(std)`, `stderr(std)`
|
||||
%
|
||||
process_create(Exe, Args, Options) :- call_with_error_context(process_create_(Exe, Args, Options), predicate-process_create/3).
|
||||
|
||||
process_create_(Exe, Args, Options) :-
|
||||
must_be(chars, Exe),
|
||||
must_be(list, Args),
|
||||
maplist(must_be(chars), Args),
|
||||
must_be(list, Options),
|
||||
check_options(
|
||||
[
|
||||
option([stdin], valid_stdio, stdin(std), stdin(Stdin)),
|
||||
option([stdout], valid_stdio, stdout(std), stdout(Stdout)),
|
||||
option([stderr], valid_stdio, stderr(std), stderr(Stderr)),
|
||||
option([env, environment], valid_env, environment([]), Env),
|
||||
option([process], valid_uninit_process, process(_), process(Process)),
|
||||
option([cwd], valid_cwd, cwd("."), cwd(Cwd))
|
||||
],
|
||||
Options,
|
||||
process_create_option
|
||||
),
|
||||
Stdin =.. Stdin1,
|
||||
Stdout =.. Stdout1,
|
||||
Stderr =.. Stderr1,
|
||||
simplify_env(Env, Env1),
|
||||
'$process_create'(Exe, Args, Stdin1, Stdout1, Stderr1, Env1, Cwd, Process).
|
||||
|
||||
%% process_id(+Process, -Pid).
|
||||
%
|
||||
process_id(Process, Pid) :- call_with_error_context(process_id_(Process, Pid), predicate-process_id/2).
|
||||
|
||||
process_id_(Process, Pid) :-
|
||||
valid_process(Process),
|
||||
must_be(var, Pid),
|
||||
'$process_id'(Process, Pid).
|
||||
|
||||
%% process_wait(+Process, Status).
|
||||
%
|
||||
% See `process_create/3` with `Options = []`
|
||||
%
|
||||
process_wait(Process, Status) :- call_with_error_context(process_wait(Process, Status, []), predicate-process_wait/2).
|
||||
|
||||
|
||||
%% process_wait(+Process, Status, Options).
|
||||
%
|
||||
% Wait for the process behind the process handle `Process` to exit.
|
||||
%
|
||||
% When the process exits regulary `Status` will be unified with `exit(Exit)` where `Exit` is the processes exit code.
|
||||
% When the process exits was killed `Status` will be unified with `killed(Signal)` where `Signal` is the signal number that killed the process.
|
||||
% When the process doesn't exit before the timeout `Status` will be unified with `timeout`.
|
||||
%
|
||||
% `Options` is a a list of the following options
|
||||
%
|
||||
% * timeout(Timeout) supported values for `Timeout` are 0 or `infinite`
|
||||
%
|
||||
% Each options may be specified at most once, when an option is not specified the following defaults apply:
|
||||
%
|
||||
% - timeout(infinite)
|
||||
%
|
||||
process_wait(Process, Status, Options) :- call_with_error_context(process_wait_(Process, Status, Options), predicate-process_wait/3).
|
||||
|
||||
process_wait_(Process, Status, Options) :-
|
||||
valid_process(Process),
|
||||
check_options(
|
||||
[
|
||||
option([timeout], valid_timeout, timeout(infinite), timeout(Timeout))
|
||||
],
|
||||
Options,
|
||||
process_wait_option
|
||||
),
|
||||
'$process_wait'(Process, Exit, Timeout),
|
||||
Exit = Status.
|
||||
|
||||
valid_timeout(timeout(infinite)).
|
||||
valid_timeout(timeout(0)).
|
||||
|
||||
|
||||
%% process_kill(+Process).
|
||||
%
|
||||
% Kill the process using the process handle `Process`.
|
||||
% On Unix this sends SIGKILL.
|
||||
%
|
||||
% Only works for processes spawned with `process_create/3` that have not yet been release with `process_release/1`
|
||||
%
|
||||
process_kill(Process) :- call_with_error_context(process_kill_(Process), predicate-process_kill/1).
|
||||
|
||||
process_kill_(Process) :-
|
||||
valid_process(Process),
|
||||
'$process_kill'(Process).
|
||||
|
||||
%% process_release(+Process)
|
||||
%
|
||||
% wait for the process to exit (if not already) and release process handle `Process`
|
||||
%
|
||||
% It's an error if `Process` is not a valid process handle
|
||||
%
|
||||
process_release(Process) :- call_with_error_context(process_release_(Process), predicate-process_release/1).
|
||||
|
||||
process_release_(Process) :-
|
||||
valid_process(Process),
|
||||
process_wait(Process, _),
|
||||
'$process_release'(Process).
|
||||
|
||||
|
||||
must_be_known_options(Valid, Options, Domain) :- call_with_error_context(must_be_known_options_(Valid, [], Options, Domain),predicate-must_be_known_options/3).
|
||||
|
||||
must_be_known_options_(_, _, [], _).
|
||||
must_be_known_options_(Valid, Found, [X|XS], Domain) :-
|
||||
( functor(X, Option, 1) -> true
|
||||
; domain_error(Domain, X, [])
|
||||
) ,
|
||||
( member(Option, Found) -> domain_error(non_duplicate_options, Option , [])
|
||||
; member(Option, Valid) -> true
|
||||
; domain_error(Domain, Option, [])
|
||||
),
|
||||
must_be_known_options_(Valid, [Option | Found], XS, Domain).
|
||||
|
||||
check_options(KnownOptions, Options, Domain) :- call_with_error_context(check_options_(KnownOptions, Options, Domain), predicate-check_options/3).
|
||||
|
||||
check_options_(KnownOptions, Options, Domain) :-
|
||||
maplist(option_names, KnownOptions, Namess),
|
||||
append(Namess, Names),
|
||||
must_be_known_options(Names, Options, Domain),
|
||||
extract_options(KnownOptions, Options).
|
||||
|
||||
option_names(option(Names,_,_,_), Names).
|
||||
|
||||
extract_options(KnownOptions, Options) :- call_with_error_context(extract_options_(KnownOptions, Options), predicate-extract_options/2).
|
||||
|
||||
extract_options_([], _).
|
||||
extract_options_([X | XS], Options) :-
|
||||
option(Kinds, Pred, Default, Choice) = X,
|
||||
tfilter(find_option(Kinds), Options, Solutions),
|
||||
( Solutions = [] -> Choice = Default
|
||||
; Solutions = [Provided] -> functor(Pred, Name, Arity), ArityP1 is Arity+1, call_with_error_context(call(Pred, Provided),predicate-Name/ArityP1), Choice = Provided
|
||||
; domain_error(non_conflicting_options, Solutions, [])
|
||||
),
|
||||
extract_options_(XS, Options).
|
||||
|
||||
find_option(Names, Found, T) :-
|
||||
functor(Found, Name, 1),
|
||||
memberd_t(Name, Names, T).
|
||||
|
||||
valid_stdio(IO) :- arg(1, IO, Arg),
|
||||
( var(Arg) -> instantiation_error([])
|
||||
; valid_stdio_(Arg) -> true
|
||||
; domain_error(stdio_spec, Arg, [])
|
||||
).
|
||||
|
||||
valid_stdio_(std).
|
||||
valid_stdio_(null).
|
||||
valid_stdio_(pipe(Stream)) :- must_be(var, Stream).
|
||||
valid_stdio_(file(Path)) :- must_be(chars, Path).
|
||||
|
||||
valid_env(env(E)) :-
|
||||
must_be(list, E),
|
||||
( valid_env_(E) -> true
|
||||
; domain_error(process_create_option, env(E), [])
|
||||
).
|
||||
valid_env(environment(E)) :-
|
||||
must_be(list, E),
|
||||
( valid_env_(E) -> true
|
||||
; domain_error(process_create_option, environment(E), [])
|
||||
).
|
||||
|
||||
valid_env_([]).
|
||||
valid_env_([N=V|ES]) :-
|
||||
must_be(chars, N),
|
||||
must_be(chars, V),
|
||||
valid_env_(ES).
|
||||
|
||||
valid_uninit_process(process(Process)) :- must_be(var, Process).
|
||||
|
||||
valid_process(Process) :- var(Process) -> instantiation_error([]) ; true.
|
||||
|
||||
valid_cwd(cwd(Cwd)) :- must_be(chars, Cwd).
|
||||
|
||||
simplify_env(E, [Kind, Envs1]) :- E =.. [Kind, Envs], simplify_env_(Envs, Envs1).
|
||||
|
||||
simplify_env_([],[]).
|
||||
simplify_env_([N=V|E],[[N, V]|E1]) :- simplify_env_(E, E1).
|
||||
@@ -4787,6 +4787,46 @@ impl Machine {
|
||||
self.shell();
|
||||
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
|
||||
}
|
||||
&Instruction::CallProcessCreate => {
|
||||
try_or_throw!(self.machine_st, self.process_create());
|
||||
step_or_fail!(self, self.machine_st.p += 1);
|
||||
}
|
||||
&Instruction::ExecuteProcessCreate => {
|
||||
try_or_throw!(self.machine_st, self.process_create());
|
||||
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
|
||||
}
|
||||
&Instruction::CallProcessId => {
|
||||
try_or_throw!(self.machine_st, self.process_id());
|
||||
step_or_fail!(self, self.machine_st.p += 1);
|
||||
}
|
||||
&Instruction::ExecuteProcessId => {
|
||||
try_or_throw!(self.machine_st, self.process_id());
|
||||
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
|
||||
}
|
||||
&Instruction::CallProcessWait => {
|
||||
try_or_throw!(self.machine_st, self.process_wait());
|
||||
step_or_fail!(self, self.machine_st.p += 1);
|
||||
}
|
||||
&Instruction::ExecuteProcessWait => {
|
||||
try_or_throw!(self.machine_st, self.process_wait());
|
||||
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
|
||||
}
|
||||
&Instruction::CallProcessKill => {
|
||||
try_or_throw!(self.machine_st, self.process_kill());
|
||||
step_or_fail!(self, self.machine_st.p += 1);
|
||||
}
|
||||
&Instruction::ExecuteProcessKill => {
|
||||
try_or_throw!(self.machine_st, self.process_kill());
|
||||
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
|
||||
}
|
||||
&Instruction::CallProcessRelease => {
|
||||
try_or_throw!(self.machine_st, self.process_release());
|
||||
step_or_fail!(self, self.machine_st.p += 1);
|
||||
}
|
||||
&Instruction::ExecuteProcessRelease => {
|
||||
try_or_throw!(self.machine_st, self.process_release());
|
||||
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
|
||||
}
|
||||
&Instruction::CallPid => {
|
||||
self.pid();
|
||||
step_or_fail!(self, self.machine_st.p += 1);
|
||||
|
||||
@@ -44,6 +44,7 @@ pub(crate) enum ValidType {
|
||||
// PredicateIndicator,
|
||||
// Variable
|
||||
TcpListener,
|
||||
Process,
|
||||
}
|
||||
|
||||
impl ValidType {
|
||||
@@ -67,6 +68,7 @@ impl ValidType {
|
||||
// ValidType::PredicateIndicator => atom!("predicate_indicator"),
|
||||
// ValidType::Variable => atom!("variable")
|
||||
ValidType::TcpListener => atom!("tcp_listener"),
|
||||
ValidType::Process => atom!("process"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,6 +407,17 @@ impl MachineState {
|
||||
[atom_as_cell((atom!("stream"))), cell(culprit)]
|
||||
);
|
||||
|
||||
MachineError {
|
||||
stub,
|
||||
location: None,
|
||||
}
|
||||
}
|
||||
ExistenceError::Process(culprit) => {
|
||||
let stub = functor!(
|
||||
atom!("existence_error"),
|
||||
[atom_as_cell((atom!("process"))), cell(culprit)]
|
||||
);
|
||||
|
||||
MachineError {
|
||||
stub,
|
||||
location: None,
|
||||
@@ -590,6 +603,15 @@ impl MachineState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn unreachable_error(&self) -> MachineError {
|
||||
let stub = functor!(atom!("system_error"));
|
||||
|
||||
MachineError {
|
||||
stub,
|
||||
location: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
pub(super) fn ffi_error(&self, err: FFIError) -> MachineError {
|
||||
let error_atom = match err {
|
||||
@@ -1003,6 +1025,7 @@ pub enum ExistenceError {
|
||||
},
|
||||
SourceSink(HeapCellValue),
|
||||
Stream(HeapCellValue),
|
||||
Process(HeapCellValue),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
||||
@@ -23,6 +23,8 @@ use std::fmt::Debug;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::hash::Hash;
|
||||
use std::io;
|
||||
use std::io::PipeReader;
|
||||
use std::io::PipeWriter;
|
||||
use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write};
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::net::{Shutdown, TcpStream};
|
||||
@@ -588,6 +590,8 @@ arena_allocated_impl_for_stream!(StandardOutputStream, StandardOutputStream);
|
||||
arena_allocated_impl_for_stream!(StandardErrorStream, StandardErrorStream);
|
||||
arena_allocated_impl_for_stream!(CharReader<CallbackStream>, CallbackStream);
|
||||
arena_allocated_impl_for_stream!(CharReader<InputChannelStream>, InputChannelStream);
|
||||
arena_allocated_impl_for_stream!(CharReader<PipeReader>, PipeReader);
|
||||
arena_allocated_impl_for_stream!(CharReader<PipeWriter>, PipeWriter);
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Stream {
|
||||
@@ -608,6 +612,8 @@ pub enum Stream {
|
||||
StandardError(TypedArenaPtr<StandardErrorStream>),
|
||||
Callback(TypedArenaPtr<CallbackStream>),
|
||||
InputChannel(TypedArenaPtr<InputChannelStream>),
|
||||
PipeReader(TypedArenaPtr<PipeReader>),
|
||||
PipeWriter(TypedArenaPtr<PipeWriter>),
|
||||
}
|
||||
|
||||
impl From<TypedArenaPtr<ReadlineStream>> for Stream {
|
||||
@@ -688,6 +694,8 @@ impl Stream {
|
||||
ArenaHeaderTag::InputChannelStream => {
|
||||
Stream::InputChannel(unsafe { ptr.as_typed_ptr() })
|
||||
}
|
||||
ArenaHeaderTag::PipeReader => Stream::PipeReader(unsafe { ptr.as_typed_ptr() }),
|
||||
ArenaHeaderTag::PipeWriter => Stream::PipeWriter(unsafe { ptr.as_typed_ptr() }),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -726,6 +734,8 @@ impl Stream {
|
||||
Stream::StandardError(ptr) => ptr.header_ptr(),
|
||||
Stream::Callback(ptr) => ptr.header_ptr(),
|
||||
Stream::InputChannel(ptr) => ptr.header_ptr(),
|
||||
Stream::PipeReader(ptr) => ptr.header_ptr(),
|
||||
Stream::PipeWriter(ptr) => ptr.header_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,6 +758,8 @@ impl Stream {
|
||||
Stream::StandardError(ref ptr) => &ptr.options,
|
||||
Stream::Callback(ref ptr) => &ptr.options,
|
||||
Stream::InputChannel(ref ptr) => &ptr.options,
|
||||
Stream::PipeReader(ref ptr) => &ptr.options,
|
||||
Stream::PipeWriter(ref ptr) => &ptr.options,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,6 +782,8 @@ impl Stream {
|
||||
Stream::StandardError(ref mut ptr) => &mut ptr.options,
|
||||
Stream::Callback(ref mut ptr) => &mut ptr.options,
|
||||
Stream::InputChannel(ref mut ptr) => &mut ptr.options,
|
||||
Stream::PipeReader(ref mut ptr) => &mut ptr.options,
|
||||
Stream::PipeWriter(ref mut ptr) => &mut ptr.options,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,6 +807,8 @@ impl Stream {
|
||||
Stream::StandardError(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
Stream::Callback(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
Stream::InputChannel(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
Stream::PipeReader(ptr) => ptr.lines_read += incr_num_lines_read,
|
||||
Stream::PipeWriter(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -816,6 +832,8 @@ impl Stream {
|
||||
Stream::StandardError(ptr) => ptr.lines_read = value,
|
||||
Stream::Callback(ptr) => ptr.lines_read = value,
|
||||
Stream::InputChannel(ptr) => ptr.lines_read = value,
|
||||
Stream::PipeReader(ptr) => ptr.lines_read = value,
|
||||
Stream::PipeWriter(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -839,6 +857,8 @@ impl Stream {
|
||||
Stream::StandardError(ptr) => ptr.lines_read,
|
||||
Stream::Callback(ptr) => ptr.lines_read,
|
||||
Stream::InputChannel(ptr) => ptr.lines_read,
|
||||
Stream::PipeReader(ptr) => ptr.lines_read,
|
||||
Stream::PipeWriter(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -856,6 +876,8 @@ impl CharRead for Stream {
|
||||
Stream::StaticString(src) => (*src).peek_char(),
|
||||
Stream::Byte(cursor) => (*cursor).peek_char(),
|
||||
Stream::InputChannel(cursor) => (*cursor).peek_char(),
|
||||
Stream::PipeReader(cursor) => (*cursor).peek_char(),
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
Stream::HttpWrite(_) => Some(Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
@@ -865,7 +887,8 @@ impl CharRead for Stream {
|
||||
| Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::Null(_)
|
||||
| Stream::Callback(_) => Some(Err(std::io::Error::new(
|
||||
| Stream::Callback(_)
|
||||
| Stream::PipeWriter(_) => Some(Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::ReadFromOutputStream,
|
||||
))),
|
||||
@@ -884,6 +907,7 @@ impl CharRead for Stream {
|
||||
Stream::StaticString(src) => (*src).read_char(),
|
||||
Stream::Byte(cursor) => (*cursor).read_char(),
|
||||
Stream::InputChannel(cursor) => (*cursor).read_char(),
|
||||
Stream::PipeReader(cursor) => (*cursor).read_char(),
|
||||
#[cfg(feature = "http")]
|
||||
Stream::HttpWrite(_) => Some(Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
@@ -893,7 +917,8 @@ impl CharRead for Stream {
|
||||
| Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::Null(_)
|
||||
| Stream::Callback(_) => Some(Err(std::io::Error::new(
|
||||
| Stream::Callback(_)
|
||||
| Stream::PipeWriter(_) => Some(Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::ReadFromOutputStream,
|
||||
))),
|
||||
@@ -911,13 +936,15 @@ impl CharRead for Stream {
|
||||
Stream::Readline(rl_stream) => rl_stream.put_back_char(c),
|
||||
Stream::StaticString(src) => src.put_back_char(c),
|
||||
Stream::Byte(cursor) => cursor.put_back_char(c),
|
||||
Stream::PipeReader(cursor) => cursor.put_back_char(c),
|
||||
#[cfg(feature = "http")]
|
||||
Stream::HttpWrite(_) => {}
|
||||
Stream::OutputFile(_)
|
||||
| Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::Null(_)
|
||||
| Stream::Callback(_) => {}
|
||||
| Stream::Callback(_)
|
||||
| Stream::PipeWriter(_) => {}
|
||||
Stream::InputChannel(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -934,13 +961,15 @@ impl CharRead for Stream {
|
||||
Stream::StaticString(ref mut src) => src.consume(nread),
|
||||
Stream::Byte(ref mut cursor) => cursor.consume(nread),
|
||||
Stream::InputChannel(ref mut cursor) => cursor.consume(nread),
|
||||
Stream::PipeReader(ref mut cursor) => cursor.consume(nread),
|
||||
#[cfg(feature = "http")]
|
||||
Stream::HttpWrite(_) => {}
|
||||
Stream::OutputFile(_)
|
||||
| Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::Null(_)
|
||||
| Stream::Callback(_) => {}
|
||||
| Stream::Callback(_)
|
||||
| Stream::PipeWriter(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -959,6 +988,7 @@ impl Read for Stream {
|
||||
Stream::StaticString(src) => (*src).read(buf),
|
||||
Stream::Byte(cursor) => (*cursor).read(buf),
|
||||
Stream::InputChannel(cursor) => (*cursor).read(buf),
|
||||
Stream::PipeReader(cursor) => (*cursor).read(buf),
|
||||
#[cfg(feature = "http")]
|
||||
Stream::HttpWrite(_) => Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
@@ -967,7 +997,8 @@ impl Read for Stream {
|
||||
Stream::OutputFile(_)
|
||||
| Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::Callback(_) => Err(std::io::Error::new(
|
||||
| Stream::Callback(_)
|
||||
| Stream::PipeWriter(_) => Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::ReadFromOutputStream,
|
||||
)),
|
||||
@@ -989,6 +1020,7 @@ impl Write for Stream {
|
||||
Stream::StandardError(stream) => stream.write(buf),
|
||||
#[cfg(feature = "http")]
|
||||
Stream::HttpWrite(ref mut stream) => stream.get_mut().write(buf),
|
||||
Stream::PipeWriter(ref mut stream) => stream.get_mut().write(buf),
|
||||
#[cfg(feature = "http")]
|
||||
Stream::HttpRead(_) => Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
@@ -998,7 +1030,8 @@ impl Write for Stream {
|
||||
Stream::StaticString(_)
|
||||
| Stream::InputChannel(_)
|
||||
| Stream::Readline(_)
|
||||
| Stream::InputFile(..) => Err(std::io::Error::new(
|
||||
| Stream::InputFile(..)
|
||||
| Stream::PipeReader(_) => Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::WriteToInputStream,
|
||||
)),
|
||||
@@ -1015,6 +1048,7 @@ impl Write for Stream {
|
||||
Stream::Callback(ref mut callback_stream) => callback_stream.stream.get_mut().flush(),
|
||||
Stream::StandardError(stream) => stream.stream.flush(),
|
||||
Stream::StandardOutput(stream) => stream.stream.flush(),
|
||||
Stream::PipeWriter(ref mut stream) => stream.stream.get_mut().flush(),
|
||||
#[cfg(feature = "http")]
|
||||
Stream::HttpWrite(ref mut stream) => stream.stream.get_mut().flush(),
|
||||
#[cfg(feature = "http")]
|
||||
@@ -1026,7 +1060,8 @@ impl Write for Stream {
|
||||
Stream::StaticString(_)
|
||||
| Stream::InputChannel(_)
|
||||
| Stream::Readline(_)
|
||||
| Stream::InputFile(_) => Err(std::io::Error::new(
|
||||
| Stream::InputFile(_)
|
||||
| Stream::PipeReader(_) => Err(std::io::Error::new(
|
||||
ErrorKind::PermissionDenied,
|
||||
StreamError::FlushToInputStream,
|
||||
)),
|
||||
@@ -1192,6 +1227,8 @@ impl Stream {
|
||||
Stream::StandardError(stream) => stream.past_end_of_stream,
|
||||
Stream::Callback(stream) => stream.past_end_of_stream,
|
||||
Stream::InputChannel(stream) => stream.past_end_of_stream,
|
||||
Stream::PipeReader(stream) => stream.past_end_of_stream,
|
||||
Stream::PipeWriter(stream) => stream.past_end_of_stream,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1220,6 +1257,8 @@ impl Stream {
|
||||
Stream::StandardError(stream) => stream.past_end_of_stream = value,
|
||||
Stream::Callback(stream) => stream.past_end_of_stream = value,
|
||||
Stream::InputChannel(stream) => stream.past_end_of_stream = value,
|
||||
Stream::PipeReader(stream) => stream.past_end_of_stream = value,
|
||||
Stream::PipeWriter(stream) => stream.past_end_of_stream = value,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1330,7 +1369,8 @@ impl Stream {
|
||||
| Stream::InputChannel(_)
|
||||
| Stream::Readline(_)
|
||||
| Stream::StaticString(_)
|
||||
| Stream::InputFile(..) => atom!("read"),
|
||||
| Stream::InputFile(..)
|
||||
| Stream::PipeReader(_) => atom!("read"),
|
||||
Stream::NamedTcp(..) => atom!("read_append"),
|
||||
Stream::OutputFile(file) if file.is_append => atom!("append"),
|
||||
#[cfg(feature = "http")]
|
||||
@@ -1338,7 +1378,8 @@ impl Stream {
|
||||
Stream::OutputFile(_)
|
||||
| Stream::StandardError(_)
|
||||
| Stream::StandardOutput(_)
|
||||
| Stream::Callback(_) => {
|
||||
| Stream::Callback(_)
|
||||
| Stream::PipeWriter(_) => {
|
||||
atom!("write")
|
||||
}
|
||||
Stream::Null(_) => atom!(""),
|
||||
@@ -1372,6 +1413,20 @@ impl Stream {
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn from_pipe_writer(writer: io::PipeWriter, arena: &mut Arena) -> Stream {
|
||||
Stream::PipeWriter(arena_alloc!(
|
||||
ManuallyDrop::new(StreamLayout::new(CharReader::new(writer))),
|
||||
arena
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn from_pipe_reader(reader: io::PipeReader, arena: &mut Arena) -> Stream {
|
||||
Stream::PipeReader(arena_alloc!(
|
||||
ManuallyDrop::new(StreamLayout::new(CharReader::new(reader))),
|
||||
arena
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn from_tcp_stream(address: Atom, tcp_stream: TcpStream, arena: &mut Arena) -> Self {
|
||||
tcp_stream.set_read_timeout(None).unwrap();
|
||||
@@ -1512,6 +1567,16 @@ impl Stream {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stream::PipeReader(mut stream) => {
|
||||
stream.drop_payload();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stream::PipeWriter(mut stream) => {
|
||||
stream.drop_payload();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Stream::Null(_) => Ok(()),
|
||||
|
||||
Stream::Readline(_) | Stream::StandardOutput(_) | Stream::StandardError(_) => {
|
||||
@@ -1538,6 +1603,7 @@ impl Stream {
|
||||
| Stream::Readline(_)
|
||||
| Stream::StaticString(_)
|
||||
| Stream::InputFile(..)
|
||||
| Stream::PipeReader(_)
|
||||
| Stream::Null(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
@@ -1556,6 +1622,7 @@ impl Stream {
|
||||
| Stream::Byte(_)
|
||||
| Stream::OutputFile(..)
|
||||
| Stream::Callback(_)
|
||||
| Stream::PipeWriter(_)
|
||||
| Stream::Null(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::num::NonZeroU32;
|
||||
use std::process;
|
||||
use std::process::Child;
|
||||
use std::process::Stdio;
|
||||
#[cfg(feature = "http")]
|
||||
use std::str::FromStr;
|
||||
#[cfg(feature = "http")]
|
||||
@@ -8393,6 +8395,431 @@ impl Machine {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) fn process_create(&mut self) -> CallResult {
|
||||
fn stub_gen() -> Vec<FunctorElement> {
|
||||
functor_stub(atom!("process_create"), 3)
|
||||
}
|
||||
|
||||
// String
|
||||
let exe_r = self.deref_register(1);
|
||||
// [String,...]
|
||||
let args_r = self.deref_register(2);
|
||||
// [std] | [null] | [pipe, Var] | [file, String]
|
||||
let stdin_r = self.deref_register(3);
|
||||
let stdout_r = self.deref_register(4);
|
||||
let stderr_r = self.deref_register(5);
|
||||
// [env | environment, [[String, String],...]]
|
||||
let env_r = self.deref_register(6);
|
||||
// String ("." for keep current cwd)
|
||||
let cwd_r = self.deref_register(7);
|
||||
// Var
|
||||
let pid_r = self.deref_register(8);
|
||||
|
||||
let exe = self
|
||||
.machine_st
|
||||
.value_to_str_like(exe_r)
|
||||
.expect("invalid values should have been rejected on the prolog side");
|
||||
|
||||
let args = self
|
||||
.machine_st
|
||||
.try_from_list(args_r, stub_gen)
|
||||
.expect("invalid values should have been rejected on the prolog side")
|
||||
.into_iter()
|
||||
.map(|arg| {
|
||||
self.machine_st
|
||||
.value_to_str_like(arg)
|
||||
.expect("invalid values should have been rejected on the prolog side")
|
||||
.as_str()
|
||||
.to_string()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let stdin_args = self.machine_st.try_from_list(stdin_r, stub_gen)?;
|
||||
let stdin = self.handle_input_stream(stdin_args)?;
|
||||
|
||||
let stdout_args = self.machine_st.try_from_list(stdout_r, stub_gen)?;
|
||||
let stdout = self.handle_output_stream(stdout_args)?;
|
||||
|
||||
let stderr_args = self.machine_st.try_from_list(stderr_r, stub_gen)?;
|
||||
let stderr = self.handle_output_stream(stderr_args)?;
|
||||
|
||||
let env_args = self.machine_st.try_from_list(env_r, stub_gen)?;
|
||||
|
||||
let clear_env = match env_args[0].to_atom() {
|
||||
Some(atom!("env")) => true,
|
||||
Some(atom!("environment")) => false,
|
||||
_ => panic!("Invalid value for clear_env"),
|
||||
};
|
||||
|
||||
let envs = self
|
||||
.machine_st
|
||||
.try_from_list(env_args[1], stub_gen)?
|
||||
.into_iter()
|
||||
.map(|entry| {
|
||||
let entry = self.machine_st.try_from_list(entry, stub_gen)?;
|
||||
let name = self
|
||||
.machine_st
|
||||
.value_to_str_like(entry[0])
|
||||
.expect("invalid values should have been rejected on the prolog side")
|
||||
.as_str()
|
||||
.to_string();
|
||||
let value = self
|
||||
.machine_st
|
||||
.value_to_str_like(entry[1])
|
||||
.expect("invalid values should have been rejected on the prolog side")
|
||||
.as_str()
|
||||
.to_string();
|
||||
Ok((name, value))
|
||||
})
|
||||
.collect::<Result<Vec<_>, MachineStub>>()?;
|
||||
|
||||
let cwd = self
|
||||
.machine_st
|
||||
.value_to_str_like(cwd_r)
|
||||
.expect("invalid values should have been rejected on the prolog side");
|
||||
|
||||
let mut command = std::process::Command::new(&*exe.as_str());
|
||||
command.args(args);
|
||||
|
||||
if &*cwd.as_str() != "." {
|
||||
command.current_dir(&*cwd.as_str());
|
||||
}
|
||||
|
||||
if clear_env {
|
||||
command.env_clear();
|
||||
}
|
||||
|
||||
command
|
||||
.envs(envs)
|
||||
.stdin(stdin)
|
||||
.stdout(stdout)
|
||||
.stderr(stderr);
|
||||
|
||||
match command.spawn() {
|
||||
Ok(child) => {
|
||||
let child_process_alloc: TypedArenaPtr<Child> =
|
||||
arena_alloc!(child, &mut self.machine_st.arena);
|
||||
|
||||
unify!(
|
||||
self.machine_st,
|
||||
pid_r,
|
||||
typed_arena_ptr_as_cell!(child_process_alloc)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => {
|
||||
let perm_error = self.machine_st.permission_error(
|
||||
Permission::Create,
|
||||
atom!("process"),
|
||||
stub_gen(),
|
||||
);
|
||||
Err(self.machine_st.error_form(perm_error, stub_gen()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_output_stream(&mut self, args: Vec<HeapCellValue>) -> Result<Stdio, MachineStub> {
|
||||
Ok(match args[0].to_atom() {
|
||||
Some(atom!("std")) => Stdio::inherit(),
|
||||
Some(atom!("null")) => Stdio::null(),
|
||||
Some(atom!("pipe")) => {
|
||||
let (reader, writer) = match std::io::pipe() {
|
||||
Ok(pipe_pair) => pipe_pair,
|
||||
Err(_) => {
|
||||
return Err(self.machine_st.open_permission_error(
|
||||
atom!("anonymous_pipe"),
|
||||
atom!("process_create"),
|
||||
3,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let stream = Stream::from_pipe_reader(reader, &mut self.machine_st.arena);
|
||||
|
||||
self.indices
|
||||
.add_stream(stream, atom!("process_create"), 3)
|
||||
.map_err(|stub_gen| stub_gen(&mut self.machine_st))?;
|
||||
|
||||
self.machine_st
|
||||
.bind(args[1].as_var().unwrap(), stream.into());
|
||||
|
||||
Stdio::from(writer)
|
||||
}
|
||||
Some(atom!("file")) => {
|
||||
let path = self.machine_st.value_to_str_like(args[1]).unwrap();
|
||||
|
||||
let file = match std::fs::File::open(&*path.as_str()) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
return Err(self.machine_st.open_permission_error(
|
||||
args[1],
|
||||
atom!("process_create"),
|
||||
3,
|
||||
));
|
||||
}
|
||||
};
|
||||
Stdio::from(file)
|
||||
}
|
||||
_ => {
|
||||
panic!("Invalid stdout tag")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_input_stream(&mut self, args: Vec<HeapCellValue>) -> Result<Stdio, MachineStub> {
|
||||
Ok(match args[0].to_atom() {
|
||||
Some(atom!("std")) => Stdio::inherit(),
|
||||
Some(atom!("null")) => Stdio::null(),
|
||||
Some(atom!("pipe")) => {
|
||||
let (reader, writer) = match std::io::pipe() {
|
||||
Ok(pipe_pair) => pipe_pair,
|
||||
Err(_) => {
|
||||
return Err(self.machine_st.open_permission_error(
|
||||
atom!("anonymous_pipe"),
|
||||
atom!("process_create"),
|
||||
3,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let stream = Stream::from_pipe_writer(writer, &mut self.machine_st.arena);
|
||||
|
||||
self.indices
|
||||
.add_stream(stream, atom!("process_create"), 3)
|
||||
.map_err(|stub_gen| stub_gen(&mut self.machine_st))?;
|
||||
|
||||
self.machine_st
|
||||
.bind(args[1].as_var().unwrap(), stream.into());
|
||||
|
||||
Stdio::from(reader)
|
||||
}
|
||||
Some(atom!("file")) => {
|
||||
let path = self.machine_st.value_to_str_like(args[1]).unwrap();
|
||||
|
||||
let file = match std::fs::File::open(&*path.as_str()) {
|
||||
Ok(file) => file,
|
||||
Err(_) => {
|
||||
return Err(self.machine_st.open_permission_error(
|
||||
args[1],
|
||||
atom!("process_create"),
|
||||
3,
|
||||
));
|
||||
}
|
||||
};
|
||||
Stdio::from(file)
|
||||
}
|
||||
_ => {
|
||||
panic!("Invalid stdin tag")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn process_id(&mut self) -> CallResult {
|
||||
fn stub_gen() -> Vec<FunctorElement> {
|
||||
functor_stub(atom!("process_id"), 2)
|
||||
}
|
||||
|
||||
// Process
|
||||
let process_r = self.deref_register(1);
|
||||
// Pid
|
||||
let pid_r = self.deref_register(2);
|
||||
|
||||
let Some(ptr) = process_r.to_untyped_arena_ptr() else {
|
||||
let err = self.machine_st.type_error(ValidType::Process, process_r);
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
};
|
||||
|
||||
let process = match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::ChildProcess, child_process) => {
|
||||
child_process
|
||||
}
|
||||
(ArenaHeaderTag::Dropped, _dropped) => {
|
||||
let err = self.machine_st.existence_error(ExistenceError::Process(process_r));
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
_ => {
|
||||
let err = self.machine_st.type_error(ValidType::Process, process_r);
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
);
|
||||
|
||||
self.machine_st.bind(
|
||||
pid_r.as_var().unwrap(),
|
||||
fixnum_as_cell!(Fixnum::build_with(process.id())),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn process_wait(&mut self) -> CallResult {
|
||||
fn stub_gen() -> Vec<FunctorElement> {
|
||||
functor_stub(atom!("process_wait"), 3)
|
||||
}
|
||||
|
||||
// Process
|
||||
let process_r = self.deref_register(1);
|
||||
// Var | Status
|
||||
let status_r = self.deref_register(2);
|
||||
// timeout | 0
|
||||
let timeout_r = self.deref_register(3);
|
||||
|
||||
let Some(ptr) = process_r.to_untyped_arena_ptr() else {
|
||||
let err = self.machine_st.type_error(ValidType::Process, process_r);
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
};
|
||||
|
||||
let mut process = match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::ChildProcess, child_process) => {
|
||||
child_process
|
||||
}
|
||||
(ArenaHeaderTag::Dropped, _dropped) => {
|
||||
let err = self.machine_st.existence_error(ExistenceError::Process(process_r));
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
_ => {
|
||||
let err = self.machine_st.type_error(ValidType::Process, process_r);
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
);
|
||||
|
||||
let status = if let Some(atom) = timeout_r.to_atom() {
|
||||
match atom {
|
||||
atom!("infinite") => process.wait().map(Some),
|
||||
_ => {
|
||||
panic!("Invalid Timeout value")
|
||||
}
|
||||
}
|
||||
} else if let Some(timeout) = timeout_r.to_fixnum() {
|
||||
if timeout.get_num() == 0 {
|
||||
process.try_wait()
|
||||
} else {
|
||||
panic!("Invalid Timeout value")
|
||||
}
|
||||
} else {
|
||||
panic!("Invalid Timeout value")
|
||||
};
|
||||
|
||||
match status {
|
||||
Ok(None) => {
|
||||
unify!(self.machine_st, status_r, atom_as_cell!(atom!("timeout")));
|
||||
Ok(())
|
||||
}
|
||||
Ok(Some(exit_status)) => {
|
||||
if let Some(exit_code) = exit_status.code() {
|
||||
let mut writer =
|
||||
Heap::functor_writer(functor!(atom!("exit"), [fixnum(exit_code)]));
|
||||
|
||||
match writer(&mut self.machine_st.heap) {
|
||||
Ok(loc) => {
|
||||
unify!(self.machine_st, status_r, loc);
|
||||
}
|
||||
Err(resource_err_loc) => {
|
||||
self.machine_st.throw_resource_error(resource_err_loc);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
|
||||
if let Some(signal) = ExitStatusExt::signal(&exit_status) {
|
||||
let mut writer =
|
||||
Heap::functor_writer(functor!(atom!("killed"), [fixnum(signal)]));
|
||||
|
||||
match writer(&mut self.machine_st.heap) {
|
||||
Ok(loc) => {
|
||||
unify!(self.machine_st, status_r, loc);
|
||||
}
|
||||
Err(resource_err_loc) => {
|
||||
self.machine_st.throw_resource_error(resource_err_loc);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
let err = self.machine_st.unreachable_error();
|
||||
Err(self.machine_st.error_form(err, stub_gen()))
|
||||
}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
let err = self.machine_st.unreachable_error();
|
||||
Err(self.machine_st.error_form(err, stub_gen()))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
let perm_error = self.machine_st.permission_error(
|
||||
Permission::Modify,
|
||||
atom!("process"),
|
||||
stub_gen(),
|
||||
);
|
||||
Err(self.machine_st.error_form(perm_error, stub_gen()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn process_kill(&mut self) -> CallResult {
|
||||
fn stub_gen() -> Vec<FunctorElement> {
|
||||
functor_stub(atom!("process_kill"), 1)
|
||||
}
|
||||
|
||||
// Pid
|
||||
let process_r = self.deref_register(1);
|
||||
|
||||
let Some(ptr) = process_r.to_untyped_arena_ptr() else {
|
||||
let err = self.machine_st.type_error(ValidType::Process, process_r);
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
};
|
||||
|
||||
let mut process = match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::ChildProcess, child_process) => {
|
||||
child_process
|
||||
}
|
||||
(ArenaHeaderTag::Dropped, _dropped) => {
|
||||
let err = self.machine_st.existence_error(ExistenceError::Process(process_r));
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
_ => {
|
||||
let err = self.machine_st.type_error(ValidType::Process, process_r);
|
||||
return Err(self.machine_st.error_form(err, stub_gen()));
|
||||
}
|
||||
);
|
||||
|
||||
if process.kill().is_err() {
|
||||
let perm_error =
|
||||
self.machine_st
|
||||
.permission_error(Permission::Modify, atom!("process"), stub_gen());
|
||||
return Err(self.machine_st.error_form(perm_error, stub_gen()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn process_release(&mut self) -> CallResult {
|
||||
fn stub_gen() -> Vec<FunctorElement> {
|
||||
functor_stub(atom!("process_release"), 1)
|
||||
}
|
||||
|
||||
let process = self.deref_register(1);
|
||||
|
||||
if let Some(ptr) = process.to_untyped_arena_ptr() {
|
||||
match_untyped_arena_ptr!(ptr,
|
||||
(ArenaHeaderTag::ChildProcess, child_process) => {
|
||||
child_process.drop_payload();
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let err = self.machine_st.type_error(ValidType::Process, process);
|
||||
|
||||
Err(self.machine_st.error_form(err, stub_gen()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn chars_base64(&mut self) -> CallResult {
|
||||
let padding = cell_as_atom!(self.deref_register(3));
|
||||
|
||||
@@ -218,6 +218,24 @@ macro_rules! match_untyped_arena_ptr_pat_body {
|
||||
#[allow(unused_braces)]
|
||||
$code
|
||||
}};
|
||||
($ptr:ident, PipeReader, $listener:ident, $code:expr) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut $listener = unsafe { $ptr.as_typed_ptr::<PipeReader>() };
|
||||
#[allow(unused_braces)]
|
||||
$code
|
||||
}};
|
||||
($ptr:ident, PipeWriter, $listener:ident, $code:expr) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut $listener = unsafe { $ptr.as_typed_ptr::<PipeWriter>() };
|
||||
#[allow(unused_braces)]
|
||||
$code
|
||||
}};
|
||||
($ptr:ident, ChildProcess, $listener:ident, $code:expr) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut $listener = unsafe { $ptr.as_typed_ptr::<std::process::Child>() };
|
||||
#[allow(unused_braces)]
|
||||
$code
|
||||
}};
|
||||
($ptr:ident, $($tags:tt)|+, $s:ident, $code:expr) => {{
|
||||
let $s = Stream::from_tag($ptr.get_tag(), $ptr);
|
||||
#[allow(unused_braces)]
|
||||
@@ -240,6 +258,8 @@ macro_rules! match_untyped_arena_ptr_pat {
|
||||
| ArenaHeaderTag::InputChannelStream
|
||||
| ArenaHeaderTag::StandardOutputStream
|
||||
| ArenaHeaderTag::StandardErrorStream
|
||||
| ArenaHeaderTag::PipeReader
|
||||
| ArenaHeaderTag::PipeWriter
|
||||
};
|
||||
($tag:ident) => {
|
||||
ArenaHeaderTag::$tag
|
||||
|
||||
@@ -27,6 +27,7 @@ use_module(library(ordsets)).
|
||||
use_module(library(os)).
|
||||
use_module(library(pairs)).
|
||||
use_module(library(pio)).
|
||||
use_module(library(process)).
|
||||
use_module(library(queues)).
|
||||
use_module(library(random)).
|
||||
use_module(library(reif)).
|
||||
|
||||
@@ -45,3 +45,4 @@
|
||||
true.
|
||||
true.
|
||||
true.
|
||||
true.
|
||||
|
||||
59
tests/scryer/cli/src_tests/process.md
Normal file
59
tests/scryer/cli/src_tests/process.md
Normal file
@@ -0,0 +1,59 @@
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("", [], [invalid, process(P)]), process_kill(P), halt'
|
||||
use_module(library(process)),process_create([],[],[invalid,process(P)]),process_kill(P),halt causes: error(domain_error(process_create_option,invalid),[predicate-process_create/3,predicate-check_options/3,predicate-must_be_known_options/3])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("", [], [invalid(_Var), process(P)]), process_kill(P), halt'
|
||||
use_module(library(process)),process_create([],[],[invalid(_Var),process(P)]),process_kill(P),halt causes: error(domain_error(process_create_option,invalid),[predicate-process_create/3,predicate-check_options/3,predicate-must_be_known_options/3])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("", [], [stdin(null), stdin(null), process(P)]), process_kill(P), halt'
|
||||
use_module(library(process)),process_create([],[],[stdin(null),stdin(null),process(P)]),process_kill(P),halt causes: error(domain_error(non_duplicate_options,stdin),[predicate-process_create/3,predicate-check_options/3,predicate-must_be_known_options/3])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("", [], [env([]), environment([]), process(P)]), process_kill(P), halt'
|
||||
use_module(library(process)),process_create([],[],[env([]),environment([]),process(P)]),process_kill(P),halt causes: error(domain_error(non_conflicting_options,[env([]),environment([])]),[predicate-process_create/3,predicate-check_options/3,predicate-extract_options/2])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_wait(pid, _Status, [invalid(_Var), timeout(0)]), halt'
|
||||
use_module(library(process)),process_wait(pid,_Status,[invalid(_Var),timeout(0)]),halt causes: error(domain_error(process_wait_option,invalid),[predicate-process_wait/3,predicate-check_options/3,predicate-must_be_known_options/3])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("", [], [stdin(invalid), process(P)]), process_kill(P), halt'
|
||||
use_module(library(process)),process_create([],[],[stdin(invalid),process(P)]),process_kill(P),halt causes: error(domain_error(stdio_spec,invalid),[predicate-process_create/3,predicate-check_options/3,predicate-extract_options/2,predicate-valid_stdio/1])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_wait(50, _Status), halt'
|
||||
use_module(library(process)),process_wait(50,_Status),halt causes: error(type_error(process,50),[predicate-process_wait/2,predicate-process_wait/3|process_wait/3])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_kill(50), halt'
|
||||
use_module(library(process)),process_kill(50),halt causes: error(type_error(process,50),[predicate-process_kill/1|process_kill/1])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_id(50,_Pid), halt'
|
||||
use_module(library(process)),process_id(50,_Pid),halt causes: error(type_error(process,50),[predicate-process_id/2|process_id/2])
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_release(50), halt'
|
||||
use_module(library(process)),process_release(50),halt causes: error(type_error(process,50),[predicate-process_release/1,predicate-process_wait/2,predicate-process_wait/3|process_wait/3])
|
||||
|
||||
```
|
||||
14
tests/scryer/cli/unix/process.md
Normal file
14
tests/scryer/cli/unix/process.md
Normal file
@@ -0,0 +1,14 @@
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("false", [], [process(P)]), process_wait(P, exit(1)), halt'
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), use_module(library(format)), process_create("sh", [], [process(P), stdout(null), stdin(pipe(S))]), format(S, "exit 1~n", []), process_wait(P, exit(1)), halt'
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("sh", ["-c", "sleep 5"], [process(P), stdout(null)]), process_kill(P), process_wait(P, killed(9)), halt'
|
||||
|
||||
```
|
||||
9
tests/scryer/cli/windows/process.md
Normal file
9
tests/scryer/cli/windows/process.md
Normal file
@@ -0,0 +1,9 @@
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), process_create("cmd", ["/C", "exit", "1"], [process(P)]), process_wait(P, exit(1)), halt'
|
||||
|
||||
```
|
||||
|
||||
```trycmd
|
||||
$ scryer-prolog -f --no-add-history -g 'use_module(library(process)), use_module(library(format)), process_create("cmd", [], [process(P), stdout(null), stdin(pipe(S))]), format(S, "exit 1~n", []), process_wait(P, exit(1)), halt'
|
||||
|
||||
```
|
||||
@@ -19,9 +19,20 @@ mod src_tests;
|
||||
ignore = "miri isolation, unsupported operation: can't call foreign function"
|
||||
)]
|
||||
fn cli_tests() {
|
||||
trycmd::TestCases::new()
|
||||
let cases = trycmd::TestCases::new();
|
||||
cases
|
||||
.default_bin_name("scryer-prolog")
|
||||
.case("tests/scryer/cli/issues/*.toml")
|
||||
.case("tests/scryer/cli/src_tests/*.toml")
|
||||
.case("tests/scryer/cli/src_tests/*.md");
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
cases.case("tests/scryer/cli/windows/*.md");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
cases.case("tests/scryer/cli/unix/*.md");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user