From 00e6d12d5021b6bb3ab3738a3e118fde32239c78 Mon Sep 17 00:00:00 2001 From: Patrick Ferris Date: Mon, 7 Sep 2026 10:23:14 +0100 Subject: [PATCH] Add Eio.Vars capability Eio.Vars.t provides readonly access to the parent environment. --- lib_eio/eio.ml | 2 ++ lib_eio/eio.mli | 6 ++++++ lib_eio/unix/eio_unix.ml | 4 +++- lib_eio/unix/eio_unix.mli | 4 ++++ lib_eio/unix/private.ml | 11 +++++++++++ lib_eio/vars.ml | 30 +++++++++++++++++++++++++++++ lib_eio/vars.mli | 24 +++++++++++++++++++++++ lib_eio_linux/eio_linux.ml | 1 + lib_eio_linux/tests/spawn.md | 17 ++++++++++------- lib_eio_posix/eio_posix.ml | 1 + lib_eio_posix/test/spawn.md | 23 +++++++++++++--------- lib_eio_windows/eio_windows.ml | 1 + tests/process.md | 35 ++++++++++++++++++++++++++++++++++ 13 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 lib_eio/vars.ml create mode 100644 lib_eio/vars.mli diff --git a/lib_eio/eio.ml b/lib_eio/eio.ml index 9f9ef328e..f6d09ee97 100644 --- a/lib_eio/eio.ml +++ b/lib_eio/eio.ml @@ -23,6 +23,7 @@ end module Buf_write = Buf_write module Net = Net module Process = Process +module Vars = Vars module Domain_manager = Domain_manager module Time = Time module File = File @@ -35,6 +36,7 @@ module Stdenv = struct let stderr (t : ) = t#stderr let net (t : ) = t#net let process_mgr (t : ) = t#process_mgr + let vars (t : ) = t#vars let domain_mgr (t : ) = t#domain_mgr let clock (t : ) = t#clock let mono_clock (t : ) = t#mono_clock diff --git a/lib_eio/eio.mli b/lib_eio/eio.mli index 75090d5c9..e3a1af566 100644 --- a/lib_eio/eio.mli +++ b/lib_eio/eio.mli @@ -181,6 +181,9 @@ module Fs = Fs (** Managing child processes. *) module Process = Process +(** Accessing environment variables *) +module Vars = Vars + (** {2 Time} *) (** Clocks, time, sleeping and timeouts. *) @@ -244,6 +247,9 @@ module Stdenv : sig val process_mgr : -> 'a (** [process_mgr t] allows you to manage child processes. *) + val vars : -> 'a + (** [vars t] allows you to access environment variables. *) + (** {1 Domains (using multiple CPU cores)} To use this, see {!Domain_manager}. diff --git a/lib_eio/unix/eio_unix.ml b/lib_eio/unix/eio_unix.ml index a0b83528b..cd840f72a 100644 --- a/lib_eio/unix/eio_unix.ml +++ b/lib_eio/unix/eio_unix.ml @@ -34,6 +34,7 @@ module Stdenv = struct net : [`Unix | `Generic] Eio.Net.ty r; domain_mgr : Eio.Domain_manager.ty r; process_mgr : Process.mgr_ty r; + vars : Eio.Vars.ty r; clock : float Eio.Time.clock_ty r; mono_clock : Eio.Time.Mono.ty r; fs : Eio.Fs.dir_ty Eio.Path.t; @@ -45,7 +46,7 @@ module Stdenv = struct let override ?stdin ?stdout ?stderr ?net ?domain_mgr - ?process_mgr ?clock ?mono_clock ?fs ?cwd + ?process_mgr ?vars ?clock ?mono_clock ?fs ?cwd ?secure_random ?debug ?backend_id (env : ) : base = object @@ -55,6 +56,7 @@ module Stdenv = struct method net = Option.value ~default:env#net net method domain_mgr = Option.value ~default:env#domain_mgr domain_mgr method process_mgr = Option.value ~default:env#process_mgr process_mgr + method vars = Option.value ~default:env#vars vars method clock = Option.value ~default:env#clock clock method mono_clock = Option.value ~default:env#mono_clock mono_clock method fs = Option.value ~default:env#fs fs diff --git a/lib_eio/unix/eio_unix.mli b/lib_eio/unix/eio_unix.mli index 2542ce3fa..1c4a6e98a 100644 --- a/lib_eio/unix/eio_unix.mli +++ b/lib_eio/unix/eio_unix.mli @@ -95,6 +95,7 @@ module Stdenv : sig net : [`Unix | `Generic] Eio.Net.ty r; domain_mgr : Eio.Domain_manager.ty r; process_mgr : Process.mgr_ty r; + vars : Eio.Vars.ty r; clock : float Eio.Time.clock_ty r; mono_clock : Eio.Time.Mono.ty r; fs : Eio.Fs.dir_ty Eio.Path.t; @@ -114,6 +115,7 @@ module Stdenv : sig ?net:[ `Generic | `Unix ] Eio.Net.ty r -> ?domain_mgr:Eio.Domain_manager.ty r -> ?process_mgr:Process.mgr_ty r -> + ?vars:Eio.Vars.ty r -> ?clock:float Eio.Time.clock_ty r -> ?mono_clock:Eio.Time.Mono.ty r -> ?fs:Eio.Fs.dir_ty Eio.Path.t -> @@ -156,6 +158,8 @@ module Private : sig val setsockopt : Fd.t -> 'a Eio.Net.Sockopt.t -> 'a -> unit val getsockopt : Fd.t -> 'a Eio.Net.Sockopt.t -> 'a + + val vars : Eio.Vars.ty r end module Pi = Pi diff --git a/lib_eio/unix/private.ml b/lib_eio/unix/private.ml index a32706d02..556c89a42 100644 --- a/lib_eio/unix/private.ml +++ b/lib_eio/unix/private.ml @@ -295,3 +295,14 @@ let getsockopt : type a. Fd.t -> a Eio.Net.Sockopt.t -> a = fun fd opt -> | Net.Sockopt_float bo -> Unix.getsockopt_float fd bo | _ -> raise (Eio.Net.err Invalid_option) with Unix.Unix_error (code, name, arg) -> raise (Err.v code name arg) + +module Vars = struct + type t = unit + let get_all () = Unix.environment () |> Eio.Process.Env.of_array + let get_opt () name = + try Some (Unix.getenv name) with Not_found -> None +end + +let vars = + let handler = Eio.Vars.Pi.vars (module Vars) in + Eio.Resource.T ((), handler) diff --git a/lib_eio/vars.ml b/lib_eio/vars.ml new file mode 100644 index 000000000..6c1d50ebc --- /dev/null +++ b/lib_eio/vars.ml @@ -0,0 +1,30 @@ +open Std + +type ty = [ `Vars ] +type 'a t = ([> ty] as 'a) r + +module Pi = struct + module type VARS = sig + type t + + val get_all : t -> Process.Env.t + val get_opt : t -> string -> string option + end + + type (_, _, _) Resource.pi += + | Vars : ('t, (module VARS with type t = 't), [> ty ]) Resource.pi + + let vars (type t) (module X : VARS with type t = t) = + Resource.handler [ H (Vars, (module X)) ] +end + +let get_all t = + let (Resource.T (t, ops)) = t in + let module X = (val (Resource.get ops Pi.Vars)) in + X.get_all t + +let get_opt t name = + let (Resource.T (t, ops)) = t in + let module X = (val (Resource.get ops Pi.Vars)) in + X.get_opt t name + diff --git a/lib_eio/vars.mli b/lib_eio/vars.mli new file mode 100644 index 000000000..217195be6 --- /dev/null +++ b/lib_eio/vars.mli @@ -0,0 +1,24 @@ +open Std + +type ty = [ `Vars ] +type 'a t = ([> ty] as 'a) r + +(** {1 Accessing environment variables} *) + +val get_all : _ t -> Process.Env.t +(** [get_all vars] gets the full list of environment variables. *) + +val get_opt : _ t -> string -> string option +(** [get_opt vars name] will get the environment variable called [name]. + Returns [None] if [name] does not exist. *) + +module Pi : sig + module type VARS = sig + type t + + val get_all : t -> Process.Env.t + val get_opt : t -> string -> string option + end + + val vars : (module VARS with type t = 't) -> ('t, ty) Resource.handler +end diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index cade086c0..bdf3a8d18 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -84,6 +84,7 @@ let stdenv ~run_event_loop = method stderr = Flow.stderr method net = Net.v method process_mgr = Process.mgr + method vars = Eio_unix.Private.vars method domain_mgr = domain_mgr ~run_event_loop method clock = Time.clock method mono_clock = Time.mono_clock diff --git a/lib_eio_linux/tests/spawn.md b/lib_eio_linux/tests/spawn.md index b5c71899e..ba06dbc71 100644 --- a/lib_eio_linux/tests/spawn.md +++ b/lib_eio_linux/tests/spawn.md @@ -7,8 +7,6 @@ open Eio.Std module Env = Eio.Process.Env module Process = Eio_linux.Low_level.Process - -let default_env = Unix.environment () |> Env.of_array ``` ## Spawning processes @@ -31,7 +29,8 @@ FOO=bar Changing directory: ```ocaml -# Eio_linux.run @@ fun _env -> +# Eio_linux.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let child = Process.spawn ~sw Process.Fork_action.[ chdir "/"; @@ -47,7 +46,8 @@ Changing directory: Changing directory using a file descriptor: ```ocaml -# Eio_linux.run @@ fun _env -> +# Eio_linux.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let root = Eio_linux.Low_level.openat2 ~sw "/" @@ -71,7 +71,8 @@ Changing directory using a file descriptor: Exit status: ```ocaml -# Eio_linux.run @@ fun _env -> +# Eio_linux.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let child = Process.spawn ~sw Process.Fork_action.[ execve "/usr/bin/env" @@ -85,7 +86,8 @@ Exit status: Failure starting child: ```ocaml -# Eio_linux.run @@ fun _env -> +# Eio_linux.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> Process.spawn ~sw Process.Fork_action.[ chdir "/idontexist"; @@ -99,7 +101,8 @@ Exception: Unix.Unix_error(Unix.ENOENT, "chdir", "") Signalling a running child: ```ocaml -# Eio_linux.run @@ fun _env -> +# Eio_linux.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let child = Process.spawn ~sw Process.Fork_action.[ diff --git a/lib_eio_posix/eio_posix.ml b/lib_eio_posix/eio_posix.ml index 9ca183dd9..ed9432115 100644 --- a/lib_eio_posix/eio_posix.ml +++ b/lib_eio_posix/eio_posix.ml @@ -33,6 +33,7 @@ let run main = method clock = Time.clock method mono_clock = Time.mono_clock method net = Net.v + method vars = Eio_unix.Private.vars method process_mgr = Process.mgr method domain_mgr = Domain_mgr.v method cwd = (Fs.cwd :> Eio.Fs.dir_ty Eio.Path.t) diff --git a/lib_eio_posix/test/spawn.md b/lib_eio_posix/test/spawn.md index 12b941626..9ca450cce 100644 --- a/lib_eio_posix/test/spawn.md +++ b/lib_eio_posix/test/spawn.md @@ -7,8 +7,6 @@ open Eio.Std module Env = Eio.Process.Env module Process = Eio_posix.Low_level.Process - -let default_env = Unix.environment () |> Env.of_array ``` ## Spawning processes @@ -31,7 +29,8 @@ FOO=bar Changing directory: ```ocaml -# Eio_posix.run @@ fun _env -> +# Eio_posix.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let child = Process.spawn ~sw Process.Fork_action.[ chdir "/"; @@ -47,7 +46,8 @@ Changing directory: Changing directory using a file descriptor: ```ocaml -# Eio_posix.run @@ fun _env -> +# Eio_posix.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let root = Eio_posix.Low_level.openat ~sw ~mode:0 Fs "/" Eio_posix.Low_level.Open_flags.(rdonly + directory) in let child = Process.spawn ~sw Process.Fork_action.[ @@ -64,7 +64,8 @@ Changing directory using a file descriptor: Exit status: ```ocaml -# Eio_posix.run @@ fun _env -> +# Eio_posix.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let child = Process.spawn ~sw Process.Fork_action.[ execve "/usr/bin/env" @@ -78,7 +79,8 @@ Exit status: Failure starting child: ```ocaml -# Eio_posix.run @@ fun _env -> +# Eio_posix.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> Process.spawn ~sw Process.Fork_action.[ chdir "/idontexist"; @@ -92,7 +94,8 @@ Exception: Unix.Unix_error(Unix.ENOENT, "chdir", "") Signalling a running child: ```ocaml -# Eio_posix.run @@ fun _env -> +# Eio_posix.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let child = Process.spawn ~sw Process.Fork_action.[ @@ -163,7 +166,8 @@ let read_all pipe = Swapping FDs (note: plain sh can't handle multi-digit FDs!): ```ocaml -# Eio_posix.run @@ fun _env -> +# Eio_posix.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let pipe1_r, pipe1_w = Eio_unix.pipe sw in let pipe2_r, pipe2_w = Eio_unix.pipe sw in @@ -208,7 +212,8 @@ Swapping FDs (note: plain sh can't handle multi-digit FDs!): Keeping an FD open: ```ocaml -# Eio_posix.run @@ fun _env -> +# Eio_posix.run @@ fun env -> + let default_env = Eio.Stdenv.vars env |> Eio.Vars.get_all in Switch.run @@ fun sw -> let pipe1_r, pipe1_w = Eio_unix.pipe sw in let child = diff --git a/lib_eio_windows/eio_windows.ml b/lib_eio_windows/eio_windows.ml index d8f2a931f..a33d8b159 100755 --- a/lib_eio_windows/eio_windows.ml +++ b/lib_eio_windows/eio_windows.ml @@ -34,6 +34,7 @@ let run main = method cwd = (Fs.cwd :> Eio.Fs.dir_ty Eio.Path.t) method fs = (Fs.fs :> Eio.Fs.dir_ty Eio.Path.t) method process_mgr = failwith "process operations not supported on Windows yet" + method vars = Eio_unix.Private.vars method secure_random = Flow.secure_random method backend_id = "windows" end diff --git a/tests/process.md b/tests/process.md index 233f7b65b..3492ec0a0 100644 --- a/tests/process.md +++ b/tests/process.md @@ -233,6 +233,16 @@ A custom environment: - : string = ":2" ``` +Using the parent's environment explicitly: + +```ocaml +# run @@ fun mgr env -> + Unix.putenv "DISPLAY" ":1"; + let env = Eio.Stdenv.vars env |> Eio.Vars.get_all in + Process.parse_out ~env mgr Eio.Buf_read.line ["sh"; "-c"; "echo $DISPLAY"];; +- : string = ":1" +``` + Eio's child reaping code doesn't interfere with OCaml's process spawning: ```ocaml @@ -320,3 +330,28 @@ val e : Env.t = ["" "c=5" "c=6"] ``` + +Using the environment capability: + +```ocaml +# run @@ fun _mgr env -> + Unix.putenv "DISPLAY" ":1"; + let vars = Eio.Stdenv.vars env in + Eio.Vars.get_opt vars "DISPLAY";; +- : string option = Some ":1" +``` + +```ocaml +# run @@ fun _mgr env -> + let vars = Eio.Stdenv.vars env in + Eio.Vars.get_opt vars "THIS_VAR_PROBABLY_WILL_NOT_EXIST";; +- : string option = None +``` + +```ocaml +# run @@ fun _mgr env -> + Unix.putenv "DISPLAY" ":1"; + let env = Eio.Stdenv.vars env |> Eio.Vars.get_all in + Eio.Process.Env.get_opt "DISPLAY" env;; +- : string option = Some ":1" +```