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 {{{