• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1258

12 Dec 2025 03:57PM UTC coverage: 33.638% (-0.006%) from 33.644%
1258

push

github

badlop
Container: Apply commit a22c88a

ejabberdctl.template: Show meaningful error when ERL_DIST_PORT is in use

15554 of 46240 relevant lines covered (33.64%)

1078.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.67
/src/gen_mod.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : gen_mod.erl
3
%%% Author  : Alexey Shchepin <alexey@process-one.net>
4
%%% Purpose :
5
%%% Created : 24 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
6
%%%
7
%%%
8
%%% ejabberd, Copyright (C) 2002-2025   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 along
21
%%% with this program; if not, write to the Free Software Foundation, Inc.,
22
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
%%%
24
%%%----------------------------------------------------------------------
25
-module(gen_mod).
26
-behaviour(supervisor).
27
-author('alexey@process-one.net').
28

29
-export([init/1, start_link/0, start_child/3, start_child/4,
30
         stop_child/1, stop_child/2, prep_stop/0, stop/0, config_reloaded/0]).
31
-export([start_module/2, stop_module/2, stop_module_keep_config/2,
32
         get_opt/2, set_opt/3, get_opt_hosts/1, is_equal_opt/3,
33
         get_module_opt/3, get_module_opts/2, get_module_opt_hosts/2,
34
         loaded_modules/1, loaded_modules_with_opts/1,
35
         get_hosts/2, get_module_proc/2, is_loaded/2, is_loaded_elsewhere/2,
36
         start_modules/0, start_modules/1, stop_modules/0, stop_modules/1,
37
         db_mod/2, ram_db_mod/2]).
38
-export([validate/2]).
39

40
%% Deprecated functions
41
%% update_module/3 is used by test suite ONLY
42
-export([update_module/3]).
43
-deprecated([{update_module, 3}]).
44

45
-include("logger.hrl").
46
-include_lib("stdlib/include/ms_transform.hrl").
47

48
-include("ejabberd_commands.hrl").
49

50
-record(ejabberd_module,
51
        {module_host = {undefined, <<"">>} :: {atom(), binary()},
52
         opts = [] :: opts() | '_' | '$2',
53
         registrations = [] :: [registration()],
54
         order = 0 :: integer()}).
55

56
-type opts() :: #{atom() => term()}.
57
-type db_type() :: atom().
58
-type opt_desc() :: #{desc => binary() | [binary()],
59
                      value => string() | binary()}.
60
-type opt_doc() :: {atom(), opt_desc()} | {atom(), opt_desc(), [opt_doc()]}.
61

62
-type component() :: ejabberd_sm | ejabberd_local.
63
-type registration() ::
64
        {hook, atom(), atom(), integer()} |
65
        {hook, atom(), atom(), integer(), binary() | global} |
66
        {hook, atom(), module(), atom(), integer()} |
67
        {hook_subscribe, atom(), atom(), [any()]} |
68
        {hook_subscribe, atom(), atom(), [any()], binary() | global} |
69
        {hook_subscribe, atom(), module(), atom(), [any()]} |
70
        {hook_subscribe, atom(), module(), atom(), [any()], binary() | global} |
71
        {commands, [ejabberd_commands()]} |
72
        {iq_handler, component(), binary(), atom()} |
73
        {iq_handler, component(), binary(), module(), atom()}.
74
-export_type([registration/0]).
75

76
-callback start(binary(), opts()) ->
77
    ok | {ok, pid()} |
78
    {ok, [registration()]} | {error, term()}.
79
-callback prep_stop(binary()) -> any().
80
-callback stop(binary()) -> any().
81
-callback reload(binary(), opts(), opts()) -> ok | {ok, pid()} | {error, term()}.
82
-callback mod_opt_type(atom()) -> econf:validator().
83
-callback mod_options(binary()) -> [{atom(), term()} | atom()].
84
-callback mod_doc() -> #{desc => binary() | [binary()],
85
                         note => string(),
86
                         opts => [opt_doc()],
87
                         example => [string()] | [{binary(), [string()]}]}.
88
-callback depends(binary(), opts()) -> [{module(), hard | soft}].
89

90
-optional_callbacks([mod_opt_type/1, reload/3, prep_stop/1]).
91

92
-export_type([opts/0]).
93
-export_type([db_type/0]).
94

95
-ifndef(GEN_SERVER).
96
-define(GEN_SERVER, gen_server).
97
-endif.
98

99
start_link() ->
100
    case supervisor:start_link({local, ejabberd_gen_mod_sup}, ?MODULE, []) of
11✔
101
        {ok, Pid} ->
102
            start_modules(),
11✔
103
            {ok, Pid};
11✔
104
        Err ->
105
            Err
×
106
    end.
107

108
init([]) ->
109
    ejabberd_hooks:add(config_reloaded, ?MODULE, config_reloaded, 60),
11✔
110
    ejabberd_hooks:add(host_up, ?MODULE, start_modules, 40),
11✔
111
    ejabberd_hooks:add(host_down, ?MODULE, stop_modules, 70),
11✔
112
    ets:new(ejabberd_modules,
11✔
113
            [named_table, public,
114
             {keypos, #ejabberd_module.module_host},
115
             {read_concurrency, true}]),
116
    {ok, {{one_for_one, 10, 1}, []}}.
11✔
117

118
-spec prep_stop() -> ok.
119
prep_stop() ->
120
    prep_stop_modules().
11✔
121

122
-spec stop() -> ok.
123
stop() ->
124
    ejabberd_hooks:delete(config_reloaded, ?MODULE, config_reloaded, 60),
11✔
125
    ejabberd_hooks:delete(host_up, ?MODULE, start_modules, 40),
11✔
126
    ejabberd_hooks:delete(host_down, ?MODULE, stop_modules, 70),
11✔
127
    stop_modules(),
11✔
128
    ejabberd_sup:stop_child(ejabberd_gen_mod_sup).
11✔
129

130
-spec start_child(module(), binary(), opts()) -> {ok, pid()} | {error, any()}.
131
start_child(Mod, Host, Opts) ->
132
    start_child(Mod, Host, Opts, get_module_proc(Host, Mod)).
233✔
133

134
-spec start_child(module(), binary(), opts(), atom()) -> {ok, pid()} | {error, any()}.
135
start_child(Mod, Host, Opts, Proc) ->
136
    Spec = {Proc, {?GEN_SERVER, start_link,
413✔
137
                   [{local, Proc}, Mod, [Host, Opts],
138
                    ejabberd_config:fsm_limit_opts([])]},
139
            transient, timer:minutes(1), worker, [Mod]},
140
    supervisor:start_child(ejabberd_gen_mod_sup, Spec).
413✔
141

142
-spec stop_child(module(), binary()) -> ok | {error, any()}.
143
stop_child(Mod, Host) ->
144
    stop_child(get_module_proc(Host, Mod)).
233✔
145

146
-spec stop_child(atom()) -> ok | {error, any()}.
147
stop_child(Proc) ->
148
    supervisor:terminate_child(ejabberd_gen_mod_sup, Proc),
413✔
149
    supervisor:delete_child(ejabberd_gen_mod_sup, Proc).
413✔
150

151
-spec start_modules() -> any().
152
start_modules() ->
153
    Hosts = ejabberd_option:hosts(),
11✔
154
    ?INFO_MSG("Loading modules for ~ts", [misc:format_hosts_list(Hosts)]),
11✔
155
    lists:foreach(fun start_modules/1, Hosts).
11✔
156

157
-spec start_modules(binary()) -> ok.
158
start_modules(Host) ->
159
    Modules = ejabberd_option:modules(Host),
110✔
160
    lists:foreach(
110✔
161
        fun({Module, Opts, Order}) ->
162
            start_module(Host, Module, Opts, Order)
2,539✔
163
        end, Modules).
164

165
-spec start_module(binary(), atom()) -> ok | {ok, pid()} | {error, not_found_in_config}.
166
start_module(Host, Module) ->
167
    Modules = ejabberd_option:modules(Host),
×
168
    case lists:keyfind(Module, 1, Modules) of
×
169
        {_, Opts, Order} ->
170
            start_module(Host, Module, Opts, Order);
×
171
        false ->
172
            {error, not_found_in_config}
×
173
    end.
174

175
-spec start_module(binary(), atom(), opts(), integer()) -> ok | {ok, pid()}.
176
start_module(Host, Module, Opts, Order) ->
177
    ?DEBUG("Loading ~ts at ~ts", [Module, Host]),
2,550✔
178
    store_options(Host, Module, Opts, Order),
2,550✔
179
    try case Module:start(Host, Opts) of
2,550✔
180
            ok -> ok;
330✔
181
            {ok, Pid} when is_pid(Pid) -> {ok, Pid};
512✔
182
            {ok, Registrations} when is_list(Registrations) ->
183
                store_options(Host, Module, Opts, Registrations, Order),
1,708✔
184
                add_registrations(Host, Module, Registrations),
1,708✔
185
                ok;
1,708✔
186
            Err ->
187
                ets:delete(ejabberd_modules, {Module, Host}),
×
188
                erlang:error({bad_return, Module, Err})
×
189
        end
190
    catch
191
        Class:Reason:StackTrace ->
192
            ets:delete(ejabberd_modules, {Module, Host}),
×
193
            ErrorText = format_module_error(
×
194
                          Module,
195
                          start,
196
                          2,
197
                          Opts,
198
                          Class,
199
                          Reason,
200
                          StackTrace),
201
            ?CRITICAL_MSG(ErrorText, []),
×
202
            maybe_halt_ejabberd(),
×
203
            erlang:raise(Class, Reason, StackTrace)
×
204
    end.
205

206
-spec reload_modules(binary()) -> ok.
207
reload_modules(Host) ->
208
    NewMods = ejabberd_option:modules(Host),
20✔
209
    OldMods = lists:reverse(loaded_modules_with_opts(Host)),
20✔
210
    lists:foreach(
20✔
211
      fun({Mod, _Opts}) ->
212
              case lists:keymember(Mod, 1, NewMods) of
454✔
213
                  false ->
214
                      stop_module(Host, Mod);
×
215
                  true ->
216
                      ok
454✔
217
              end
218
      end, OldMods),
219
    lists:foreach(
20✔
220
      fun({Mod, Opts, Order}) ->
221
              case lists:keymember(Mod, 1, OldMods) of
465✔
222
                  false ->
223
                      start_module(Host, Mod, Opts, Order);
11✔
224
                  true ->
225
                      ok
454✔
226
              end
227
      end, NewMods),
228
    lists:foreach(
20✔
229
      fun({Mod, OldOpts}) ->
230
              case lists:keyfind(Mod, 1, NewMods) of
454✔
231
                  {_, NewOpts, Order} ->
232
                      if OldOpts /= NewOpts ->
454✔
233
                              reload_module(Host, Mod, NewOpts, OldOpts, Order);
×
234
                         true ->
235
                              ok
454✔
236
                      end;
237
                  _ ->
238
                      ok
×
239
              end
240
      end, OldMods).
241

242
-spec reload_module(binary(), module(), opts(), opts(), integer()) -> ok | {ok, pid()}.
243
reload_module(Host, Module, NewOpts, OldOpts, Order) ->
244
    case erlang:function_exported(Module, reload, 3) of
80✔
245
        true ->
246
            ?DEBUG("Reloading ~ts at ~ts", [Module, Host]),
80✔
247
            store_options(Host, Module, NewOpts, Order),
80✔
248
            try case Module:reload(Host, NewOpts, OldOpts) of
80✔
249
                    ok -> ok;
80✔
250
                    {ok, Pid} when is_pid(Pid) -> {ok, Pid};
×
251
                    Err -> erlang:error({bad_return, Module, Err})
×
252
                end
253
            catch
254
                Class:Reason:StackTrace ->
255
                    ErrorText = format_module_error(
×
256
                                  Module,
257
                                  reload,
258
                                  3,
259
                                  NewOpts,
260
                                  Class,
261
                                  Reason,
262
                                  StackTrace),
263
                    ?CRITICAL_MSG(ErrorText, []),
×
264
                    erlang:raise(Class, Reason, StackTrace)
×
265
            end;
266
        false ->
267
            ?WARNING_MSG("Module ~ts doesn't support reloading "
×
268
                         "and will be restarted", [Module]),
×
269
            stop_module(Host, Module),
×
270
            start_module(Host, Module, NewOpts, Order)
×
271
    end.
272

273
-spec update_module(binary(), module(), opts()) -> ok | {ok, pid()}.
274
update_module(Host, Module, Opts) ->
275
    case ets:lookup(ejabberd_modules, {Module, Host}) of
80✔
276
        [#ejabberd_module{opts = OldOpts, order = Order}] ->
277
            NewOpts = maps:merge(OldOpts, Opts),
80✔
278
            reload_module(Host, Module, NewOpts, OldOpts, Order);
80✔
279
        [] ->
280
            erlang:error({module_not_loaded, Module, Host})
×
281
    end.
282

283
-spec store_options(binary(), module(), opts(), integer()) -> true.
284
store_options(Host, Module, Opts, Order) ->
285
    case ets:lookup(ejabberd_modules, {Module, Host}) of
2,630✔
286
        [M] ->
287
            store_options(
80✔
288
              Host, Module, Opts, M#ejabberd_module.registrations, Order);
289
        [] ->
290
            store_options(Host, Module, Opts, [], Order)
2,550✔
291
    end.
292

293
-spec store_options(binary(), module(), opts(), [registration()], integer()) -> true.
294
store_options(Host, Module, Opts, Registrations, Order) ->
295
    ets:insert(ejabberd_modules,
4,338✔
296
               #ejabberd_module{module_host = {Module, Host},
297
                                opts = Opts,
298
                                registrations = Registrations,
299
                                order = Order}).
300

301
maybe_halt_ejabberd() ->
302
    case is_app_running(ejabberd) of
×
303
        false ->
304
            ?CRITICAL_MSG("ejabberd initialization was aborted "
×
305
                          "because a module start failed.",
306
                          []),
×
307
            ejabberd:halt();
×
308
        true ->
309
            ok
×
310
    end.
311

312
is_app_running(AppName) ->
313
    Timeout = 15000,
×
314
    lists:keymember(AppName, 1,
×
315
                    application:which_applications(Timeout)).
316

317
-spec prep_stop_modules() -> ok.
318
prep_stop_modules() ->
319
    lists:foreach(
11✔
320
      fun(Host) ->
321
              prep_stop_modules(Host)
110✔
322
      end, ejabberd_option:hosts()).
323

324
-spec prep_stop_modules(binary()) -> ok.
325
prep_stop_modules(Host) ->
326
    Modules = lists:reverse(loaded_modules_with_opts(Host)),
110✔
327
    lists:foreach(
110✔
328
        fun({Module, _Args}) ->
329
                prep_stop_module_keep_config(Host, Module)
2,535✔
330
        end, Modules).
331

332
-spec stop_modules() -> ok.
333
stop_modules() ->
334
    lists:foreach(
11✔
335
      fun(Host) ->
336
              stop_modules(Host)
110✔
337
      end, ejabberd_option:hosts()).
338

339
-spec stop_modules(binary()) -> ok.
340
stop_modules(Host) ->
341
    Modules = lists:reverse(loaded_modules_with_opts(Host)),
110✔
342
    lists:foreach(
110✔
343
        fun({Module, _Args}) ->
344
                stop_module_keep_config(Host, Module)
2,535✔
345
        end, Modules).
346

347
-spec stop_module(binary(), atom()) -> error | ok.
348
stop_module(Host, Module) ->
349
    stop_module_keep_config(Host, Module).
16✔
350

351
-spec prep_stop_module_keep_config(binary(), atom()) -> error | ok.
352
prep_stop_module_keep_config(Host, Module) ->
353
    ?DEBUG("Preparing to stop ~ts at ~ts", [Module, Host]),
2,535✔
354
    try Module:prep_stop(Host) of
2,535✔
355
        _ ->
356
            ok
2✔
357
    catch
358
        error:undef:_St ->
359
            ok;
2,533✔
360
        Class:Reason:StackTrace ->
361
            ?ERROR_MSG("Failed to prepare stop module ~ts at ~ts:~n** ~ts",
×
362
                       [Module,
363
                        Host,
364
                        misc:format_exception(2, Class, Reason, StackTrace)]),
×
365
            error
×
366
    end.
367

368
-spec stop_module_keep_config(binary(), atom()) -> error | ok.
369
stop_module_keep_config(Host, Module) ->
370
    ?DEBUG("Stopping ~ts at ~ts", [Module, Host]),
2,551✔
371
    Registrations =
2,551✔
372
        case ets:lookup(ejabberd_modules, {Module, Host}) of
373
            [M] ->
374
                M#ejabberd_module.registrations;
2,550✔
375
            [] ->
376
                []
1✔
377
        end,
378
    del_registrations(Host, Module, Registrations),
2,551✔
379
    try Module:stop(Host) of
2,551✔
380
        _ ->
381
            ets:delete(ejabberd_modules, {Module, Host}),
2,551✔
382
            ok
2,551✔
383
    catch
384
        Class:Reason:StackTrace ->
385
            ?ERROR_MSG("Failed to stop module ~ts at ~ts:~n** ~ts",
×
386
                       [Module,
387
                        Host,
388
                        misc:format_exception(2, Class, Reason, StackTrace)]),
×
389
            error
×
390
    end.
391

392
-spec add_registrations(binary(), module(), [registration()]) -> ok.
393
add_registrations(Host, Module, Registrations) ->
394
    lists:foreach(
1,708✔
395
      fun({hook, Hook, Function, Seq}) ->
396
              ejabberd_hooks:add(Hook, Host, Module, Function, Seq);
9,045✔
397
         ({hook, Hook, Function, Seq, Host1}) when is_integer(Seq) ->
398
              ejabberd_hooks:add(Hook, Host1, Module, Function, Seq);
638✔
399
         ({hook, Hook, Module1, Function, Seq}) when is_integer(Seq) ->
400
              ejabberd_hooks:add(Hook, Host, Module1, Function, Seq);
×
401
         ({hook_subscribe, Hook, Function, InitArg}) ->
402
              ejabberd_hooks:subscribe(Hook, Host, Module, Function, InitArg);
9✔
403
         ({hook_subscribe, Hook, Function, InitArg, Host1}) when is_binary(Host1) or (Host1 == global) ->
404
              ejabberd_hooks:subscribe(Hook, Host1, Module, Function, InitArg);
×
405
         ({hook_subscribe, Hook, Module1, Function, InitArg}) ->
406
              ejabberd_hooks:subscribe(Hook, Host, Module1, Function, InitArg);
×
407
         ({hook_subscribe, Hook, Module1, Function, InitArg, Host1}) ->
408
              ejabberd_hooks:subscribe(Hook, Host1, Module1, Function, InitArg);
×
409
         ({commands, Commands}) ->
410
              ejabberd_commands:register_commands(Host, Module, Commands);
304✔
411
         ({iq_handler, Component, NS, Function}) ->
412
              gen_iq_handler:add_iq_handler(
1,423✔
413
                Component, Host, NS, Module, Function);
414
         ({iq_handler, Component, NS, Module1, Function}) ->
415
              gen_iq_handler:add_iq_handler(
×
416
                Component, Host, NS, Module1, Function)
417
      end, Registrations).
418

419
-spec del_registrations(binary(), module(), [registration()]) -> ok.
420
del_registrations(Host, Module, Registrations) ->
421
    lists:foreach(
2,551✔
422
      fun({hook, Hook, Function, Seq}) ->
423
              ejabberd_hooks:delete(Hook, Host, Module, Function, Seq);
9,045✔
424
         ({hook, Hook, Function, Seq, Host1}) when is_integer(Seq) ->
425
              ejabberd_hooks:delete(Hook, Host1, Module, Function, Seq);
638✔
426
         ({hook, Hook, Module1, Function, Seq}) when is_integer(Seq) ->
427
              ejabberd_hooks:delete(Hook, Host, Module1, Function, Seq);
×
428
         ({hook_subscribe, Hook, Function, InitArg}) ->
429
              ejabberd_hooks:unsubscribe(Hook, Host, Module, Function, InitArg);
9✔
430
         ({hook_subscribe, Hook, Function, InitArg, Host1}) when is_binary(Host1) or (Host1 == global) ->
431
              ejabberd_hooks:unsubscribe(Hook, Host1, Module, Function, InitArg);
×
432
         ({hook_subscribe, Hook, Module1, Function, InitArg}) ->
433
              ejabberd_hooks:unsubscribe(Hook, Host, Module1, Function, InitArg);
×
434
         ({hook_subscribe, Hook, Module1, Function, InitArg, Host1}) ->
435
              ejabberd_hooks:unsubscribe(Hook, Host1, Module1, Function, InitArg);
×
436
         ({commands, Commands}) ->
437
              ejabberd_commands:unregister_commands(Host, Module, Commands);
304✔
438
         ({iq_handler, Component, NS, _Function}) ->
439
              gen_iq_handler:remove_iq_handler(Component, Host, NS);
1,423✔
440
         ({iq_handler, Component, NS, _Module, _Function}) ->
441
              gen_iq_handler:remove_iq_handler(Component, Host, NS)
×
442
      end, Registrations).
443

444
-spec get_opt(atom(), opts()) -> any().
445
get_opt(Opt, Opts) ->
446
    maps:get(Opt, Opts).
502,904✔
447

448
-spec set_opt(atom(), term(), opts()) -> opts().
449
set_opt(Opt, Val, Opts) ->
450
    maps:put(Opt, Val, Opts).
420✔
451

452
-spec get_module_opt(global | binary(), atom(), atom()) -> any().
453
get_module_opt(global, Module, Opt) ->
454
    get_module_opt(ejabberd_config:get_myname(), Module, Opt);
1,988✔
455
get_module_opt(Host, Module, Opt) ->
456
    Opts = get_module_opts(Host, Module),
491,162✔
457
    get_opt(Opt, Opts).
491,133✔
458

459
-spec get_module_opt_hosts(binary(), module()) -> [binary()].
460
get_module_opt_hosts(Host, Module) ->
461
    Opts = get_module_opts(Host, Module),
2,231✔
462
    get_opt_hosts(Opts).
2,231✔
463

464
-spec get_opt_hosts(opts()) -> [binary()].
465
get_opt_hosts(Opts) ->
466
    case get_opt(hosts, Opts) of
2,995✔
467
        L when L == [] orelse L == undefined ->
468
            [get_opt(host, Opts)];
2,995✔
469
        L ->
470
            L
×
471
    end.
472

473
-spec get_module_opts(binary(), module()) -> opts().
474
get_module_opts(Host, Module) ->
475
    try ets:lookup_element(ejabberd_modules, {Module, Host}, 3)
494,277✔
476
    catch _:badarg -> erlang:error({module_not_loaded, Module, Host})
29✔
477
    end.
478

479
-spec db_mod(binary() | global | db_type() | opts(), module()) -> module().
480
db_mod(T, M) ->
481
    db_mod(db_type, T, M).
173,034✔
482

483
-spec ram_db_mod(binary() | global | db_type() | opts(), module()) -> module().
484
ram_db_mod(T, M) ->
485
    db_mod(ram_db_type, T, M).
8,212✔
486

487
-spec db_mod(db_type | ram_db_type,
488
             binary() | global | db_type() | opts(), module()) -> module().
489
db_mod(Opt, Host, Module) when is_binary(Host) orelse Host == global ->
490
    db_mod(Opt, get_module_opt(Host, Module, Opt), Module);
180,053✔
491
db_mod(Opt, Opts, Module) when is_map(Opts) ->
492
    db_mod(Opt, get_opt(Opt, Opts), Module);
1,193✔
493
db_mod(_Opt, Type, Module) when is_atom(Type) ->
494
    list_to_existing_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(Type)).
181,246✔
495

496
-spec loaded_modules(binary()) -> [atom()].
497
loaded_modules(Host) ->
498
    Mods = ets:select(
×
499
             ejabberd_modules,
500
             ets:fun2ms(
501
               fun(#ejabberd_module{module_host = {Mod, H},
502
                                    order = Order}) when H == Host ->
503
                       {Mod, Order}
504
               end)),
505
    [Mod || {Mod, _} <- lists:keysort(2, Mods)].
×
506

507
-spec loaded_modules_with_opts(binary()) -> [{atom(), opts()}].
508
loaded_modules_with_opts(Host) ->
509
    Mods = ets:select(
240✔
510
             ejabberd_modules,
511
             ets:fun2ms(
512
               fun(#ejabberd_module{module_host = {Mod, H}, opts = Opts,
513
                                    order = Order}) when H == Host ->
514
                       {Mod, Opts, Order}
515
               end)),
516
    [{Mod, Opts} || {Mod, Opts, _} <- lists:keysort(3, Mods)].
240✔
517

518
-spec get_hosts(opts(), binary()) -> [binary()].
519
get_hosts(Opts, Prefix) ->
520
    case get_opt(hosts, Opts) of
×
521
        undefined ->
522
            case get_opt(host, Opts) of
×
523
                undefined ->
524
                    [<<Prefix/binary, Host/binary>> || Host <- ejabberd_option:hosts()];
×
525
                Host ->
526
                    [Host]
×
527
            end;
528
        Hosts ->
529
            Hosts
×
530
    end.
531

532
-spec get_module_proc(binary() | global, atom()) -> atom().
533
get_module_proc(global, Base) ->
534
    get_module_proc(<<"global">>, Base);
×
535
get_module_proc(Host, Base) ->
536
    binary_to_atom(
68,135✔
537
      <<(erlang:atom_to_binary(Base, latin1))/binary, "_", Host/binary>>,
538
      latin1).
539

540
-spec is_loaded(binary(), atom()) -> boolean().
541
is_loaded(Host, Module) ->
542
    ets:member(ejabberd_modules, {Module, Host}).
23,946✔
543

544
-spec is_loaded_elsewhere(binary(), atom()) -> boolean().
545
is_loaded_elsewhere(Host, Module) ->
546
    ets:select_count(
547
      ejabberd_modules,
548
      ets:fun2ms(
549
        fun(#ejabberd_module{module_host = {Mod, H}}) ->
550
                (Mod == Module) and (H /= Host)
551
        end)) /= 0.
1,233✔
552

553
-spec config_reloaded() -> ok.
554
config_reloaded() ->
555
    lists:foreach(fun reload_modules/1, ejabberd_option:hosts()).
2✔
556

557
-spec is_equal_opt(atom(), opts(), opts()) -> true | {false, any(), any()}.
558
is_equal_opt(Opt, NewOpts, OldOpts) ->
559
    NewVal = get_opt(Opt, NewOpts),
×
560
    OldVal = get_opt(Opt, OldOpts),
×
561
    if NewVal /= OldVal ->
×
562
            {false, NewVal, OldVal};
×
563
       true ->
564
            true
×
565
    end.
566

567
%%%===================================================================
568
%%% Formatters
569
%%%===================================================================
570
-spec format_module_error(atom(), start | reload, non_neg_integer(), opts(),
571
                          error | exit | throw, any(),
572
                          [tuple()]) -> iolist().
573
format_module_error(Module, Fun, Arity, Opts, Class, Reason, St) ->
574
    case {Class, Reason} of
×
575
        {error, {bad_return, Module, {error, _} = Err}} ->
576
            io_lib:format("Failed to ~ts module ~ts: ~ts",
×
577
                          [Fun, Module, misc:format_val(Err)]);
578
        {error, {bad_return, Module, Ret}} ->
579
            io_lib:format("Module ~ts returned unexpected value from ~ts/~B:~n"
×
580
                          "** Error: ~p~n"
581
                          "** Hint: this is either not an ejabberd module "
582
                          "or it implements ejabberd API incorrectly",
583
                          [Module, Fun, Arity, Ret]);
584
        _ ->
585
            io_lib:format("Internal error of module ~ts has "
×
586
                          "occurred during ~ts:~n"
587
                          "** Options: ~p~n"
588
                          "** ~ts",
589
                          [Module, Fun, Opts,
590
                           misc:format_exception(2, Class, Reason, St)])
591
    end.
592

593
%%%===================================================================
594
%%% Validation
595
%%%===================================================================
596
-spec validator(binary()) -> econf:validator().
597
validator(Host) ->
598
    econf:options(
130✔
599
      #{modules =>
600
            econf:and_then(
601
              econf:map(
602
                econf:beam([{start, 2}, {stop, 1},
603
                            {mod_options, 1}, {depends, 2}]),
604
                econf:options(
605
                  #{db_type => econf:atom(),
606
                    ram_db_type => econf:atom(),
607
                    '_' => econf:any()})),
608
              fun(L) ->
609
                      Validators = maps:from_list(
130✔
610
                                     lists:map(
611
                                       fun({Mod, Opts}) ->
612
                                               {Mod, validator(Host, Mod, Opts)}
3,004✔
613
                                       end, L)),
614
                      Validator = econf:options(Validators, [unique]),
130✔
615
                      Validator(L)
130✔
616
              end)}).
617

618
-spec validator(binary(), module(), [{atom(), term()}]) -> econf:validator().
619
validator(Host, Module, Opts) ->
620
    {Required, {DefaultOpts1, Validators}} =
3,004✔
621
        lists:mapfoldl(
622
          fun({M, DefOpts}, {DAcc, VAcc}) ->
623
                  lists:mapfoldl(
3,007✔
624
                    fun({Opt, Def}, {DAcc1, VAcc1}) ->
625
                            {[], {DAcc1#{Opt => Def},
16,075✔
626
                                  VAcc1#{Opt => get_opt_type(Host, Module, M, Opt)}}};
627
                       (Opt, {DAcc1, VAcc1}) ->
628
                            {[Opt], {DAcc1,
×
629
                                     VAcc1#{Opt => get_opt_type(Host, Module, M, Opt)}}}
630
                    end, {DAcc, VAcc}, DefOpts)
631
          end, {#{}, #{}}, get_defaults(Host, Module, Opts)),
632
    econf:and_then(
3,004✔
633
      econf:options(
634
        Validators,
635
        [{required, lists:usort(lists:flatten(Required))},
636
         {return, map}, unique]),
637
      fun(Opts1) ->
638
              maps:merge(DefaultOpts1, Opts1)
3,004✔
639
      end).
640

641
-spec validate(binary(), [{module(), opts()}]) ->
642
                      {ok, [{module(), opts(), integer()}]} |
643
                      econf:error_return().
644
validate(Host, ModOpts) ->
645
    case econf:validate(validator(Host), [{modules, ModOpts}]) of
130✔
646
        {ok, [{modules, ModOpts1}]} ->
647
            try sort_modules(Host, ModOpts1)
130✔
648
            catch throw:{?MODULE, Reason} ->
649
                    {error, Reason, [modules]}
×
650
            end;
651
        {error, _, _} = Err ->
652
            Err
×
653
    end.
654

655
-spec get_defaults(binary(), module(), [{atom(), term()}]) ->
656
                          [{module(), [{atom(), term()} | atom()]}].
657
get_defaults(Host, Module, Opts) ->
658
    DefaultOpts = Module:mod_options(Host),
3,004✔
659
    [{Module, DefaultOpts}|
3,004✔
660
     lists:filtermap(
661
       fun({Opt, T1}) when Opt == db_type; Opt == ram_db_type ->
662
               T2 = proplists:get_value(Opt, Opts, T1),
822✔
663
               DBMod = list_to_atom(atom_to_list(Module) ++ "_" ++ atom_to_list(T2)),
822✔
664
               case code:ensure_loaded(DBMod) of
822✔
665
                   {module, _} ->
666
                       case erlang:function_exported(DBMod, mod_options, 1) of
822✔
667
                           true ->
668
                               {true, {DBMod, DBMod:mod_options(Host)}};
3✔
669
                           false ->
670
                               false
819✔
671
                       end;
672
                   _ ->
673
                       false
×
674
               end;
675
          (_) ->
676
               false
15,234✔
677
       end, DefaultOpts)].
678

679
-spec get_opt_type(binary(), module(), module(), atom()) -> econf:validator().
680
get_opt_type(Host, Mod, SubMod, Opt) ->
681
    Type = try SubMod:mod_opt_type(Opt)
16,075✔
682
    catch _:_ -> Mod:mod_opt_type(Opt)
×
683
    end,
684
    econf:and_then(
16,075✔
685
      fun(B) ->
686
              ejabberd_config:replace_keywords(Host, B)
1,753✔
687
      end,
688
      Type).
689

690
-spec sort_modules(binary(), [{module(), opts()}]) -> {ok, [{module(), opts(), integer()}]}.
691
sort_modules(Host, ModOpts) ->
692
    G = digraph:new([acyclic]),
130✔
693
    lists:foreach(
130✔
694
      fun({Mod, Opts}) ->
695
              digraph:add_vertex(G, Mod, Opts),
3,004✔
696
              Deps = Mod:depends(Host, Opts),
3,004✔
697
              lists:foreach(
3,004✔
698
                fun({DepMod, Type}) ->
699
                        case lists:keyfind(DepMod, 1, ModOpts) of
982✔
700
                            false when Type == hard ->
701
                                throw({?MODULE, {missing_module_dep, Mod, DepMod}});
×
702
                            false when Type == soft ->
703
                                warn_soft_dep_fail(DepMod, Mod);
7✔
704
                            {DepMod, DepOpts} ->
705
                                digraph:add_vertex(G, DepMod, DepOpts),
975✔
706
                                case digraph:add_edge(G, DepMod, Mod) of
975✔
707
                                    {error, {bad_edge, Path}} ->
708
                                        warn_cyclic_dep(Path);
×
709
                                    _ ->
710
                                        ok
975✔
711
                                end
712
                        end
713
                end, Deps)
714
      end, ModOpts),
715
    {Result, _} = lists:mapfoldl(
130✔
716
                    fun(V, Order) ->
717
                            {M, O} = digraph:vertex(G, V),
3,004✔
718
                            {{M, O, Order}, Order+1}
3,004✔
719
                    end, 1, digraph_utils:topsort(G)),
720
    digraph:delete(G),
130✔
721
    {ok, Result}.
130✔
722

723
-spec warn_soft_dep_fail(module(), module()) -> ok.
724
warn_soft_dep_fail(DepMod, Mod) ->
725
    ?WARNING_MSG("Module ~ts is recommended for module "
7✔
726
                 "~ts but is not found in the config",
727
                 [DepMod, Mod]).
7✔
728

729
-spec warn_cyclic_dep([module()]) -> ok.
730
warn_cyclic_dep(Path) ->
731
    ?WARNING_MSG("Cyclic dependency detected between modules ~ts. "
×
732
                 "This is either a bug, or the modules are not "
733
                 "supposed to work together in this configuration. "
734
                 "The modules will still be loaded though",
735
                 [misc:format_cycle(Path)]).
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc