Current section

Files

Jump to
cloudi_core src cloudi_core_i_spawn.erl
Raw

src/cloudi_core_i_spawn.erl

%-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
% ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
%%%
%%%------------------------------------------------------------------------
%%% @doc
%%% ==CloudI Spawn==
%%% @end
%%%
%%% BSD LICENSE
%%%
%%% Copyright (c) 2011-2016, Michael Truog <mjtruog at gmail dot com>
%%% All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions are met:
%%%
%%% * Redistributions of source code must retain the above copyright
%%% notice, this list of conditions and the following disclaimer.
%%% * Redistributions in binary form must reproduce the above copyright
%%% notice, this list of conditions and the following disclaimer in
%%% the documentation and/or other materials provided with the
%%% distribution.
%%% * All advertising materials mentioning features or use of this
%%% software must display the following acknowledgment:
%%% This product includes software developed by Michael Truog
%%% * The name of the author may not be used to endorse or promote
%%% products derived from this software without specific prior
%%% written permission
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%% CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
%%% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
%%% CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
%%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
%%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
%%% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
%%% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
%%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
%%% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
%%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
%%% DAMAGE.
%%%
%%% @author Michael Truog <mjtruog [at] gmail (dot) com>
%%% @copyright 2011-2016 Michael Truog
%%% @version 1.5.2 {@date} {@time}
%%%------------------------------------------------------------------------
-module(cloudi_core_i_spawn).
-author('mjtruog [at] gmail (dot) com').
%% external interface
-export([environment_lookup/0,
environment_transform/1,
environment_transform/2,
start_internal/15,
start_external/18,
update_external/3,
update_internal_f/9,
update_external_f/12]).
-include("cloudi_logger.hrl").
-include("cloudi_core_i_configuration.hrl").
-include("cloudi_core_i_constants.hrl").
-define(CREATE_INTERNAL, cloudi_core_i_services_internal_sup:create_internal).
-define(CREATE_EXTERNAL, cloudi_core_i_services_external_sup:create_external).
% environmental variables used by CloudI API initialization
-define(ENVIRONMENT_THREAD_COUNT, "CLOUDI_API_INIT_THREAD_COUNT").
-define(ENVIRONMENT_PROTOCOL, "CLOUDI_API_INIT_PROTOCOL").
-define(ENVIRONMENT_BUFFER_SIZE, "CLOUDI_API_INIT_BUFFER_SIZE").
-ifdef(CLOUDI_CORE_STANDALONE).
-ifdef(ERLANG_OTP_VERSION_16).
-else.
-ifdef(ERLANG_OTP_VERSION_17).
-else.
-dialyzer({no_match, start_external_spawn/15}).
-endif.
-endif.
-endif.
%%%------------------------------------------------------------------------
%%% External interface
%%%------------------------------------------------------------------------
% all environment variables currently in the Erlang VM shell
% are used as possible substitution values
environment_lookup() ->
trie:new(lists:map(fun(Entry) ->
cloudi_string:splitl($=, Entry, input)
end, os:getenv())).
environment_transform(String) ->
EnvironmentLookup = environment_lookup(),
environment_transform(String, EnvironmentLookup).
% update external service strings based on the Erlang VM shell
% environmental variables
environment_transform(String, EnvironmentLookup) ->
environment_transform(String, [], undefined, EnvironmentLookup).
start_internal(ProcessIndex, ProcessCount, GroupLeader,
Module, Args, Timeout, Prefix,
TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh,
DestListDeny, DestListAllow, ConfigOptions, ID)
when is_integer(ProcessIndex), is_integer(ProcessCount),
is_atom(Module), is_list(Args), is_integer(Timeout), is_list(Prefix),
is_integer(TimeoutAsync), is_integer(TimeoutSync),
is_integer(TimeoutTerm),
is_record(ConfigOptions, config_service_options),
is_binary(ID) ->
true = (DestRefresh =:= immediate_closest) orelse
(DestRefresh =:= lazy_closest) orelse
(DestRefresh =:= immediate_furthest) orelse
(DestRefresh =:= lazy_furthest) orelse
(DestRefresh =:= immediate_random) orelse
(DestRefresh =:= lazy_random) orelse
(DestRefresh =:= immediate_local) orelse
(DestRefresh =:= lazy_local) orelse
(DestRefresh =:= immediate_remote) orelse
(DestRefresh =:= lazy_remote) orelse
(DestRefresh =:= immediate_newest) orelse
(DestRefresh =:= lazy_newest) orelse
(DestRefresh =:= immediate_oldest) orelse
(DestRefresh =:= lazy_oldest) orelse
(DestRefresh =:= none),
DestDeny = if
DestListDeny =:= undefined ->
undefined;
is_list(DestListDeny) ->
trie:new(DestListDeny)
end,
DestAllow = if
DestListAllow =:= undefined ->
undefined;
is_list(DestListAllow) ->
trie:new(DestListAllow)
end,
case reltool_util:is_module_loaded(Module, Timeout) of
{ok, NewTimeout} ->
% Erlang application startup is asynchronous, so wait for the
% module to be loaded or timeout
?CREATE_INTERNAL(ProcessIndex, ProcessCount, GroupLeader,
Module, Args, NewTimeout, Prefix,
TimeoutAsync, TimeoutSync, TimeoutTerm,
DestRefresh, DestDeny, DestAllow,
ConfigOptions, ID);
{error, Reason} ->
?LOG_ERROR("loading ~p failed: ~p", [Module, Reason]),
{error, {service_internal_module_not_loaded, Module}}
end.
start_external(ProcessIndex, ProcessCount, ThreadsPerProcess,
Filename, Arguments, Environment,
Protocol, BufferSize, Timeout, Prefix,
TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh,
DestListDeny, DestListAllow, ConfigOptions, ID) ->
case start_external_params(ProcessIndex, ProcessCount, ThreadsPerProcess,
Filename, Arguments, Environment,
Protocol, BufferSize, Timeout, Prefix,
TimeoutAsync, TimeoutSync, TimeoutTerm,
DestRefresh, DestListDeny, DestListAllow,
ConfigOptions, ID) of
{ok,
SpawnProcess, SpawnProtocol, SocketPath,
Rlimits, Owner, Directory,
CommandLine, NewFilename, NewArguments,
EnvironmentLookup, DestDeny, DestAllow} ->
case start_external_threads(ThreadsPerProcess,
ProcessIndex,
ProcessCount,
CommandLine,
Protocol, SocketPath,
BufferSize, Timeout,
Prefix,
TimeoutAsync,
TimeoutSync,
TimeoutTerm,
DestRefresh,
DestDeny, DestAllow,
ConfigOptions, ID) of
{ok, Pids, Ports} ->
start_external_spawn(SpawnProcess,
SpawnProtocol,
SocketPath,
Pids, Ports,
Rlimits, Owner, Directory,
ThreadsPerProcess,
CommandLine,
NewFilename,
NewArguments,
Environment,
EnvironmentLookup,
Protocol, BufferSize);
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
update_external(Pids, Ports,
[ProcessIndex, ProcessCount, ThreadsPerProcess,
Filename, Arguments, Environment,
Protocol, BufferSize, Timeout, Prefix,
TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh,
DestListDeny, DestListAllow, ConfigOptions, ID]) ->
case start_external_params(ProcessIndex, ProcessCount, ThreadsPerProcess,
Filename, Arguments, Environment,
Protocol, BufferSize, Timeout, Prefix,
TimeoutAsync, TimeoutSync, TimeoutTerm,
DestRefresh, DestListDeny, DestListAllow,
ConfigOptions, ID) of
{ok,
SpawnProcess, SpawnProtocol, SocketPath,
Rlimits, Owner, Directory,
CommandLine, NewFilename, NewArguments,
EnvironmentLookup, _, _} ->
case start_external_spawn(SpawnProcess, SpawnProtocol, SocketPath,
Pids, Ports, Rlimits, Owner, Directory,
ThreadsPerProcess,
CommandLine, NewFilename, NewArguments,
Environment, EnvironmentLookup,
Protocol, BufferSize) of
{ok, Pids} ->
[Pid ! {'cloudi_service_update_state', CommandLine}
|| Pid <- Pids],
ok;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
update_internal_f(NewDestRefresh, NewTimeoutInit,
NewTimeoutAsync, NewTimeoutSync,
NewDestListDeny, NewDestListAllow,
OptionsKeys, NewConfigOptions,
[GroupLeader,
Module, Args, OldTimeoutInit, Prefix,
OldTimeoutAsync, OldTimeoutSync, TimeoutTerm,
OldDestRefresh, OldDestListDeny, OldDestListAllow,
OldConfigOptions, ID]) ->
[GroupLeader,
Module, Args,
if
NewTimeoutInit =:= undefined ->
OldTimeoutInit;
is_integer(NewTimeoutInit) ->
NewTimeoutInit
end,
Prefix,
if
NewTimeoutAsync =:= undefined ->
OldTimeoutAsync;
is_integer(NewTimeoutAsync) ->
NewTimeoutAsync
end,
if
NewTimeoutSync =:= undefined ->
OldTimeoutSync;
is_integer(NewTimeoutSync) ->
NewTimeoutSync
end,
TimeoutTerm,
if
NewDestRefresh =:= undefined ->
OldDestRefresh;
is_atom(NewDestRefresh) ->
NewDestRefresh
end,
if
NewDestListDeny =:= invalid ->
OldDestListDeny;
NewDestListDeny =:= undefined; is_list(NewDestListDeny) ->
NewDestListDeny
end,
if
NewDestListAllow =:= invalid ->
OldDestListAllow;
NewDestListAllow =:= undefined; is_list(NewDestListAllow) ->
NewDestListAllow
end,
cloudi_core_i_configuration:service_options_copy(OptionsKeys,
OldConfigOptions,
NewConfigOptions),
ID].
update_external_f(NewFilename, NewArguments, NewEnvironment,
NewDestRefresh,
NewTimeoutInit, NewTimeoutAsync, NewTimeoutSync,
NewDestListDeny, NewDestListAllow,
OptionsKeys, NewConfigOptions,
[ThreadsPerProcess,
OldFilename, OldArguments, OldEnvironment,
Protocol, BufferSize, OldTimeoutInit, Prefix,
OldTimeoutAsync, OldTimeoutSync, TimeoutTerm,
OldDestRefresh, OldDestListDeny, OldDestListAllow,
OldConfigOptions, ID]) ->
[ThreadsPerProcess,
if
is_list(NewFilename) ->
NewFilename;
NewFilename =:= undefined ->
OldFilename
end,
if
is_list(NewArguments) ->
NewArguments;
NewArguments =:= undefined ->
OldArguments
end,
if
is_list(NewEnvironment) ->
NewEnvironment;
NewEnvironment =:= undefined ->
OldEnvironment
end,
Protocol, BufferSize,
if
is_integer(NewTimeoutInit) ->
NewTimeoutInit;
NewTimeoutInit =:= undefined ->
OldTimeoutInit
end,
Prefix,
if
is_integer(NewTimeoutAsync) ->
NewTimeoutAsync;
NewTimeoutAsync =:= undefined ->
OldTimeoutAsync
end,
if
is_integer(NewTimeoutSync) ->
NewTimeoutSync;
NewTimeoutSync =:= undefined ->
OldTimeoutSync
end,
TimeoutTerm,
if
NewDestRefresh =:= undefined ->
OldDestRefresh;
is_atom(NewDestRefresh) ->
NewDestRefresh
end,
if
NewDestListDeny =:= invalid ->
OldDestListDeny;
NewDestListDeny =:= undefined; is_list(NewDestListDeny) ->
NewDestListDeny
end,
if
NewDestListAllow =:= invalid ->
OldDestListAllow;
NewDestListAllow =:= undefined; is_list(NewDestListAllow) ->
NewDestListAllow
end,
cloudi_core_i_configuration:service_options_copy(OptionsKeys,
OldConfigOptions,
NewConfigOptions),
ID].
%%%------------------------------------------------------------------------
%%% Private functions
%%%------------------------------------------------------------------------
rlimits(#config_service_options{limit = L}) ->
cloudi_core_i_os_process:limit_format(L).
owner(#config_service_options{owner = L}, EnvironmentLookup) ->
cloudi_core_i_os_process:owner_format(L, EnvironmentLookup).
directory(#config_service_options{directory = Directory}, EnvironmentLookup) ->
cloudi_core_i_os_process:directory_format(Directory, EnvironmentLookup).
create_socket_path(TemporaryDirectory, ID)
when is_binary(ID) ->
Path = filename:join([TemporaryDirectory,
"cloudi_socket_" ++
uuid:uuid_to_string(ID, nodash) ++ "_"]),
false = filelib:is_file(Path),
Path.
start_external_params(ProcessIndex, ProcessCount, ThreadsPerProcess,
Filename, Arguments, Environment,
Protocol, BufferSize, Timeout, Prefix,
TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh,
DestListDeny, DestListAllow, ConfigOptions, ID)
when is_integer(ProcessIndex), is_integer(ProcessCount),
is_integer(ThreadsPerProcess), ThreadsPerProcess > 0,
is_list(Filename), is_list(Arguments), is_list(Environment),
is_integer(BufferSize), is_integer(Timeout), is_list(Prefix),
is_integer(TimeoutAsync), is_integer(TimeoutSync),
is_integer(TimeoutTerm),
is_record(ConfigOptions, config_service_options),
is_binary(ID) ->
true = (Protocol =:= tcp) orelse
(Protocol =:= udp) orelse
(Protocol =:= local),
true = (DestRefresh =:= immediate_closest) orelse
(DestRefresh =:= lazy_closest) orelse
(DestRefresh =:= immediate_furthest) orelse
(DestRefresh =:= lazy_furthest) orelse
(DestRefresh =:= immediate_random) orelse
(DestRefresh =:= lazy_random) orelse
(DestRefresh =:= immediate_local) orelse
(DestRefresh =:= lazy_local) orelse
(DestRefresh =:= immediate_remote) orelse
(DestRefresh =:= lazy_remote) orelse
(DestRefresh =:= immediate_newest) orelse
(DestRefresh =:= lazy_newest) orelse
(DestRefresh =:= immediate_oldest) orelse
(DestRefresh =:= lazy_oldest) orelse
(DestRefresh =:= none),
DestDeny = if
DestListDeny =:= undefined ->
undefined;
is_list(DestListDeny) ->
trie:new(DestListDeny)
end,
DestAllow = if
DestListAllow =:= undefined ->
undefined;
is_list(DestListAllow) ->
trie:new(DestListAllow)
end,
TemporaryDirectory = case os:getenv("TMPDIR") of
false ->
"/tmp";
L ->
L
end,
SocketPath = create_socket_path(TemporaryDirectory, ID),
EnvironmentLookup = environment_lookup(),
case start_external_params_parse(Filename, Arguments, ConfigOptions,
EnvironmentLookup) of
{ok, CommandLine, NewFilename, NewArguments, Directory} ->
Rlimits = rlimits(ConfigOptions),
Owner = owner(ConfigOptions, EnvironmentLookup),
case supool:get(cloudi_core_i_os_spawn) of
SpawnProcess when is_pid(SpawnProcess) ->
SpawnProtocol = if
Protocol =:= tcp ->
$t; % inet
Protocol =:= udp ->
$u; % inet
Protocol =:= local ->
$l % tcp local (unix domain socket)
end,
{ok,
SpawnProcess, SpawnProtocol, SocketPath,
Rlimits, Owner, Directory,
CommandLine, NewFilename, NewArguments,
EnvironmentLookup, DestDeny, DestAllow};
undefined ->
{error, noproc}
end;
{error, _} = Error ->
Error
end.
start_external_params_parse(Filename, Arguments, ConfigOptions,
EnvironmentLookup) ->
case filename_parse(Filename, EnvironmentLookup) of
{ok, NewFilename} ->
case arguments_parse(Arguments, EnvironmentLookup) of
{ok, NewArguments} ->
CommandLine = [NewFilename |
string:tokens(NewArguments, [0])],
case directory(ConfigOptions, EnvironmentLookup) of
{ok, Directory} ->
{ok,
CommandLine,
NewFilename,
NewArguments,
Directory};
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
start_external_thread(ThreadsPerProcess, Pids, Ports, ThreadsPerProcess,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) ->
{ok, lists:reverse(Pids), lists:reverse(Ports)};
start_external_thread(I, Pids, Ports, ThreadsPerProcess,
ProcessIndex, ProcessCount, CommandLine,
Protocol, SocketPath, BufferSize, Timeout,
Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm,
DestRefresh, DestDeny, DestAllow,
ConfigOptions, ID) ->
case ?CREATE_EXTERNAL(Protocol, SocketPath,
I + ThreadsPerProcess * ProcessIndex,
ProcessIndex, ProcessCount, CommandLine,
BufferSize, Timeout,
Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm,
DestRefresh, DestDeny, DestAllow,
ConfigOptions, ID) of
{ok, Pid, Port} ->
start_external_thread(I + 1, [Pid | Pids], [Port | Ports],
ThreadsPerProcess,
ProcessIndex, ProcessCount, CommandLine,
Protocol, SocketPath, BufferSize, Timeout,
Prefix, TimeoutAsync, TimeoutSync,
TimeoutTerm,
DestRefresh, DestDeny, DestAllow,
ConfigOptions, ID);
{error, _} = Error ->
lists:foreach(fun(P) -> erlang:exit(P, kill) end, Pids),
Error
end.
start_external_threads(ThreadsPerProcess,
ProcessIndex, ProcessCount, CommandLine,
Protocol, SocketPath, BufferSize, Timeout,
Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm,
DestRefresh, DestDeny, DestAllow,
ConfigOptions, ID) ->
start_external_thread(0, [], [], ThreadsPerProcess,
ProcessIndex, ProcessCount, CommandLine,
Protocol, SocketPath, BufferSize, Timeout,
Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm,
DestRefresh, DestDeny, DestAllow,
ConfigOptions, ID).
start_external_spawn(SpawnProcess, SpawnProtocol, SocketPath,
Pids, Ports, Rlimits, Owner, Directory,
ThreadsPerProcess, CommandLine,
Filename, Arguments, Environment,
EnvironmentLookup, Protocol, BufferSize) ->
SpawnEnvironment = environment_parse(Environment, ThreadsPerProcess,
Protocol, BufferSize,
EnvironmentLookup),
{UserI, UserStr, GroupI, GroupStr} = Owner,
case cloudi_core_i_os_spawn:spawn(SpawnProcess,
SpawnProtocol,
string_terminate(SocketPath),
Ports,
Rlimits,
UserI,
string_terminate(UserStr),
GroupI,
string_terminate(GroupStr),
string_terminate(Directory),
string_terminate(Filename),
string_terminate(Arguments),
SpawnEnvironment) of
{ok, OsPid} ->
?LOG_INFO("OS pid ~p spawned ~p~n ~p",
[OsPid, Pids, CommandLine]),
{ok, Pids};
{error, _} = Error ->
Error
end.
% transform a string using a lookup containing environment variables
% (the loop doesn't have error conditions by design)
environment_transform([], Output, undefined, _) ->
lists:reverse(Output);
environment_transform([$\\, $$ | String], Output,
undefined, EnvironmentLookup) ->
environment_transform(String, [$$ | Output], undefined, EnvironmentLookup);
environment_transform([$$ | String], Output, undefined, EnvironmentLookup) ->
case String of
[${ | Rest] ->
environment_transform(Rest, Output, [], EnvironmentLookup);
_ ->
environment_transform(String, Output, [], EnvironmentLookup)
end;
environment_transform([C | String], Output, undefined, EnvironmentLookup) ->
environment_transform(String, [C | Output], undefined, EnvironmentLookup);
environment_transform([$} | String], Output, Key, EnvironmentLookup) ->
environment_transform_value(Key, String, Output, EnvironmentLookup);
environment_transform([C | String], Output, Key, EnvironmentLookup)
when (C >= $A andalso C =< $Z); (C == $_);
(C >= $a andalso C =< $z);
(C >= $0 andalso C =< $9) ->
% handles ASCII only
environment_transform(String, Output, [C | Key], EnvironmentLookup);
environment_transform(String, Output, Key, EnvironmentLookup) ->
environment_transform_value(Key, String, Output, EnvironmentLookup).
environment_transform_value([], String, Output, EnvironmentLookup) ->
environment_transform(String, Output, undefined, EnvironmentLookup);
environment_transform_value(Key, String, Output, EnvironmentLookup) ->
case trie:find(lists:reverse(Key), EnvironmentLookup) of
{ok, Value} ->
environment_transform(String, lists:reverse(Value) ++ Output,
undefined, EnvironmentLookup);
error ->
environment_transform(String, Output,
undefined, EnvironmentLookup)
end.
% update filename, including path
filename_parse(Filename, EnvironmentLookup) ->
case environment_transform(Filename, EnvironmentLookup) of
[] ->
{error, {service_external_file_path_invalid_expanded, Filename}};
NewFilename ->
{ok, NewFilename}
end.
% remove beginning whitespace and validate delimiters
% within the command-line arguments
arguments_parse([32 | Arguments], EnvironmentLookup) ->
arguments_parse(Arguments, EnvironmentLookup);
arguments_parse(Arguments, EnvironmentLookup) ->
NewArguments = environment_transform(Arguments, EnvironmentLookup),
case arguments_parse([], none, NewArguments) of
{ok, _} = Success ->
Success;
Reason when is_atom(Reason) ->
{error, {Reason, Arguments}}
end.
arguments_parse(Output, none, []) ->
{ok, lists:reverse(Output)};
arguments_parse(_, _, []) ->
service_external_args_malformed;
arguments_parse(Output, none, [$' | T]) ->
arguments_parse(Output, $', T);
arguments_parse(Output, none, [$" | T]) ->
arguments_parse(Output, $", T);
arguments_parse(Output, none, [$` | T]) ->
arguments_parse(Output, $`, T);
arguments_parse(Output, none, [32 | [32 | _] = T]) ->
arguments_parse(Output, none, T);
arguments_parse(Output, none, [32 | T]) ->
arguments_parse([0 | Output], none, T);
arguments_parse(Output, $', [$' | T]) ->
arguments_parse(Output, none, T);
arguments_parse(Output, $", [$" | T]) ->
arguments_parse(Output, none, T);
arguments_parse(Output, $`, [$` | T]) ->
arguments_parse(Output, none, T);
arguments_parse(Output, Delim, [H | T]) ->
arguments_parse([H | Output], Delim, T).
% add CloudI API environmental variables and format into a single
% string that is easy to use in C/C++
environment_parse(Environment0, ThreadsPerProcess0,
Protocol0, BufferSize0, EnvironmentLookup0) ->
ThreadsPerProcess1 = erlang:integer_to_list(ThreadsPerProcess0),
Protocol1 = erlang:atom_to_list(Protocol0),
BufferSize1 = erlang:integer_to_list(BufferSize0),
Environment1 = lists:keystore(?ENVIRONMENT_THREAD_COUNT, 1, Environment0,
{?ENVIRONMENT_THREAD_COUNT,
ThreadsPerProcess1}),
Environment2 = lists:keystore(?ENVIRONMENT_PROTOCOL, 1, Environment1,
{?ENVIRONMENT_PROTOCOL,
Protocol1}),
Environment3 = lists:keystore(?ENVIRONMENT_BUFFER_SIZE, 1, Environment2,
{?ENVIRONMENT_BUFFER_SIZE,
BufferSize1}),
EnvironmentLookup1 = trie:store(?ENVIRONMENT_THREAD_COUNT,
ThreadsPerProcess1,
EnvironmentLookup0),
EnvironmentLookup2 = trie:store(?ENVIRONMENT_PROTOCOL,
Protocol1,
EnvironmentLookup1),
EnvironmentLookup3 = trie:store(?ENVIRONMENT_BUFFER_SIZE,
BufferSize1,
EnvironmentLookup2),
environment_format(Environment3, EnvironmentLookup3).
environment_format_value([], _) ->
[];
environment_format_value(_, []) ->
[];
environment_format_value([_ | _] = K, [_ | _] = V) ->
K ++ [$= | V] ++ [0].
environment_format(Environment, EnvironmentLookup) ->
environment_format([], Environment, EnvironmentLookup).
environment_format(Output, [], _) ->
Output;
environment_format(Output, [{K, V} | Environment], EnvironmentLookup) ->
NewK = environment_transform(K, EnvironmentLookup),
NewV = environment_transform(V, EnvironmentLookup),
environment_format(Output ++ environment_format_value(NewK, NewV),
Environment, EnvironmentLookup).
% terminate the string for easy access within C/C++
string_terminate([]) ->
[0];
string_terminate([_ | _] = L) ->
L ++ [0].