Reimplement environment and the environment stack in Rust

This reimplements the environment stack in Rust.
This commit is contained in:
ridiculousfish 2023-04-29 12:43:41 -07:00 committed by Peter Ammon
parent 8ec1467dda
commit e71b75e0e4
4 changed files with 355 additions and 1171 deletions

View file

@ -1,8 +1,13 @@
use super::var::{EnvVar, EnvVarFlags};
use crate::ffi::{wchar_t, wcharz_t, wcstring_list_ffi_t};
use super::environment::{self, EnvNull, EnvStack, EnvStackRef, Environment};
use super::var::{ElectricVar, EnvVar, EnvVarFlags, Statuses};
use crate::env::EnvMode;
use crate::event::Event;
use crate::ffi::{event_list_ffi_t, wchar_t, wcharz_t, wcstring_list_ffi_t};
use crate::null_terminated_array::OwningNullTerminatedArrayRefFFI;
use crate::signal::Signal;
use crate::wchar_ffi::WCharToFFI;
use crate::wchar_ffi::{AsWstr, WCharFromFFI};
use cxx::{CxxWString, UniquePtr};
use cxx::{CxxVector, CxxWString, UniquePtr};
use std::pin::Pin;
#[allow(clippy::module_inception)]
@ -20,9 +25,15 @@ mod env_ffi {
}
extern "C++" {
include!("env.h");
include!("null_terminated_array.h");
include!("wutil.h");
type event_list_ffi_t = super::event_list_ffi_t;
type wcstring_list_ffi_t = super::wcstring_list_ffi_t;
type wcharz_t = super::wcharz_t;
type OwningNullTerminatedArrayRefFFI =
crate::null_terminated_array::OwningNullTerminatedArrayRefFFI;
}
extern "Rust" {
@ -64,6 +75,81 @@ mod env_ffi {
values: &wcstring_list_ffi_t,
) -> Box<EnvVar>;
}
extern "Rust" {
type EnvNull;
#[cxx_name = "getf"]
fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar;
#[cxx_name = "get_names"]
fn get_names_ffi(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>);
#[cxx_name = "env_null_create"]
fn env_null_create_ffi() -> Box<EnvNull>;
}
extern "Rust" {
type Statuses;
#[cxx_name = "get_status"]
fn get_status_ffi(&self) -> i32;
#[cxx_name = "get_pipestatus"]
fn get_pipestatus_ffi(&self) -> &Vec<i32>;
#[cxx_name = "get_kill_signal"]
fn get_kill_signal_ffi(&self) -> i32;
}
extern "Rust" {
#[cxx_name = "EnvDyn"]
type EnvDynFFI;
fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar;
fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>);
}
extern "Rust" {
#[cxx_name = "EnvStackRef"]
type EnvStackRefFFI;
fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar;
fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>);
fn is_principal(&self) -> bool;
fn get_last_statuses(&self) -> Box<Statuses>;
fn set_last_statuses(&self, status: i32, kill_signal: i32, pipestatus: &CxxVector<i32>);
fn set(
&self,
name: &CxxWString,
flags: u16,
vals: &wcstring_list_ffi_t,
) -> EnvStackSetResult;
fn remove(&self, name: &CxxWString, flags: u16) -> EnvStackSetResult;
fn get_pwd_slash(&self) -> UniquePtr<CxxWString>;
fn set_pwd_from_getcwd(&self);
fn push(&mut self, new_scope: bool);
fn pop(&mut self);
// Returns a ``Box<OwningNullTerminatedArrayRefFFI>.into_raw()``.
fn export_array(&self) -> *mut OwningNullTerminatedArrayRefFFI;
fn snapshot(&self) -> Box<EnvDynFFI>;
// Access a variable stack that only represents globals.
// Do not push or pop from this.
fn env_get_globals_ffi() -> Box<EnvStackRefFFI>;
// Access the principal variable stack.
fn env_get_principal_ffi() -> Box<EnvStackRefFFI>;
fn universal_sync(&self, always: bool, out_events: Pin<&mut event_list_ffi_t>);
}
extern "Rust" {
#[cxx_name = "var_is_electric"]
fn var_is_electric_ffi(name: &CxxWString) -> bool;
#[cxx_name = "rust_env_init"]
fn rust_env_init_ffi(do_uvars: bool);
#[cxx_name = "env_flags_for"]
fn env_flags_for_ffi(name: wcharz_t) -> u8;
}
}
pub use env_ffi::EnvStackSetResult;
@ -117,3 +203,167 @@ fn env_var_create_ffi(vals: &wcstring_list_ffi_t, flags: u8) -> Box<EnvVar> {
pub fn env_var_create_from_name_ffi(name: wcharz_t, values: &wcstring_list_ffi_t) -> Box<EnvVar> {
Box::new(EnvVar::new_from_name_vec(name.as_wstr(), values.from_ffi()))
}
fn env_null_create_ffi() -> Box<EnvNull> {
Box::new(EnvNull::new())
}
/// FFI wrapper around dyn Environment.
pub struct EnvDynFFI(Box<dyn Environment>);
impl EnvDynFFI {
fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
EnvironmentFFI::getf_ffi(&*self.0, name, mode)
}
fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) {
EnvironmentFFI::get_names_ffi(&*self.0, flags, out)
}
}
/// FFI wrapper around EnvStackRef.
pub struct EnvStackRefFFI(EnvStackRef);
impl EnvStackRefFFI {
fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
EnvironmentFFI::getf_ffi(&*self.0, name, mode)
}
fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) {
EnvironmentFFI::get_names_ffi(&*self.0, flags, out)
}
fn is_principal(&self) -> bool {
self.0.is_principal()
}
fn get_pwd_slash(&self) -> UniquePtr<CxxWString> {
self.0.get_pwd_slash().to_ffi()
}
fn push(&self, new_scope: bool) {
self.0.push(new_scope)
}
fn pop(&self) {
self.0.pop()
}
fn get_last_statuses(&self) -> Box<Statuses> {
Box::new(self.0.get_last_statuses())
}
fn set_last_statuses(&self, status: i32, kill_signal: i32, pipestatus: &CxxVector<i32>) {
let statuses = Statuses {
status,
kill_signal: if kill_signal == 0 {
None
} else {
Some(Signal::new(kill_signal))
},
pipestatus: pipestatus.as_slice().to_vec(),
};
self.0.set_last_statuses(statuses)
}
fn set_pwd_from_getcwd(&self) {
self.0.set_pwd_from_getcwd()
}
fn set(&self, name: &CxxWString, flags: u16, vals: &wcstring_list_ffi_t) -> EnvStackSetResult {
let mode = EnvMode::from_bits(flags).expect("Invalid mode bits");
self.0.set(name.as_wstr(), mode, vals.from_ffi())
}
fn remove(&self, name: &CxxWString, flags: u16) -> EnvStackSetResult {
let mode = EnvMode::from_bits(flags).expect("Invalid mode bits");
self.0.remove(name.as_wstr(), mode)
}
fn export_array(&self) -> *mut OwningNullTerminatedArrayRefFFI {
Box::into_raw(Box::new(OwningNullTerminatedArrayRefFFI(
self.0.export_array(),
)))
}
fn snapshot(&self) -> Box<EnvDynFFI> {
Box::new(EnvDynFFI(self.0.snapshot()))
}
fn universal_sync(
self: &EnvStackRefFFI,
always: bool,
mut out_events: Pin<&mut event_list_ffi_t>,
) {
let events: Vec<Box<Event>> = self.0.universal_sync(always);
for event in events {
out_events.as_mut().push(Box::into_raw(event).cast());
}
}
}
impl Statuses {
fn get_status_ffi(&self) -> i32 {
self.status
}
fn get_pipestatus_ffi(&self) -> &Vec<i32> {
&self.pipestatus
}
fn get_kill_signal_ffi(&self) -> i32 {
match self.kill_signal {
Some(sig) => sig.code(),
None => 0,
}
}
}
fn env_get_globals_ffi() -> Box<EnvStackRefFFI> {
Box::new(EnvStackRefFFI(EnvStack::globals().clone()))
}
fn env_get_principal_ffi() -> Box<EnvStackRefFFI> {
Box::new(EnvStackRefFFI(EnvStack::principal().clone()))
}
// We have to implement these directly to make cxx happy, even though they're implemented in the EnvironmentFFI trait.
impl EnvNull {
pub fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
EnvironmentFFI::getf_ffi(self, name, mode)
}
pub fn get_names_ffi(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) {
EnvironmentFFI::get_names_ffi(self, flags, out)
}
}
trait EnvironmentFFI: Environment {
/// FFI helper.
/// This returns either null, or the result of Box.into_raw().
/// This is a workaround for the difficulty of passing an Option through FFI.
fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar {
match self.getf(
name.as_wstr(),
EnvMode::from_bits(mode).expect("Invalid mode bits"),
) {
None => std::ptr::null_mut(),
Some(var) => Box::into_raw(Box::new(var)),
}
}
fn get_names_ffi(&self, mode: u16, mut out: Pin<&mut wcstring_list_ffi_t>) {
let names = self.get_names(EnvMode::from_bits(mode).expect("Invalid mode bits"));
for name in names {
out.as_mut().push(name.to_ffi());
}
}
}
impl<T: Environment + ?Sized> EnvironmentFFI for T {}
fn var_is_electric_ffi(name: &CxxWString) -> bool {
ElectricVar::for_name(name.as_wstr()).is_some()
}
fn rust_env_init_ffi(do_uvars: bool) {
environment::env_init(do_uvars);
}
fn env_flags_for_ffi(name: wcharz_t) -> u8 {
EnvVar::flags_for(name.as_wstr()).bits()
}

File diff suppressed because it is too large Load diff

View file

@ -17,10 +17,14 @@
#include "maybe.h"
#include "wutil.h"
struct event_list_ffi_t;
#if INCLUDE_RUST_HEADERS
#include "env/env_ffi.rs.h"
#else
struct EnvVar;
struct EnvNull;
struct EnvStackRef;
#endif
/// FFI helper for events.
@ -42,8 +46,6 @@ struct owning_null_terminated_array_t;
extern size_t read_byte_limit;
extern bool curses_initialized;
struct Event;
// Flags that may be passed as the 'mode' in env_stack_t::set() / environment_t::get().
enum : uint16_t {
/// Default mode. Used with `env_stack_t::get()` to indicate the caller doesn't care what scope
@ -114,32 +116,23 @@ void env_init(const struct config_paths_t *paths = nullptr, bool do_uvars = true
void misc_init();
/// env_var_t is an immutable value-type data structure representing the value of an environment
/// variable.
/// variable. This wraps the EnvVar type from Rust.
class env_var_t {
public:
using env_var_flags_t = uint8_t;
public:
enum {
flag_export = 1 << 0, // whether the variable is exported
flag_read_only = 1 << 1, // whether the variable is read only
flag_pathvar = 1 << 2, // whether the variable is a path variable
};
// Constructors.
env_var_t() : env_var_t{std::vector<wcstring>{}, 0} {}
env_var_t() : env_var_t(wcstring_list_ffi_t{}, 0) {}
env_var_t(const wcstring_list_ffi_t &vals, uint8_t flags);
env_var_t(const env_var_t &);
env_var_t(env_var_t &&) = default;
env_var_t(std::vector<wcstring> vals, env_var_flags_t flags);
env_var_t(wcstring val, env_var_flags_t flags)
: env_var_t{std::vector<wcstring>{std::move(val)}, flags} {}
// Constructors that infer the flags from a name.
env_var_t(const wchar_t *name, std::vector<wcstring> vals);
env_var_t(const wchar_t *name, wcstring val)
: env_var_t{name, std::vector<wcstring>{std::move(val)}} {}
// Construct from FFI. This transfers ownership of the EnvVar, which should originate
// in Box::into_raw().
static env_var_t new_ffi(EnvVar *ptr);
@ -163,33 +156,7 @@ class env_var_t {
/// \return the character used when delimiting quoted expansion.
wchar_t get_delimiter() const;
/// \return a copy of this variable with new values.
env_var_t setting_vals(std::vector<wcstring> vals) const {
return env_var_t{std::move(vals), get_flags()};
}
env_var_t setting_exports(bool exportv) const {
env_var_flags_t flags = get_flags();
if (exportv) {
flags |= flag_export;
} else {
flags &= ~flag_export;
}
return env_var_t{as_list(), flags};
}
env_var_t setting_pathvar(bool pathvar) const {
env_var_flags_t flags = get_flags();
if (pathvar) {
flags |= flag_pathvar;
} else {
flags &= ~flag_pathvar;
}
return env_var_t{as_list(), flags};
}
static env_var_flags_t flags_for(const wchar_t *name);
static std::shared_ptr<const std::vector<wcstring>> empty_list();
env_var_t &operator=(const env_var_t &);
env_var_t &operator=(env_var_t &&) = default;
@ -228,29 +195,22 @@ class environment_t {
/// The null environment contains nothing.
class null_environment_t : public environment_t {
public:
null_environment_t() = default;
~null_environment_t() override;
null_environment_t();
~null_environment_t();
maybe_t<env_var_t> get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override;
std::vector<wcstring> get_names(env_mode_flags_t flags) const override;
private:
rust::Box<EnvNull> impl_;
};
/// A mutable environment which allows scopes to be pushed and popped.
class env_stack_impl_t;
class env_stack_t final : public environment_t {
friend class parser_t;
/// The implementation. Do not access this directly.
std::unique_ptr<env_stack_impl_t> impl_;
/// All environment stacks are guarded by a global lock.
acquired_lock<env_stack_impl_t> acquire_impl();
acquired_lock<const env_stack_impl_t> acquire_impl() const;
explicit env_stack_t(std::unique_ptr<env_stack_impl_t> impl);
/// \return whether we are the principal stack.
bool is_principal() const { return this == principal_ref().get(); }
bool is_principal() const;
public:
~env_stack_t() override;
@ -334,6 +294,12 @@ class env_stack_t final : public environment_t {
// Access a variable stack that only represents globals.
// Do not push or pop from this.
static env_stack_t &globals();
private:
env_stack_t(rust::Box<EnvStackRef> imp);
/// The implementation. Do not access this directly.
rust::Box<EnvStackRef> impl_;
};
bool get_use_posix_spawn();

View file

@ -2,7 +2,6 @@
// replacement. There is no logic still in here that needs to be ported to rust.
#ifndef FISH_EVENT_H
#ifdef INCLUDE_RUST_HEADERS
#define FISH_EVENT_H
#include <unistd.h>
@ -13,11 +12,16 @@
#include <vector>
#include "common.h"
#include "cxx.h"
#include "global_safety.h"
#include "wutil.h"
class parser_t;
#if INCLUDE_RUST_HEADERS
#include "event.rs.h"
#else
struct Event;
#endif
/// The process id that is used to match any process id.
// TODO: Remove after porting functions.cpp
@ -34,4 +38,3 @@ void event_fire_generic(parser_t &parser, const wcstring &name,
const std::vector<wcstring> &args = g_empty_string_list);
#endif
#endif