From 761fd217c79caea8ca2131507ac871125729929b Mon Sep 17 00:00:00 2001 From: Hugo Mills Date: Tue, 22 Sep 2020 17:49:44 +0100 Subject: [PATCH] Add environ and environ_pass parameters to exec/3 The {environ, EnvMap} option sets values in the environment of the executed process, using the key of the map as the environment variable name and the value of the map as the environment variable's value. The {environ_pass, EnvList} option takes a list of environment variable names, and takes their values from the current environment to pass into the subprocess. If a variable is specified in both options, the environ option takes precedence. Signed-off-by: Hugo Mills --- c_src/proto_command.c | 42 ++++++++++++++++++++++++++++++----- c_src/proto_command.h | 3 +-- c_src/supervisor_loop.c | 5 ++--- src/subproc.erl | 6 +++++ src/subproc_master_driver.erl | 36 +++++++++++++++++++++++++++++- 5 files changed, 81 insertions(+), 11 deletions(-) diff --git a/c_src/proto_command.c b/c_src/proto_command.c index 1ad6015..c298a35 100644 --- a/c_src/proto_command.c +++ b/c_src/proto_command.c @@ -37,6 +37,7 @@ #define OPT_GROUP 0x47 // getgrnam() #define OPT_CWD 0x43 #define OPT_ARGV0 0x30 +#define OPT_ENV 0x45 //---------------------------------------------------------------------------- @@ -106,12 +107,18 @@ void free_command(struct comm_t *comm) comm->exec_opts.argv = NULL; } - // TODO: comm->exec_opts.envp, once it's added - if (comm->exec_opts.cwd != NULL) { free(comm->exec_opts.cwd); comm->exec_opts.cwd = NULL; } + + if (comm->exec_opts.env != NULL) { + char **ptr; + for (ptr = comm->exec_opts.env; *ptr != NULL; ++ptr) + free(*ptr); + free(comm->exec_opts.env); + comm->exec_opts.env = NULL; + } } //---------------------------------------------------------------------------- @@ -203,6 +210,16 @@ int get_group_gid(char *group, gid_t *gid) return 0; } +static +void free_env(struct comm_t *comm) { + char **env; + env = comm->exec_opts.env; + while (*env != NULL) + free(*env++); + free(comm->exec_opts.env); + comm->exec_opts.env = NULL; +} + //---------------------------------------------------------------------------- static @@ -257,11 +274,10 @@ int parse_exec_command(unsigned char *data, size_t size, struct comm_t *comm) return ERR_PARSE; } - // TODO: list of env vars to set - // TODO: list of env vars to clear - char *argv0; char *ugname; + size_t items; + char **env_set; while (read_at < size) { switch (data[read_at++]) { // numeric options @@ -335,6 +351,22 @@ int parse_exec_command(unsigned char *data, size_t size, struct comm_t *comm) free(comm->exec_opts.argv[0]); comm->exec_opts.argv[0] = argv0; break; + case OPT_ENV: + if (comm->exec_opts.env != NULL) + free_env(comm); + if (size - read_at < 2) + return ERR_PARSE; + items = unpack16(data + read_at); + read_at += 2; + comm->exec_opts.env = env_set = calloc(items, sizeof(char *)); + while(items-- > 0) { + if ((*env_set = parse_nonempty_string(data, size, &read_at)) == NULL) { + free_env(comm); + return ERR_PARSE; + } + env_set += 1; + } + break; default: // unrecognized option tag diff --git a/c_src/proto_command.h b/c_src/proto_command.h index d845a08..0611335 100644 --- a/c_src/proto_command.h +++ b/c_src/proto_command.h @@ -23,8 +23,7 @@ struct comm_t { char *command; char **argv; char *cwd; - // TODO: struct { char *name; char *value } *env_set; - // TODO: char **env_clear; + char **env; unsigned int termsig; uint64_t sigmask; // ignored signals // modes: diff --git a/c_src/supervisor_loop.c b/c_src/supervisor_loop.c index 5af19a9..6990452 100644 --- a/c_src/supervisor_loop.c +++ b/c_src/supervisor_loop.c @@ -2,6 +2,7 @@ #include #include // snprintf() +#include #include #include @@ -679,8 +680,6 @@ int child_spawn(struct comm_t *cmd, child_t *child, void *buffer, int *fds) } } - // TODO: set environment - struct sigaction ignore_action; memset(&ignore_action, 0, sizeof(ignore_action)); ignore_action.sa_handler = SIG_IGN; @@ -689,7 +688,7 @@ int child_spawn(struct comm_t *cmd, child_t *child, void *buffer, int *fds) if ((cmd->exec_opts.sigmask & (((uint64_t)1) << (sig - 1))) != 0) sigaction(sig, &ignore_action, NULL); - execve(cmd->exec_opts.command, cmd->exec_opts.argv, NULL); + execve(cmd->exec_opts.command, cmd->exec_opts.argv, cmd->exec_opts.env); // if we got here, exec() must have failed int error[2] = { STAGE_EXEC, errno }; send(fds_confirm[WRITE_END], error, sizeof(error), MSG_NOSIGNAL); diff --git a/src/subproc.erl b/src/subproc.erl index cc2e05a..0fd2c8f 100644 --- a/src/subproc.erl +++ b/src/subproc.erl @@ -75,6 +75,8 @@ | {user, uid() | string()} | {group, gid() | string()} | {cd, file:filename()} + | {environ, map()} + | {environ_pass, [string()]} | {argv0, file:filename()}. %% Options controlling how the subprocess is executed. %% @@ -109,6 +111,10 @@ %% root rights %%
  • `{cd,_}' -- when set, the subprocess will be spawned in specified %% directory
  • +%%
  • `{environ,_}' -- set environment variables for the subprocess. +%% Overrides `environ_pass'
  • +%%
  • `{environ_pass,_}' -- list of environment variables in the current +%% environment to pass through to the subprocess
  • %%
  • `{argv0,_}' -- when set, process name (sometimes called `argv[0]' or %% `$0') will be set to this value
  • %% diff --git a/src/subproc_master_driver.erl b/src/subproc_master_driver.erl index c8a80c5..6b2f9d7 100644 --- a/src/subproc_master_driver.erl +++ b/src/subproc_master_driver.erl @@ -217,6 +217,8 @@ close(Port) -> | {user, uid() | user()} | {group, gid() | group()} | {cd, file:filename()} + | {environ, map()} + | {environ_pass, [string()]} | {argv0, file:filename()}, STDIO :: {bidir, FDRW} | {in, FDR} | {out, FDW} | {in_out, {FDR, FDW}}, FDRW :: subproc:os_fd(), @@ -551,6 +553,8 @@ build_exec_request(Command, Args, Options) -> group :: undefined | gid() | group(), nice :: undefined | integer(), cwd :: undefined | file:filename(), + environ :: undefined | map(), + environ_pass :: undefined | [string()], argv0 :: undefined | file:filename() }). @@ -614,6 +618,10 @@ exec_option({group, Group}, Opts) when is_integer(Group); is_list(Group) -> Opts#exec{group = Group}; exec_option({cd, Dir}, Opts) when is_list(Dir) -> Opts#exec{cwd = Dir}; +exec_option({environ, Env}, Opts) when is_map(Env) -> + Opts#exec{environ = Env}; +exec_option({environ_pass, Vars}, Opts) when is_list(Vars) -> + Opts#exec{environ_pass = Vars}; exec_option({argv0, Name}, Opts) when is_list(Name) -> Opts#exec{argv0 = Name}. @@ -642,6 +650,8 @@ is_option({nice, _}) -> true; is_option({user, _}) -> true; is_option({group, _}) -> true; is_option({cd, _}) -> true; +is_option({environ, _}) -> true; +is_option({environ_pass, _}) -> true; is_option({argv0, _}) -> true; is_option(_Value) -> false. @@ -718,6 +728,7 @@ build_exec_options(Opts = #exec{}) -> end, SigIgnMask = make_signal_mask(Opts#exec.ignore_signals, 0), OptSigIgn = <<16#73:8, SigIgnMask:64>>, + OptEnv = build_environment_variables(Opts#exec.environ, Opts#exec.environ_pass), OptNice = case Opts of #exec{nice = undefined} -> []; #exec{nice = Nice} -> <<16#70:8, Nice:8>> @@ -741,7 +752,7 @@ build_exec_options(Opts = #exec{}) -> #exec{argv0 = Name} -> pack_tagged_string(16#30, Name) end, _Result = [ - OptTermSig, OptSigIgn, OptCwd, OptUser, OptGroup, OptNice, OptArgv0 + OptTermSig, OptSigIgn, OptCwd, OptUser, OptGroup, OptNice, OptEnv, OptArgv0 ]. %% @doc Make a signal mask from list of signals (names and/or numbers). @@ -763,6 +774,29 @@ make_signal_mask([SigName | Rest], Mask) when is_atom(SigName) -> make_signal_mask(_SignalList, _Mask) -> erlang:error(bad_signal). +%% @doc Build the list of environment variables & values to use + +-spec build_environment_variables(undefined | map(), undefined | [iolist()]) -> + iolist(). + +build_environment_variables(undefined, Pass) -> + build_environment_variables(#{}, Pass); +build_environment_variables(Set, undefined) -> + build_environment_variables(Set, []); +build_environment_variables(SetVars, Pass) -> + PassVars0 = [{Var, os:getenv(Var)} || Var <- Pass], + PassVars1 = [VarDef || {_, Value} = VarDef <- PassVars0, Value =/= false], + PassVars = maps:from_list(PassVars1), + AllVars = maps:merge(PassVars, SetVars), + + KVs = maps:to_list(AllVars), + Strings = [pack_string([K, "=", V]) || {K, V} <- KVs], + case Strings of + [] -> []; + Data -> + [<<16#45:8>>, <<(length(Strings)):16>>, Strings] + end. + %% }}} %%---------------------------------------------------------- %% string packing {{{