1 %%%----------------------------------------------------------------------
2 %%% File    : gen_mod.erl
3 %%% Author  : Alexey Shchepin <[email protected]>
4 %%% Purpose :
5 %%% Created : 24 Jan 2003 by Alexey Shchepin <[email protected]>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2012   ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 %%% 02111-1307 USA
24 %%%
25 %%%----------------------------------------------------------------------
26 
27 
28 -module(gen_mod).
29 
30 -author('[email protected]').
31 
32 -export([start/0, start_module/3, stop_module/2, stop_module_keep_config/2, get_opt/2,
33 	 get_opt/3, get_opt_host/3, get_module_opt/4, get_module_opt_host/3,
34 	 loaded_modules/1, loaded_modules_with_opts/1, get_hosts/2, get_module_proc/2,
35 	 get_module_proc_existing/2, is_loaded/2]).
36 
37 -export([behaviour_info/1]).
38 
39 -include("ejabberd.hrl").
40 
41 -record(ejabberd_module, {module_host, opts}).
42 
43 %% module_host = {Module::atom(), Host::string()}
44 
45 
46 behaviour_info(callbacks) -> [{start, 2}, {stop, 1}];
47 behaviour_info(_Other) -> undefined.
48 
49 start() ->
50     ets:new(ejabberd_modules,
51 	    [named_table, public, {keypos, #ejabberd_module.module_host}]),
52     ok.
53 
54 start_module(Host, Module, Opts) ->
55     MTokens = string:tokens(atom_to_list(Module), "_"),
56     case lists:split(length(MTokens) - 1, MTokens) of
57       {ModulePlainList, ["odbc"]} ->
58 	  Module2 = list_to_atom(string:join(ModulePlainList, "_")),
59 	  ?WARNING_MSG("The module ~p is obsolete. Replace it with ~p and add the option {backend, odbc}",
60 		       [Module, Module2]),
61 	  start_module2(Host, Module2, [{backend, odbc} | Opts]);
62       _ -> start_module2(Host, Module, Opts)
63     end.
64 
65 start_module2(Host, Module, Opts) ->
66     set_module_opts_mnesia(Host, Module, Opts),
67     ets:insert(ejabberd_modules,
68 	       #ejabberd_module{module_host = {Module, Host}, opts = Opts}),
69     try Module:start(Host, Opts) catch
70       Class:Reason ->
71 	  del_module_mnesia(Host, Module),
72 	  ets:delete(ejabberd_modules, {Module, Host}),
73 	  ErrorText =
74 	      io_lib:format("Problem starting the module ~p for host ~p ~n options: ~p~n ~p: ~p~n stacktarce: ~p",
75 			    [Module, Host, Opts, Class, Reason, erlang:get_stacktrace()]),
76 	  ?CRITICAL_MSG(ErrorText, []),
77 	  case is_app_running(ejabberd) of
78 	    true -> erlang:raise(Class, Reason, erlang:get_stacktrace());
79 	    false ->
80 		?CRITICAL_MSG("ejabberd initialization was aborted because a module start failed.",
81 			      []),
82 		timer:sleep(3000),
83 		erlang:halt(string:substr(lists:flatten(ErrorText), 1, 199))
84 	  end
85     end.
86 
87 is_app_running(AppName) ->
88         %% Use a high timeout to prevent a false positive in a high load system
89     Timeout = 15000,
90     lists:keymember(AppName, 1, application:which_applications(Timeout)).
91 
92 %% @doc Stop the module in a host, and forget its configuration.
93 stop_module(Host, Module) ->
94     case stop_module_keep_config(Host, Module) of
95       error -> error;
96       ok -> del_module_mnesia(Host, Module)
97     end.
98 
99 %% @doc Stop the module in a host, but keep its configuration.
100 %% As the module configuration is kept in the Mnesia local_config table,
101 %% when ejabberd is restarted the module will be started again.
102 %% This function is useful when ejabberd is being stopped
103 %% and it stops all modules.
104 stop_module_keep_config(Host, Module) ->
105     case catch Module:stop(Host) of
106       {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), error;
107       {wait, ProcList} when is_list(ProcList) ->
108 	  lists:foreach(fun wait_for_process/1, ProcList),
109 	  ets:delete(ejabberd_modules, {Module, Host}),
110 	  ok;
111       {wait, Process} ->
112 	  wait_for_process(Process), ets:delete(ejabberd_modules, {Module, Host}), ok;
113       _ -> ets:delete(ejabberd_modules, {Module, Host}), ok
114     end.
115 
116 wait_for_process(Process) ->
117     MonitorReference = erlang:monitor(process, Process),
118     wait_for_stop(Process, MonitorReference).
119 
120 wait_for_stop(Process, MonitorReference) ->
121     receive
122       {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok
123       after 5000 -> catch exit(whereis(Process), kill), wait_for_stop1(MonitorReference)
124     end.
125 
126 wait_for_stop1(MonitorReference) ->
127     receive {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> ok end.
128 
129 get_opt(Opt, Opts) ->
130     case lists:keysearch(Opt, 1, Opts) of
131       false ->
132 	              % TODO: replace with more appropriate function
133 	  throw({undefined_option, Opt});
134       {value, {_, Val}} -> Val
135     end.
136 
137 get_opt(Opt, Opts, Default) ->
138     case lists:keysearch(Opt, 1, Opts) of
139       false -> Default;
140       {value, {_, Val}} -> Val
141     end.
142 
143 get_module_opt(global, Module, Opt, Default) ->
144     Hosts = (?MYHOSTS),
145     [Value | Values] = lists:map(fun (Host) -> get_module_opt(Host, Module, Opt, Default)
146 				 end,
147 				 Hosts),
148     Same_all = lists:all(fun (Other_value) -> Other_value == Value end, Values),
149     case Same_all of
150       true -> Value;
151       false -> Default
152     end;
153 get_module_opt(Host, Module, Opt, Default) ->
154     OptsList = ets:lookup(ejabberd_modules, {Module, Host}),
155     case OptsList of
156       [] ->
157 	  OptsList2 = ets:lookup(ejabberd_modules, {Module, global}),
158 	  case OptsList2 of
159 	    [] -> Default;
160 	    [#ejabberd_module{opts = Opts} | _] -> get_opt(Opt, Opts, Default)
161 	  end;
162       [#ejabberd_module{opts = Opts} | _] -> get_opt(Opt, Opts, Default)
163     end.
164 
165 get_module_opt_host(Host, Module, Default) ->
166     Val = get_module_opt(Host, Module, host, Default),
167     re:replace(Val, "@HOST@", Host, [global, {return, list}]).
168 
169 get_opt_host(Host, Opts, Default) ->
170     case Host of
171       global ->
172 	  Val = get_opt(host, Opts, Default),
173 	  {global, re:replace(Val, ".@HOST@", "", [global, {return, list}])};
174       Host ->
175 	  Val = get_opt(host, Opts, Default),
176 	  re:replace(Val, "@HOST@", Host, [global, {return, list}])
177     end.
178 
179 loaded_modules(Host) ->
180     ets:select(ejabberd_modules,
181 	       [{#ejabberd_module{_ = '_', module_host = {'$1', Host}}, [], ['$1']}]).
182 
183 loaded_modules_with_opts(Host) ->
184     ets:select(ejabberd_modules,
185 	       [{#ejabberd_module{_ = '_', module_host = {'$1', Host}, opts = '$2'}, [],
186 		 [{{'$1', '$2'}}]}]).
187 
188 set_module_opts_mnesia(global, _Module, _Opts) ->
189         %% Modules on the global host are usually static, so we shouldn't manipulate them.
190     ok;
191 set_module_opts_mnesia(Host, Module, Opts) ->
192     Modules = case ejabberd_config:get_local_option({modules, Host}) of
193 		undefined -> [];
194 		Ls -> Ls
195 	      end,
196     Modules1 = lists:keydelete(Module, 1, Modules),
197     Modules2 = [{Module, Opts} | Modules1],
198     ejabberd_config:add_local_option({modules, Host}, Modules2).
199 
200 del_module_mnesia(global, _Module) ->
201         %% Modules on the global host are usually static, so we shouldn't manipulate them.
202     ok;
203 del_module_mnesia(Host, Module) ->
204     Modules = case ejabberd_config:get_local_option({modules, Host}) of
205 		undefined -> [];
206 		Ls -> Ls
207 	      end,
208     Modules1 = lists:keydelete(Module, 1, Modules),
209     ejabberd_config:add_local_option({modules, Host}, Modules1).
210 
211 get_hosts(Opts, Prefix) ->
212     case catch gen_mod:get_opt(hosts, Opts) of
213       {'EXIT', _Error1} ->
214 	  case catch gen_mod:get_opt(host, Opts) of
215 	    {'EXIT', _Error2} -> [Prefix ++ Host || Host <- ?MYHOSTS];
216 	    Host -> [Host]
217 	  end;
218       Hosts -> Hosts
219     end.
220 
221 get_module_proc_existing(Host, Base) ->
222     Proc = get_module_proc(Host, Base),
223         %% If the process doesn't exist for Host, it may exist for global
224     case {whereis(Proc), Host == global} of
225       {undefined, false} -> get_module_proc(global, Base);
226       {undefined, true} -> not_existing;
227       {_, _} -> Proc
228     end.
229 
230 get_module_proc(Host, Base) when is_binary(Host) ->
231     get_module_proc(binary_to_list(Host), Base);
232 get_module_proc(global, Base) -> list_to_atom(atom_to_list(Base) ++ "__global");
233 get_module_proc(Host, {frontend, Base}) -> get_module_proc("frontend_" ++ Host, Base);
234 get_module_proc(Host, Base) -> list_to_atom(atom_to_list(Base) ++ "_" ++ Host).
235 
236 %% @spec(Host::string() | global, Module::atom()) -> true | false
237 %% @doc Check if the module is loaded in this host (or global), or not.
238 is_loaded(Host, Module) ->
239     ets:member(ejabberd_modules, {Module, Host}) orelse
240       ets:member(ejabberd_modules, {Module, global}).