1 %%%-------------------------------------------------------------------
2 %%% File    : ejabberd_admin.erl
3 %%% Author  : Mickael Remond <[email protected]>
4 %%% Purpose : Administrative functions and commands
5 %%% Created :  7 May 2006 by Mickael Remond <[email protected]>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2012   ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License
21 %%% along with this program; if not, write to the Free Software
22 %%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23 %%% 02111-1307 USA
24 %%%
25 %%%-------------------------------------------------------------------
26 
27 
28 -module(ejabberd_admin).
29 
30 -author('[email protected]').
31 
32 -export([start/0, stop/0, status/0, reopen_log/0, stop_kindly/2,
33 	 send_service_message_all_mucs/2, update_list/0, update/1, register/3,
34 	 unregister/2, registered_users/1, get_last_info/0, get_last_info/2,
35 	 import_file/1, import_dir/1, delete_expired_messages/0, delete_old_messages/1,
36 	 set_master/1, backup_mnesia/1, restore_mnesia/1, dump_mnesia/1, dump_table/2,
37 	 load_mnesia/1, install_fallback_mnesia/1, dump_to_textfile/1, dump_to_textfile/2,
38 	 mnesia_change_nodename/4, restore/1]).         %% Server
39 
40          %% Erlang
41 
42 
43          %% Accounts
44 
45 
46          %% For debugging gen_storage
47 
48 
49          %% Migration jabberd1.4
50 
51 
52          %% Purge DB
53 
54 
55          %% Mnesia
56 
57 
58                    % Still used by some modules
59 
60 
61 -include("ejabberd.hrl").
62 
63 -include("ejabberd_commands.hrl").
64 
65 start() -> ejabberd_commands:register_commands(commands()).
66 
67 stop() -> ejabberd_commands:unregister_commands(commands()).
68 
69 %%%
70 %%% ejabberd commands
71 %%%
72 
73 
74 commands() ->
75     [     %% The commands status, stop and restart are implemented also in ejabberd_ctl
76 	  %% They are defined here so that other interfaces can use them too
77      #ejabberd_commands{name = status, tags = [server],
78 			desc = "Get status of the ejabberd server", module = ?MODULE,
79 			function = status, args = [], result = {res, restuple}},
80      #ejabberd_commands{name = stop, tags = [server], desc = "Stop ejabberd gracefully",
81 			module = init, function = stop, args = [],
82 			result = {res, rescode}},
83      #ejabberd_commands{name = restart, tags = [server],
84 			desc = "Restart ejabberd gracefully", module = init,
85 			function = restart, args = [], result = {res, rescode}},
86      #ejabberd_commands{name = reopen_log, tags = [logs, server],
87 			desc = "Reopen the log files", module = ?MODULE,
88 			function = reopen_log, args = [], result = {res, rescode}},
89      #ejabberd_commands{name = stop_kindly, tags = [server],
90 			desc = "Inform users and rooms, wait, and stop the server",
91 			module = ?MODULE, function = stop_kindly,
92 			args = [{delay, integer}, {announcement, string}],
93 			result = {res, rescode}},
94      #ejabberd_commands{name = get_loglevel, tags = [logs, server],
95 			desc = "Get the current loglevel", module = ejabberd_loglevel,
96 			function = get, args = [],
97 			result =
98 			    {leveltuple,
99 			     {tuple,
100 			      [{levelnumber, integer}, {levelatom, atom},
101 			       {leveldesc, string}]}}},
102      #ejabberd_commands{name = update_list, tags = [server],
103 			desc = "List modified modules that can be updated",
104 			module = ?MODULE, function = update_list, args = [],
105 			result = {modules, {list, {module, string}}}},
106      #ejabberd_commands{name = update, tags = [server],
107 			desc = "Update the given module, or use the keyword: all",
108 			module = ?MODULE, function = update, args = [{module, string}],
109 			result = {res, restuple}},
110      #ejabberd_commands{name = register, tags = [accounts], desc = "Register a user",
111 			module = ?MODULE, function = register,
112 			args = [{user, string}, {host, string}, {password, string}],
113 			result = {res, restuple}},
114      #ejabberd_commands{name = unregister, tags = [accounts], desc = "Unregister a user",
115 			module = ?MODULE, function = unregister,
116 			args = [{user, string}, {host, string}],
117 			result = {res, restuple}},
118      #ejabberd_commands{name = registered_users, tags = [accounts],
119 			desc = "List all registered users in HOST", module = ?MODULE,
120 			function = registered_users, args = [{host, string}],
121 			result = {users, {list, {username, string}}}},
122      #ejabberd_commands{name = gli, tags = [accounts],
123 			desc = "Get information about last access of an account",
124 			module = ?MODULE, function = get_last_info, args = [],
125 			result =
126 			    {lastinfo,
127 			     {tuple, [{timestamp, integer}, {status, string}]}}},
128      #ejabberd_commands{name = get_last_info, tags = [accounts],
129 			desc = "Get information about last access of an account",
130 			module = ?MODULE, function = get_last_info,
131 			args = [{user, string}, {host, string}],
132 			result =
133 			    {lastinfo,
134 			     {tuple, [{timestamp, integer}, {status, string}]}}},
135      #ejabberd_commands{name = import_file, tags = [mnesia],
136 			desc = "Import user data from jabberd14 spool file",
137 			module = ?MODULE, function = import_file, args = [{file, string}],
138 			result = {res, restuple}},
139      #ejabberd_commands{name = import_dir, tags = [mnesia],
140 			desc = "Import users data from jabberd14 spool dir",
141 			module = ?MODULE, function = import_dir, args = [{file, string}],
142 			result = {res, restuple}},
143      #ejabberd_commands{name = import_piefxis, tags = [mnesia],
144 			desc = "Import users data from a PIEFXIS file (XEP-0227)",
145 			module = ejabberd_piefxis, function = import_file,
146 			args = [{file, string}], result = {res, rescode}},
147      #ejabberd_commands{name = export_piefxis, tags = [mnesia],
148 			desc =
149 			    "Export data of all users in the server to PIEFXIS files (XEP-0227)",
150 			module = ejabberd_piefxis, function = export_server,
151 			args = [{dir, string}], result = {res, rescode}},
152      #ejabberd_commands{name = export_piefxis_host, tags = [mnesia],
153 			desc =
154 			    "Export data of users in a host to PIEFXIS files (XEP-0227)",
155 			module = ejabberd_piefxis, function = export_host,
156 			args = [{dir, string}, {host, string}], result = {res, rescode}},
157      #ejabberd_commands{name = delete_expired_messages, tags = [purge],
158 			desc = "Delete expired offline messages from database",
159 			module = ?MODULE, function = delete_expired_messages, args = [],
160 			result = {res, rescode}},
161      #ejabberd_commands{name = delete_old_messages, tags = [purge],
162 			desc = "Delete offline messages older than DAYS",
163 			module = ?MODULE, function = delete_old_messages,
164 			args = [{days, integer}], result = {res, rescode}},
165      #ejabberd_commands{name = set_master, tags = [mnesia],
166 			desc = "Set master node of the clustered Mnesia tables",
167 			longdesc =
168 			    "If you provide as nodename \"self\", this node will be set as its own master.",
169 			module = ?MODULE, function = set_master,
170 			args = [{nodename, string}], result = {res, restuple}},
171      #ejabberd_commands{name = mnesia_change_nodename, tags = [mnesia],
172 			desc = "Change the erlang node name in a backup file",
173 			module = ?MODULE, function = mnesia_change_nodename,
174 			args =
175 			    [{oldnodename, string}, {newnodename, string},
176 			     {oldbackup, string}, {newbackup, string}],
177 			result = {res, restuple}},
178      #ejabberd_commands{name = backup, tags = [mnesia],
179 			desc = "Store the database to backup file", module = ?MODULE,
180 			function = backup_mnesia, args = [{file, string}],
181 			result = {res, restuple}},
182      #ejabberd_commands{name = restore, tags = [mnesia],
183 			desc = "Restore the database from backup file", module = ?MODULE,
184 			function = restore_mnesia, args = [{file, string}],
185 			result = {res, restuple}},
186      #ejabberd_commands{name = dump, tags = [mnesia],
187 			desc = "Dump the database to text file", module = ?MODULE,
188 			function = dump_mnesia, args = [{file, string}],
189 			result = {res, restuple}},
190      #ejabberd_commands{name = dump_table, tags = [mnesia],
191 			desc = "Dump a table to text file", module = ?MODULE,
192 			function = dump_table, args = [{file, string}, {table, string}],
193 			result = {res, restuple}},
194      #ejabberd_commands{name = load, tags = [mnesia],
195 			desc = "Restore the database from text file", module = ?MODULE,
196 			function = load_mnesia, args = [{file, string}],
197 			result = {res, restuple}},
198      #ejabberd_commands{name = install_fallback, tags = [mnesia],
199 			desc = "Install the database from a fallback file",
200 			module = ?MODULE, function = install_fallback_mnesia,
201 			args = [{file, string}], result = {res, restuple}}].
202 
203 %%%
204 %%% Server management
205 %%%
206 
207 
208 status() ->
209     {InternalStatus, ProvidedStatus} = init:get_status(),
210     String1 = io_lib:format("The node ~p is ~p. Status: ~p",
211 			    [node(), InternalStatus, ProvidedStatus]),
212     {Is_running, String2} = case lists:keysearch(ejabberd, 1,
213 						 application:which_applications())
214 				of
215 			      false ->
216 				  {ejabberd_not_running,
217 				   "ejabberd is not running in that node."};
218 			      {value, {_, _, Version}} ->
219 				  {ok,
220 				   io_lib:format("ejabberd ~s is running in that node",
221 						 [Version])}
222 			    end,
223     {Is_running, String1 ++ String2}.
224 
225 reopen_log() ->
226     ejabberd_hooks:run(reopen_log_hook, []),
227         %% TODO: Use the Reopen log API for logger_h ?
228     ejabberd_logger_h:reopen_log(),
229     case application:get_env(sasl, sasl_error_logger) of
230       {ok, {file, SASLfile}} ->
231 	  error_logger:delete_report_handler(sasl_report_file_h),
232 	  ejabberd_logger_h:rotate_log(SASLfile),
233 	  error_logger:add_report_handler(sasl_report_file_h,
234 					  {SASLfile, get_sasl_error_logger_type()});
235       _ -> false
236     end,
237     ok.
238 
239 %% Function copied from Erlang/OTP lib/sasl/src/sasl.erl which doesn't export it
240 get_sasl_error_logger_type() ->
241     case application:get_env(sasl, errlog_type) of
242       {ok, error} -> error;
243       {ok, progress} -> progress;
244       {ok, all} -> all;
245       {ok, Bad} -> exit({bad_config, {sasl, {errlog_type, Bad}}});
246       _ -> all
247     end.
248 
249 %%%
250 %%% Stop Kindly
251 %%%
252 
253 
254 stop_kindly(DelaySeconds, AnnouncementText) ->
255     Subject = io_lib:format("Server stop in ~p seconds!", [DelaySeconds]),
256     WaitingDesc = io_lib:format("Waiting ~p seconds", [DelaySeconds]),
257     Steps = [{"Stopping ejabberd port listeners", ejabberd_listener, stop_listeners, []},
258 	     {"Sending announcement to connected users", mod_announce,
259 	      send_announcement_to_all, [?MYNAME, Subject, AnnouncementText]},
260 	     {"Sending service message to MUC rooms", ejabberd_admin,
261 	      send_service_message_all_mucs, [Subject, AnnouncementText]},
262 	     {WaitingDesc, timer, sleep, [DelaySeconds * 1000]},
263 	     {"Stopping ejabberd", application, stop, [ejabberd]},
264 	     {"Stopping Mnesia", mnesia, stop, []},
265 	     {"Stopping Erlang node", init, stop, []}],
266     NumberLast = length(Steps),
267     TimestampStart = calendar:datetime_to_gregorian_seconds({date(), time()}),
268     lists:foldl(fun ({Desc, Mod, Func, Args}, NumberThis) ->
269 			SecondsDiff = calendar:datetime_to_gregorian_seconds({date(),
270 									      time()})
271 					- TimestampStart,
272 			io:format("[~p/~p ~ps] ~s... ",
273 				  [NumberThis, NumberLast, SecondsDiff, Desc]),
274 			Result = apply(Mod, Func, Args),
275 			io:format("~p~n", [Result]),
276 			NumberThis + 1
277 		end,
278 		1, Steps),
279     ok.
280 
281 send_service_message_all_mucs(Subject, AnnouncementText) ->
282     Message = io_lib:format("~s~n~s", [Subject, AnnouncementText]),
283     lists:foreach(fun (ServerHost) ->
284 			  MUCHost = gen_mod:get_module_opt_host(ServerHost, mod_muc,
285 								"conference.@HOST@"),
286 			  MUCHostB = list_to_binary(MUCHost),
287 			  mod_muc:broadcast_service_message(MUCHostB, Message)
288 		  end,
289 		  ?MYHOSTS).
290 
291 %%%
292 %%% ejabberd_update
293 %%%
294 
295 
296 update_list() ->
297     {ok, _Dir, UpdatedBeams, _Script, _LowLevelScript, _Check} =
298 	ejabberd_update:update_info(),
299     [atom_to_list(Beam) || Beam <- UpdatedBeams].
300 
301 update("all") -> [update_module(ModStr) || ModStr <- update_list()], {ok, []};
302 update(ModStr) -> update_module(ModStr).
303 
304 update_module(ModuleNameString) ->
305     ModuleName = list_to_atom(ModuleNameString),
306     case ejabberd_update:update([ModuleName]) of
307       {ok, _Res} -> {ok, []};
308       {error, Reason} -> {error, Reason}
309     end.
310 
311 %%%
312 %%% Account management
313 %%%
314 
315 
316 register(User, Host, Password) ->
317     case ejabberd_auth:try_register(User, Host, Password) of
318       {atomic, ok} ->
319 	  {ok, io_lib:format("User ~s@~s successfully registered", [User, Host])};
320       {atomic, exists} ->
321 	  String = io_lib:format("User ~s@~s already registered at node ~p",
322 				 [User, Host, node()]),
323 	  {exists, String};
324       {error, Reason} ->
325 	  String = io_lib:format("Can't register user ~s@~s at node ~p: ~p",
326 				 [User, Host, node(), Reason]),
327 	  {cannot_register, String}
328     end.
329 
330 unregister(User, Host) -> ejabberd_auth:remove_user(User, Host), {ok, ""}.
331 
332 registered_users(Host) ->
333     Users = ejabberd_auth:get_vh_registered_users(Host),
334     SUsers = lists:sort(Users),
335     lists:map(fun ({U, _S}) -> U end, SUsers).
336 
337 get_last_info() -> get_last_info("badlop", "localhost").
338 
339 get_last_info(User, Server) ->
340     case mod_last:get_last_info(User, Server) of
341       {ok, TimeStamp, Status} -> {TimeStamp, Status};
342       not_found -> {"never", ""}
343     end.
344 
345 %%%
346 %%% Migration management
347 %%%
348 
349 
350 import_file(Path) ->
351     case jd2ejd:import_file(Path) of
352       ok -> {ok, ""};
353       {error, Reason} ->
354 	  String = io_lib:format("Can't import jabberd14 spool file ~p at node ~p: ~p",
355 				 [filename:absname(Path), node(), Reason]),
356 	  {cannot_import_file, String}
357     end.
358 
359 import_dir(Path) ->
360     case jd2ejd:import_dir(Path) of
361       ok -> {ok, ""};
362       {error, Reason} ->
363 	  String = io_lib:format("Can't import jabberd14 spool dir ~p at node ~p: ~p",
364 				 [filename:absname(Path), node(), Reason]),
365 	  {cannot_import_dir, String}
366     end.
367 
368 %%%
369 %%% Purge DB
370 %%%
371 
372 
373 delete_expired_messages() -> mod_offline:remove_expired_messages(), ok.
374 
375 delete_old_messages(Days) -> mod_offline:remove_old_messages(Days), ok.
376 
377 %%%
378 %%% Mnesia management
379 %%%
380 
381 
382 set_master("self") -> set_master(node());
383 set_master(NodeString) when is_list(NodeString) -> set_master(list_to_atom(NodeString));
384 set_master(Node) when is_atom(Node) ->
385     case mnesia:set_master_nodes([Node]) of
386       ok -> {ok, ""};
387       {error, Reason} ->
388 	  String = io_lib:format("Can't set master node ~p at node ~p:~n~p",
389 				 [Node, node(), Reason]),
390 	  {error, String}
391     end.
392 
393 backup_mnesia(Path) ->
394     case mnesia:backup(Path) of
395       ok -> {ok, ""};
396       {error, Reason} ->
397 	  String = io_lib:format("Can't store backup in ~p at node ~p: ~p",
398 				 [filename:absname(Path), node(), Reason]),
399 	  {cannot_backup, String}
400     end.
401 
402 restore_mnesia(Path) ->
403     case ejabberd_admin:restore(Path) of
404       {atomic, _} -> {ok, ""};
405       {error, Reason} ->
406 	  String = io_lib:format("Can't restore backup from ~p at node ~p: ~p",
407 				 [filename:absname(Path), node(), Reason]),
408 	  {cannot_restore, String};
409       {aborted, {no_exists, Table}} ->
410 	  String =
411 	      io_lib:format("Can't restore backup from ~p at node ~p: Table ~p does not exist.",
412 			    [filename:absname(Path), node(), Table]),
413 	  {table_not_exists, String};
414       {aborted, enoent} ->
415 	  String =
416 	      io_lib:format("Can't restore backup from ~p at node ~p: File not found.",
417 			    [filename:absname(Path), node()]),
418 	  {file_not_found, String}
419     end.
420 
421 %% Mnesia database restore
422 %% This function is called from ejabberd_ctl, ejabberd_web_admin and
423 %% mod_configure/adhoc
424 restore(Path) ->
425     mnesia:restore(Path, [{keep_tables, keep_tables()}, {default_op, skip_tables}]).
426 
427 %% This function return a list of tables that should be kept from a previous
428 %% version backup.
429 %% Obsolete tables or tables created by module who are no longer used are not
430 %% restored and are ignored.
431 keep_tables() ->
432     lists:flatten([acl, passwd, config, local_config, keep_modules_tables()]).
433 
434 %% Returns the list of modules tables in use, according to the list of actually
435 %% loaded modules
436 keep_modules_tables() ->
437     lists:map(fun (Module) -> module_tables(Module) end, gen_mod:loaded_modules(?MYNAME)).
438 
439 %% TODO: This mapping should probably be moved to a callback function in each
440 %% module.
441 %% Mapping between modules and their tables
442 module_tables(mod_announce) -> [motd, motd_users];
443 module_tables(mod_irc) -> [irc_custom];
444 module_tables(mod_last) -> [last_activity];
445 module_tables(mod_muc) -> [muc_room, muc_registered];
446 module_tables(mod_offline) -> [offline_msg];
447 module_tables(mod_privacy) -> [privacy];
448 module_tables(mod_private) -> [private_storage];
449 module_tables(mod_pubsub) -> [pubsub_node];
450 module_tables(mod_roster) -> [roster];
451 module_tables(mod_shared_roster) -> [sr_group, sr_user];
452 module_tables(mod_vcard) -> [vcard, vcard_search];
453 module_tables(_Other) -> [].
454 
455 get_local_tables() ->
456     Tabs1 = lists:delete(schema, mnesia:system_info(local_tables)),
457     Tabs = lists:filter(fun (T) ->
458 				case mnesia:table_info(T, storage_type) of
459 				  disc_copies -> true;
460 				  disc_only_copies -> true;
461 				  _ -> false
462 				end
463 			end,
464 			Tabs1),
465     Tabs.
466 
467 dump_mnesia(Path) -> Tabs = get_local_tables(), dump_tables(Path, Tabs).
468 
469 dump_table(Path, STable) -> Table = list_to_atom(STable), dump_tables(Path, [Table]).
470 
471 dump_tables(Path, Tables) ->
472     case dump_to_textfile(Path, Tables) of
473       ok -> {ok, ""};
474       {error, Reason} ->
475 	  String = io_lib:format("Can't store dump in ~p at node ~p: ~p",
476 				 [filename:absname(Path), node(), Reason]),
477 	  {cannot_dump, String}
478     end.
479 
480 dump_to_textfile(File) -> Tabs = get_local_tables(), dump_to_textfile(File, Tabs).
481 
482 dump_to_textfile(File, Tabs) ->
483     dump_to_textfile(mnesia:system_info(is_running), Tabs, file:open(File, [write])).
484 
485 dump_to_textfile(yes, Tabs, {ok, F}) ->
486     Defs = lists:map(fun (T) ->
487 			     {T,
488 			      [{record_name, mnesia:table_info(T, record_name)},
489 			       {attributes, mnesia:table_info(T, attributes)}]}
490 		     end,
491 		     Tabs),
492     io:format(F, "~p.~n", [{tables, Defs}]),
493     lists:foreach(fun (T) -> dump_tab(F, T) end, Tabs),
494     file:close(F);
495 dump_to_textfile(_, _, {ok, F}) -> file:close(F), {error, mnesia_not_running};
496 dump_to_textfile(_, _, {error, Reason}) -> {error, Reason}.
497 
498 dump_tab(F, T) ->
499     W = mnesia:table_info(T, wild_pattern),
500     {atomic, All} = mnesia:transaction(fun () -> mnesia:match_object(T, W, read) end),
501     lists:foreach(fun (Term) -> io:format(F, "~p.~n", [setelement(1, Term, T)]) end, All).
502 
503 load_mnesia(Path) ->
504     case mnesia:load_textfile(Path) of
505       {atomic, ok} -> {ok, ""};
506       {error, Reason} ->
507 	  String = io_lib:format("Can't load dump in ~p at node ~p: ~p",
508 				 [filename:absname(Path), node(), Reason]),
509 	  {cannot_load, String}
510     end.
511 
512 install_fallback_mnesia(Path) ->
513     case mnesia:install_fallback(Path) of
514       ok -> {ok, ""};
515       {error, Reason} ->
516 	  String = io_lib:format("Can't install fallback from ~p at node ~p: ~p",
517 				 [filename:absname(Path), node(), Reason]),
518 	  {cannot_fallback, String}
519     end.
520 
521 mnesia_change_nodename(FromString, ToString, Source, Target) ->
522     From = list_to_atom(FromString),
523     To = list_to_atom(ToString),
524     Switch = fun (Node) when Node == From ->
525 		     io:format("     - Replacing nodename: '~p' with: '~p'~n",
526 			       [From, To]),
527 		     To;
528 		 (Node) when Node == To ->
529 		                     %% throw({error, already_exists});
530 		     io:format("     - Node: '~p' will not be modified (it is already '~p')~n",
531 			       [Node, To]),
532 		     Node;
533 		 (Node) ->
534 		     io:format("     - Node: '~p' will not be modified (it is not '~p')~n",
535 			       [Node, From]),
536 		     Node
537 	     end,
538     Convert = fun ({schema, db_nodes, Nodes}, Acc) ->
539 		      io:format(" +++ db_nodes ~p~n", [Nodes]),
540 		      {[{schema, db_nodes, lists:map(Switch, Nodes)}], Acc};
541 		  ({schema, version, Version}, Acc) ->
542 		      io:format(" +++ version: ~p~n", [Version]),
543 		      {[{schema, version, Version}], Acc};
544 		  ({schema, cookie, Cookie}, Acc) ->
545 		      io:format(" +++ cookie: ~p~n", [Cookie]),
546 		      {[{schema, cookie, Cookie}], Acc};
547 		  ({schema, Tab, CreateList}, Acc) ->
548 		      io:format("~n * Checking table: '~p'~n", [Tab]),
549 		      Keys = [ram_copies, disc_copies, disc_only_copies],
550 		      OptSwitch = fun ({Key, Val}) ->
551 					  case lists:member(Key, Keys) of
552 					    true ->
553 						io:format("   + Checking key: '~p'~n",
554 							  [Key]),
555 						{Key, lists:map(Switch, Val)};
556 					    false -> {Key, Val}
557 					  end
558 				  end,
559 		      Res = {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc},
560 		      Res;
561 		  (Other, Acc) -> {[Other], Acc}
562 	      end,
563     mnesia:traverse_backup(Source, Target, Convert, switched).