From: Markus Triska Date: Sat, 11 Jul 2020 16:41:18 +0000 (+0200) Subject: ADDED: library(os), reasoning about environment variables. X-Git-Tag: v0.8.127~14^2~1 X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=eb872f8fca9d8bfdc62d7e13b8a744bb144ada7f;p=scryer-prolog.git ADDED: library(os), reasoning about environment variables. Together with library(files), this addresses #511. --- diff --git a/README.md b/README.md index 28bed19d..6c431ac6 100644 --- a/README.md +++ b/README.md @@ -442,6 +442,8 @@ The modules that ship with Scryer Prolog are also called Predicates for opening and accepting TCP connections as streams. TLS negotiation is performed via the option `tls(true)` in `socket_client_open/3`, yielding secure encrypted connections. +* [`os`](src/lib/os.pl) + Predicates for reasoning about environment variables. * [`crypto`](src/lib/crypto.pl) Cryptographically secure random numbers and hashes, HMAC-based key derivation (HKDF), password-based key derivation (PBKDF2), diff --git a/src/clause_types.rs b/src/clause_types.rs index 66222a6d..64e3c2dd 100644 --- a/src/clause_types.rs +++ b/src/clause_types.rs @@ -306,6 +306,9 @@ pub enum SystemClauseType { Ed25519KeyPairPublicKey, LoadHTML, LoadXML, + GetEnv, + SetEnv, + UnsetEnv, } impl SystemClauseType { @@ -510,6 +513,9 @@ impl SystemClauseType { &SystemClauseType::Ed25519KeyPairPublicKey => clause_name!("$ed25519_keypair_public_key"), &SystemClauseType::LoadHTML => clause_name!("$load_html"), &SystemClauseType::LoadXML => clause_name!("$load_xml"), + &SystemClauseType::GetEnv => clause_name!("$getenv"), + &SystemClauseType::SetEnv => clause_name!("$setenv"), + &SystemClauseType::UnsetEnv => clause_name!("$unsetenv"), } } @@ -694,6 +700,9 @@ impl SystemClauseType { ("$ed25519_keypair_public_key", 2) => Some(SystemClauseType::Ed25519KeyPairPublicKey), ("$load_html", 3) => Some(SystemClauseType::LoadHTML), ("$load_xml", 3) => Some(SystemClauseType::LoadXML), + ("$getenv", 2) => Some(SystemClauseType::GetEnv), + ("$setenv", 2) => Some(SystemClauseType::SetEnv), + ("$unsetenv", 1) => Some(SystemClauseType::UnsetEnv), _ => None, } } diff --git a/src/lib/error.pl b/src/lib/error.pl index b75dc310..7e2189c4 100644 --- a/src/lib/error.pl +++ b/src/lib/error.pl @@ -44,6 +44,7 @@ must_be_(var, Term) :- ). must_be_(integer, Term) :- check_(integer, integer, Term). must_be_(atom, Term) :- check_(atom, atom, Term). +must_be_(character, T) :- check_(character, character, T). must_be_(list, Term) :- check_(ilist, list, Term). must_be_(type, Term) :- check_(type, type, Term). must_be_(boolean, Term) :- check_(boolean, boolean, Term). @@ -56,6 +57,10 @@ check_(Pred, Type, Term) :- boolean(B) :- ( B == true ; B == false ). +character(C) :- + atom(C), + atom_length(C, 1). + ilist(V) :- var(V), instantiation_error(must_be/2). ilist([]). ilist([_|Ls]) :- ilist(Ls). @@ -63,6 +68,7 @@ ilist([_|Ls]) :- ilist(Ls). type(type). type(integer). type(atom). +type(character). type(list). type(var). type(boolean). @@ -90,6 +96,7 @@ can_be(Type, Term) :- can_(integer, Term) :- integer(Term). can_(atom, Term) :- atom(Term). +can_(character, T) :- character(T). can_(list, Term) :- list_or_partial_list(Term). can_(boolean, Term) :- boolean(Term). diff --git a/src/lib/os.pl b/src/lib/os.pl new file mode 100644 index 00000000..35bfc789 --- /dev/null +++ b/src/lib/os.pl @@ -0,0 +1,58 @@ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Predicates for reasoning about the operating system (OS) environment. + Written July 2020 by Markus Triska (triska@metalevel.at). + + Lists of characters are used throughout to represent keys and values. + + Example: + + ?- getenv("LANG", Ls). + Ls = "en_US.UTF-8" + ; false. + + Public domain code. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +:- module(os, [getenv/2, + setenv/2, + unsetenv/1]). + +:- use_module(library(error)). +:- use_module(library(charsio)). +:- use_module(library(lists)). + +getenv(Key, Value) :- + must_be_env_var(Key), + '$getenv'(Key, Value). + +setenv(Key, Value) :- + must_be_env_var(Key), + must_be_chars(Value), + '$setenv'(Key, Value). + +unsetenv(Key) :- + must_be_env_var(Key), + '$unsetenv'(Key). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + For now, we only support a restricted subset of variable names. + + The reason is that Rust may panic if a key is empty, contains an + ASCII equals sign '=' or the NUL character '\0', or when the value + contains the NUL character. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +must_be_env_var(Cs) :- + must_be_chars(Cs), + Cs = [_|_], + ( maplist(permitted, Cs) -> true + ; domain_error(env_var, Cs, os) + ). + +permitted(C) :- char_type(C, alnum). +permitted(C) :- char_type(C, ascii_punctuation). +permitted('_'). + +must_be_chars(Cs) :- + must_be(list, Cs), + maplist(must_be(character), Cs). diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 6027f570..e8db40ce 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -32,6 +32,7 @@ use std::net::{TcpListener, TcpStream}; use std::ops::Sub; use std::rc::Rc; use std::num::NonZeroU32; +use std::env; use std::time::{Duration, SystemTime}; use crate::cpu_time::ProcessTime; @@ -5637,6 +5638,28 @@ impl MachineState { } } } + &SystemClauseType::GetEnv => { + let key = self.heap_pstr_iter(self[temp_v!(1)]).to_string(); + match env::var(key) { + Ok(value) => { + let cstr = self.heap.put_complete_string(&value); + self.unify(self[temp_v!(2)], cstr); + } + _ => { + self.fail = true; + return Ok(()); + } + } + } + &SystemClauseType::SetEnv => { + let key = self.heap_pstr_iter(self[temp_v!(1)]).to_string(); + let value = self.heap_pstr_iter(self[temp_v!(2)]).to_string(); + env::set_var(key, value); + } + &SystemClauseType::UnsetEnv => { + let key = self.heap_pstr_iter(self[temp_v!(1)]).to_string(); + env::remove_var(key); + } }; return_from_clause!(self.last_call, self)