From 0b93cb7ecefcb283c3421ddd2e0544733da4bc08 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 27 Apr 2017 19:44:58 +0300 Subject: [PATCH] Store options using p1_options module --- rebar.config | 2 +- src/ejabberd_admin.erl | 2 +- src/ejabberd_app.erl | 4 +- src/ejabberd_config.erl | 239 ++++++++++++++---------------------- src/ejabberd_db_modules.erl | 44 +++++++ src/ejabberd_options.erl | 7 +- 6 files changed, 143 insertions(+), 155 deletions(-) create mode 100644 src/ejabberd_db_modules.erl diff --git a/rebar.config b/rebar.config index 8b1454de9..8ab6c4559 100644 --- a/rebar.config +++ b/rebar.config @@ -19,7 +19,7 @@ %%%---------------------------------------------------------------------- {deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.2.1"}}}, - {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.8"}}}, + {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", "470539a"}}, {cache_tab, ".*", {git, "https://github.com/processone/cache_tab", "b0c787a"}}, {fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.11"}}}, {stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.8"}}}, diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index 15cd78c02..a615b2fee 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -616,7 +616,7 @@ restore(Path) -> %% Obsolete tables or tables created by module who are no longer used are not %% restored and are ignored. keep_tables() -> - lists:flatten([acl, passwd, config, local_config, + lists:flatten([acl, passwd, config, keep_modules_tables()]). %% Returns the list of modules tables in use, according to the list of actually diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 214c38b21..c96cd95f6 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -46,9 +46,9 @@ start(normal, _Args) -> start_apps(), start_elixir_application(), ejabberd:check_app(ejabberd), - ejabberd_mnesia:start(), setup_if_elixir_conf_used(), ejabberd_config:start(), + ejabberd_mnesia:start(), set_settings_from_config(), file_queue_init(), maybe_add_nameservers(), @@ -59,6 +59,7 @@ start(normal, _Args) -> {T2, _} = statistics(wall_clock), ?INFO_MSG("ejabberd ~s is started in the node ~p in ~.2fs", [?VERSION, node(), (T2-T1)/1000]), + lists:foreach(fun erlang:garbage_collect/1, processes()), {ok, SupPid}; Err -> Err @@ -161,6 +162,7 @@ start_apps() -> crypto:start(), ejabberd:start_app(sasl), ejabberd:start_app(ssl), + ejabberd:start_app(p1_utils), ejabberd:start_app(fast_yaml), ejabberd:start_app(fast_tls), ejabberd:start_app(xmpp), diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 139549413..041b5b6a1 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -37,8 +37,7 @@ 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, - dump/0]). + use_cache/1, cache_size/1, cache_missed/1, cache_life_time/1]). -export([start/2]). @@ -55,6 +54,7 @@ -include("logger.hrl"). -include("ejabberd_config.hrl"). -include_lib("kernel/include/file.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). -callback opt_type(atom()) -> function() | [atom()]. @@ -68,7 +68,8 @@ start() -> ConfigFile = get_ejabberd_config_path(), ?INFO_MSG("Loading configuration from ~s", [ConfigFile]), - mnesia_init(), + p1_options:start_link(ejabberd_options), + p1_options:start_link(ejabberd_db_modules), State1 = load_file(ConfigFile), UnixTime = p1_time_compat:system_time(seconds), SharedKey = case erlang:get_cookie() of @@ -101,22 +102,9 @@ hosts_to_start(State) -> %% At the moment, these functions are mainly used to setup unit tests. -spec start(Hosts :: [binary()], Opts :: [acl:acl() | local_config()]) -> ok. start(Hosts, Opts) -> - mnesia_init(), set_opts(set_hosts_in_options(Hosts, #state{opts = Opts})), ok. -mnesia_init() -> - case catch mnesia:table_info(local_config, storage_type) of - disc_copies -> - mnesia:delete_table(local_config); - _ -> - ok - end, - ejabberd_mnesia:create(?MODULE, local_config, - [{ram_copies, [node()]}, - {local_content, true}, - {attributes, record_info(fields, local_config)}]). - %% @doc Get the filename of the ejabberd configuration file. %% The filename can be specified with: erl -config "/path/to/ejabberd.yml". %% It can also be specified with the environtment variable EJABBERD_CONFIG_PATH. @@ -762,30 +750,18 @@ append_option({Opt, Host}, Val, State) -> set_opts(State) -> Opts = State#state.opts, - F = fun() -> - lists:foreach( - fun({node_start, _}) -> ok; - ({shared_key, _}) -> ok; - (Key) -> mnesia:delete({local_config, Key}) - end, mnesia:all_keys(local_config)), - lists:foreach(fun mnesia:write/1, Opts) - end, - case mnesia:transaction(F) of - {atomic, _} -> - recompile_options(), - set_log_level(); - {aborted,{no_exists,Table}} -> - MnesiaDirectory = mnesia:system_info(directory), - ?CRITICAL_MSG("Error reading Mnesia database spool files:~n" - "The Mnesia database couldn't read the spool file for the table '~p'.~n" - "ejabberd needs read and write access in the directory:~n ~s~n" - "Maybe the problem is a change in the computer hostname,~n" - "or a change in the Erlang node name, which is currently:~n ~p~n" - "Check the ejabberd guide for details about changing the~n" - "computer hostname or Erlang node name.~n", - [Table, MnesiaDirectory, node()]), - exit("Error reading Mnesia database") - end. + ets:select_delete(ejabberd_options, + ets:fun2ms( + fun({{node_start, _}, _}) -> false; + ({{shared_key, _}, _}) -> false; + (_) -> true + end)), + lists:foreach( + fun(#local_config{key = {Opt, Host}, value = Val}) -> + p1_options:insert(ejabberd_options, Opt, Host, Val) + end, Opts), + p1_options:compile(ejabberd_options), + set_log_level(). set_log_level() -> Level = get_option( @@ -802,12 +778,9 @@ add_local_option(Opt, Val) -> add_option(Opt, Val) when is_atom(Opt) -> add_option({Opt, global}, Val); -add_option(Opt, Val) -> - mnesia:transaction(fun() -> - mnesia:write(#local_config{key = Opt, - value = Val}) - end), - recompile_options(). +add_option({Opt, Host}, Val) -> + p1_options:insert(ejabberd_options, Opt, Host, Val), + p1_options:compile(ejabberd_options). -spec prepare_opt_val(any(), any(), check_fun(), any()) -> any(). @@ -882,10 +855,9 @@ get_option(Opt, F, Default) -> 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 + case ejabberd_options:Key(Host) of + {ok, Val} -> prepare_opt_val(Opt, Val, F, Default); + undefined -> Default end; false -> Default @@ -896,46 +868,71 @@ has_option(Opt) -> get_option(Opt, fun(_) -> true end, false). init_module_db_table(Modules) -> - catch ets:new(module_db, [named_table, public, bag, - {read_concurrency, true}]), %% Dirty hack for mod_pubsub - ets:insert(module_db, {mod_pubsub, mnesia}), - ets:insert(module_db, {mod_pubsub, sql}), + p1_options:insert(ejabberd_db_modules, mod_pubsub, mnesia, true), + p1_options:insert(ejabberd_db_modules, mod_pubsub, sql, true), lists:foreach( fun(M) -> case re:split(atom_to_list(M), "_", [{return, list}]) of [_] -> ok; Parts -> - [Suffix|T] = lists:reverse(Parts), - BareMod = string:join(lists:reverse(T), "_"), - ets:insert(module_db, {list_to_atom(BareMod), - list_to_atom(Suffix)}) + [H|T] = lists:reverse(Parts), + Suffix = list_to_atom(H), + BareMod = list_to_atom(string:join(lists:reverse(T), "_")), + case is_behaviour(BareMod, M) of + true -> + p1_options:insert(ejabberd_db_modules, + BareMod, Suffix, true); + false -> + ok + end end - end, Modules). + end, Modules), + p1_options:compile(ejabberd_db_modules). + +is_behaviour(Behav, Mod) -> + try Mod:module_info(attributes) of + [] -> + %% Stripped module? + true; + Attrs -> + lists:any( + fun({behaviour, L}) -> lists:member(Behav, L); + ({behavior, L}) -> lists:member(Behav, L); + (_) -> false + end, Attrs) + catch _:_ -> + true + end. -spec v_db(module(), atom()) -> atom(). v_db(Mod, internal) -> v_db(Mod, mnesia); v_db(Mod, odbc) -> v_db(Mod, sql); v_db(Mod, Type) -> - case ets:match_object(module_db, {Mod, Type}) of - [_|_] -> Type; - [] -> erlang:error(badarg) + case ejabberd_db_modules:is_known(Mod) of + true -> + case ejabberd_db_modules:Mod(Type) of + {ok, _} -> Type; + _ -> erlang:error(badarg) + end; + false -> + erlang:error(badarg) end. -spec v_dbs(module()) -> [atom()]. v_dbs(Mod) -> - lists:flatten(ets:match(module_db, {Mod, '$1'})). + ejabberd_db_modules:get_scope(Mod). -spec v_dbs_mods(module()) -> [module()]. v_dbs_mods(Mod) -> - lists:map(fun([M]) -> + lists:map(fun(M) -> binary_to_atom(<<(atom_to_binary(Mod, utf8))/binary, "_", (atom_to_binary(M, utf8))/binary>>, utf8) - end, ets:match(module_db, {Mod, '$1'})). + end, v_dbs(Mod)). -spec default_db(module()) -> atom(). default_db(Module) -> @@ -976,13 +973,18 @@ get_modules_with_options() -> init_module_db_table(AllMods), lists:foldl( fun(Mod, D) -> - case catch Mod:opt_type('') of - Opts when is_list(Opts) -> - lists:foldl( - fun(Opt, Acc) -> - dict:append(Opt, Mod, Acc) - end, D, Opts); - {'EXIT', {undef, _}} -> + case is_behaviour(?MODULE, Mod) orelse Mod == ?MODULE of + true -> + try Mod:opt_type('') of + Opts when is_list(Opts) -> + lists:foldl( + fun(Opt, Acc) -> + dict:append(Opt, Mod, Acc) + end, D, Opts) + catch _:undef -> + D + end; + false -> D end end, dict:new(), AllMods). @@ -1022,23 +1024,21 @@ validate_opts(#state{opts = Opts} = State) -> %% Return the list of hosts with a given auth method get_vh_by_auth_method(AuthMethod) -> - Cfgs = mnesia:dirty_match_object(local_config, - #local_config{key = {auth_method, '_'}, - _ = '_'}), - lists:flatmap( - fun(#local_config{key = {auth_method, Host}, value = M}) -> - Methods = if not is_list(M) -> [M]; - true -> M - end, - case lists:member(AuthMethod, Methods) of - true when Host == global -> - get_myhosts(); - true -> - [Host]; - false -> - [] - end - end, Cfgs). + Hosts = ejabberd_options:get_scope(auth_method), + get_vh_by_auth_method(AuthMethod, Hosts, []). + +get_vh_by_auth_method(Method, [Host|Hosts], Result) -> + Methods = get_option({auth_method, Host}, fun(Ms) -> Ms end, []), + case lists:member(Method, Methods) of + true when Host == global -> + get_myhosts(); + true -> + get_vh_by_auth_method(Method, Hosts, [Host|Result]); + false -> + get_vh_by_auth_method(Method, Hosts, Result) + end; +get_vh_by_auth_method(_, [], Result) -> + Result. %% @spec (Path::string()) -> true | false is_file_readable(Path) -> @@ -1445,64 +1445,3 @@ 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(), - case misc:compile_exprs(ejabberd_options, Exprs) of - ok -> ok; - {error, _} = Err -> - ?CRITICAL_MSG("Failed to compile ejabberd_options:~n~s", - [string:join(Exprs, io_lib:nl())]), - erlang:error(Err) - 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]. - -%% @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_db_modules.erl b/src/ejabberd_db_modules.erl new file mode 100644 index 000000000..2a92f3ae0 --- /dev/null +++ b/src/ejabberd_db_modules.erl @@ -0,0 +1,44 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @doc +%%% This is a stub module which will be replaced during +%%% configuration load via p1_options:compile/1 +%%% The only purpose of this file is to shut up xref/dialyzer +%%% @end +%%% Created : 27 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_db_modules). + +%% API +-export([is_known/1, get_scope/1]). + +%%%=================================================================== +%%% API +%%%=================================================================== +is_known(_) -> + false. + +get_scope(_) -> + []. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/ejabberd_options.erl b/src/ejabberd_options.erl index bcde437eb..228b891c3 100644 --- a/src/ejabberd_options.erl +++ b/src/ejabberd_options.erl @@ -2,7 +2,7 @@ %%% @author Evgeny Khramtsov %%% @doc %%% This is a stub module which will be replaced during -%%% configuration load, see ejabberd_config:recompile_options/0. +%%% configuration load via p1_options:compile/1 %%% The only purpose of this file is to shut up xref/dialyzer %%% @end %%% Created : 16 Apr 2017 by Evgeny Khramtsov @@ -28,7 +28,7 @@ -module(ejabberd_options). %% API --export([is_known/1]). +-export([is_known/1, get_scope/1]). %%%=================================================================== %%% API @@ -36,6 +36,9 @@ is_known(_) -> false. +get_scope(_) -> + []. + %%%=================================================================== %%% Internal functions %%%===================================================================