Add Eio.Vars.t to Stdenv - #923
Conversation
avsm
left a comment
There was a problem hiding this comment.
Some high level thoughts:
- getenv/putenv aren't multithreaded safe, so is this a good opportunity to provide domain safety by introducing a mutex, so that if accesses go via Eio.Vars they are synchronised? We cant do anything about direct uses of Unix but this is better than nothing
- bear in mind that secure_getenv will result in a null if setuid binary, but I think this is handled at the Unix library level.
- The Eio.Vars layer is where we should be dealing with portability, so a really useful future addition would be to add a PATH parser using our shiny new Nt_path and Posix_path modules. On Windows it's ; separated and on POSIX : separated.
|
Thanks for the review Anil!! All very reasonable to me and I'll incorporate this soon! |
|
Re:threadsafety-- do you think, in practice, this is a big issue? It seems that the functions are mostly not thread safe when used together across domains. But are a lot of programs doing a lot of "putting"? |
|
I think the |
2887bce to
6b4f6d7
Compare
If it happens at start of day, do you think there's much chance of hitting race conditions with other domains? 6b4f6d7 adds |
I think if something's exposed in the API, it should be as safe as possible. If we're exposing |
That's fair enough. I have added a mutex in the |
Access to environment variables.
Fix off-by-one error in splitting environment variables.
| val get_all : _ t -> (string * string) list | ||
| (** [get_all vars] gets the full list of environment variables. *) | ||
|
|
||
| val get : _ t -> string -> string |
There was a problem hiding this comment.
I suggest either making this get_opt, or raising a more useful Eio.Io exception (giving the name of the variable that was missing).
| val get_path : _ t -> string list | ||
| (** [get_path var] gets the ["PATH"] variable and returns the paths as a list. | ||
|
|
||
| @raise Not_found if ["PATH"] does not exist.*) |
There was a problem hiding this comment.
I'm not very keen on this. There are lots of PATH-like variables (e.g. MANPATH). Probably this should take the variable name too.
There was a problem hiding this comment.
Ok, maybe something like?
val get_paths : _ t -> string -> string list
(** [get_paths var name] gets the value associated with [name] and splits it using the backend-specific path separator. *)There was a problem hiding this comment.
That looks good to me. Probably also worth specifying what happens with repeated separators -- are empty segments preserved? (I'm not even sure if that's valid in PATHs)
|
|
||
| let try_get t name = | ||
| try | ||
| Eio.traceln "%s is %a" name Fmt.(quote string) (Eio.Vars.get t name) |
There was a problem hiding this comment.
| Eio.traceln "%s is %a" name Fmt.(quote string) (Eio.Vars.get t name) | |
| Eio.traceln "%s is %S" name (Eio.Vars.get t name) |
shorter, and works correctly with strings containing quotes.
| module Vars = Vars | ||
| (** An {! Eio.System} interface using standard {! Unix} functions. *) |
There was a problem hiding this comment.
Should be in Private (and remove the System bit, or the whole comment).
I'm not even sure it needs to be a module; we could just have Private.vars be the actual resource. It can work out the path separator for itself easily enough from Sys.os_type. Then each backend can just do method vars = Eio_unix.Private.vars.
| (** Managing child processes. *) | ||
| module Process = Process | ||
|
|
||
| (** {2 Environment Variables} *) |
There was a problem hiding this comment.
Could leave out the heading. Putting environment variables under Processes seems fine.
| val get : t -> string -> string | ||
| val get_path : t -> string list | ||
| val put : t -> name:string -> value:string -> unit | ||
| val put_path : t -> string list -> unit |
There was a problem hiding this comment.
setenv probably makes more sense for the name than putenv here (in C, setenv is the newer one).
Would be good to have unsetenv too.
|
|
||
| (** {1 Accessing environment variables} *) | ||
|
|
||
| val get_all : _ t -> (string * string) list |
There was a problem hiding this comment.
It's not very helpful that get_all returns a list of pairs, but Process.spawn ~env expects an array of strings.
There was a problem hiding this comment.
The reasoning here was that I saw a few distinct use cases with Process.spawn ~env:
- The user wants to inherit the environment from the parent in which case they provide no value to
~env, so there is no need forget_allto agree on the type. - The user wants to provide a filtered set of the parent process's environment variables in which case they are likely going to want to have access to
(name, value)pairs in order to do the filtering before having to rejoin them to be astring array. Of course, some filtering could be done on a version ofget_allthat returns astring array, for example,String.starts_with ~prefix:"OCAML" v. - The user wants to extend the variables to include everything from the parent in addition to some executable-specific environment variables: this is the most compelling case for
get_allto return astring array.
This does not take into account other use cases for get_all like finding some environment variables and their values for in-process OCaml logic where, I think, (name, value) pairs make the most sense.
I'm happy to make this return a string array (and we could provide a split function) but I thought I would lay out some of the reasoning for the current API.
Lemme know what you think :-)
There was a problem hiding this comment.
If anything, this is case to change the Process.spawn API to take a list instead. It's a bit of a throwback to the C API to access an OCaml array here
There was a problem hiding this comment.
I've opened #930, which changes the type spawn uses to Process.Env.t. Currently it's an alias of string array, but we can change it later once people have a chance to start using the new functions in there.
get_all can then use the same type. Though possibly it should just be Process_mgr.current_environment or something. The process manager already implicitly has access to the environment, since you can use ?env:None so it probably makes sense to put it there.
|
Now I'm wondering how useful it is to be able to mutable the environment globally like this. The uses I can think of are:
It might be simpler if |
I think this could be a fine approach, essentially making the environment as handled by Eio readonly (probably 90% of uses cases). It does not allow for @avsm's needs stated here #923 (comment) (though as you say, you can always default to the racier However, I think it would be nice to be able to write portable Eio applications that can |
|
I agree it's useful to not have a dependency on Eio_unix for 'pure' Eio applications. However, I'm also happy with a read-only environment for now, given how horrendously messy mutating it is... |
|
Thanks for adding those functions in #930 @talex5 ! Should the let vars () = Unix.environment () |> Eio.Process.Env.of_arrayso that it reflects EDIT: I suspect it should be somewhat lazy. I'm running nix and my environment is pretty big ( |
|
I think it should read the current environment each time you call it. Otherwise, there's no way to get that. |
Access to environment variables after some discussion in #922