diff --git a/lib_eio/process.ml b/lib_eio/process.ml index 83b3c1795..de5893f73 100644 --- a/lib_eio/process.ml +++ b/lib_eio/process.ml @@ -93,13 +93,15 @@ module Env = struct Printf.sprintf "%s=%s" name value let get_opt name t = - Name.validate name; - let prefix = name ^ "=" in - Array.find_opt (Name.starts_with ~prefix) t - |> Option.map (fun e -> - let i = String.length prefix in - String.sub e i (String.length e - i) - ) + if name = "" then None + else ( + let prefix = name ^ "=" in + Array.find_opt (Name.starts_with ~prefix) t + |> Option.map (fun e -> + let i = String.length prefix in + String.sub e i (String.length e - i) + ) + ) let override bindings t = List.iter validate_binding bindings; @@ -163,6 +165,9 @@ module Pi = struct type tag type t + val environment : t -> Env.t + val getenv_opt : t -> string -> string option + val pipe : t -> sw:Switch.t -> @@ -221,6 +226,16 @@ let signal (type tag) (t : [> tag ty] r) s = let module X = (val (Resource.get ops Pi.Process)) in X.signal v s +let environment (type tag) (t : [> tag mgr_ty] r) = + let (Resource.T (v, ops)) = t in + let module X = (val (Resource.get ops Pi.Mgr)) in + X.environment v + +let getenv_opt (type tag) (t : [> tag mgr_ty] r) = + let (Resource.T (v, ops)) = t in + let module X = (val (Resource.get ops Pi.Mgr)) in + X.getenv_opt v + let spawn (type tag) ~sw (t : [> tag mgr_ty] r) ?cwd ?stdin ?stdout ?stderr ?env ?executable args : tag ty r = let (Resource.T (v, ops)) = t in let module X = (val (Resource.get ops Pi.Mgr)) in diff --git a/lib_eio/process.mli b/lib_eio/process.mli index eb6a02d99..4c7125bd3 100644 --- a/lib_eio/process.mli +++ b/lib_eio/process.mli @@ -52,6 +52,8 @@ type 'a mgr = 'a r constraint 'a = [> [> `Generic] mgr_ty] (** A process manager capable of spawning new processes. *) +(** {2 Environment variables} *) + module Env : sig (** A list of environment variable entries. @@ -79,9 +81,8 @@ module Env : sig This just adds [xs] to {!empty} using {!override}. *) val get_opt : string -> t -> string option - (** [get_opt name t] is the value of [name] in [t], or [None] if there is no such binding. - - @raise Invalid_argument if [name] is not a valid name. *) + (** [get_opt name t] is the value of [name] in [t], or [None] if there is no such binding + (or if [name] is not a valid name). *) val override : (string * string option) list -> t -> t (** [override bindings t] is a new environment which is like [t] @@ -109,6 +110,13 @@ module Env : sig val pp : t Fmt.t end +val environment : _ mgr -> Env.t +(** [environment t] returns a snapshot of this process's current environment. *) + +val getenv_opt : _ mgr -> string -> string option +(** [getenv_opt t name] will get the environment variable called [name]. + Returns [None] if [name] does not exist. *) + (** {2 Processes} *) val pid : _ t -> int @@ -228,6 +236,9 @@ module Pi : sig type tag type t + val environment : t -> Env.t + val getenv_opt : t -> string -> string option + val pipe : t -> sw:Switch.t -> diff --git a/lib_eio/unix/process.ml b/lib_eio/unix/process.ml index 2ef4ec751..41e9a20f1 100644 --- a/lib_eio/unix/process.ml +++ b/lib_eio/unix/process.ml @@ -136,6 +136,9 @@ end) = struct type tag = [ `Generic | `Unix ] + let environment _t = Unix.environment () |> Eio.Process.Env.of_array + let getenv_opt _t name = Sys.getenv_opt name + let pipe _ ~sw = (Private.pipe sw :> ([Eio.Resource.close_ty | Eio.Flow.source_ty] r * [Eio.Resource.close_ty | Eio.Flow.sink_ty] r)) diff --git a/lib_eio_linux/tests/spawn.md b/lib_eio_linux/tests/spawn.md index b5c71899e..e3264a7cc 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment in Switch.run @@ fun sw -> let child = Process.spawn ~sw Process.Fork_action.[ diff --git a/lib_eio_posix/test/spawn.md b/lib_eio_posix/test/spawn.md index 12b941626..f8a163da7 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment 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.process_mgr env |> Eio.Process.environment in Switch.run @@ fun sw -> let pipe1_r, pipe1_w = Eio_unix.pipe sw in let child = diff --git a/tests/process.md b/tests/process.md index 233f7b65b..c6a44d860 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.Process.environment mgr 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 @@ -293,10 +303,8 @@ Invalid environment variable name "k=" # e |> Env.override ["k", Some "v=1"] |> Env.get_opt "k";; - : string option = Some "v=1" -# try Env.(get_opt "" empty) |> ignore - with Invalid_argument x -> print_endline x;; -Invalid environment variable name "" -- : unit = () +# Env.(get_opt "" (of_array [| "=foo" |]));; +- : string option = None # try e |> Env.override ["", None] |> ignore with Invalid_argument x -> print_endline x;; @@ -320,3 +328,26 @@ val e : Env.t = ["" "c=5" "c=6"] ``` + +Using the environment capability: + +```ocaml +# run @@ fun mgr _env -> + Unix.putenv "DISPLAY" ":1"; + Eio.Process.getenv_opt mgr "DISPLAY";; +- : string option = Some ":1" +``` + +```ocaml +# run @@ fun mgr _env -> + Eio.Process.getenv_opt mgr "THIS_VAR_PROBABLY_WILL_NOT_EXIST";; +- : string option = None +``` + +```ocaml +# run @@ fun mgr _env -> + Unix.putenv "DISPLAY" ":1"; + let env = Eio.Process.environment mgr in + Eio.Process.Env.get_opt "DISPLAY" env;; +- : string option = Some ":1" +```