./ct_report/coverage/ejabberd_admin.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% File : ejabberd_admin.erl
3 %%% Author : Mickael Remond <mremond@process-one.net>
4 %%% Purpose : Administrative functions and commands
5 %%% Created : 7 May 2006 by Mickael Remond <mremond@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 %%%
24 %%%-------------------------------------------------------------------
25
26 -module(ejabberd_admin).
27 -author('mickael.remond@process-one.net').
28
29 -define(REGISTER_WORKERS_NUM, 10).
30
31 -export([start/0, stop/0,
32 %% Server
33 status/0,
34 send_service_message_all_mucs/2,
35 %% Accounts
36 register/3, register/2, unregister/2,
37 registered_users/1,
38 import_users/1,
39 %% Purge DB
40 delete_expired_messages/1, delete_old_messages/2,
41 %% Mnesia
42 set_master/1,
43 backup_mnesia/1, restore_mnesia/1,
44 dump_mnesia/1, dump_table/2, load_mnesia/1,
45 install_fallback_mnesia/1,
46 dump_to_textfile/1, dump_to_textfile/2,
47 mnesia_change_nodename/4,
48 restore/1, % Still used by some modules%%
49 get_loglevel/0,
50 join_cluster/1, leave_cluster/0,
51 remove_from_cluster/1]).
52
53 -export([registrator_proc/1]).
54
55 -ignore_xref([
56 backup_mnesia/1, delete_expired_messages/1, delete_old_messages/2,
57 dump_mnesia/1, dump_table/2, dump_to_textfile/1, dump_to_textfile/2,
58 get_loglevel/0, import_users/1, install_fallback_mnesia/1,
59 join_cluster/1, leave_cluster/0, load_mnesia/1, mnesia_change_nodename/4,
60 register/2, register/3, registered_users/1, remove_from_cluster/1,
61 restore_mnesia/1, send_service_message_all_mucs/2, set_master/1, status/0,
62 stop/0, unregister/2]).
63
64 -include("mongoose.hrl").
65 -include("ejabberd_commands.hrl").
66
67 start() ->
68 80 ejabberd_commands:register_commands(commands()).
69
70 stop() ->
71
:-(
ejabberd_commands:unregister_commands(commands()).
72
73 %%%
74 %%% ejabberd commands
75 %%%
76
77 -spec commands() -> [ejabberd_commands:cmd()].
78 commands() ->
79 80 [
80 %% The commands status, stop and restart are implemented also in ejabberd_ctl
81 %% They are defined here so that other interfaces can use them too
82 #ejabberd_commands{name = status, tags = [server],
83 desc = "Get status of the ejabberd server",
84 module = ?MODULE, function = status,
85 args = [], result = {res, restuple}},
86 #ejabberd_commands{name = restart, tags = [server],
87 desc = "Restart ejabberd gracefully",
88 module = init, function = restart,
89 args = [], result = {res, rescode}},
90 #ejabberd_commands{name = get_loglevel, tags = [logs, server],
91 desc = "Get the current loglevel",
92 module = ?MODULE, function = get_loglevel,
93 args = [],
94 result = {res, restuple}},
95 #ejabberd_commands{name = register, tags = [accounts],
96 desc = "Register a user",
97 module = ?MODULE, function = register,
98 args = [{host, binary}, {password, binary}],
99 result = {res, restuple}},
100 #ejabberd_commands{name = register_identified, tags = [accounts],
101 desc = "Register a user with a specific jid",
102 module = ?MODULE, function = register,
103 args = [{user, binary}, {host, binary}, {password, binary}],
104 result = {res, restuple}},
105 #ejabberd_commands{name = unregister, tags = [accounts],
106 desc = "Unregister a user",
107 module = ?MODULE, function = unregister,
108 args = [{user, binary}, {host, binary}],
109 result = {res, restuple}},
110 #ejabberd_commands{name = registered_users, tags = [accounts],
111 desc = "List all registered users in HOST",
112 module = ?MODULE, function = registered_users,
113 args = [{host, binary}],
114 result = {users, {list, {user_jid, binary}}}},
115 #ejabberd_commands{name = import_users, tags = [accounts],
116 desc = "Import users from CSV file",
117 module = ?MODULE, function = import_users,
118 args = [{file, string}],
119 result = {users, {list, {res, {tuple,
120 [{result, atom},
121 {user, binary}]}}}}},
122 #ejabberd_commands{name = delete_expired_messages, tags = [purge],
123 desc = "Delete expired offline messages from database",
124 module = ?MODULE, function = delete_expired_messages,
125 args = [{host, binary}], result = {res, restuple}},
126 #ejabberd_commands{name = delete_old_messages, tags = [purge],
127 desc = "Delete offline messages older than DAYS",
128 module = ?MODULE, function = delete_old_messages,
129 args = [{host, binary}, {days, integer}], result = {res, restuple}},
130 #ejabberd_commands{name = set_master, tags = [mnesia],
131 desc = "Set master node of the clustered Mnesia tables",
132 longdesc = "If you provide as nodename \"self\", this "
133 "node will be set as its own master.",
134 module = ?MODULE, function = set_master,
135 args = [{nodename, string}], result = {res, restuple}},
136 #ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
137 desc = "Change the erlang node name in a backup file",
138 module = ?MODULE, function = mnesia_change_nodename,
139 args = [{oldnodename, string}, {newnodename, string},
140 {oldbackup, string}, {newbackup, string}],
141 result = {res, restuple}},
142 #ejabberd_commands{name = backup, tags = [mnesia],
143 desc = "Store the database to backup file (only Mnesia)",
144 module = ?MODULE, function = backup_mnesia,
145 args = [{file, string}], result = {res, restuple}},
146 #ejabberd_commands{name = restore, tags = [mnesia],
147 desc = "Restore the database from backup file (only Mnesia)",
148 module = ?MODULE, function = restore_mnesia,
149 args = [{file, string}], result = {res, restuple}},
150 #ejabberd_commands{name = dump, tags = [mnesia],
151 desc = "Dump the database to text file (only Mnesia)",
152 module = ?MODULE, function = dump_mnesia,
153 args = [{file, string}], result = {res, restuple}},
154 #ejabberd_commands{name = dump_table, tags = [mnesia],
155 desc = "Dump a table to text file (only Mnesia)",
156 module = ?MODULE, function = dump_table,
157 args = [{file, string}, {table, string}], result = {res, restuple}},
158 #ejabberd_commands{name = load, tags = [mnesia],
159 desc = "Restore the database from text file (only Mnesia)",
160 module = ?MODULE, function = load_mnesia,
161 args = [{file, string}], result = {res, restuple}},
162 #ejabberd_commands{name = install_fallback, tags = [mnesia],
163 desc = "Install the database from a fallback file (only Mnesia)",
164 module = ?MODULE, function = install_fallback_mnesia,
165 args = [{file, string}], result = {res, restuple}},
166 #ejabberd_commands{name = join_cluster, tags = [server],
167 desc = "Join the node to a cluster. Call it from the joining node.
168 Use `-f` or `--force` flag to avoid question prompt and force join the node",
169 module = ?MODULE, function = join_cluster,
170 args = [{node, string}],
171 result = {res, restuple}},
172 #ejabberd_commands{name = leave_cluster, tags = [server],
173 desc = "Leave a cluster. Call it from the node that is going to leave.
174 Use `-f` or `--force` flag to avoid question prompt and force leave the node from cluster",
175 module = ?MODULE, function = leave_cluster,
176 args = [],
177 result = {res, restuple}},
178 #ejabberd_commands{name = remove_from_cluster, tags = [server],
179 desc = "Remove dead node from the cluster. Call it from the member of the cluster.
180 Use `-f` or `--force` flag to avoid question prompt and force remove the node",
181 module = ?MODULE, function = remove_from_cluster,
182 args = [{node, string}],
183 result = {res, restuple}}
184 ].
185
186
187 %%%
188 %%% Server management
189 %%%
190 -spec remove_from_cluster(string()) -> {ok, string()} |
191 {node_is_alive, string()} |
192 {mnesia_error, string()} |
193 {rpc_error, string()}.
194 remove_from_cluster(NodeString) ->
195 1 Node = list_to_atom(NodeString),
196 1 IsNodeAlive = mongoose_cluster:is_node_alive(Node),
197 1 case IsNodeAlive of
198 true ->
199 1 remove_rpc_alive_node(Node);
200 false ->
201
:-(
remove_dead_node(Node)
202 end.
203
204 remove_dead_node(DeadNode) ->
205
:-(
try mongoose_cluster:remove_from_cluster(DeadNode) of
206 ok ->
207
:-(
String = io_lib:format("The dead node ~p has been removed from the cluster~n", [DeadNode]),
208
:-(
{ok, String}
209 catch
210 error:{node_is_alive, DeadNode} ->
211
:-(
String = io_lib:format("The node ~p is alive but shoud not be.~n", [DeadNode]),
212
:-(
{node_is_alive, String};
213 error:{del_table_copy_schema, R} ->
214
:-(
String = io_lib:format("Cannot delete table schema~n. Reason: ~p", [R]),
215
:-(
{mnesia_error, String}
216 end.
217
218 remove_rpc_alive_node(AliveNode) ->
219 1 case rpc:call(AliveNode, mongoose_cluster, leave, []) of
220 {badrpc, Reason} ->
221
:-(
String = io_lib:format("Cannot remove the node ~p~n. RPC Reason: ~p", [AliveNode, Reason]),
222
:-(
{rpc_error, String};
223 ok ->
224 1 String = io_lib:format("The node ~p has been removed from the cluster~n", [AliveNode]),
225 1 {ok, String};
226 Unknown ->
227
:-(
String = io_lib:format("Unknown error: ~p~n", [Unknown]),
228
:-(
{rpc_error, String}
229 end.
230
231 -spec join_cluster(string()) -> {ok, string()} | {pang, string()} | {already_joined, string()} |
232 {mnesia_error, string()} | {error, string()}.
233 join_cluster(NodeString) ->
234 6 NodeAtom = list_to_atom(NodeString),
235 6 NodeList = mnesia:system_info(db_nodes),
236 6 case lists:member(NodeAtom, NodeList) of
237 true ->
238 1 String = io_lib:format("The node ~s has already joined the cluster~n", [NodeString]),
239 1 {already_joined, String};
240 _ ->
241 5 do_join_cluster(NodeAtom)
242 end.
243
244 do_join_cluster(Node) ->
245 5 try mongoose_cluster:join(Node) of
246 ok ->
247 5 String = io_lib:format("You have successfully joined the node ~p to the cluster with node member ~p~n", [node(), Node]),
248 5 {ok, String}
249 catch
250 error:pang ->
251
:-(
String = io_lib:format("Timeout while attempting to connect to node ~s~n", [Node]),
252
:-(
{pang, String};
253 error:{cant_get_storage_type, {T, E, R}} ->
254
:-(
String = io_lib:format("Cannot get storage type for table ~p~n. Reason: ~p:~p", [T, E, R]),
255
:-(
{mnesia_error, String};
256 E:R:S ->
257
:-(
{error, {E, R, S}}
258 end.
259
260 -spec leave_cluster() -> {ok, string()} | {error, term()} | {not_in_cluster, string()}.
261 leave_cluster() ->
262 8 NodeList = mnesia:system_info(running_db_nodes),
263 8 ThisNode = node(),
264 8 case NodeList of
265 [ThisNode] ->
266 2 String = io_lib:format("The node ~p is not in the cluster~n", [node()]),
267 2 {not_in_cluster, String};
268 _ ->
269 6 do_leave_cluster()
270 end.
271
272 do_leave_cluster() ->
273 6 try mongoose_cluster:leave() of
274 ok ->
275 6 String = io_lib:format("The node ~p has successfully left the cluster~n", [node()]),
276 6 {ok, String}
277 catch
278 E:R ->
279
:-(
{error, {E, R}}
280 end.
281
282
283 -spec status() -> {'mongooseim_not_running', io_lib:chars()} | {'ok', io_lib:chars()}.
284 status() ->
285
:-(
{InternalStatus, ProvidedStatus} = init:get_status(),
286
:-(
String1 = io_lib:format("The node ~p is ~p. Status: ~p",
287 [node(), InternalStatus, ProvidedStatus]),
288
:-(
{Is_running, String2} =
289 case lists:keysearch(mongooseim, 1, application:which_applications()) of
290 false ->
291
:-(
{mongooseim_not_running, "mongooseim is not running in that node."};
292 {value, {_, _, Version}} ->
293
:-(
{ok, io_lib:format("mongooseim ~s is running in that node", [Version])}
294 end,
295
:-(
{Is_running, String1 ++ String2}.
296
297
298 -spec send_service_message_all_mucs(Subject :: string() | binary(),
299 AnnouncementText :: string() | binary()) -> 'ok'.
300 send_service_message_all_mucs(Subject, AnnouncementText) ->
301
:-(
Message = io_lib:format("~s~n~s", [Subject, AnnouncementText]),
302
:-(
lists:foreach(
303 fun(Host) ->
304
:-(
MUCHost = gen_mod:get_module_opt_subhost(Host, mod_muc, mod_muc:default_host()),
305
:-(
mod_muc:broadcast_service_message(MUCHost, Message)
306 end,
307 ?MYHOSTS).
308
309 %%%
310 %%% Account management
311 %%%
312
313 -spec register(Host :: jid:server(),
314 Password :: binary()) -> mongoose_account_api:register_result().
315 register(Host, Password) ->
316 1 {Result, _} = mongoose_account_api:register_generated_user(Host, Password),
317 1 Result.
318
319 -spec register(User :: jid:user(),
320 Host :: jid:server(),
321 Password :: binary()) -> mongoose_account_api:register_result().
322 register(User, Host, Password) ->
323 2107 mongoose_account_api:register_user(User, Host, Password).
324
325 -spec unregister(User :: jid:user(),
326 Host :: jid:server()) -> mongoose_account_api:unregister_result().
327 unregister(User, Host) ->
328 2149 mongoose_account_api:unregister_user(User, Host).
329
330
331 -spec registered_users(Host :: jid:server()) -> [binary()].
332 registered_users(Host) ->
333 3 mongoose_account_api:list_users(Host).
334
335 -spec import_users(Filename :: string()) -> [{ok, jid:user()} |
336 {exists, jid:user()} |
337 {not_allowed, jid:user()} |
338 {invalid_jid, jid:user()} |
339 {null_password, jid:user()} |
340 {bad_csv, binary()}].
341 import_users(File) ->
342
:-(
{ok, CsvStream} = erl_csv:decode_new_s(File),
343
:-(
Workers = spawn_link_workers(),
344
:-(
WorkersQueue = queue:from_list(Workers),
345
:-(
do_import(CsvStream, WorkersQueue).
346
347 -spec do_import(erl_csv:csv_stream(), Workers :: queue:queue()) ->
348 [{ok, jid:user()} |
349 {exists, jid:user()} |
350 {not_allowed, jid:user()} |
351 {invalid_jid, jid:user()} |
352 {null_password, jid:user()} |
353 {bad_csv, binary()}].
354 do_import(stream_end, WQueue) ->
355
:-(
Workers = queue:to_list(WQueue),
356
:-(
lists:flatmap(fun get_results_from_registrator/1, Workers);
357 do_import(Stream, WQueue) ->
358
:-(
{ok, Decoded, MoreStream} = erl_csv:decode_s(Stream),
359
:-(
WQueue1 = send_job_to_next_worker(Decoded, WQueue),
360
:-(
do_import(MoreStream, WQueue1).
361
362 -spec spawn_link_workers() -> [pid()].
363 spawn_link_workers() ->
364
:-(
[ spawn_link(?MODULE, registrator_proc, [self()]) ||
365
:-(
_ <- lists:seq(1, ?REGISTER_WORKERS_NUM)].
366
367 -spec get_results_from_registrator(Worker :: pid()) ->
368 [{ok, jid:user()} |
369 {exists, jid:user()} |
370 {not_allowed, jid:user()} |
371 {invalid_jid, jid:user()} |
372 {null_password, jid:user()} |
373 {bad_csv, binary()}].
374 get_results_from_registrator(Pid) ->
375
:-(
Pid ! get_result,
376
:-(
receive
377
:-(
{result, Result} -> Result
378 end.
379
380 send_job_to_next_worker([], WQueue) ->
381
:-(
WQueue;
382 send_job_to_next_worker([Record], WQueue) ->
383
:-(
{{value, Worker}, Q1} = queue:out(WQueue),
384
:-(
Worker ! {proccess, Record},
385
:-(
queue:in(Worker, Q1).
386
387 -spec registrator_proc(Manager :: pid()) -> ok.
388 registrator_proc(Manager) ->
389
:-(
registrator_proc(Manager, []).
390
391 -spec registrator_proc(Manager :: pid(), any()) -> ok.
392 registrator_proc(Manager, Result) ->
393
:-(
receive
394 {proccess, Data} ->
395
:-(
RegisterResult = do_register(Data),
396
:-(
registrator_proc(Manager, [RegisterResult | Result]);
397
:-(
get_result -> Manager ! {result, Result}
398 end,
399
:-(
ok.
400
401 -spec do_register([binary()]) -> {ok, jid:user()} |
402 {exists, jid:user()} |
403 {not_allowed, jid:user()} |
404 {invalid_jid, jid:user()} |
405 {null_password, jid:user()} |
406 {bad_csv, binary()}.
407 do_register([User, Host, Password]) ->
408
:-(
case ejabberd_auth:try_register(jid:make(User, Host, <<>>), Password) of
409
:-(
{error, Reason} -> {Reason, User};
410
:-(
_ -> {ok, User}
411 end;
412
413 do_register(List) ->
414
:-(
JoinBinary = fun(Elem, <<"">>) -> Elem;
415
:-(
(Elem, Acc) -> <<Elem/binary, ",", Acc/binary>>
416 end,
417
:-(
Info = lists:foldr(JoinBinary, <<"">>, List),
418
:-(
{bad_csv, Info}.
419
420 get_loglevel() ->
421 1 Level = mongoose_logs:get_global_loglevel(),
422 1 Number = mongoose_logs:loglevel_keyword_to_number(Level),
423 1 String = io_lib:format("global loglevel is ~p, which means '~p'", [Number, Level]),
424 1 {ok, String}.
425
426 %%%
427 %%% Purge DB
428 %%%
429
430 -spec delete_expired_messages(jid:lserver()) -> {ok, iolist()} | {error, iolist()}.
431 delete_expired_messages(LServer) ->
432 1 case mongoose_domain_api:get_domain_host_type(LServer) of
433 {ok, HostType} ->
434 1 case mod_offline:remove_expired_messages(HostType, LServer) of
435 {ok, C} ->
436 1 {ok, io_lib:format("Removed ~p messages", [C])};
437 {error, Reason} ->
438
:-(
{error, io_lib:format("Can't delete expired messages: ~n~p", [Reason])}
439 end;
440 {error, not_found} ->
441
:-(
{error, "Unknown domain"}
442 end.
443
444 -spec delete_old_messages(jid:lserver(), Days :: integer()) -> {ok, iolist()} | {error, iolist()}.
445 delete_old_messages(LServer, Days) ->
446 1 case mongoose_domain_api:get_domain_host_type(LServer) of
447 {ok, HostType} ->
448 1 case mod_offline:remove_old_messages(HostType, LServer, Days) of
449 {ok, C} ->
450 1 {ok, io_lib:format("Removed ~p messages", [C])};
451 {error, Reason} ->
452
:-(
{error, io_lib:format("Can't remove old messages: ~n~p", [Reason])}
453 end;
454 {error, not_found} ->
455
:-(
{error, "Unknown domain"}
456 end.
457
458
459 %%%
460 %%% Mnesia management
461 %%%
462
463 -spec set_master(Node :: atom() | string()) -> {'error', io_lib:chars()} | {'ok', []}.
464 set_master("self") ->
465 2 set_master(node());
466 set_master(NodeString) when is_list(NodeString) ->
467 1 set_master(list_to_atom(NodeString));
468 set_master(Node) when is_atom(Node) ->
469 3 case mnesia:set_master_nodes([Node]) of
470 ok ->
471 3 {ok, ""};
472 {error, Reason} ->
473
:-(
String = io_lib:format("Can't set master node ~p at node ~p:~n~p",
474 [Node, node(), Reason]),
475
:-(
{error, String}
476 end.
477
478 -spec backup_mnesia(file:name()) -> {'cannot_backup', io_lib:chars()} | {'ok', []}.
479 backup_mnesia(Path) ->
480 1 case mnesia:backup(Path) of
481 ok ->
482 1 {ok, ""};
483 {error, Reason} ->
484
:-(
String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
485 [filename:absname(Path), node(), Reason]),
486
:-(
{cannot_backup, String}
487 end.
488
489 -spec restore_mnesia(file:name()) -> {'cannot_restore', io_lib:chars()}
490 | {'file_not_found', io_lib:chars()}
491 | {'ok', []}
492 | {'table_not_exists', io_lib:chars()}.
493 restore_mnesia(Path) ->
494 3 ErrorString=lists:flatten( io_lib:format("Can't restore backup from ~p at node ~p: ",
495 [filename:absname(Path), node()])),
496 3 case ejabberd_admin:restore(Path) of
497 {atomic, _} ->
498 1 {ok, ""};
499 {aborted, {no_exists, Table}} ->
500
:-(
String = io_lib:format("~sTable ~p does not exist.", [ErrorString, Table]),
501
:-(
{table_not_exists, String};
502 {aborted, enoent} ->
503 2 String = ErrorString ++ "File not found.",
504 2 {file_not_found, String};
505 {aborted, Reason} ->
506
:-(
String = io_lib:format("~s~p", [ErrorString, Reason]),
507
:-(
{cannot_restore, String}
508 end.
509
510 %% @doc Mnesia database restore
511 %% This function is called from ejabberd_ctl, ejabberd_web_admin and
512 %% mod_configure/adhoc
513 restore(Path) ->
514 3 mnesia:restore(Path, [{keep_tables, keep_tables()},
515 {default_op, skip_tables}]).
516
517 %% @doc This function return a list of tables that should be kept from a
518 %% previous version backup.
519 %% Obsolete tables or tables created by module who are no longer used are not
520 %% restored and are ignored.
521 -spec keep_tables() -> [atom()].
522 keep_tables() ->
523 3 lists:flatten([acl, passwd, disco_publish, keep_modules_tables()]).
524
525 %% @doc Returns the list of modules tables in use, according to the list of
526 %% actually loaded modules
527 -spec keep_modules_tables() -> [[atom()]]. % list of lists
528 keep_modules_tables() ->
529 3 lists:map(fun(Module) -> module_tables(Module) end,
530 gen_mod:loaded_modules()).
531
532 %% TODO: This mapping should probably be moved to a callback function in each module.
533 %% @doc Mapping between modules and their tables
534 -spec module_tables(_) -> [atom()].
535
:-(
module_tables(mod_announce) -> [motd, motd_users];
536
:-(
module_tables(mod_irc) -> [irc_custom];
537 3 module_tables(mod_last) -> [last_activity];
538
:-(
module_tables(mod_muc) -> [muc_room, muc_registered];
539 3 module_tables(mod_offline) -> [offline_msg];
540
:-(
module_tables(mod_privacy) -> [privacy];
541
:-(
module_tables(mod_private) -> [private_storage];
542
:-(
module_tables(mod_pubsub) -> [pubsub_node];
543 3 module_tables(mod_roster) -> [roster];
544
:-(
module_tables(mod_shared_roster) -> [sr_group, sr_user];
545 3 module_tables(mod_vcard) -> [vcard, vcard_search];
546 36 module_tables(_Other) -> [].
547
548 -spec get_local_tables() -> [any()].
549 get_local_tables() ->
550 1 Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
551 1 Tabs = lists:filter(
552 fun(T) ->
553 33 case mnesia:table_info(T, storage_type) of
554 13 disc_copies -> true;
555 5 disc_only_copies -> true;
556 15 _ -> false
557 end
558 end, Tabs1),
559 1 Tabs.
560
561 -spec dump_mnesia(file:name()) -> {'cannot_dump', io_lib:chars()} | {'ok', []}.
562 dump_mnesia(Path) ->
563 1 Tabs = get_local_tables(),
564 1 dump_tables(Path, Tabs).
565
566 -spec dump_table(file:name(), STable :: string()) ->
567 {'cannot_dump', io_lib:chars()} | {'ok', []}.
568 dump_table(Path, STable) ->
569 1 Table = list_to_atom(STable),
570 1 dump_tables(Path, [Table]).
571
572 -spec dump_tables(file:name(), Tables :: [atom()]) ->
573 {'cannot_dump', io_lib:chars()} | {'ok', []}.
574 dump_tables(Path, Tables) ->
575 2 case dump_to_textfile(Path, Tables) of
576 ok ->
577 2 {ok, ""};
578 {error, Reason} ->
579
:-(
String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
580 [filename:absname(Path), node(), Reason]),
581
:-(
{cannot_dump, String}
582 end.
583
584 -spec dump_to_textfile(file:name()) -> 'ok' | {'error', atom()}.
585 dump_to_textfile(File) ->
586
:-(
Tabs = get_local_tables(),
587
:-(
dump_to_textfile(File, Tabs).
588
589 -spec dump_to_textfile(file:name(), Tabs :: list()) -> 'ok' | {'error', atom()}.
590 dump_to_textfile(File, Tabs) ->
591 2 dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])).
592
593 -spec dump_to_textfile(any(),
594 any(),
595 {'error', atom()} | {'ok', pid() | {'file_descriptor', atom() | tuple(), _}}
596 ) -> 'ok' | {'error', atom()}.
597 dump_to_textfile(yes, Tabs, {ok, F}) ->
598 2 Defs = lists:map(
599 19 fun(T) -> {T, [{record_name, mnesia:table_info(T, record_name)},
600 {attributes, mnesia:table_info(T, attributes)}]}
601 end,
602 Tabs),
603 2 io:format(F, "~p.~n", [{tables, Defs}]),
604 2 lists:foreach(fun(T) -> dump_tab(F, T) end, Tabs),
605 2 file:close(F);
606 dump_to_textfile(_, _, {ok, F}) ->
607
:-(
file:close(F),
608
:-(
{error, mnesia_not_running};
609 dump_to_textfile(_, _, {error, Reason}) ->
610
:-(
{error, Reason}.
611
612 -spec dump_tab(pid(), atom()) -> 'ok'.
613 dump_tab(F, T) ->
614 19 W = mnesia:table_info(T, wild_pattern),
615 19 {atomic, All} = mnesia:transaction(
616 19 fun() -> mnesia:match_object(T, W, read) end),
617 19 lists:foreach(
618 36 fun(Term) -> io:format(F, "~p.~n", [setelement(1, Term, T)]) end, All).
619
620 -spec load_mnesia(file:name()) -> {'cannot_load', io_lib:chars()} | {'ok', []}.
621 load_mnesia(Path) ->
622 2 case mnesia:load_textfile(Path) of
623 {atomic, ok} ->
624 2 {ok, ""};
625 {error, Reason} ->
626
:-(
String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
627 [filename:absname(Path), node(), Reason]),
628
:-(
{cannot_load, String}
629 end.
630
631 -spec install_fallback_mnesia(file:name()) ->
632 {'cannot_fallback', io_lib:chars()} | {'ok', []}.
633 install_fallback_mnesia(Path) ->
634
:-(
case mnesia:install_fallback(Path) of
635 ok ->
636
:-(
{ok, ""};
637 {error, Reason} ->
638
:-(
String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
639 [filename:absname(Path), node(), Reason]),
640
:-(
{cannot_fallback, String}
641 end.
642
643 -spec mnesia_change_nodename(string(), string(), _, _) -> {ok, _} | {error, _}.
644 mnesia_change_nodename(FromString, ToString, Source, Target) ->
645
:-(
From = list_to_atom(FromString),
646
:-(
To = list_to_atom(ToString),
647
:-(
Switch =
648 fun
649 (Node) when Node == From ->
650
:-(
io:format(" - Replacing nodename: '~p' with: '~p'~n", [From, To]),
651
:-(
To;
652 (Node) when Node == To ->
653 %% throw({error, already_exists});
654
:-(
io:format(" - Node: '~p' will not be modified (it is already '~p')~n", [Node, To]),
655
:-(
Node;
656 (Node) ->
657
:-(
io:format(" - Node: '~p' will not be modified (it is not '~p')~n", [Node, From]),
658
:-(
Node
659 end,
660
:-(
Convert =
661 fun
662 ({schema, db_nodes, Nodes}, Acc) ->
663
:-(
io:format(" +++ db_nodes ~p~n", [Nodes]),
664
:-(
{[{schema, db_nodes, lists:map(Switch, Nodes)}], Acc};
665 ({schema, version, Version}, Acc) ->
666
:-(
io:format(" +++ version: ~p~n", [Version]),
667
:-(
{[{schema, version, Version}], Acc};
668 ({schema, cookie, Cookie}, Acc) ->
669
:-(
io:format(" +++ cookie: ~p~n", [Cookie]),
670
:-(
{[{schema, cookie, Cookie}], Acc};
671 ({schema, Tab, CreateList}, Acc) ->
672
:-(
io:format("~n * Checking table: '~p'~n", [Tab]),
673
:-(
Keys = [ram_copies, disc_copies, disc_only_copies],
674
:-(
OptSwitch =
675 fun({Key, Val}) ->
676
:-(
case lists:member(Key, Keys) of
677 true ->
678
:-(
io:format(" + Checking key: '~p'~n", [Key]),
679
:-(
{Key, lists:map(Switch, Val)};
680
:-(
false-> {Key, Val}
681 end
682 end,
683
:-(
Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc},
684
:-(
Res;
685 (Other, Acc) ->
686
:-(
{[Other], Acc}
687 end,
688
:-(
mnesia:traverse_backup(Source, Target, Convert, switched).
Line Hits Source