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