]> granicus.if.org Git - ejabberd/commitdiff
Store options using p1_options module
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>
Thu, 27 Apr 2017 16:44:58 +0000 (19:44 +0300)
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>
Thu, 27 Apr 2017 16:44:58 +0000 (19:44 +0300)
rebar.config
src/ejabberd_admin.erl
src/ejabberd_app.erl
src/ejabberd_config.erl
src/ejabberd_db_modules.erl [new file with mode: 0644]
src/ejabberd_options.erl

index 8b1454de998109106c45ad5d9e7d6b0bbbf68773..8ab6c4559ddb499f61584fe5b1212b264ed79a64 100644 (file)
@@ -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"}}},
index 15cd78c0289e49af4c5d3d69e5a10537969119fb..a615b2fee6cc4124b5c53dee3fb54af137fed58b 100644 (file)
@@ -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
index 214c38b2108bc92b96d45a7c79fef10d0c5a82d2..c96cd95f656acb02587f370cea5a1dc0dbef05f4 100644 (file)
@@ -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),
index 1395494130d81e234f4f5829dcc79e20130e966d..041b5b6a137f830f28d697a96191908789e3ef7e 100644 (file)
@@ -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 (file)
index 0000000..2a92f3a
--- /dev/null
@@ -0,0 +1,44 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @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 <ekhramtsov@process-one.net>
+%%%
+%%%
+%%% 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
+%%%===================================================================
index bcde437eb0f63da7cfbecdfd981866a04667769b..228b891c3d20e9518abacbf430ab793898f5ef0a 100644 (file)
@@ -2,7 +2,7 @@
 %%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
 %%% @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 <ekhramtsov@process-one.net>
@@ -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
 %%%===================================================================