• 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

52.6
/src/mod_muc_mnesia.erl
1
%%%-------------------------------------------------------------------
2
%%% File    : mod_muc_mnesia.erl
3
%%% Author  : Evgeny Khramtsov <ekhramtsov@process-one.net>
4
%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
5
%%%
6
%%%
7
%%% ejabberd, Copyright (C) 2002-2025   ProcessOne
8
%%%
9
%%% This program is free software; you can redistribute it and/or
10
%%% modify it under the terms of the GNU General Public License as
11
%%% published by the Free Software Foundation; either version 2 of the
12
%%% License, or (at your option) any later version.
13
%%%
14
%%% This program is distributed in the hope that it will be useful,
15
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
16
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
%%% General Public License for more details.
18
%%%
19
%%% You should have received a copy of the GNU General Public License along
20
%%% with this program; if not, write to the Free Software Foundation, Inc.,
21
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
%%%
23
%%%----------------------------------------------------------------------
24

25
-module(mod_muc_mnesia).
26

27
-behaviour(mod_muc).
28
-behaviour(mod_muc_room).
29

30
%% API
31
-export([init/2, import/3, store_room/5, restore_room/3, forget_room/3,
32
         can_use_nick/4, get_rooms/2, get_nick/3, get_nicks/2, set_nick/4]).
33
-export([register_online_room/4, unregister_online_room/4, find_online_room/3,
34
         get_online_rooms/3, count_online_rooms/2, rsm_supported/0,
35
         register_online_user/4, unregister_online_user/4,
36
         count_online_rooms_by_user/3, get_online_rooms_by_user/3,
37
         find_online_room_by_pid/2]).
38
-export([set_affiliation/6, set_affiliations/4, get_affiliation/5,
39
         get_affiliations/3, search_affiliation/4]).
40
%% gen_server callbacks
41
-export([start_link/2, init/1, handle_cast/2, handle_call/3, handle_info/2,
42
         terminate/2, code_change/3]).
43
-export([need_transform/1, transform/1]).
44

45
-include("mod_muc.hrl").
46
-include("logger.hrl").
47
-include_lib("xmpp/include/xmpp.hrl").
48
-include_lib("stdlib/include/ms_transform.hrl").
49

50
-record(state, {}).
51

52
%%%===================================================================
53
%%% API
54
%%%===================================================================
55
init(Host, Opts) ->
56
    Spec = {?MODULE, {?MODULE, start_link, [Host, Opts]},
206✔
57
            transient, 5000, worker, [?MODULE]},
58
    case supervisor:start_child(ejabberd_backend_sup, Spec) of
206✔
59
        {ok, _Pid} -> ok;
11✔
60
        %% Maybe started for a vhost which only wanted mnesia for ram
61
        %% and this vhost wants mnesia for persitent storage too
62
        {error, {already_started, _Pid}} ->
63
            init([Host, Opts]);
195✔
64
        Err -> Err
×
65
    end.
66

67
start_link(Host, Opts) ->
68
    Name = gen_mod:get_module_proc(Host, ?MODULE),
11✔
69
    gen_server:start_link({local, Name}, ?MODULE, [Host, Opts], []).
11✔
70

71
store_room(_LServer, Host, Name, Opts, _) ->
72
    F = fun () ->
228✔
73
                mnesia:write(#muc_room{name_host = {Name, Host},
228✔
74
                                       opts = Opts})
75
        end,
76
    mnesia:transaction(F).
228✔
77

78
restore_room(_LServer, Host, Name) ->
79
    try mnesia:dirty_read(muc_room, {Name, Host}) of
136✔
80
        [#muc_room{opts = Opts}] -> Opts;
×
81
        _ -> error
136✔
82
    catch
83
        _:_ -> {error, db_failure}
×
84
    end.
85

86
forget_room(_LServer, Host, Name) ->
87
    F = fun () -> mnesia:delete({muc_room, {Name, Host}})
222✔
88
        end,
89
    mnesia:transaction(F).
222✔
90

91
can_use_nick(_LServer, ServiceOrRoom, JID, Nick) ->
92
    {LUser, LServer, _} = jid:tolower(JID),
264✔
93
    LUS = {LUser, LServer},
264✔
94
    MatchSpec = case (jid:decode(ServiceOrRoom))#jid.lserver of
264✔
95
        ServiceOrRoom -> [{'==', {element, 2, '$1'}, ServiceOrRoom}];
×
96
        Service -> [{'orelse',
264✔
97
                    {'==', {element, 2, '$1'}, Service},
98
                    {'==', {element, 2, '$1'}, ServiceOrRoom} }]
99
                end,
100
    case catch mnesia:dirty_select(muc_registered,
264✔
101
                                   [{#muc_registered{us_host = '$1',
102
                                                     nick = Nick, _ = '_'},
103
                                     MatchSpec,
104
                                     ['$_']}])
105
        of
106
      {'EXIT', _Reason} -> true;
×
107
      [] -> true;
264✔
108
      [#muc_registered{us_host = {U, _Host}}] -> U == LUS
×
109
    end.
110

111
get_rooms(_LServer, Host) ->
112
    mnesia:dirty_select(muc_room,
103✔
113
                        [{#muc_room{name_host = {'_', Host},
114
                                    _ = '_'},
115
                          [], ['$_']}]).
116

117
get_nick(_LServer, Host, From) ->
118
    {LUser, LServer, _} = jid:tolower(From),
42✔
119
    LUS = {LUser, LServer},
42✔
120
    case mnesia:dirty_read(muc_registered, {LUS, Host}) of
42✔
121
        [] -> error;
18✔
122
        [#muc_registered{nick = Nick}] -> Nick
24✔
123
    end.
124

125
get_nicks(_LServer, Host) ->
126
    mnesia:dirty_select(muc_registered,
×
127
                        [{#muc_registered{us_host = {{'$1', '$2'}, Host},
128
                                          nick = '$3', _ = '_'},
129
                          [],
130
                          [{{'$1', '$2', '$3'}}]
131
                         }]).
132

133
set_nick(_LServer, ServiceOrRoom, From, Nick) ->
134
    {LUser, LServer, _} = jid:tolower(From),
176✔
135
    LUS = {LUser, LServer},
176✔
136
    F = fun () ->
176✔
137
                case Nick of
176✔
138
                    <<"">> ->
139
                        mnesia:delete({muc_registered, {LUS, ServiceOrRoom}}),
164✔
140
                        ok;
164✔
141
                    _ ->
142
                        Service = (jid:decode(ServiceOrRoom))#jid.lserver,
12✔
143
                        MatchSpec = case (ServiceOrRoom == Service) of
12✔
144
                            true -> [{'==', {element, 2, '$1'}, ServiceOrRoom}];
12✔
145
                            false -> [{'orelse',
×
146
                                        {'==', {element, 2, '$1'}, Service},
147
                                        {'==', {element, 2, '$1'}, ServiceOrRoom} }]
148
                                    end,
149
                        Allow = case mnesia:select(
12✔
150
                                       muc_registered,
151
                                       [{#muc_registered{us_host = '$1', nick = Nick, _ = '_'},
152
                                         MatchSpec,
153
                                         ['$_']}]) of
154
                                    [] when (ServiceOrRoom == Service) ->
155
                                        NickRegistrations = mnesia:select(
9✔
156
                                            muc_registered,
157
                                            [{#muc_registered{us_host = '$1', nick = Nick, _ = '_'},
158
                                                [],
159
                                                ['$_']}]),
160
                                        not lists:any(fun({_, {_NRUS, NRServiceOrRoom}, _Nick}) ->
9✔
161
                                                              Service == (jid:decode(NRServiceOrRoom))#jid.lserver end,
×
162
                                                      NickRegistrations);
163
                                    [] -> true;
×
164
                                    [#muc_registered{us_host = {_U, Host}}]
165
                                      when (Host == Service) and (ServiceOrRoom /= Service) ->
166
                                        false;
×
167
                                    [#muc_registered{us_host = {U, _Host}}] ->
168
                                        U == LUS
3✔
169
                                end,
170
                        if Allow ->
12✔
171
                                mnesia:write(#muc_registered{
9✔
172
                                                us_host = {LUS, ServiceOrRoom},
173
                                                nick = Nick}),
174
                                ok;
9✔
175
                           true ->
176
                                false
3✔
177
                        end
178
                end
179
        end,
180
    mnesia:transaction(F).
176✔
181

182
set_affiliation(_ServerHost, _Room, _Host, _JID, _Affiliation, _Reason) ->
183
    {error, not_implemented}.
18✔
184

185
set_affiliations(_ServerHost, _Room, _Host, _Affiliations) ->
186
    {error, not_implemented}.
88✔
187

188
get_affiliation(_ServerHost, _Room, _Host, _LUser, _LServer) ->
189
    {error, not_implemented}.
826✔
190

191
get_affiliations(_ServerHost, _Room, _Host) ->
192
    {error, not_implemented}.
86✔
193

194
search_affiliation(_ServerHost, _Room, _Host, _Affiliation) ->
195
    {error, not_implemented}.
22✔
196

197
register_online_room(_ServerHost, Room, Host, Pid) ->
198
    F = fun() ->
136✔
199
                mnesia:write(
136✔
200
                  #muc_online_room{name_host = {Room, Host}, pid = Pid})
201
        end,
202
    mnesia:transaction(F).
136✔
203

204
unregister_online_room(_ServerHost, Room, Host, Pid) ->
205
    F = fun () ->
136✔
206
                mnesia:delete_object(
136✔
207
                  #muc_online_room{name_host = {Room, Host}, pid = Pid})
208
        end,
209
    mnesia:transaction(F).
136✔
210

211
find_online_room(_ServerHost, Room, Host) ->
212
    find_online_room(Room, Host).
1,428✔
213

214
find_online_room(Room, Host) ->
215
    case mnesia:dirty_read(muc_online_room, {Room, Host}) of
1,428✔
216
        [] -> error;
290✔
217
        [#muc_online_room{pid = Pid}] -> {ok, Pid}
1,138✔
218
    end.
219

220
find_online_room_by_pid(_ServerHost, Pid) ->
221
    Res =
136✔
222
    mnesia:dirty_select(
223
        muc_online_room,
224
        ets:fun2ms(
225
            fun(#muc_online_room{name_host = {Name, Host}, pid = PidS})
226
                   when PidS == Pid -> {Name, Host}
227
            end)),
228
    case Res of
136✔
229
        [{Name, Host}] -> {ok, Name, Host};
136✔
230
        _ -> error
×
231
    end.
232

233
count_online_rooms(_ServerHost, Host) ->
234
    ets:select_count(
12✔
235
      muc_online_room,
236
      ets:fun2ms(
237
        fun(#muc_online_room{name_host = {_, H}}) ->
238
                H == Host
239
        end)).
240

241
get_online_rooms(_ServerHost, Host,
242
                 #rsm_set{max = Max, 'after' = After, before = undefined})
243
  when is_binary(After), After /= <<"">> ->
244
    lists:reverse(get_online_rooms(next, {After, Host}, Host, 0, Max, []));
3✔
245
get_online_rooms(_ServerHost, Host,
246
                 #rsm_set{max = Max, 'after' = undefined, before = Before})
247
  when is_binary(Before), Before /= <<"">> ->
248
    get_online_rooms(prev, {Before, Host}, Host, 0, Max, []);
×
249
get_online_rooms(_ServerHost, Host,
250
                 #rsm_set{max = Max, 'after' = undefined, before = <<"">>}) ->
251
    get_online_rooms(last, {<<"">>, Host}, Host, 0, Max, []);
×
252
get_online_rooms(_ServerHost, Host, #rsm_set{max = Max}) ->
253
    lists:reverse(get_online_rooms(first, {<<"">>, Host}, Host, 0, Max, []));
×
254
get_online_rooms(_ServerHost, Host, undefined) ->
255
    mnesia:dirty_select(
28✔
256
      muc_online_room,
257
      ets:fun2ms(
258
        fun(#muc_online_room{name_host = {Name, H}, pid = Pid})
259
              when H == Host -> {Name, Host, Pid}
260
        end)).
261

262
-spec get_online_rooms(prev | next | last | first,
263
                       {binary(), binary()}, binary(),
264
                       non_neg_integer(), non_neg_integer() | undefined,
265
                       [{binary(), binary(), pid()}]) ->
266
                              [{binary(), binary(), pid()}].
267
get_online_rooms(_Action, _Key, _Host, Count, Max, Items) when Count >= Max ->
268
    Items;
×
269
get_online_rooms(Action, Key, Host, Count, Max, Items) ->
270
    Call = fun() ->
3✔
271
                   case Action of
3✔
272
                       prev -> mnesia:dirty_prev(muc_online_room, Key);
×
273
                       next -> mnesia:dirty_next(muc_online_room, Key);
3✔
274
                       last -> mnesia:dirty_last(muc_online_room);
×
275
                       first -> mnesia:dirty_first(muc_online_room)
×
276
                   end
277
           end,
278
    NewAction = case Action of
3✔
279
                    last -> prev;
×
280
                    first -> next;
×
281
                    _ -> Action
3✔
282
                end,
283
    try Call() of
3✔
284
        '$end_of_table' ->
285
            Items;
3✔
286
        {Room, Host} = NewKey ->
287
            case find_online_room(Room, Host) of
×
288
                {ok, Pid} ->
289
                    get_online_rooms(NewAction, NewKey, Host,
×
290
                                     Count + 1, Max, [{Room, Host, Pid}|Items]);
291
                error ->
292
                    get_online_rooms(NewAction, NewKey, Host,
×
293
                                     Count, Max, Items)
294
            end;
295
        NewKey ->
296
            get_online_rooms(NewAction, NewKey, Host, Count, Max, Items)
×
297
    catch _:{aborted, {badarg, _}} ->
298
            Items
×
299
    end.
300

301
rsm_supported() ->
302
    true.
18✔
303

304
register_online_user(_ServerHost, {U, S, R}, Room, Host) ->
305
    ets:insert(muc_online_users,
223✔
306
               #muc_online_users{us = {U, S}, resource = R,
307
                                 room = Room, host = Host}).
308

309
unregister_online_user(_ServerHost, {U, S, R}, Room, Host) ->
310
    ets:delete_object(muc_online_users,
223✔
311
                      #muc_online_users{us = {U, S}, resource = R,
312
                                        room = Room, host = Host}).
313

314
count_online_rooms_by_user(ServerHost, U, S) ->
315
    MucHost = hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)),
258✔
316
    ets:select_count(
258✔
317
      muc_online_users,
318
      ets:fun2ms(
319
        fun(#muc_online_users{us = {U1, S1}, host = Host}) ->
320
                U == U1 andalso S == S1 andalso MucHost == Host
321
        end)).
322

323
get_online_rooms_by_user(ServerHost, U, S) ->
324
    MucHost = hd(gen_mod:get_module_opt_hosts(ServerHost, mod_muc)),
×
325
    ets:select(
×
326
      muc_online_users,
327
      ets:fun2ms(
328
        fun(#muc_online_users{us = {U1, S1}, room = Room, host = Host})
329
              when U == U1 andalso S == S1 andalso MucHost == Host -> {Room, Host}
330
        end)).
331

332
import(_LServer, <<"muc_room">>,
333
       [Name, RoomHost, SOpts, _TimeStamp]) ->
334
    Opts = mod_muc:opts_to_binary(ejabberd_sql:decode_term(SOpts)),
×
335
    mnesia:dirty_write(
×
336
      #muc_room{name_host = {Name, RoomHost},
337
                opts = Opts});
338
import(_LServer, <<"muc_registered">>,
339
       [J, RoomHost, Nick, _TimeStamp]) ->
340
    #jid{user = U, server = S} = jid:decode(J),
×
341
    mnesia:dirty_write(
×
342
      #muc_registered{us_host = {{U, S}, RoomHost},
343
                      nick = Nick}).
344

345
%%%===================================================================
346
%%% gen_server callbacks
347
%%%===================================================================
348
init([_Host, Opts]) ->
349
    MyHosts = mod_muc_opt:hosts(Opts),
206✔
350
    case gen_mod:db_mod(Opts, mod_muc) of
206✔
351
        ?MODULE ->
352
            ejabberd_mnesia:create(?MODULE, muc_room,
206✔
353
                                   [{disc_copies, [node()]},
354
                                    {attributes,
355
                                     record_info(fields, muc_room)}]),
356
            ejabberd_mnesia:create(?MODULE, muc_registered,
206✔
357
                                   [{disc_copies, [node()]},
358
                                    {attributes,
359
                                     record_info(fields, muc_registered)},
360
                                    {index, [nick]}]);
361
        _ ->
362
            ok
×
363
    end,
364
    case gen_mod:ram_db_mod(Opts, mod_muc) of
206✔
365
        ?MODULE ->
366
            ejabberd_mnesia:create(?MODULE, muc_online_room,
206✔
367
                                   [{ram_copies, [node()]},
368
                                    {type, ordered_set},
369
                                    {attributes, record_info(fields, muc_online_room)}]),
370
            catch ets:new(muc_online_users, [bag, named_table, public, {keypos, 2}]),
206✔
371
            lists:foreach(
206✔
372
              fun(MyHost) ->
373
                      clean_table_from_bad_node(node(), MyHost)
206✔
374
              end, MyHosts),
375
            mnesia:subscribe(system);
206✔
376
        _ ->
377
            ok
×
378
    end,
379
    {ok, #state{}}.
206✔
380

381
handle_call(Request, From, State) ->
382
    ?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
×
383
    {noreply, State}.
×
384

385
handle_cast(Msg, State) ->
386
    ?WARNING_MSG("Unexpected cast: ~p", [Msg]),
×
387
    {noreply, State}.
×
388

389
handle_info({mnesia_system_event, {mnesia_down, Node}}, State) ->
390
    clean_table_from_bad_node(Node),
×
391
    {noreply, State};
×
392
handle_info({mnesia_system_event, {mnesia_up, _Node}}, State) ->
393
    {noreply, State};
×
394
handle_info(Info, State) ->
395
    ?WARNING_MSG("Unexpected info: ~p", [Info]),
×
396
    {noreply, State}.
×
397

398
terminate(_Reason, _State) ->
399
    ok.
×
400

401
code_change(_OldVsn, State, _Extra) ->
402
    {ok, State}.
×
403

404
%%%===================================================================
405
%%% Internal functions
406
%%%===================================================================
407
clean_table_from_bad_node(Node) ->
408
    F = fun() ->
×
409
                Es = mnesia:select(
×
410
                       muc_online_room,
411
                       [{#muc_online_room{pid = '$1', _ = '_'},
412
                         [{'==', {node, '$1'}, Node}],
413
                         ['$_']}]),
414
                lists:foreach(fun(E) ->
×
415
                                      mnesia:delete_object(E)
×
416
                              end, Es)
417
        end,
418
    mnesia:async_dirty(F).
×
419

420
clean_table_from_bad_node(Node, Host) ->
421
    F = fun() ->
206✔
422
                Es = mnesia:select(
206✔
423
                       muc_online_room,
424
                       [{#muc_online_room{pid = '$1',
425
                                          name_host = {'_', Host},
426
                                          _ = '_'},
427
                         [{'==', {node, '$1'}, Node}],
428
                         ['$_']}]),
429
                lists:foreach(fun(E) ->
206✔
430
                                      mnesia:delete_object(E)
×
431
                              end, Es)
432
        end,
433
    mnesia:async_dirty(F).
206✔
434

435
need_transform({muc_room, {N, H}, _})
436
  when is_list(N) orelse is_list(H) ->
437
    ?INFO_MSG("Mnesia table 'muc_room' will be converted to binary", []),
×
438
    true;
×
439
need_transform({muc_room, {_N, _H}, Opts}) ->
440
    case {lists:keymember(allow_private_messages, 1, Opts),
×
441
          lists:keymember(hats_defs, 1, Opts)} of
442
        {true, _} ->
443
            ?INFO_MSG("Mnesia table 'muc_room' will be converted to allowpm", []),
×
444
            true;
×
445
        {false, false} ->
446
            ?INFO_MSG("Mnesia table 'muc_room' will be converted to Hats 0.3.0", []),
×
447
            true;
×
448
        {false, true} ->
449
            false
×
450
    end;
451

452
need_transform({muc_registered, {{U, S}, H}, Nick})
453
  when is_list(U) orelse is_list(S) orelse is_list(H) orelse is_list(Nick) ->
454
    ?INFO_MSG("Mnesia table 'muc_registered' will be converted to binary", []),
×
455
    true;
×
456
need_transform(_) ->
457
    false.
×
458

459
transform({muc_room, {N, H}, Opts} = R)
460
  when is_list(N) orelse is_list(H) ->
461
    R#muc_room{name_host = {iolist_to_binary(N), iolist_to_binary(H)},
×
462
               opts = mod_muc:opts_to_binary(Opts)};
463
transform(#muc_room{opts = Opts} = R) ->
464
    Opts2 = case lists:keyfind(allow_private_messages, 1, Opts) of
×
465
        {_, Value} when is_boolean(Value) ->
466
            Value2 = case Value of
×
467
                         true -> anyone;
×
468
                         false -> none
×
469
                     end,
470
            lists:keyreplace(allow_private_messages, 1, Opts, {allowpm, Value2});
×
471
        _ ->
472
            Opts
×
473
    end,
474
    Opts4 =
×
475
        case lists:keyfind(hats_defs, 1, Opts2) of
476
            false ->
477
                {hats_users, HatsUsers} = lists:keyfind(hats_users, 1, Opts2),
×
478
                {HatsDefs, HatsUsers2} =
×
479
                    lists:foldl(fun({Jid, UriTitleList}, {Defs, Assigns}) ->
480
                                   Defs2 =
×
481
                                       lists:foldl(fun({Uri, Title}, AccDef) ->
482
                                                      maps:put(Uri, {Title, <<"">>}, AccDef)
×
483
                                                   end,
484
                                                   Defs,
485
                                                   UriTitleList),
486
                                   Assigns2 =
×
487
                                       maps:put(Jid,
488
                                                [Uri || {Uri, _Title} <- UriTitleList],
×
489
                                                Assigns),
490
                                   {Defs2, Assigns2}
×
491
                                end,
492
                                {maps:new(), maps:new()},
493
                                HatsUsers),
494
                Opts3 =
×
495
                    lists:keyreplace(hats_users, 1, Opts2, {hats_users, maps:to_list(HatsUsers2)}),
496
                [{hats_defs, maps:to_list(HatsDefs)} | Opts3];
×
497
            {_, _} ->
498
                Opts2
×
499
        end,
500
    R#muc_room{opts = Opts4};
×
501
transform(#muc_registered{us_host = {{U, S}, H}, nick = Nick} = R) ->
502
    R#muc_registered{us_host = {{iolist_to_binary(U), iolist_to_binary(S)},
×
503
                                iolist_to_binary(H)},
504
                     nick = iolist_to_binary(Nick)}.
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