diff options
author | Evgeny Khramtsov <ekhramtsov@process-one.net> | 2019-06-14 12:33:26 +0300 |
---|---|---|
committer | Evgeny Khramtsov <ekhramtsov@process-one.net> | 2019-06-14 12:33:26 +0300 |
commit | a02cff0e780bb735531594c4ece81e8628f79782 (patch) | |
tree | 6fe7d8219d14f58183be1741fcea262c216db447 /src/gen_mod.erl | |
parent | Return 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.erl | 895 |
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)]). |