diff --git a/src/ejabberd.erl b/src/ejabberd.erl index 7b83f19a1..78ef10089 100644 --- a/src/ejabberd.erl +++ b/src/ejabberd.erl @@ -27,8 +27,8 @@ -module(ejabberd). -author('alexey@process-one.net'). --export([start/0, stop/0, start_app/1, - get_pid_file/0]). +-export([start/0, stop/0, start_app/1, start_app/2, + get_pid_file/0, check_app/1]). -include("logger.hrl"). @@ -51,27 +51,93 @@ get_pid_file() -> Path end. -start_app(App) when not is_list(App) -> - start_app([App]); -start_app([App|Apps]) -> +start_app(App) -> + start_app(App, temporary). + +start_app(App, Type) -> + StartFlag = not is_loaded(), + start_app(App, Type, StartFlag). + +check_app(App) -> + StartFlag = not is_loaded(), + spawn(fun() -> check_app_modules(App, StartFlag) end), + ok. + +is_loaded() -> + Apps = application:which_applications(), + lists:keymember(ejabberd, 1, Apps). + +start_app(App, Type, StartFlag) when not is_list(App) -> + start_app([App], Type, StartFlag); +start_app([App|Apps], Type, StartFlag) -> case application:start(App) of ok -> - start_app(Apps); + spawn(fun() -> check_app_modules(App, StartFlag) end), + start_app(Apps, Type, StartFlag); {error, {already_started, _}} -> - start_app(Apps); + start_app(Apps, Type, StartFlag); {error, {not_started, DepApp}} -> case lists:member(DepApp, [App|Apps]) of true -> - ?CRITICAL_MSG("failed to start application '~p': " - "circular dependency on '~p' detected", - [App, DepApp]), - erlang:error(application_start_failed); + Reason = io_lib:format( + "failed to start application '~p': " + "circular dependency on '~p' detected", + [App, DepApp]), + exit_or_halt(Reason, StartFlag); false -> - start_app([DepApp,App|Apps]) + start_app([DepApp,App|Apps], Type, StartFlag) end; Err -> - ?CRITICAL_MSG("failed to start application '~p': ~p", [App, Err]), - erlang:error(application_start_failed) + Reason = io_lib:format("failed to start application '~p': ~p", + [App, Err]), + exit_or_halt(Reason, StartFlag) end; -start_app([]) -> +start_app([], _Type, _StartFlag) -> ok. + +check_app_modules(App, StartFlag) -> + {A, B, C} = now(), + random:seed(A, B, C), + sleep(5000), + case application:get_key(App, modules) of + {ok, Mods} -> + lists:foreach( + fun(Mod) -> + case code:which(Mod) of + non_existing -> + File = get_module_file(App, Mod), + Reason = io_lib:format( + "couldn't find module ~s " + "needed for application '~p'", + [File, App]), + exit_or_halt(Reason, StartFlag); + _ -> + sleep(10) + end + end, Mods); + _ -> + %% No modules? This is strange + ok + end. + +exit_or_halt(Reason, StartFlag) -> + ?CRITICAL_MSG(Reason, []), + if StartFlag -> + %% Wait for the critical message is written in the console/log + timer:sleep(1000), + halt(string:substr(lists:flatten(Reason), 1, 199)); + true -> + erlang:error(application_start_failed) + end. + +sleep(N) -> + timer:sleep(random:uniform(N)). + +get_module_file(App, Mod) -> + BaseName = atom_to_list(Mod), + case code:lib_dir(App, ebin) of + {error, _} -> + BaseName; + Dir -> + filename:join([Dir, BaseName ++ ".beam"]) + end. diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 4a95a11f8..ed67f0335 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -42,6 +42,7 @@ start(normal, _Args) -> ejabberd_logger:start(), write_pid_file(), start_apps(), + ejabberd:check_app(ejabberd), randoms:start(), db_init(), start(), @@ -52,7 +53,6 @@ start(normal, _Args) -> ejabberd_admin:start(), gen_mod:start(), ejabberd_config:start(), - ejabberd_check:config(), connect_nodes(), Sup = ejabberd_sup:start_link(), ejabberd_rdbms:start(), @@ -112,7 +112,7 @@ db_init() -> _ -> ok end, - application:start(mnesia, permanent), + ejabberd:start_app(mnesia, permanent), mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity). %% Start all the modules in all the hosts diff --git a/src/ejabberd_check.erl b/src/ejabberd_check.erl deleted file mode 100644 index 5dde19186..000000000 --- a/src/ejabberd_check.erl +++ /dev/null @@ -1,111 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_check.erl -%%% Author : Mickael Remond -%%% Purpose : Check ejabberd configuration and -%%% Created : 27 Feb 2008 by Mickael Remond -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_check). - --export([libs/0, config/0]). - --include("ejabberd.hrl"). --include("logger.hrl"). --include("ejabberd_config.hrl"). - -%% TODO: -%% We want to implement library checking at launch time to issue -%% human readable user messages. -libs() -> - ok. - -%% @doc Consistency check on ejabberd configuration -config() -> - check_database_modules(). - -check_database_modules() -> - [check_database_module(M)||M<-get_db_used()]. - -check_database_module(odbc) -> - check_modules(odbc, [odbc, odbc_app, odbc_sup, ejabberd_odbc, ejabberd_odbc_sup, odbc_queries]); -check_database_module(mysql) -> - check_modules(mysql, [mysql, mysql_auth, mysql_conn, mysql_recv]); -check_database_module(pgsql) -> - check_modules(pgsql, [pgsql, pgsql_proto, pgsql_tcp, pgsql_util]). - -%% @doc Issue a critical error and throw an exit if needing module is -%% missing. -check_modules(DB, Modules) -> - case get_missing_modules(Modules) of - [] -> - ok; - MissingModules when is_list(MissingModules) -> - ?CRITICAL_MSG("ejabberd is configured to use '~p', but the following Erlang modules are not installed: '~p'", [DB, MissingModules]), - exit(database_module_missing) - end. - - -%% @doc Return the list of undefined modules -get_missing_modules(Modules) -> - lists:filter(fun(Module) -> - case catch Module:module_info() of - {'EXIT', {undef, _}} -> - true; - _ -> false - end - end, Modules). - -%% @doc Return the list of databases used -get_db_used() -> - %% Retrieve domains with a database configured: - Domains = - ets:match(local_config, #local_config{key={odbc_server, '$1'}, - value='$2'}), - %% Check that odbc is the auth method used for those domains: - %% and return the database name - DBs = lists:foldr( - fun([Domain, DB], Acc) -> - case check_odbc_option( - ejabberd_config:get_local_option( - {auth_method, Domain}, fun(V) -> V end)) of - true -> [get_db_type(DB)|Acc]; - _ -> Acc - end - end, - [], Domains), - lists:usort(DBs). - -%% @doc Depending in the DB definition, return which type of DB this is. -%% Note that MSSQL is detected as ODBC. -%% @spec (DB) -> mysql | pgsql | odbc -get_db_type(DB) when is_tuple(DB) -> - element(1, DB); -get_db_type(DB) when is_list(DB) -> - odbc. - -%% @doc Return true if odbc option is used -check_odbc_option(odbc) -> - true; -check_odbc_option(AuthMethods) when is_list(AuthMethods) -> - lists:member(odbc, AuthMethods); -check_odbc_option(_) -> - false. diff --git a/src/ejabberd_rdbms.erl b/src/ejabberd_rdbms.erl index b35786cde..a7324e4ca 100644 --- a/src/ejabberd_rdbms.erl +++ b/src/ejabberd_rdbms.erl @@ -34,7 +34,8 @@ -include("logger.hrl"). start() -> - case lists:any(fun needs_odbc/1, ?MYHOSTS) of + case lists:any(fun(H) -> needs_odbc(H) /= false end, + ?MYHOSTS) of true -> start_hosts(); false -> @@ -45,14 +46,15 @@ start() -> start_hosts() -> lists:foreach(fun (Host) -> case needs_odbc(Host) of - true -> start_odbc(Host); + {true, App} -> start_odbc(Host, App); false -> ok end end, ?MYHOSTS). %% Start the ODBC module on the given host -start_odbc(Host) -> +start_odbc(Host, App) -> + ejabberd:start_app(App), Supervisor_name = gen_mod:get_module_proc(Host, ejabberd_odbc_sup), ChildSpec = {Supervisor_name, @@ -64,11 +66,21 @@ start_odbc(Host) -> ?ERROR_MSG("Start of supervisor ~p failed:~n~p~nRetrying." "..~n", [Supervisor_name, _Error]), - start_odbc(Host) + start_odbc(Host, App) end. -%% Returns true if we have configured odbc_server for the given host +%% Returns {true, App} if we have configured odbc_server for the given host needs_odbc(Host) -> LHost = jlib:nameprep(Host), - ejabberd_config:get_local_option( - {odbc_server, LHost}, fun(_) -> true end, false). + case ejabberd_config:get_local_option( + {odbc_server, LHost}, fun(Res) -> Res end) of + {mysql, _, _, _, _} -> {true, p1_mysql}; + {pgsql, _, _, _, _} -> {true, p1_pgsql}; + {mysql, _, _, _, _, _} -> {true, p1_mysql}; + {pgsql, _, _, _, _, _} -> {true, p1_pgsql}; + S -> + case catch iolist_to_binary(S) of + {'EXIT', _} -> false; + _ -> true + end + end.