%-*-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 %%% %%% MIT License %%% %%% Copyright (c) 2011-2020 Michael Truog %%% %%% Permission is hereby granted, free of charge, to any person obtaining a %%% copy of this software and associated documentation files (the "Software"), %%% to deal in the Software without restriction, including without limitation %%% the rights to use, copy, modify, merge, publish, distribute, sublicense, %%% and/or sell copies of the Software, and to permit persons to whom the %%% Software is furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING %%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER %%% DEALINGS IN THE SOFTWARE. %%% %%% @author Michael Truog %%% @copyright 2011-2020 Michael Truog %%% @version 2.0.1 {@date} {@time} %%%------------------------------------------------------------------------ -module(cloudi_core_i_spawn). -author('mjtruog at protonmail dot com'). %% external interface -export([start_internal/18, start_external/21, status_internal/3, status_external/4, 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(PROCESS_START_INTERNAL, cloudi_core_i_services_internal_sup:process_start). -define(PROCESS_START_EXTERNAL, cloudi_core_i_services_external_sup:process_start). % 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). -dialyzer({no_match, [start_external_spawn/20, update_external/3]}). -endif. -type error_reason_start_internal() :: {service_internal_module_not_loaded, any()}. -type error_reason_start_external() :: {service_external_file_path_invalid_expanded | service_external_file_path_invalid_unicode | service_external_args_invalid_quotes | service_external_args_invalid_unicode | service_external_env_invalid_expanded | service_external_env_invalid_unicode, any()}. -export_type([error_reason_start_internal/0, error_reason_start_external/0]). %%%------------------------------------------------------------------------ %%% External interface %%%------------------------------------------------------------------------ start_internal(ProcessIndex, ProcessCount, TimeStart, TimeRestart, Restarts, GroupLeader, Module, Args, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, DestListDeny, DestListAllow, ConfigOptions, ID) when is_integer(ProcessIndex), is_integer(ProcessCount), is_integer(TimeStart), is_integer(Restarts), 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, TimeoutNew} -> % Erlang application startup is asynchronous, so wait for the % module to be loaded or timeout ?PROCESS_START_INTERNAL(ProcessIndex, ProcessCount, TimeStart, TimeRestart, Restarts, GroupLeader, Module, Args, TimeoutNew, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, DestDeny, DestAllow, ConfigOptions, ID); {error, Reason} -> ?LOG_ERROR("loading ~p failed: ~tp", [Module, Reason]), {error, {service_internal_module_not_loaded, Module}} end. start_external(ProcessIndex, ProcessCount, TimeStart, TimeRestart, Restarts, ThreadsPerProcess, Filename, Arguments, Environment, Protocol, BufferSize, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, DestListDeny, DestListAllow, ConfigOptions, ID) -> case start_external_spawn_params(ProcessIndex, ProcessCount, ThreadsPerProcess, Filename, Arguments, Environment, Protocol, BufferSize, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, ConfigOptions, ID) of {ok, SpawnProcess, SpawnProtocol, SocketPath, Rlimits, Owner, Nice, CGroup, Chroot, SyscallLock, Directory, CommandLine, FilenameNew, ArgumentsNew, EnvironmentLookup} -> {ok, DestDeny, DestAllow} = start_external_threads_params(DestListDeny, DestListAllow), ConfigOptionsNew = ConfigOptions#config_service_options{ cgroup = CGroup}, case start_external_threads(ThreadsPerProcess, ProcessIndex, ProcessCount, TimeStart, TimeRestart, Restarts, CommandLine, Protocol, SocketPath, BufferSize, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, DestDeny, DestAllow, ConfigOptionsNew, ID) of {ok, Pids, Ports} -> start_external_spawn(SpawnProcess, SpawnProtocol, SocketPath, Pids, Ports, Rlimits, Owner, Nice, CGroup, Chroot, SyscallLock, Directory, ThreadsPerProcess, CommandLine, FilenameNew, ArgumentsNew, Environment, EnvironmentLookup, Protocol, BufferSize); {error, _} = Error -> Error end; {error, _} = Error -> Error end. status_internal(CountProcess, [_GroupLeader, Module, _Args, _TimeoutInit, Prefix, _TimeoutAsync, _TimeoutSync, _TimeoutTerm, _DestRefresh, _DestListDeny, _DestListAllow, _ConfigOptions, _ID], Status) -> [{type, internal}, {prefix, Prefix}, {module, Module}, {count_process, CountProcess} | Status]. status_external(CountProcess, CountThread, [_ThreadsPerProcess, Filename, _Arguments, _Environment, _Protocol, _BufferSize, _TimeoutInit, Prefix, _TimeoutAsync, _TimeoutSync, _TimeoutTerm, _DestRefresh, _DestListDeny, _DestListAllow, _ConfigOptions, _ID], Status) -> [{type, external}, {prefix, Prefix}, {file_path, Filename}, {count_process, CountProcess}, {count_thread, CountThread} | Status]. 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_spawn_params(ProcessIndex, ProcessCount, ThreadsPerProcess, Filename, Arguments, Environment, Protocol, BufferSize, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, ConfigOptions, ID) of {ok, SpawnProcess, SpawnProtocol, SocketPath, Rlimits, Owner, Nice, CGroup, Chroot, SyscallLock, Directory, CommandLine, FilenameNew, ArgumentsNew, EnvironmentLookup} -> case start_external_spawn(SpawnProcess, SpawnProtocol, SocketPath, Pids, Ports, Rlimits, Owner, Nice, CGroup, Chroot, SyscallLock, Directory, ThreadsPerProcess, CommandLine, FilenameNew, ArgumentsNew, 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(DestRefreshNew, TimeoutInitNew, TimeoutAsyncNew, TimeoutSyncNew, DestListDenyNew, DestListAllowNew, OptionsKeys, ConfigOptionsNew, [GroupLeader, Module, Args, TimeoutInitOld, Prefix, TimeoutAsyncOld, TimeoutSyncOld, TimeoutTerm, DestRefreshOld, DestListDenyOld, DestListAllowOld, ConfigOptionsOld, ID]) -> [GroupLeader, Module, Args, if TimeoutInitNew =:= undefined -> TimeoutInitOld; is_integer(TimeoutInitNew) -> TimeoutInitNew end, Prefix, if TimeoutAsyncNew =:= undefined -> TimeoutAsyncOld; is_integer(TimeoutAsyncNew) -> TimeoutAsyncNew end, if TimeoutSyncNew =:= undefined -> TimeoutSyncOld; is_integer(TimeoutSyncNew) -> TimeoutSyncNew end, TimeoutTerm, if DestRefreshNew =:= undefined -> DestRefreshOld; is_atom(DestRefreshNew) -> DestRefreshNew end, if DestListDenyNew =:= invalid -> DestListDenyOld; DestListDenyNew =:= undefined; is_list(DestListDenyNew) -> DestListDenyNew end, if DestListAllowNew =:= invalid -> DestListAllowOld; DestListAllowNew =:= undefined; is_list(DestListAllowNew) -> DestListAllowNew end, cloudi_core_i_configuration:service_options_copy(OptionsKeys, ConfigOptionsOld, ConfigOptionsNew), ID]. update_external_f(FilenameNew, ArgumentsNew, EnvironmentNew, DestRefreshNew, TimeoutInitNew, TimeoutAsyncNew, TimeoutSyncNew, DestListDenyNew, DestListAllowNew, OptionsKeys, ConfigOptionsNew, [ThreadsPerProcess, FilenameOld, ArgumentsOld, EnvironmentOld, Protocol, BufferSize, TimeoutInitOld, Prefix, TimeoutAsyncOld, TimeoutSyncOld, TimeoutTerm, DestRefreshOld, DestListDenyOld, DestListAllowOld, ConfigOptionsOld, ID]) -> [ThreadsPerProcess, if is_list(FilenameNew) -> FilenameNew; FilenameNew =:= undefined -> FilenameOld end, if is_list(ArgumentsNew) -> ArgumentsNew; ArgumentsNew =:= undefined -> ArgumentsOld end, if is_list(EnvironmentNew) -> EnvironmentNew; EnvironmentNew =:= undefined -> EnvironmentOld end, Protocol, BufferSize, if is_integer(TimeoutInitNew) -> TimeoutInitNew; TimeoutInitNew =:= undefined -> TimeoutInitOld end, Prefix, if is_integer(TimeoutAsyncNew) -> TimeoutAsyncNew; TimeoutAsyncNew =:= undefined -> TimeoutAsyncOld end, if is_integer(TimeoutSyncNew) -> TimeoutSyncNew; TimeoutSyncNew =:= undefined -> TimeoutSyncOld end, TimeoutTerm, if DestRefreshNew =:= undefined -> DestRefreshOld; is_atom(DestRefreshNew) -> DestRefreshNew end, if DestListDenyNew =:= invalid -> DestListDenyOld; DestListDenyNew =:= undefined; is_list(DestListDenyNew) -> DestListDenyNew end, if DestListAllowNew =:= invalid -> DestListAllowOld; DestListAllowNew =:= undefined; is_list(DestListAllowNew) -> DestListAllowNew end, cloudi_core_i_configuration:service_options_copy(OptionsKeys, ConfigOptionsOld, ConfigOptionsNew), 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). nice(#config_service_options{nice = Nice}) -> Nice. cgroup(#config_service_options{cgroup = L}, EnvironmentLookup) -> cloudi_core_i_os_process:cgroup_format(L, EnvironmentLookup). chroot(#config_service_options{chroot = Chroot}, EnvironmentLookup) -> cloudi_core_i_os_process:chroot_format(Chroot, EnvironmentLookup). syscall_lock(#config_service_options{syscall_lock = SyscallLock}) -> cloudi_core_i_os_process:syscall_lock_format(SyscallLock). 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_spawn_params(ProcessIndex, ProcessCount, ThreadsPerProcess, Filename, Arguments, Environment, Protocol, BufferSize, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, 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), TemporaryDirectory = case os:getenv("TMPDIR") of false -> "/tmp"; L -> L end, SocketPath = create_socket_path(TemporaryDirectory, ID), EnvironmentLookup = cloudi_environment:lookup(), case start_external_spawn_params_parse(Filename, Arguments, ConfigOptions, EnvironmentLookup) of {ok, CommandLine, FilenameNew, ArgumentsNew, Chroot, Directory} -> case supool:get(?OS_SPAWN_POOL) 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, Rlimits = rlimits(ConfigOptions), Owner = owner(ConfigOptions, EnvironmentLookup), Nice = nice(ConfigOptions), CGroup = cgroup(ConfigOptions, EnvironmentLookup), SyscallLock = syscall_lock(ConfigOptions), {ok, SpawnProcess, SpawnProtocol, SocketPath, Rlimits, Owner, Nice, CGroup, Chroot, SyscallLock, Directory, CommandLine, FilenameNew, ArgumentsNew, EnvironmentLookup}; undefined -> {error, noproc} end; {error, _} = Error -> Error end. start_external_threads_params(DestListDeny, DestListAllow) -> DestDeny = if DestListDeny =:= undefined -> undefined; is_list(DestListDeny) -> trie:new(DestListDeny) end, DestAllow = if DestListAllow =:= undefined -> undefined; is_list(DestListAllow) -> trie:new(DestListAllow) end, {ok, DestDeny, DestAllow}. start_external_spawn_params_parse(Filename, Arguments, ConfigOptions, EnvironmentLookup) -> case filename_parse(Filename, EnvironmentLookup) of {ok, FilenameNew} -> case arguments_parse(Arguments, EnvironmentLookup) of {ok, ArgumentsNew, ArgumentsList} -> CommandLine = [FilenameNew | ArgumentsList], case chroot(ConfigOptions, EnvironmentLookup) of {ok, Chroot} -> case directory(ConfigOptions, EnvironmentLookup) of {ok, Directory} -> {ok, CommandLine, FilenameNew, ArgumentsNew, Chroot, Directory}; {error, _} = Error -> Error end; {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, TimeStart, TimeRestart, Restarts, CommandLine, Protocol, SocketPath, BufferSize, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, DestDeny, DestAllow, ConfigOptions, ID) -> case ?PROCESS_START_EXTERNAL(Protocol, SocketPath, I + ThreadsPerProcess * ProcessIndex, ProcessIndex, ProcessCount, TimeStart, TimeRestart, Restarts, 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, TimeStart, TimeRestart, Restarts, 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, TimeStart, TimeRestart, Restarts, CommandLine, Protocol, SocketPath, BufferSize, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, DestDeny, DestAllow, ConfigOptions, ID) -> start_external_thread(0, [], [], ThreadsPerProcess, ProcessIndex, ProcessCount, TimeStart, TimeRestart, Restarts, CommandLine, Protocol, SocketPath, BufferSize, Timeout, Prefix, TimeoutAsync, TimeoutSync, TimeoutTerm, DestRefresh, DestDeny, DestAllow, ConfigOptions, ID). start_external_spawn(SpawnProcess, SpawnProtocol, SocketPath, Pids, Ports, Rlimits, Owner, Nice, CGroup, Chroot, SyscallLock, Directory, ThreadsPerProcess, CommandLine, Filename, Arguments, Environment, EnvironmentLookup, Protocol, BufferSize) -> case environment_parse(Environment, ThreadsPerProcess, Protocol, BufferSize, EnvironmentLookup) of {ok, SpawnEnvironment} -> {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), Nice, string_terminate(Chroot), SyscallLock, string_terminate(Directory), string_terminate(Filename), string_terminate(Arguments), SpawnEnvironment) of {ok, OSPid} -> ?LOG_INFO("OS pid ~p spawned ~p~n ~tp", [OSPid, Pids, CommandLine]), case cloudi_core_i_os_process:cgroup_set(OSPid, CGroup) of ok -> {ok, Pids}; {error, _} = Error -> Error end; {error, _} = Error -> Error end; {error, _} = Error -> Error end. % update filename, including path filename_parse(Filename, EnvironmentLookup) -> case cloudi_environment:transform(Filename, EnvironmentLookup) of [] -> {error, {service_external_file_path_invalid_expanded, Filename}}; ExpandedFilename -> case unicode:characters_to_binary(ExpandedFilename, utf8) of {_, _, _} -> {error, {service_external_file_path_invalid_unicode, ExpandedFilename}}; Utf8Filename -> {ok, erlang:binary_to_list(Utf8Filename)} end 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) -> ArgumentsNew = cloudi_environment:transform(Arguments, EnvironmentLookup), case arguments_parse([], [], [], none, ArgumentsNew) of {ok, _, _} = Success -> Success; Reason when is_atom(Reason) -> {error, {Reason, Arguments}} end. arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, []) -> {ok, lists:reverse(ArgumentsBinary), lists:reverse([lists:reverse(Argument) | ArgumentsList])}; arguments_parse(_, _, _, _, []) -> service_external_args_invalid_quotes; arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, [$' | T]) -> arguments_parse(ArgumentsBinary, ArgumentsList, Argument, $', T); arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, [$" | T]) -> arguments_parse(ArgumentsBinary, ArgumentsList, Argument, $", T); arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, [$` | T]) -> arguments_parse(ArgumentsBinary, ArgumentsList, Argument, $`, T); arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, [32 | [32 | _] = T]) -> arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, T); arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, [32 | T]) -> arguments_parse([0 | ArgumentsBinary], [lists:reverse(Argument) | ArgumentsList], [], none, T); arguments_parse(ArgumentsBinary, ArgumentsList, Argument, $', [$' | T]) -> arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, T); arguments_parse(ArgumentsBinary, ArgumentsList, Argument, $", [$" | T]) -> arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, T); arguments_parse(ArgumentsBinary, ArgumentsList, Argument, $`, [$` | T]) -> arguments_parse(ArgumentsBinary, ArgumentsList, Argument, none, T); arguments_parse(ArgumentsBinary, ArgumentsList, Argument, Delim, [H | T]) -> case unicode:characters_to_binary([H], utf8) of {_, _, _} -> service_external_args_invalid_unicode; Utf8H -> ArgumentsBinaryNew = lists:reverse(erlang:binary_to_list(Utf8H), ArgumentsBinary), arguments_parse(ArgumentsBinaryNew, ArgumentsList, [H | Argument], Delim, T) end. % 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([], _, K, _) -> {error, {service_external_env_invalid_expanded, K}}; environment_format_value(_, [], _, _) -> {ok, []}; environment_format_value([_ | _] = ExpandedK, [_ | _] = ExpandedV, K, V) -> case unicode:characters_to_binary(ExpandedK, utf8) of {_, _, _} -> {error, {service_external_env_invalid_unicode, K}}; Utf8K -> case unicode:characters_to_binary(ExpandedV, utf8) of {_, _, _} -> {error, {service_external_env_invalid_unicode, V}}; Utf8V -> {ok, [Utf8K, $=, Utf8V, 0]} end end. environment_format(Environment, EnvironmentLookup) -> environment_format([], Environment, EnvironmentLookup). environment_format(Output, [], _) -> {ok, erlang:iolist_to_binary(lists:reverse(Output))}; environment_format(Output, [{K, V} | Environment], EnvironmentLookup) -> ExpandedK = cloudi_environment:transform(K, EnvironmentLookup), ExpandedV = cloudi_environment:transform(V, EnvironmentLookup), case environment_format_value(ExpandedK, ExpandedV, K, V) of {ok, Pair} -> environment_format([Pair | Output], Environment, EnvironmentLookup); {error, _} = Error -> Error end. % terminate the string for easy access within C/C++ string_terminate([]) -> <<0>>; string_terminate([_ | _] = L) -> erlang:iolist_to_binary([L, 0]).