diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 7d05e30d1..bfb4192fd 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -38,7 +38,8 @@ is_elixir_enabled/0, v_dbs/1, v_dbs_mods/1, default_db/1, default_db/2, default_ram_db/1, default_ram_db/2, default_queue_type/1, queue_dir/0, fsm_limit_opts/1, - use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1]). + use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1, + dump/0]). -export([start/2]). @@ -773,6 +774,7 @@ set_opts(State) -> end, case mnesia:transaction(F) of {atomic, _} -> + recompile_options(), set_log_level(); {aborted,{no_exists,Table}} -> MnesiaDirectory = mnesia:system_info(directory), @@ -788,7 +790,7 @@ set_opts(State) -> end. set_log_level() -> - Level = ejabberd_config:get_option( + Level = get_option( loglevel, fun(P) when P>=0, P=<5 -> P end, 4), @@ -806,7 +808,8 @@ add_option(Opt, Val) -> mnesia:transaction(fun() -> mnesia:write(#local_config{key = Opt, value = Val}) - end). + end), + recompile_options(). -spec prepare_opt_val(any(), any(), check_fun(), any()) -> any(). @@ -871,22 +874,23 @@ get_option(Opt, F) -> get_option(Opt, F, Default) when is_atom(Opt) -> get_option({Opt, global}, F, Default); get_option(Opt, F, Default) -> - case Opt of - {O, global} when is_atom(O) -> ok; - {O, H} when is_atom(O), is_binary(H) -> ok; - _ -> ?WARNING_MSG("Option ~p has invalid (outdated?) format. " - "This is likely a bug", [Opt]) - end, - case ets:lookup(local_config, Opt) of - [#local_config{value = Val}] -> - prepare_opt_val(Opt, Val, F, Default); - _ -> - case Opt of - {Key, Host} when Host /= global -> - get_option({Key, global}, F, Default); - _ -> - Default - end + {Key, Host} = case Opt of + {O, global} when is_atom(O) -> Opt; + {O, H} when is_atom(O), is_binary(H) -> Opt; + _ -> + ?WARNING_MSG("Option ~p has invalid (outdated?) " + "format. This is likely a bug", [Opt]), + {undefined, global} + end, + case ejabberd_options:is_known(Key) of + true -> + try ejabberd_options:Key(Host) of + Val -> prepare_opt_val(Opt, Val, F, Default) + catch _:function_clause -> + Default + end; + false -> + Default end. -spec has_option(atom() | {atom(), global | binary()}) -> any(). @@ -1481,7 +1485,7 @@ opt_type(_) -> -spec may_hide_data(any()) -> any(). may_hide_data(Data) -> - case ejabberd_config:get_option( + case get_option( hide_sensitive_log_data, fun(false) -> false; (true) -> true @@ -1531,3 +1535,79 @@ cache_missed(Host) -> %% NOTE: the integer value returned is in *seconds* cache_life_time(Host) -> get_option({cache_life_time, Host}, opt_type(cache_life_time), 3600). + +%%%=================================================================== +%%% Dynamic config compilation +%%%=================================================================== +-spec recompile_options() -> ok. +recompile_options() -> + Exprs = get_exprs(), + try compile_exprs(Exprs) + catch E:R -> + ?CRITICAL_MSG("Failed to compile ejabberd_options:~n~s", + [string:join(Exprs, io_lib:nl())]), + erlang:raise(E, R, erlang:get_stacktrace()) + end. + +-spec get_exprs() -> [string()]. +get_exprs() -> + Opts = lists:foldl( + fun(#local_config{key = {Opt, Host}, value = Val}, D) -> + Hosts = maps:get(Opt, D, #{}), + maps:put(Opt, maps:put(Host, Val, Hosts), D) + end, #{}, ets:tab2list(local_config)), + Funs = maps:fold( + fun(Opt, Vals, Acc) -> + HostVals = lists:reverse(lists:keysort(1, maps:to_list(Vals))), + [string:join( + lists:map( + fun({global, Val}) -> + io_lib:format("'~s'(_) -> ~p", [Opt, Val]); + ({Host, Val}) -> + io_lib:format("'~s'(~p) -> ~p", [Opt, Host, Val]) + end, HostVals), + ";" ++ io_lib:nl()) ++ "."|Acc] + end, [], Opts), + Module = "-module(ejabberd_options).", + Export = "-compile(export_all).", + Knowns = maps:fold( + fun(Opt, _, Acc) -> + io_lib:format("is_known('~s') -> true;~n", [Opt]) ++ Acc + end, "", Opts) ++ "is_known(_) -> false.", + [Module, Export, Knowns|Funs]. + +-spec compile_exprs([string()]) -> ok. +compile_exprs(Exprs) -> + Forms = lists:map( + fun(Expr) -> + {ok, Tokens, _} = erl_scan:string(lists:flatten(Expr)), + {ok, Form} = erl_parse:parse_form(Tokens), + Form + end, Exprs), + {ok, Code} = case compile:forms(Forms, []) of + {ok, ejabberd_options, Bin} -> {ok, Bin}; + {ok, ejabberd_options, Bin, _Warnings} -> {ok, Bin}; + Error -> Error + end, + {module, _} = code:load_binary(ejabberd_options, "nofile", Code), + ok. + +%% @doc This is only for debugging purposes, likely to report a bug +-spec dump() -> ok. +dump() -> + ETSFile = filename:join("/tmp", "ejabberd_options.ets"), + ErlFile = filename:join("/tmp", "ejabberd_options.erl"), + ETSData = io_lib:format("~p~n", [ets:tab2list(local_config)]), + ErlData = io_lib:format("~s~n", [str:join(get_exprs(), io_lib:nl())]), + case file:write_file(ETSFile, ETSData) of + ok -> io:format("ETS data written to ~s~n", [ETSFile]); + {error, Reason1} -> + io:format("Failed to write to ~s: ~s", + [ETSFile, file:format_error(Reason1)]) + end, + case file:write_file(ErlFile, ErlData) of + ok -> io:format("Dynamic module written to ~s~n", [ErlFile]); + {error, Reason2} -> + io:format("Failed to write to ~s: ~s", + [ErlFile, file:format_error(Reason2)]) + end. diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 6e3ba4599..fc6cacc5f 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -408,17 +408,6 @@ add_listener(PortIP, Module, Opts) -> PortIP1 = {Port, IPT, Proto}, case start_listener(PortIP1, Module, Opts) of {ok, _Pid} -> - Ports = case ejabberd_config:get_option( - listen, fun validate_cfg/1) of - undefined -> - []; - Ls -> - Ls - end, - Ports1 = lists:keydelete(PortIP1, 1, Ports), - Ports2 = [{PortIP1, Module, Opts} | Ports1], - Ports3 = lists:map(fun transform_option/1, Ports2), - ejabberd_config:add_option(listen, Ports3), ok; {error, {already_started, _Pid}} -> {error, {already_started, PortIP}}; diff --git a/src/ejabberd_options.erl b/src/ejabberd_options.erl new file mode 100644 index 000000000..bcde437eb --- /dev/null +++ b/src/ejabberd_options.erl @@ -0,0 +1,41 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @doc +%%% This is a stub module which will be replaced during +%%% configuration load, see ejabberd_config:recompile_options/0. +%%% The only purpose of this file is to shut up xref/dialyzer +%%% @end +%%% Created : 16 Apr 2017 by Evgeny Khramtsov +%%% +%%% +%%% ejabberd, Copyright (C) 2002-2017 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%------------------------------------------------------------------- +-module(ejabberd_options). + +%% API +-export([is_known/1]). + +%%%=================================================================== +%%% API +%%%=================================================================== +is_known(_) -> + false. + +%%%=================================================================== +%%% Internal functions +%%%===================================================================