summaryrefslogtreecommitdiff
path: root/src/gen_mod.erl
diff options
context:
space:
mode:
authorEvgeny Khramtsov <ekhramtsov@process-one.net>2019-06-14 12:33:26 +0300
committerEvgeny Khramtsov <ekhramtsov@process-one.net>2019-06-14 12:33:26 +0300
commita02cff0e780bb735531594c4ece81e8628f79782 (patch)
tree6fe7d8219d14f58183be1741fcea262c216db447 /src/gen_mod.erl
parentReturn jid_malformed error when sending presence without nick to conference (diff)
Use new configuration validator
Diffstat (limited to 'src/gen_mod.erl')
-rw-r--r--src/gen_mod.erl895
1 files changed, 267 insertions, 628 deletions
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index b972285f..f0a35e31 100644
--- a/src/gen_mod.erl
+++ b/src/gen_mod.erl
@@ -22,39 +22,25 @@
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-
-module(gen_mod).
-
--behaviour(ejabberd_config).
-behaviour(supervisor).
-
-author('alexey@process-one.net').
-export([init/1, start_link/0, start_child/3, start_child/4,
stop_child/1, stop_child/2, config_reloaded/0]).
-export([start_module/2, stop_module/2, stop_module_keep_config/2,
- get_opt/2, get_opt_hosts/2, opt_type/1, is_equal_opt/3,
- get_module_opt/3, get_module_opt_host/3,
+ get_opt/2, set_opt/3, get_opt_hosts/1, is_equal_opt/3,
+ get_module_opt/3, get_module_opt_hosts/2,
loaded_modules/1, loaded_modules_with_opts/1,
get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2,
start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
- db_mod/2, db_mod/3, ram_db_mod/2, ram_db_mod/3,
- is_db_configured/2]).
+ db_mod/2, ram_db_mod/2]).
+-export([validate/2]).
%% Deprecated functions
--export([get_opt/3, get_opt/4, get_module_opt/4, get_module_opt/5,
- get_opt_host/3, get_opt_hosts/3, db_type/2, db_type/3,
- ram_db_type/2, ram_db_type/3, update_module_opts/3]).
--deprecated([{get_opt, 3},
- {get_opt, 4},
- {get_opt_host, 3},
- {get_opt_hosts, 3},
- {get_module_opt, 4},
- {get_module_opt, 5},
- {db_type, 2},
- {db_type, 3},
- {ram_db_type, 2},
- {ram_db_type, 3}]).
+%% update_module/3 is used by test suite ONLY
+-export([update_module/3]).
+-deprecated([{update_module, 3}]).
-include("logger.hrl").
-include_lib("stdlib/include/ms_transform.hrl").
@@ -65,14 +51,14 @@
opts = [] :: opts() | '_' | '$2',
order = 0 :: integer()}).
--type opts() :: [{atom(), any()}].
+-type opts() :: #{atom() => term()}.
-type db_type() :: atom().
-callback start(binary(), opts()) -> ok | {ok, pid()} | {error, term()}.
-callback stop(binary()) -> any().
-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()}.
--callback mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()].
--callback mod_options(binary()) -> opts().
+-callback mod_opt_type(atom()) -> econf:validator().
+-callback mod_options(binary()) -> [{atom(), term()} | atom()].
-callback depends(binary(), opts()) -> [{module(), hard | soft}].
-optional_callbacks([mod_opt_type/1, reload/3]).
@@ -103,18 +89,18 @@ init([]) ->
{read_concurrency, true}]),
{ok, {{one_for_one, 10, 1}, []}}.
--spec start_child(module(), binary() | global, opts()) -> ok | {error, any()}.
+-spec start_child(module(), binary(), opts()) -> {ok, pid()} | {error, any()}.
start_child(Mod, Host, Opts) ->
start_child(Mod, Host, Opts, get_module_proc(Host, Mod)).
--spec start_child(module(), binary() | global, opts(), atom()) -> ok | {error, any()}.
+-spec start_child(module(), binary(), opts(), atom()) -> {ok, pid()} | {error, any()}.
start_child(Mod, Host, Opts, Proc) ->
Spec = {Proc, {?GEN_SERVER, start_link,
[{local, Proc}, Mod, [Host, Opts], []]},
transient, timer:minutes(1), worker, [Mod]},
supervisor:start_child(ejabberd_gen_mod_sup, Spec).
--spec stop_child(module(), binary() | global) -> ok | {error, any()}.
+-spec stop_child(module(), binary()) -> ok | {error, any()}.
stop_child(Mod, Host) ->
stop_child(get_module_proc(Host, Mod)).
@@ -124,73 +110,22 @@ stop_child(Proc) ->
supervisor:delete_child(ejabberd_gen_mod_sup, Proc).
-spec start_modules() -> any().
-
-%% Start all the modules in all the hosts
start_modules() ->
- Hosts = ejabberd_config:get_myhosts(),
+ Hosts = ejabberd_option:hosts(),
?INFO_MSG("Loading modules for ~s", [format_hosts_list(Hosts)]),
lists:foreach(fun start_modules/1, Hosts).
-get_modules_options(Host) ->
- sort_modules(Host, ejabberd_config:get_option({modules, Host}, [])).
-
-sort_modules(Host, ModOpts) ->
- G = digraph:new([acyclic]),
- lists:foreach(
- fun({Mod, Opts}) ->
- digraph:add_vertex(G, Mod, Opts),
- Deps = try Mod:depends(Host, Opts) catch _:undef -> [] end,
- lists:foreach(
- fun({DepMod, Type}) ->
- case lists:keyfind(DepMod, 1, ModOpts) of
- false when Type == hard ->
- ErrTxt = io_lib:format(
- "Failed to load module '~s' "
- "because it depends on module '~s' "
- "which is not found in the config",
- [Mod, DepMod]),
- ?ERROR_MSG(ErrTxt, []),
- digraph:del_vertex(G, Mod),
- maybe_halt_ejabberd();
- false when Type == soft ->
- ?WARNING_MSG("Module '~s' is recommended for "
- "module '~s' but is not found in "
- "the config",
- [DepMod, Mod]);
- {DepMod, DepOpts} ->
- digraph:add_vertex(G, DepMod, DepOpts),
- case digraph:add_edge(G, DepMod, Mod) of
- {error, {bad_edge, Path}} ->
- ?WARNING_MSG("Cyclic dependency detected "
- "between modules: ~p",
- [Path]);
- _ ->
- ok
- end
- end
- end, Deps)
- end, ModOpts),
- {Result, _} = lists:mapfoldl(
- fun(V, Order) ->
- {M, O} = digraph:vertex(G, V),
- {{M, O, Order}, Order+1}
- end, 1, digraph_utils:topsort(G)),
- digraph:delete(G),
- Result.
-
-spec start_modules(binary()) -> ok.
-
start_modules(Host) ->
- Modules = get_modules_options(Host),
+ Modules = ejabberd_option:modules(Host),
lists:foreach(
fun({Module, Opts, Order}) ->
start_module(Host, Module, Opts, Order)
end, Modules).
-spec start_module(binary(), atom()) -> ok | {ok, pid()} | {error, not_found_in_config}.
-
start_module(Host, Module) ->
- Modules = get_modules_options(Host),
+ Modules = ejabberd_option:modules(Host),
case lists:keyfind(Module, 1, Modules) of
{_, Opts, Order} ->
start_module(Host, Module, Opts, Order);
@@ -200,42 +135,30 @@ start_module(Host, Module) ->
-spec start_module(binary(), atom(), opts(), integer()) -> ok | {ok, pid()}.
start_module(Host, Module, Opts, Order) ->
- start_module(Host, Module, Opts, Order, true).
-
--spec start_module(binary(), atom(), opts(), integer(), boolean()) -> ok | {ok, pid()}.
-start_module(Host, Module, Opts0, Order, NeedValidation) ->
?DEBUG("Loading ~s at ~s", [Module, Host]),
- Res = if NeedValidation ->
- validate_opts(Host, Module, Opts0);
- true ->
- {ok, Opts0}
- end,
- case Res of
- {ok, Opts} ->
- store_options(Host, Module, Opts, Order),
- try case Module:start(Host, Opts) of
- ok -> ok;
- {ok, Pid} when is_pid(Pid) -> {ok, Pid};
- Err -> erlang:error({bad_return, Module, Err})
- end
- catch ?EX_RULE(Class, Reason, Stack) ->
- StackTrace = ?EX_STACK(Stack),
- ets:delete(ejabberd_modules, {Module, Host}),
- ErrorText = format_module_error(
- Module, start, 2,
- Opts, Class, Reason,
- StackTrace),
- ?CRITICAL_MSG(ErrorText, []),
- maybe_halt_ejabberd(),
- erlang:raise(Class, Reason, StackTrace)
- end;
- {error, _ErrorText} ->
- maybe_halt_ejabberd()
+ store_options(Host, Module, Opts, Order),
+ try case Module:start(Host, Opts) of
+ ok -> ok;
+ {ok, Pid} when is_pid(Pid) -> {ok, Pid};
+ Err ->
+ ets:delete(ejabberd_modules, {Module, Host}),
+ erlang:error({bad_return, Module, Err})
+ end
+ catch ?EX_RULE(Class, Reason, Stack) ->
+ StackTrace = ?EX_STACK(Stack),
+ ets:delete(ejabberd_modules, {Module, Host}),
+ ErrorText = format_module_error(
+ Module, start, 2,
+ Opts, Class, Reason,
+ StackTrace),
+ ?CRITICAL_MSG(ErrorText, []),
+ maybe_halt_ejabberd(),
+ erlang:raise(Class, Reason, StackTrace)
end.
-spec reload_modules(binary()) -> ok.
reload_modules(Host) ->
- NewMods = get_modules_options(Host),
+ NewMods = ejabberd_option:modules(Host),
OldMods = lists:reverse(loaded_modules_with_opts(Host)),
lists:foreach(
fun({Mod, _Opts}) ->
@@ -258,13 +181,10 @@ reload_modules(Host) ->
lists:foreach(
fun({Mod, OldOpts}) ->
case lists:keyfind(Mod, 1, NewMods) of
- {_, NewOpts0, Order} ->
- case validate_opts(Host, Mod, NewOpts0) of
- {ok, OldOpts} ->
- ok;
- {ok, NewOpts} ->
+ {_, NewOpts, Order} ->
+ if OldOpts /= NewOpts ->
reload_module(Host, Mod, NewOpts, OldOpts, Order);
- {error, _} ->
+ true ->
ok
end;
_ ->
@@ -296,29 +216,22 @@ reload_module(Host, Module, NewOpts, OldOpts, Order) ->
?WARNING_MSG("Module ~s doesn't support reloading "
"and will be restarted", [Module]),
stop_module(Host, Module),
- start_module(Host, Module, NewOpts, Order, false)
+ start_module(Host, Module, NewOpts, Order)
end.
+-spec update_module(binary(), module(), opts()) -> ok | {ok, pid()}.
+update_module(Host, Module, Opts) ->
+ [#ejabberd_module{opts = OldOpts, order = Order}] =
+ ets:lookup(ejabberd_modules, {Module, Host}),
+ NewOpts = maps:merge(OldOpts, Opts),
+ reload_module(Host, Module, NewOpts, OldOpts, Order).
+
-spec store_options(binary(), module(), opts(), integer()) -> true.
store_options(Host, Module, Opts, Order) ->
ets:insert(ejabberd_modules,
#ejabberd_module{module_host = {Module, Host},
opts = Opts, order = Order}).
--spec update_module_opts(binary(), module(), opts()) -> ok | {ok, pid()} | error.
-update_module_opts(Host, Module, NewValues) ->
- case ets:lookup(ejabberd_modules, {Module, Host}) of
- [#ejabberd_module{opts = Opts, order = Order}] ->
- NewOpts = lists:foldl(
- fun({K, _} = KV, Acc) ->
- lists:keystore(K, 1, Acc, KV)
- end, Opts, NewValues),
- reload_module(Host, Module, NewOpts, Opts, Order);
- Other ->
- ?WARNING_MSG("Unable to update module opts: (~p, ~p) -> ~p",
- [Host, Module, Other])
- end.
-
maybe_halt_ejabberd() ->
case is_app_running(ejabberd) of
false ->
@@ -336,15 +249,13 @@ is_app_running(AppName) ->
application:which_applications(Timeout)).
-spec stop_modules() -> ok.
-
stop_modules() ->
lists:foreach(
fun(Host) ->
stop_modules(Host)
- end, ejabberd_config:get_myhosts()).
+ end, ejabberd_option:hosts()).
-spec stop_modules(binary()) -> ok.
-
stop_modules(Host) ->
Modules = lists:reverse(loaded_modules_with_opts(Host)),
lists:foreach(
@@ -353,7 +264,6 @@ stop_modules(Host) ->
end, Modules).
-spec stop_module(binary(), atom()) -> error | {aborted, any()} | {atomic, any()}.
-
stop_module(Host, Module) ->
case stop_module_keep_config(Host, Module) of
error -> error;
@@ -361,7 +271,6 @@ stop_module(Host, Module) ->
end.
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
-
stop_module_keep_config(Host, Module) ->
?DEBUG("Stopping ~s at ~s", [Module, Host]),
case catch Module:stop(Host) of
@@ -400,475 +309,53 @@ wait_for_stop1(MonitorReference) ->
after 5000 -> ok
end.
--type check_fun() :: fun((any()) -> any()) | {module(), atom()}.
-
-spec get_opt(atom(), opts()) -> any().
get_opt(Opt, Opts) ->
- case lists:keyfind(Opt, 1, Opts) of
- {_, Val} -> Val;
- false ->
- ?DEBUG("Attempt to read unspecified option ~s", [Opt]),
- undefined
- end.
-
--spec get_opt(atom(), opts(), check_fun() | any()) -> any().
-
-get_opt(Opt, Opts, F) when is_function(F) ->
- get_opt(Opt, Opts, undefined);
-get_opt(Opt, Opts, Default) ->
- case lists:keyfind(Opt, 1, Opts) of
- false ->
- Default;
- {_, Val} ->
- Val
- end.
+ maps:get(Opt, Opts).
--spec get_opt(atom() | {atom(), binary()}, opts(), check_fun(), any()) -> any().
-get_opt(Opt, Opts, _, Default) ->
- get_opt(Opt, Opts, Default).
+-spec set_opt(atom(), term(), opts()) -> opts().
+set_opt(Opt, Val, Opts) ->
+ maps:put(Opt, Val, Opts).
-spec get_module_opt(global | binary(), atom(), atom()) -> any().
-
+get_module_opt(global, Module, Opt) ->
+ get_module_opt(ejabberd_config:get_myname(), Module, Opt);
get_module_opt(Host, Module, Opt) ->
- get_module_opt(Host, Module, Opt, undefined).
-
--spec get_module_opt(global | binary(), atom(), atom(), any()) -> any().
-
-get_module_opt(Host, Module, Opt, F) when is_function(F) ->
- get_module_opt(Host, Module, Opt, undefined);
-get_module_opt(global, Module, Opt, Default) ->
- Hosts = ejabberd_config:get_myhosts(),
- [Value | Values] = lists:map(fun (Host) ->
- get_module_opt(Host, Module, Opt,
- Default)
- end,
- Hosts),
- Same_all = lists:all(fun (Other_value) ->
- Other_value == Value
- end,
- Values),
- case Same_all of
- true -> Value;
- false -> Default
- end;
-get_module_opt(Host, Module, Opt, Default) ->
- OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
- case OptsList of
- [] -> Default;
- [#ejabberd_module{opts = Opts} | _] ->
- get_opt(Opt, Opts, Default)
- end.
-
--spec get_module_opt(global | binary(), atom(), atom(), check_fun(), any()) -> any().
-get_module_opt(Host, Module, Opt, _, Default) ->
- get_module_opt(Host, Module, Opt, Default).
-
--spec get_module_opt_host(global | binary(), atom(), binary()) -> binary().
-
-get_module_opt_host(Host, Module, Default) ->
- Val = get_module_opt(Host, Module, host, Default),
- ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
-
--spec get_opt_host(binary(), opts(), binary()) -> binary().
-
-get_opt_host(Host, Opts, Default) ->
- Val = get_opt(host, Opts, Default),
- ejabberd_regexp:greplace(Val, <<"@HOST@">>, Host).
-
--spec get_opt_hosts(binary(), opts()) -> [binary()].
-get_opt_hosts(Host, Opts) ->
- get_opt_hosts(Host, Opts, undefined).
-
--spec get_opt_hosts(binary(), opts(), binary()) -> [binary()].
-get_opt_hosts(Host, Opts, Default) ->
- Vals = case get_opt(hosts, Opts) of
- L when L == [] orelse L == undefined ->
- case get_opt(host, Opts) of
- undefined -> [Default];
- H -> [H]
- end;
- L ->
- L
- end,
- [ejabberd_regexp:greplace(V, <<"@HOST@">>, Host) || V <- Vals].
-
--spec get_validators(binary(), {module(), [module()]}) -> list() | undef.
-get_validators(Host, {Module, SubMods}) ->
- Validators =
- dict:to_list(
- lists:foldl(
- fun(Mod, D) ->
- try list_known_opts(Host, Mod) of
- Os ->
- lists:foldl(
- fun({Opt, SubOpt} = O, Acc) ->
- SubF = Mod:mod_opt_type(O),
- F = try Mod:mod_opt_type(Opt)
- catch _:_ -> fun(X) -> X end
- end,
- dict:append_list(
- Opt, [F, {SubOpt, [SubF]}], Acc);
- (O, Acc) ->
- F = Mod:mod_opt_type(O),
- dict:store(O, [F], Acc)
- end, D, Os)
- catch _:undef ->
- D
- end
- end, dict:new(), [Module|SubMods])),
- case Validators of
- [] ->
- case have_validators(Module) of
- false ->
- case code:ensure_loaded(Module) of
- {module, _} ->
- ?WARNING_MSG("Third-party module '~s' doesn't export "
- "options validator; consider to upgrade "
- "the module", [Module]);
- _ ->
- %% Silently ignore this, the error will be
- %% generated later
- ok
- end,
- undef;
- true ->
- []
- end;
- _ ->
- Validators
- end.
-
--spec have_validators(module()) -> boolean().
-have_validators(Module) ->
- erlang:function_exported(Module, mod_options, 1)
- orelse erlang:function_exported(Module, mod_opt_type, 1).
-
--spec validate_opts(binary(), module(), opts()) -> {ok, opts()} | {error, string()}.
-validate_opts(Host, Module, Opts0) ->
- SubMods = get_submodules(Host, Module, Opts0),
- DefaultOpts = lists:flatmap(
- fun(M) ->
- try M:mod_options(Host)
- catch _:undef -> []
- end
- end, [Module|SubMods]),
- try
- Opts = merge_opts(Opts0, DefaultOpts, Module),
- {ok, case get_validators(Host, {Module, SubMods}) of
- undef ->
- Opts;
- Validators ->
- Opts1 = validate_opts(Host, Module, Opts, Validators),
- remove_duplicated_opts(Opts1)
- end}
- catch _:{missing_required_option, Opt} ->
- ErrTxt = io_lib:format("Module '~s' is missing required option '~s'",
- [Module, Opt]),
- module_error(ErrTxt);
- _:{invalid_option, Opt, Val} ->
- ErrTxt = io_lib:format("Invalid value for option '~s' of "
- "module ~s: ~s",
- [Opt, Module, misc:format_val({yaml, Val})]),
- module_error(ErrTxt);
- _:{invalid_option, Opt, Val, Reason} ->
- ErrTxt = io_lib:format("Invalid value for option '~s' of "
- "module ~s (~s): ~s",
- [Opt, Module, Reason, misc:format_val({yaml, Val})]),
- module_error(ErrTxt);
- _:{unknown_option, Opt, []} ->
- ErrTxt = io_lib:format("Unknown option '~s' of module '~s': "
- "the module doesn't have any options",
- [Opt, Module]),
- module_error(ErrTxt);
- _:{unknown_option, Opt, KnownOpts} ->
- ErrTxt = io_lib:format("Unknown option '~s' of module '~s',"
- " did you mean '~s'?"
- " Available options are: ~s",
- [Opt, Module,
- misc:best_match(Opt, KnownOpts),
- misc:join_atoms(KnownOpts, <<", ">>)]),
- module_error(ErrTxt)
- end.
-
--spec module_error(iolist()) -> {error, iolist()}.
-module_error(ErrTxt) ->
- ?ERROR_MSG(ErrTxt, []),
- {error, ErrTxt}.
-
--spec err_invalid_option(atom(), any()) -> no_return().
-err_invalid_option(Opt, Val) ->
- erlang:error({invalid_option, Opt, Val}).
-
--spec err_invalid_option(atom(), any(), iolist()) -> no_return().
-err_invalid_option(Opt, Val, Reason) ->
- erlang:error({invalid_option, Opt, Val, Reason}).
-
--spec err_unknown_option(atom(), [atom()]) -> no_return().
-err_unknown_option(Opt, KnownOpts) ->
- erlang:error({unknown_option, Opt, KnownOpts}).
-
--spec err_missing_required_option(atom()) -> no_return().
-err_missing_required_option(Opt) ->
- erlang:error({missing_required_option, Opt}).
-
-validate_opts(Host, Module, Opts, Validators) when is_list(Opts) ->
- lists:flatmap(
- fun({Opt, Val}) when is_atom(Opt) ->
- case lists:keyfind(Opt, 1, Validators) of
- {_, L} ->
- case lists:partition(fun is_function/1, L) of
- {[VFun|_], []} ->
- validate_opt(Opt, Val, VFun);
- {[VFun|_], SubValidators} ->
- try validate_opts(Host, Module, Val, SubValidators) of
- SubOpts ->
- validate_opt(Opt, SubOpts, VFun)
- catch _:bad_option ->
- err_invalid_option(Opt, Val)
- end
- end;
- false ->
- err_unknown_option(Opt, [K || {K, _} <- Validators])
- end;
- (_) ->
- erlang:error(bad_option)
- end, Opts);
-validate_opts(_, _, _, _) ->
- erlang:error(bad_option).
-
--spec validate_opt(atom(), any(), check_fun()) -> [{atom(), any()}].
-validate_opt(Opt, Val, VFun) ->
- try VFun(Val) of
- NewVal -> [{Opt, NewVal}]
- catch {invalid_syntax, Error} ->
- err_invalid_option(Opt, Val, Error);
- _:R when R /= undef ->
- err_invalid_option(Opt, Val)
- end.
-
--spec list_known_opts(binary(), module()) -> [atom() | {atom(), atom()}].
-list_known_opts(Host, Module) ->
- try Module:mod_options(Host) of
- DefaultOpts ->
- lists:flatmap(
- fun({Opt, [{A, _}|_] = Vals}) when is_atom(A) ->
- [{Opt, Val} || {Val, _} <- Vals];
- ({Opt, _}) -> [Opt];
- (Opt) -> [Opt]
- end, DefaultOpts)
- catch _:undef ->
- Module:mod_opt_type('')
- end.
-
--spec merge_opts(opts(), opts(), module()) -> opts().
-merge_opts(Opts, DefaultOpts, Module) ->
- Result =
- lists:foldr(
- fun({Opt, Default}, Acc) ->
- case lists:keyfind(Opt, 1, Opts) of
- {_, Val} ->
- case Default of
- [{A, _}|_] when is_atom(A) andalso is_list(Val) ->
- case is_opt_list(Val) of
- true ->
- [{Opt, merge_opts(Val, Default, Module)}|Acc];
- false ->
- err_invalid_option(Opt, Val)
- end;
- Val ->
- [{Opt, Default}|Acc];
- _ ->
- [{Opt, Val}, {Opt, Default}|Acc]
- end;
- _ ->
- [{Opt, Default}|Acc]
- end;
- (Opt, Acc) ->
- case lists:keyfind(Opt, 1, Opts) of
- {_, Val} ->
- [{Opt, Val}|Acc];
- false ->
- err_missing_required_option(Opt)
- end
- end, [], DefaultOpts),
- lists:foldl(
- fun({Opt, Val}, Acc) ->
- case lists:keymember(Opt, 1, Result) of
- true -> Acc;
- false -> [{Opt, Val}|Acc]
- end
- end, Result, Opts).
-
-remove_duplicated_opts([{Opt, Val}, {Opt, _Default}|Opts]) ->
- [{Opt, Val}|remove_duplicated_opts(Opts)];
-remove_duplicated_opts([{Opt, [{SubOpt, _}|_] = SubOpts}|Opts])
- when is_atom(SubOpt) ->
- [{Opt, remove_duplicated_opts(SubOpts)}|remove_duplicated_opts(Opts)];
-remove_duplicated_opts([OptVal|Opts]) ->
- [OptVal|remove_duplicated_opts(Opts)];
-remove_duplicated_opts([]) ->
- [].
-
--spec get_submodules(binary(), module(), opts()) -> [module()].
-get_submodules(Host, Module, Opts) ->
- try Module:mod_options(Host) of
- DefaultOpts ->
- Mod1 = case lists:keyfind(db_type, 1, DefaultOpts) of
- {_, T1} ->
- DBType = proplists:get_value(db_type, Opts, T1),
- [db_mod(DBType, Module)];
- false ->
- []
- end,
- Mod2 = case lists:keyfind(ram_db_type, 1, DefaultOpts) of
- {_, T2} ->
- RamDBType = proplists:get_value(ram_db_type, Opts, T2),
- [ram_db_mod(RamDBType, Module)];
- false ->
- []
- end,
- Mod1 ++ Mod2
- catch _:undef ->
- []
- end.
-
--spec format_module_error(atom(), start | reload, non_neg_integer(), opts(),
- error | exit | throw, any(),
- [erlang:stack_item()]) -> iolist().
-format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) ->
- IsLoaded = code:ensure_loaded(Module) == {module, Module},
- IsCallbackExported = erlang:function_exported(Module, Fun, Arity),
- case {Class, Reason} of
- {error, undef} when not IsLoaded ->
- io_lib:format("Failed to ~s unknown module ~s, "
- "did you mean ~s? Hint: "
- "make sure there is no typo and ~s.beam "
- "exists inside either ~s or ~s "
- "directory",
- [Fun, Module,
- misc:best_match(
- Module, ejabberd_config:get_modules()),
- Module,
- filename:dirname(code:which(?MODULE)),
- ext_mod:modules_dir()]);
- {error, undef} when not IsCallbackExported ->
- io_lib:format("Failed to ~s module ~s because "
- "it doesn't export ~s/~B callback: "
- "is it really an ejabberd module?",
- [Fun, Module, Fun, Arity]);
- {error, {bad_return, Module, {error, _} = Err}} ->
- io_lib:format("Failed to ~s module ~s: ~s",
- [Fun, Module, misc:format_val(Err)]);
- {error, {bad_return, Module, Ret}} ->
- io_lib:format("Module ~s returned unexpected value from ~s/~B:~n"
- "** Error: ~p~n"
- "** Hint: this is either not an ejabberd module "
- "or it implements ejabbed API incorrectly",
- [Module, Fun, Arity, Ret]);
- _ ->
- io_lib:format("Internal error of module ~s has "
- "occured during ~s:~n"
- "** Options: ~p~n"
- "** Class: ~p~n"
- "** Reason: ~p~n"
- "** Stacktrace: ~p",
- [Module, Fun, Opts, Class, Reason, St])
- end.
-
-format_hosts_list([Host]) ->
- Host;
-format_hosts_list([H1, H2]) ->
- [H1, " and ", H2];
-format_hosts_list([H1, H2, H3]) ->
- [H1, ", ", H2, " and ", H3];
-format_hosts_list([H1, H2|Hs]) ->
- io_lib:format("~s, ~s and ~B more hosts",
- [H1, H2, length(Hs)]).
-
--spec db_type(binary() | global, module()) -> db_type();
- (opts(), module()) -> db_type().
-
-db_type(Opts, Module) when is_list(Opts) ->
- db_type(global, Opts, Module);
-db_type(Host, Module) when is_atom(Module) ->
- case get_module_opt(Host, Module, db_type) of
- undefined ->
- ejabberd_config:default_db(Host, Module);
- Type ->
- Type
- end.
+ Opts = ets:lookup_element(ejabberd_modules, {Module, Host}, 3),
+ get_opt(Opt, Opts).
--spec db_type(binary() | global, opts(), module()) -> db_type().
+-spec get_module_opt_hosts(binary(), module()) -> [binary()].
+get_module_opt_hosts(Host, Module) ->
+ Opts = ets:lookup_element(ejabberd_modules, {Module, Host}, 3),
+ get_opt_hosts(Opts).
-db_type(Host, Opts, Module) ->
- case get_opt(db_type, Opts) of
- undefined ->
- ejabberd_config:default_db(Host, Module);
- Type ->
- Type
+-spec get_opt_hosts(opts()) -> [binary()].
+get_opt_hosts(Opts) ->
+ case get_opt(hosts, Opts) of
+ L when L == [] orelse L == undefined ->
+ [get_opt(host, Opts)];
+ L ->
+ L
end.
--spec db_mod(binary() | global | db_type(), module()) -> module().
-
-db_mod(Type, Module) when is_atom(Type) ->
- list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type));
-db_mod(Host, Module) when is_binary(Host) orelse Host == global ->
- db_mod(db_type(Host, Module), Module).
+-spec db_mod(binary() | global | db_type() | opts(), module()) -> module().
+db_mod(T, M) ->
+ db_mod(db_type, T, M).
--spec db_mod(binary() | global, opts(), module()) -> module().
+-spec ram_db_mod(binary() | global | db_type() | opts(), module()) -> module().
+ram_db_mod(T, M) ->
+ db_mod(ram_db_type, T, M).
-db_mod(Host, Opts, Module) when is_list(Opts) ->
- db_mod(db_type(Host, Opts, Module), Module).
-
--spec ram_db_type(binary() | global, module()) -> db_type();
- (opts(), module()) -> db_type().
-ram_db_type(Opts, Module) when is_list(Opts) ->
- ram_db_type(global, Opts, Module);
-ram_db_type(Host, Module) when is_atom(Module) ->
- case get_module_opt(Host, Module, ram_db_type) of
- undefined ->
- ejabberd_config:default_ram_db(Host, Module);
- Type ->
- Type
- end.
-
--spec ram_db_type(binary() | global, opts(), module()) -> db_type().
-ram_db_type(Host, Opts, Module) ->
- case get_opt(ram_db_type, Opts) of
- undefined ->
- ejabberd_config:default_ram_db(Host, Module);
- Type ->
- Type
- end.
-
--spec ram_db_mod(binary() | global | db_type(), module()) -> module().
-ram_db_mod(Type, Module) when is_atom(Type), Type /= global ->
- list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type));
-ram_db_mod(Host, Module) when is_binary(Host) orelse Host == global ->
- ram_db_mod(ram_db_type(Host, Module), Module).
-
--spec ram_db_mod(binary() | global, opts(), module()) -> module().
-ram_db_mod(Host, Opts, Module) when is_list(Opts) ->
- ram_db_mod(ram_db_type(Host, Opts, Module), Module).
-
-is_db_configured(Type, Host) ->
- lists:any(
- fun(#ejabberd_module{module_host = {_, H}, opts = Opts})
- when H == Host orelse Host == global ->
- case lists:keyfind(db_type, 1, Opts) of
- {_, Type} -> true;
- _ ->
- case lists:keyfind(ram_db_type, 1, Opts) of
- {_, Type} -> true;
- _ -> false
- end
- end;
- (_) ->
- false
- end, ets:tab2list(ejabberd_modules)).
+-spec db_mod(db_type | ram_db_type,
+ binary() | global | db_type() | opts(), module()) -> module().
+db_mod(Opt, Host, Module) when is_binary(Host) orelse Host == global ->
+ db_mod(Opt, get_module_opt(Host, Module, Opt), Module);
+db_mod(Opt, Opts, Module) when is_map(Opts) ->
+ db_mod(Opt, get_opt(Opt, Opts), Module);
+db_mod(_Opt, Type, Module) when is_atom(Type) ->
+ list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type)).
-spec loaded_modules(binary()) -> [atom()].
-
loaded_modules(Host) ->
Mods = ets:select(
ejabberd_modules,
@@ -880,7 +367,6 @@ loaded_modules(Host) ->
[Mod || {Mod, _} <- lists:keysort(2, Mods)].
-spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}].
-
loaded_modules_with_opts(Host) ->
Mods = ets:select(
ejabberd_modules,
@@ -892,13 +378,12 @@ loaded_modules_with_opts(Host) ->
[{Mod, Opts} || {Mod, Opts, _} <- lists:keysort(3, Mods)].
-spec get_hosts(opts(), binary()) -> [binary()].
-
get_hosts(Opts, Prefix) ->
case get_opt(hosts, Opts) of
undefined ->
case get_opt(host, Opts) of
undefined ->
- [<<Prefix/binary, Host/binary>> || Host <- ejabberd_config:get_myhosts()];
+ [<<Prefix/binary, Host/binary>> || Host <- ejabberd_option:hosts()];
Host ->
[Host]
end;
@@ -915,7 +400,6 @@ get_module_proc(Host, Base) ->
latin1).
-spec is_loaded(binary(), atom()) -> boolean().
-
is_loaded(Host, Module) ->
ets:member(ejabberd_modules, {Module, Host}).
@@ -930,13 +414,9 @@ is_loaded_elsewhere(Host, Module) ->
-spec config_reloaded() -> ok.
config_reloaded() ->
- lists:foreach(
- fun(Host) ->
- reload_modules(Host)
- end, ejabberd_config:get_myhosts()).
+ lists:foreach(fun reload_modules/1, ejabberd_option:hosts()).
--spec is_equal_opt(atom(), opts(), opts()) ->
- true | {false, any(), any()}.
+-spec is_equal_opt(atom(), opts(), opts()) -> true | {false, any(), any()}.
is_equal_opt(Opt, NewOpts, OldOpts) ->
NewVal = get_opt(Opt, NewOpts),
OldVal = get_opt(Opt, OldOpts),
@@ -946,28 +426,187 @@ is_equal_opt(Opt, NewOpts, OldOpts) ->
true
end.
--spec is_opt_list(term()) -> boolean().
-is_opt_list([]) ->
- true;
-is_opt_list(L) when is_list(L) ->
- lists:all(
- fun({Opt, _Val}) -> is_atom(Opt);
- (_) -> false
- end, L);
-is_opt_list(_) ->
- false.
-
--spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
-opt_type(modules) ->
- fun(Mods) ->
- lists:map(
- fun({M, A}) when is_atom(M) ->
- case is_opt_list(A) of
- true -> {M, A};
- false ->
- ?ERROR_MSG("Malformed configuration format of module ~s", [M]),
- erlang:error(badarg)
- end
- end, Mods)
- end;
-opt_type(_) -> [modules].
+%%%===================================================================
+%%% Formatters
+%%%===================================================================
+-spec format_module_error(atom(), start | reload, non_neg_integer(), opts(),
+ error | exit | throw, any(),
+ [erlang:stack_item()]) -> iolist().
+format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) ->
+ case {Class, Reason} of
+ {error, {bad_return, Module, {error, _} = Err}} ->
+ io_lib:format("Failed to ~s module ~s: ~s",
+ [Fun, Module, misc:format_val(Err)]);
+ {error, {bad_return, Module, Ret}} ->
+ io_lib:format("Module ~s returned unexpected value from ~s/~B:~n"
+ "** Error: ~p~n"
+ "** Hint: this is either not an ejabberd module "
+ "or it implements ejabbed API incorrectly",
+ [Module, Fun, Arity, Ret]);
+ _ ->
+ io_lib:format("Internal error of module ~s has "
+ "occured during ~s:~n"
+ "** Options: ~p~n"
+ "** Class: ~p~n"
+ "** Reason: ~p~n"
+ "** Stacktrace: ~p",
+ [Module, Fun, Opts, Class, Reason, St])
+ end.
+
+-spec format_hosts_list([binary()]) -> iolist().
+format_hosts_list([Host]) ->
+ Host;
+format_hosts_list([H1, H2]) ->
+ [H1, " and ", H2];
+format_hosts_list([H1, H2, H3]) ->
+ [H1, ", ", H2, " and ", H3];
+format_hosts_list([H1, H2|Hs]) ->
+ io_lib:format("~s, ~s and ~B more hosts",
+ [H1, H2, length(Hs)]).
+
+-spec format_cycle([atom()]) -> iolist().
+format_cycle([M1]) ->
+ atom_to_list(M1);
+format_cycle([M1, M2]) ->
+ [atom_to_list(M1), " and ", atom_to_list(M2)];
+format_cycle([M|Ms]) ->
+ atom_to_list(M) ++ ", " ++ format_cycle(Ms).
+
+%%%===================================================================
+%%% Validation
+%%%===================================================================
+-spec validator(binary()) -> econf:validator().
+validator(Host) ->
+ econf:options(
+ #{modules =>
+ econf:and_then(
+ econf:map(
+ econf:beam([{start, 2}, {stop, 1},
+ {mod_options, 1}, {depends, 2}]),
+ econf:options(
+ #{db_type => econf:atom(),
+ ram_db_type => econf:atom(),
+ '_' => econf:any()})),
+ fun(L) ->
+ Validators = maps:from_list(
+ lists:map(
+ fun({Mod, Opts}) ->
+ {Mod, validator(Host, Mod, Opts)}
+ end, L)),
+ Validator = econf:options(Validators, [unique]),
+ Validator(L)
+ end)}).
+
+-spec validator(binary(), module(), [{atom(), term()}]) -> econf:validator().
+validator(Host, Module, Opts) ->
+ {Required, {DefaultOpts1, Validators}} =
+ lists:mapfoldl(
+ fun({M, DefOpts}, {DAcc, VAcc}) ->
+ lists:mapfoldl(
+ fun({Opt, Def}, {DAcc1, VAcc1}) ->
+ {[], {DAcc1#{Opt => Def},
+ VAcc1#{Opt => get_opt_type(Module, M, Opt)}}};
+ (Opt, {DAcc1, VAcc1}) ->
+ {[Opt], {DAcc1,
+ VAcc1#{Opt => get_opt_type(Module, M, Opt)}}}
+ end, {DAcc, VAcc}, DefOpts)
+ end, {#{}, #{}}, get_defaults(Host, Module, Opts)),
+ econf:and_then(
+ econf:options(
+ Validators,
+ [{required, lists:usort(lists:flatten(Required))},
+ {return, map}, unique]),
+ fun(Opts1) ->
+ maps:merge(DefaultOpts1, Opts1)
+ end).
+
+-spec validate(binary(), [{module(), opts()}]) ->
+ {ok, [{module(), opts(), integer()}]} |
+ econf:error_return().
+validate(Host, ModOpts) ->
+ case econf:validate(validator(Host), [{modules, ModOpts}]) of
+ {ok, [{modules, ModOpts1}]} ->
+ try sort_modules(Host, ModOpts1)
+ catch throw:{?MODULE, Reason} ->
+ {error, Reason, [modules]}
+ end;
+ {error, _, _} = Err ->
+ Err
+ end.
+
+-spec get_defaults(binary(), module(), [{atom(), term()}]) ->
+ [{module(), [{atom(), term()} | atom()]}].
+get_defaults(Host, Module, Opts) ->
+ DefaultOpts = Module:mod_options(Host),
+ [{Module, DefaultOpts}|
+ lists:filtermap(
+ fun({Opt, T1}) when Opt == db_type; Opt == ram_db_type ->
+ T2 = proplists:get_value(Opt, Opts, T1),
+ DBMod = db_mod(Opt, T2, Module),
+ case code:ensure_loaded(DBMod) of
+ {module, _} ->
+ case erlang:function_exported(DBMod, mod_options, 1) of
+ true ->
+ {true, {DBMod, DBMod:mod_options(Host)}};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end;
+ (_) ->
+ false
+ end, DefaultOpts)].
+
+-spec get_opt_type(module(), module(), atom()) -> econf:validator().
+get_opt_type(Mod, SubMod, Opt) ->
+ try SubMod:mod_opt_type(Opt)
+ catch _:_ -> Mod:mod_opt_type(Opt)
+ end.
+
+-spec sort_modules(binary(), [{module(), opts()}]) -> {ok, [{module(), opts(), integer()}]}.
+sort_modules(Host, ModOpts) ->
+ G = digraph:new([acyclic]),
+ lists:foreach(
+ fun({Mod, Opts}) ->
+ digraph:add_vertex(G, Mod, Opts),
+ Deps = Mod:depends(Host, Opts),
+ lists:foreach(
+ fun({DepMod, Type}) ->
+ case lists:keyfind(DepMod, 1, ModOpts) of
+ false when Type == hard ->
+ throw({?MODULE, {missing_module_dep, Mod, DepMod}});
+ false when Type == soft ->
+ warn_soft_dep_fail(DepMod, Mod);
+ {DepMod, DepOpts} ->
+ digraph:add_vertex(G, DepMod, DepOpts),
+ case digraph:add_edge(G, DepMod, Mod) of
+ {error, {bad_edge, Path}} ->
+ warn_cyclic_dep(Path);
+ _ ->
+ ok
+ end
+ end
+ end, Deps)
+ end, ModOpts),
+ {Result, _} = lists:mapfoldl(
+ fun(V, Order) ->
+ {M, O} = digraph:vertex(G, V),
+ {{M, O, Order}, Order+1}
+ end, 1, digraph_utils:topsort(G)),
+ digraph:delete(G),
+ {ok, Result}.
+
+-spec warn_soft_dep_fail(module(), module()) -> ok.
+warn_soft_dep_fail(DepMod, Mod) ->
+ ?WARNING_MSG("Module ~s is recommended for module "
+ "~s but is not found in the config",
+ [DepMod, Mod]).
+
+-spec warn_cyclic_dep([module()]) -> ok.
+warn_cyclic_dep(Path) ->
+ ?WARNING_MSG("Cyclic dependency detected between modules ~s. "
+ "This is either a bug, or the modules are not "
+ "supposed to work together in this configuration. "
+ "The modules will still be loaded though",
+ [format_cycle(Path)]).