1%% ``Licensed under the Apache License, Version 2.0 (the "License");
2%% you may not use this file except in compliance with the License.
3%% You may obtain a copy of the License at
4%%
5%%     http://www.apache.org/licenses/LICENSE-2.0
6%%
7%% Unless required by applicable law or agreed to in writing, software
8%% distributed under the License is distributed on an "AS IS" BASIS,
9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10%% See the License for the specific language governing permissions and
11%% limitations under the License.
12%%
13%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
14%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
15%% AB. All Rights Reserved.''
16%%
17%%     $Id: mnesia_bup.erl,v 1.1 2008/12/17 09:53:37 mikpe Exp $
18-module(mnesia_bup).
19-export([
20	 %% Public interface
21	 iterate/4,
22	 read_schema/2,
23	 fallback_bup/0,
24	 fallback_exists/0,
25	 tm_fallback_start/1,
26	 create_schema/1,
27	 install_fallback/1,
28	 install_fallback/2,
29	 uninstall_fallback/0,
30	 uninstall_fallback/1,
31	 traverse_backup/4,
32	 traverse_backup/6,
33	 make_initial_backup/3,
34	 fallback_to_schema/0,
35	 lookup_schema/2,
36	 schema2bup/1,
37	 refresh_cookie/2,
38
39	 %% Internal
40	 fallback_receiver/2,
41	 install_fallback_master/2,
42	 uninstall_fallback_master/2,
43	 local_uninstall_fallback/2,
44	 do_traverse_backup/7,
45	 trav_apply/4
46	]).
47
48-include("mnesia.hrl").
49-import(mnesia_lib, [verbose/2, dbg_out/2]).
50
51-record(restore, {mode, bup_module, bup_data}).
52
53-record(fallback_args, {opaque,
54			scope = global,
55			module = mnesia_monitor:get_env(backup_module),
56			use_default_dir = true,
57			mnesia_dir,
58			fallback_bup,
59			fallback_tmp,
60			skip_tables = [],
61			keep_tables = [],
62			default_op = keep_tables
63		       }).
64
65%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
66%% Backup iterator
67
68%% Reads schema section and iterates over all records in a backup.
69%%
70%% Fun(BunchOfRecords, Header, Schema, Acc) is applied when a suitable amount
71%% of records has been collected.
72%%
73%% BunchOfRecords will be [] when the iteration is done.
74iterate(Mod, Fun, Opaque, Acc) ->
75    R = #restore{bup_module = Mod, bup_data = Opaque},
76    case catch read_schema_section(R) of
77	{error, Reason} ->
78	    {error, Reason};
79	{R2, {Header, Schema, Rest}} ->
80	    case catch iter(R2, Header, Schema, Fun, Acc, Rest) of
81		{ok, R3, Res} ->
82		    catch safe_apply(R3, close_read, [R3#restore.bup_data]),
83		    {ok, Res};
84		{error, Reason} ->
85		    catch safe_apply(R2, close_read, [R2#restore.bup_data]),
86		    {error, Reason};
87		{'EXIT', Pid, Reason} ->
88		    catch safe_apply(R2, close_read, [R2#restore.bup_data]),
89		    {error, {'EXIT', Pid, Reason}};
90		{'EXIT', Reason} ->
91		    catch safe_apply(R2, close_read, [R2#restore.bup_data]),
92		    {error, {'EXIT', Reason}}
93	    end
94    end.
95
96iter(R, Header, Schema, Fun, Acc, []) ->
97    case safe_apply(R, read, [R#restore.bup_data]) of
98	{R2, []} ->
99	    Res = Fun([], Header, Schema, Acc),
100	    {ok, R2, Res};
101	{R2, BupItems} ->
102	    iter(R2, Header, Schema, Fun, Acc, BupItems)
103    end;
104iter(R, Header, Schema, Fun, Acc, BupItems) ->
105    Acc2 = Fun(BupItems, Header, Schema, Acc),
106    iter(R, Header, Schema, Fun, Acc2, []).
107
108safe_apply(R, write, [_, Items]) when Items == [] ->
109    R;
110safe_apply(R, What, Args) ->
111    Abort = fun(Re) -> abort_restore(R, What, Args, Re) end,
112    receive
113	{'EXIT', Pid, Re} -> Abort({'EXIT', Pid, Re})
114    after 0 ->
115	    Mod = R#restore.bup_module,
116	    case catch apply(Mod, What, Args) of
117		{ok, Opaque, Items} when What == read ->
118		    {R#restore{bup_data = Opaque}, Items};
119		{ok, Opaque}  when What /= read->
120		    R#restore{bup_data = Opaque};
121		{error, Re} ->
122		    Abort(Re);
123		Re ->
124		    Abort(Re)
125	    end
126    end.
127
128abort_restore(R, What, Args, Reason) ->
129    Mod = R#restore.bup_module,
130    Opaque = R#restore.bup_data,
131    dbg_out("Restore aborted. ~p:~p~p -> ~p~n",
132	    [Mod, What, Args, Reason]),
133    catch apply(Mod, close_read, [Opaque]),
134    throw({error, Reason}).
135
136fallback_to_schema() ->
137    Fname = fallback_bup(),
138    fallback_to_schema(Fname).
139
140fallback_to_schema(Fname) ->
141    Mod = mnesia_backup,
142    case read_schema(Mod, Fname) of
143	{error, Reason} ->
144	    {error, Reason};
145	Schema ->
146	    case catch lookup_schema(schema, Schema) of
147		{error, _} ->
148		    {error, "No schema in fallback"};
149		List ->
150		    {ok, fallback, List}
151	    end
152    end.
153
154%% Opens Opaque reads schema and then close
155read_schema(Mod, Opaque) ->
156    R = #restore{bup_module = Mod, bup_data = Opaque},
157    case catch read_schema_section(R) of
158	{error, Reason} ->
159	    {error, Reason};
160	{R2, {_Header, Schema, _}} ->
161	    catch safe_apply(R2, close_read, [R2#restore.bup_data]),
162	    Schema
163    end.
164
165%% Open backup media and extract schema
166%% rewind backup media and leave it open
167%% Returns {R, {Header, Schema}}
168read_schema_section(R) ->
169    case catch do_read_schema_section(R) of
170	{'EXIT', Reason} ->
171	    catch safe_apply(R, close_read, [R#restore.bup_data]),
172	    {error, {'EXIT', Reason}};
173	{error, Reason} ->
174	    catch safe_apply(R, close_read, [R#restore.bup_data]),
175	    {error, Reason};
176	{R2, {H, Schema, Rest}} ->
177	    Schema2 = convert_schema(H#log_header.log_version, Schema),
178	    {R2, {H, Schema2, Rest}}
179    end.
180
181do_read_schema_section(R) ->
182    R2 = safe_apply(R, open_read, [R#restore.bup_data]),
183    {R3, RawSchema} = safe_apply(R2, read, [R2#restore.bup_data]),
184    do_read_schema_section(R3, verify_header(RawSchema), []).
185
186do_read_schema_section(R, {ok, B, C, []}, Acc) ->
187    case safe_apply(R, read, [R#restore.bup_data]) of
188	{R2, []} ->
189	    {R2, {B, Acc, []}};
190	{R2, RawSchema} ->
191	    do_read_schema_section(R2, {ok, B, C, RawSchema}, Acc)
192    end;
193
194do_read_schema_section(R, {ok, B, C, [Head | Tail]}, Acc)
195        when element(1, Head) == schema ->
196    do_read_schema_section(R, {ok, B, C, Tail}, Acc ++ [Head]);
197
198do_read_schema_section(R, {ok, B, _C, Rest}, Acc) ->
199    {R, {B, Acc, Rest}};
200
201do_read_schema_section(_R, {error, Reason}, _Acc) ->
202    {error, Reason}.
203
204verify_header([H | RawSchema])  when record(H, log_header) ->
205    Current = mnesia_log:backup_log_header(),
206    if
207	H#log_header.log_kind == Current#log_header.log_kind ->
208	    Versions = ["0.1", "1.1", Current#log_header.log_version],
209	    case lists:member(H#log_header.log_version, Versions) of
210		true ->
211		    {ok, H, Current, RawSchema};
212		false ->
213		    {error, {"Bad header version. Cannot be used as backup.", H}}
214	    end;
215	true ->
216	    {error, {"Bad kind of header. Cannot be used as backup.", H}}
217    end;
218verify_header(RawSchema) ->
219    {error, {"Missing header. Cannot be used as backup.", catch hd(RawSchema)}}.
220
221refresh_cookie(Schema, NewCookie) ->
222    case lists:keysearch(schema, 2, Schema) of
223	{value, {schema, schema, List}} ->
224	    Cs = mnesia_schema:list2cs(List),
225	    Cs2 = Cs#cstruct{cookie = NewCookie},
226	    Item = {schema, schema, mnesia_schema:cs2list(Cs2)},
227	    lists:keyreplace(schema, 2, Schema, Item);
228
229	false ->
230	    Reason = "No schema found. Cannot be used as backup.",
231	    throw({error, {Reason, Schema}})
232    end.
233
234%% Convert schema items from an external backup
235%% If backup format is the latest, no conversion is needed
236%% All supported backup formats should have their converters
237%% here as separate function clauses.
238convert_schema("0.1", Schema) ->
239    convert_0_1(Schema);
240convert_schema("1.1", Schema) ->
241    %% The new backup format is a pure extension of the old one
242    Current = mnesia_log:backup_log_header(),
243    convert_schema(Current#log_header.log_version, Schema);
244convert_schema(Latest, Schema) ->
245    H = mnesia_log:backup_log_header(),
246    if
247	H#log_header.log_version == Latest ->
248	    Schema;
249	true ->
250	    Reason = "Bad backup header version. Cannot convert schema.",
251	    throw({error, {Reason, H}})
252    end.
253
254%% Backward compatibility for 0.1
255convert_0_1(Schema) ->
256    case lists:keysearch(schema, 2, Schema) of
257	{value, {schema, schema, List}} ->
258	    Schema2 = lists:keydelete(schema, 2, Schema),
259	    Cs = mnesia_schema:list2cs(List),
260	    convert_0_1(Schema2, [], Cs);
261	false ->
262	    List = mnesia_schema:get_initial_schema(disc_copies, [node()]),
263	    Cs = mnesia_schema:list2cs(List),
264	    convert_0_1(Schema, [], Cs)
265    end.
266
267convert_0_1([{schema, cookie, Cookie} | Schema], Acc, Cs) ->
268    convert_0_1(Schema, Acc, Cs#cstruct{cookie = Cookie});
269convert_0_1([{schema, db_nodes, DbNodes} | Schema], Acc, Cs) ->
270    convert_0_1(Schema, Acc, Cs#cstruct{disc_copies = DbNodes});
271convert_0_1([{schema, version, Version} | Schema], Acc, Cs) ->
272    convert_0_1(Schema, Acc, Cs#cstruct{version = Version});
273convert_0_1([{schema, Tab, Def} | Schema], Acc, Cs) ->
274    Head =
275	case lists:keysearch(index, 1, Def) of
276	    {value, {index, PosList}} ->
277		%% Remove the snmp "index"
278		P = PosList -- [snmp],
279		Def2 = lists:keyreplace(index, 1, Def, {index, P}),
280		{schema, Tab, Def2};
281	    false ->
282		{schema, Tab, Def}
283	end,
284    convert_0_1(Schema, [Head | Acc], Cs);
285convert_0_1([Head | Schema], Acc, Cs) ->
286    convert_0_1(Schema, [Head | Acc], Cs);
287convert_0_1([], Acc, Cs) ->
288    [schema2bup({schema, schema, Cs}) | Acc].
289
290%% Returns Val or throw error
291lookup_schema(Key, Schema) ->
292    case lists:keysearch(Key, 2, Schema) of
293	{value, {schema, Key, Val}} -> Val;
294	false -> throw({error, {"Cannot lookup", Key}})
295    end.
296
297%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
298%% Backup compatibility
299
300%% Convert internal schema items to backup dito
301schema2bup({schema, Tab}) ->
302    {schema, Tab};
303schema2bup({schema, Tab, TableDef}) ->
304    {schema, Tab, mnesia_schema:cs2list(TableDef)}.
305
306%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
307%% Create schema on the given nodes
308%% Requires that old schemas has been deleted
309%% Returns ok | {error, Reason}
310create_schema([]) ->
311    create_schema([node()]);
312create_schema(Ns) when list(Ns) ->
313    case is_set(Ns) of
314	true ->
315	    create_schema(Ns, mnesia_schema:ensure_no_schema(Ns));
316	false ->
317	    {error, {combine_error, Ns}}
318    end;
319create_schema(Ns) ->
320    {error, {badarg, Ns}}.
321
322is_set(List) when list(List) ->
323    ordsets:is_set(lists:sort(List));
324is_set(_) ->
325    false.
326
327create_schema(Ns, ok) ->
328    %% Ensure that we access the intended Mnesia
329    %% directory. This function may not be called
330    %% during startup since it will cause the
331    %% application_controller to get into deadlock
332    case mnesia_lib:ensure_loaded(?APPLICATION) of
333	ok ->
334	    case mnesia_monitor:get_env(schema_location) of
335		ram ->
336		    {error, {has_no_disc, node()}};
337		_ ->
338		    case mnesia_schema:opt_create_dir(true, mnesia_lib:dir()) of
339			{error, What} ->
340			    {error, What};
341			ok ->
342			    Mod = mnesia_backup,
343			    Str = mk_str(),
344			    File = mnesia_lib:dir(Str),
345			    file:delete(File),
346			    case catch make_initial_backup(Ns, File, Mod) of
347				{ok, _Res} ->
348				    case do_install_fallback(File, Mod) of
349					ok ->
350					    file:delete(File),
351					    ok;
352					{error, Reason} ->
353					    {error, Reason}
354				    end;
355				{error, Reason} ->
356				    {error, Reason}
357			    end
358		    end
359	    end;
360	{error, Reason} ->
361	    {error, Reason}
362    end;
363create_schema(_Ns, {error, Reason}) ->
364    {error, Reason};
365create_schema(_Ns, Reason) ->
366    {error, Reason}.
367
368mk_str() ->
369    Now = [integer_to_list(I) || I <- tuple_to_list(now())],
370    lists:concat([node()] ++ Now ++ ".TMP").
371
372make_initial_backup(Ns, Opaque, Mod) ->
373    Schema = [{schema, schema, mnesia_schema:get_initial_schema(disc_copies, Ns)}],
374    O2 = do_apply(Mod, open_write, [Opaque], Opaque),
375    O3 = do_apply(Mod, write, [O2, [mnesia_log:backup_log_header()]], O2),
376    O4 = do_apply(Mod, write, [O3, Schema], O3),
377    O5 = do_apply(Mod, commit_write, [O4], O4),
378    {ok, O5}.
379
380do_apply(_, write, [_, Items], Opaque) when Items == [] ->
381    Opaque;
382do_apply(Mod, What, Args, _Opaque) ->
383    case catch apply(Mod, What, Args) of
384	{ok, Opaque2} ->  Opaque2;
385	{error, Reason} -> throw({error, Reason});
386	{'EXIT', Reason} -> throw({error, {'EXIT', Reason}})
387    end.
388
389%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
390%% Restore
391
392%% Restore schema and possibly other tables from a backup
393%% and replicate them to the necessary nodes
394%% Requires that old schemas has been deleted
395%% Returns ok | {error, Reason}
396install_fallback(Opaque) ->
397    install_fallback(Opaque, []).
398
399install_fallback(Opaque, Args) ->
400    %% Ensure that we access the intended Mnesia
401    %% directory. This function may not be called
402    %% during startup since it will cause the
403    %% application_controller to get into deadlock
404    case mnesia_lib:ensure_loaded(?APPLICATION) of
405	ok ->
406	    do_install_fallback(Opaque, Args);
407	{error, Reason} ->
408	    {error, Reason}
409    end.
410
411do_install_fallback(Opaque,  Mod) when atom(Mod) ->
412    do_install_fallback(Opaque, [{module, Mod}]);
413do_install_fallback(Opaque, Args) when list(Args) ->
414    case check_fallback_args(Args, #fallback_args{opaque = Opaque}) of
415	{ok, FA} ->
416	    do_install_fallback(FA);
417	{error, Reason} ->
418	    {error, Reason}
419    end;
420do_install_fallback(_Opaque, Args) ->
421    {error, {badarg, Args}}.
422
423check_fallback_args([Arg | Tail], FA) ->
424    case catch check_fallback_arg_type(Arg, FA) of
425	{'EXIT', _Reason} ->
426	    {error, {badarg, Arg}};
427	FA2 ->
428	    check_fallback_args(Tail, FA2)
429    end;
430check_fallback_args([], FA) ->
431    {ok, FA}.
432
433check_fallback_arg_type(Arg, FA) ->
434    case Arg of
435	{scope, global} ->
436	    FA#fallback_args{scope = global};
437	{scope, local} ->
438	    FA#fallback_args{scope = local};
439	{module, Mod} ->
440	    Mod2 = mnesia_monitor:do_check_type(backup_module, Mod),
441	    FA#fallback_args{module = Mod2};
442	{mnesia_dir, Dir} ->
443	    FA#fallback_args{mnesia_dir = Dir,
444			     use_default_dir = false};
445	{keep_tables, Tabs} ->
446	    atom_list(Tabs),
447	    FA#fallback_args{keep_tables = Tabs};
448	{skip_tables, Tabs} ->
449	    atom_list(Tabs),
450	    FA#fallback_args{skip_tables = Tabs};
451	{default_op, keep_tables} ->
452	    FA#fallback_args{default_op = keep_tables};
453	{default_op, skip_tables} ->
454	    FA#fallback_args{default_op = skip_tables}
455    end.
456
457atom_list([H | T]) when atom(H) ->
458    atom_list(T);
459atom_list([]) ->
460    ok.
461
462do_install_fallback(FA) ->
463    Pid = spawn_link(?MODULE, install_fallback_master, [self(), FA]),
464    Res =
465	receive
466	    {'EXIT', Pid, Reason} -> % if appl has trapped exit
467		{error, {'EXIT', Reason}};
468	    {Pid, Res2} ->
469		case Res2 of
470		    {ok, _} ->
471			ok;
472		    {error, Reason} ->
473			{error, {"Cannot install fallback", Reason}}
474		end
475	end,
476    Res.
477
478install_fallback_master(ClientPid, FA) ->
479    process_flag(trap_exit, true),
480    State = {start, FA},
481    Opaque = FA#fallback_args.opaque,
482    Mod = FA#fallback_args.module,
483    Res = (catch iterate(Mod, fun restore_recs/4, Opaque, State)),
484    unlink(ClientPid),
485    ClientPid ! {self(), Res},
486    exit(shutdown).
487
488restore_recs(_, _, _, stop) ->
489    throw({error, "restore_recs already stopped"});
490
491restore_recs(Recs, Header, Schema, {start, FA}) ->
492    %% No records in backup
493    Schema2 = convert_schema(Header#log_header.log_version, Schema),
494    CreateList = lookup_schema(schema, Schema2),
495    case catch mnesia_schema:list2cs(CreateList) of
496	{'EXIT', Reason} ->
497	    throw({error, {"Bad schema in restore_recs", Reason}});
498	Cs ->
499	    Ns = get_fallback_nodes(FA, Cs#cstruct.disc_copies),
500	    global:set_lock({{mnesia_table_lock, schema}, self()}, Ns, infinity),
501	    Args = [self(), FA],
502	    Pids = [spawn_link(N, ?MODULE, fallback_receiver, Args) || N <- Ns],
503	    send_fallback(Pids, {start, Header, Schema2}),
504	    Res = restore_recs(Recs, Header, Schema2, Pids),
505	    global:del_lock({{mnesia_table_lock, schema}, self()}, Ns),
506	    Res
507    end;
508
509restore_recs([], _Header, _Schema, Pids) ->
510    send_fallback(Pids, swap),
511    send_fallback(Pids, stop),
512    stop;
513
514restore_recs(Recs, _, _, Pids) ->
515    send_fallback(Pids, {records, Recs}),
516    Pids.
517
518get_fallback_nodes(FA, Ns) ->
519    This = node(),
520    case lists:member(This, Ns) of
521	true ->
522	    case FA#fallback_args.scope of
523		global ->  Ns;
524		local -> [This]
525	    end;
526	false ->
527	    throw({error, {"No disc resident schema on local node", Ns}})
528    end.
529
530send_fallback(Pids, Msg) when list(Pids), Pids /= [] ->
531    lists:foreach(fun(Pid) -> Pid ! {self(), Msg} end, Pids),
532    rec_answers(Pids, []).
533
534rec_answers([], Acc) ->
535    case {lists:keysearch(error, 1, Acc), mnesia_lib:uniq(Acc)} of
536	{{value, {error, Val}}, _} -> throw({error, Val});
537	{_, [SameAnswer]} -> SameAnswer;
538	{_, Other} -> throw({error, {"Different answers", Other}})
539    end;
540rec_answers(Pids, Acc) ->
541    receive
542	{'EXIT', Pid, stopped} ->
543	    Pids2 = lists:delete(Pid, Pids),
544	    rec_answers(Pids2, [stopped|Acc]);
545	{'EXIT', Pid, Reason} ->
546	    Pids2 = lists:delete(Pid, Pids),
547	    rec_answers(Pids2, [{error, {'EXIT', Pid, Reason}}|Acc]);
548	{Pid, Reply} ->
549	    Pids2 = lists:delete(Pid, Pids),
550	    rec_answers(Pids2, [Reply|Acc])
551    end.
552
553fallback_exists() ->
554    Fname = fallback_bup(),
555    fallback_exists(Fname).
556
557fallback_exists(Fname) ->
558    case mnesia_monitor:use_dir() of
559	true ->
560	    mnesia_lib:exists(Fname);
561	false ->
562	    case ?catch_val(active_fallback) of
563		{'EXIT', _} -> false;
564		Bool -> Bool
565	    end
566    end.
567
568fallback_name() -> "FALLBACK.BUP".
569fallback_bup() -> mnesia_lib:dir(fallback_name()).
570
571fallback_tmp_name() -> "FALLBACK.TMP".
572%% fallback_full_tmp_name() -> mnesia_lib:dir(fallback_tmp_name()).
573
574fallback_receiver(Master, FA) ->
575    process_flag(trap_exit, true),
576
577    case catch register(mnesia_fallback, self()) of
578	{'EXIT', _} ->
579	    Reason = {already_exists, node()},
580	    local_fallback_error(Master, Reason);
581	true ->
582	    FA2 = check_fallback_dir(Master, FA),
583	    Bup = FA2#fallback_args.fallback_bup,
584	    case mnesia_lib:exists(Bup) of
585		true ->
586		    Reason2 = {already_exists, node()},
587		    local_fallback_error(Master, Reason2);
588		false ->
589		    Mod = mnesia_backup,
590		    Tmp = FA2#fallback_args.fallback_tmp,
591		    R = #restore{mode = replace,
592				 bup_module = Mod,
593				 bup_data = Tmp},
594		    file:delete(Tmp),
595		    case catch fallback_receiver_loop(Master, R, FA2, schema) of
596			{error, Reason} ->
597			    local_fallback_error(Master, Reason);
598			Other ->
599			    exit(Other)
600		    end
601	    end
602    end.
603
604local_fallback_error(Master, Reason) ->
605    Master ! {self(), {error, Reason}},
606    unlink(Master),
607    exit(Reason).
608
609check_fallback_dir(Master, FA) ->
610    case mnesia:system_info(schema_location) of
611	ram ->
612	    Reason = {has_no_disc, node()},
613	    local_fallback_error(Master, Reason);
614	_ ->
615	    Dir = check_fallback_dir_arg(Master, FA),
616	    Bup = filename:join([Dir, fallback_name()]),
617	    Tmp = filename:join([Dir, fallback_tmp_name()]),
618	    FA#fallback_args{fallback_bup = Bup,
619			     fallback_tmp = Tmp,
620			     mnesia_dir = Dir}
621    end.
622
623check_fallback_dir_arg(Master, FA) ->
624    case FA#fallback_args.use_default_dir of
625	true ->
626	    mnesia_lib:dir();
627	false when FA#fallback_args.scope == local ->
628	    Dir = FA#fallback_args.mnesia_dir,
629	    case catch mnesia_monitor:do_check_type(dir, Dir) of
630		{'EXIT', _R} ->
631		    Reason = {badarg, {dir, Dir}, node()},
632		    local_fallback_error(Master, Reason);
633		AbsDir->
634		    AbsDir
635	    end;
636	false when FA#fallback_args.scope == global ->
637	    Reason = {combine_error, global, dir, node()},
638	    local_fallback_error(Master, Reason)
639    end.
640
641fallback_receiver_loop(Master, R, FA, State) ->
642    receive
643	{Master, {start, Header, Schema}} when State == schema ->
644	    Dir = FA#fallback_args.mnesia_dir,
645	    throw_bad_res(ok, mnesia_schema:opt_create_dir(true, Dir)),
646	    R2 = safe_apply(R, open_write, [R#restore.bup_data]),
647	    R3 = safe_apply(R2, write, [R2#restore.bup_data, [Header]]),
648	    BupSchema = [schema2bup(S) || S <- Schema],
649	    R4 = safe_apply(R3, write, [R3#restore.bup_data, BupSchema]),
650	    Master ! {self(), ok},
651	    fallback_receiver_loop(Master, R4, FA, records);
652
653	{Master, {records, Recs}} when State == records ->
654	    R2 = safe_apply(R, write, [R#restore.bup_data, Recs]),
655	    Master ! {self(), ok},
656	    fallback_receiver_loop(Master, R2, FA, records);
657
658	{Master, swap} when State /= schema ->
659	    ?eval_debug_fun({?MODULE, fallback_receiver_loop, pre_swap}, []),
660	    safe_apply(R, commit_write, [R#restore.bup_data]),
661	    Bup = FA#fallback_args.fallback_bup,
662	    Tmp = FA#fallback_args.fallback_tmp,
663	    throw_bad_res(ok, file:rename(Tmp, Bup)),
664	    catch mnesia_lib:set(active_fallback, true),
665	    ?eval_debug_fun({?MODULE, fallback_receiver_loop, post_swap}, []),
666	    Master ! {self(), ok},
667	    fallback_receiver_loop(Master, R, FA, stop);
668
669	{Master, stop} when State == stop ->
670	    stopped;
671
672	Msg ->
673	    safe_apply(R, abort_write, [R#restore.bup_data]),
674	    Tmp = FA#fallback_args.fallback_tmp,
675	    file:delete(Tmp),
676	    throw({error, "Unexpected msg fallback_receiver_loop", Msg})
677    end.
678
679throw_bad_res(Expected, Expected) -> Expected;
680throw_bad_res(_Expected, {error, Actual}) -> throw({error, Actual});
681throw_bad_res(_Expected, Actual) -> throw({error, Actual}).
682
683-record(local_tab, {name, storage_type, dets_args, open, close, add, record_name}).
684
685tm_fallback_start(IgnoreFallback) ->
686    mnesia_schema:lock_schema(),
687    Res = do_fallback_start(fallback_exists(), IgnoreFallback),
688    mnesia_schema: unlock_schema(),
689    case Res of
690	ok -> ok;
691	{error, Reason} -> exit(Reason)
692    end.
693
694do_fallback_start(false, _IgnoreFallback) ->
695    ok;
696do_fallback_start(true, true) ->
697    verbose("Ignoring fallback at startup, but leaving it active...~n", []),
698    mnesia_lib:set(active_fallback, true),
699    ok;
700do_fallback_start(true, false) ->
701    verbose("Starting from fallback...~n", []),
702
703    Fname = fallback_bup(),
704    Mod = mnesia_backup,
705    Ets = ?ets_new_table(mnesia_local_tables, [set, public, {keypos, 2}]),
706    case catch iterate(Mod, fun restore_tables/4, Fname, {start, Ets}) of
707	{ok, Res} ->
708	    case Res of
709		{local, _, LT} ->  %% Close the last file
710		    (LT#local_tab.close)(LT);
711		_ ->
712		    ignore
713	    end,
714	    List = ?ets_match_object(Ets, '_'),
715	    Tabs = [L#local_tab.name || L <- List, L#local_tab.name /= schema],
716	    ?ets_delete_table(Ets),
717	    mnesia_lib:swap_tmp_files(Tabs),
718	    catch dets:close(schema),
719	    Tmp = mnesia_lib:tab2tmp(schema),
720	    Dat = mnesia_lib:tab2dat(schema),
721	    case file:rename(Tmp, Dat) of
722		ok ->
723		    file:delete(Fname),
724		    ok;
725		{error, Reason} ->
726		    file:delete(Tmp),
727		    {error, {"Cannot start from fallback. Rename error.", Reason}}
728	    end;
729	{error, Reason} ->
730	    {error, {"Cannot start from fallback", Reason}};
731	{'EXIT', Reason} ->
732	    {error, {"Cannot start from fallback", Reason}}
733    end.
734
735restore_tables(Recs, Header, Schema, {start, LocalTabs}) ->
736    Dir = mnesia_lib:dir(),
737    OldDir = filename:join([Dir, "OLD_DIR"]),
738    mnesia_schema:purge_dir(OldDir, []),
739    mnesia_schema:purge_dir(Dir, [fallback_name()]),
740    init_dat_files(Schema, LocalTabs),
741    State = {new, LocalTabs},
742    restore_tables(Recs, Header, Schema, State);
743restore_tables([Rec | Recs], Header, Schema, {new, LocalTabs}) ->
744    Tab = element(1, Rec),
745    case ?ets_lookup(LocalTabs, Tab) of
746	[] ->
747	    State = {not_local, LocalTabs, Tab},
748	    restore_tables(Recs, Header, Schema, State);
749	[L] when record(L, local_tab) ->
750	    (L#local_tab.open)(Tab, L),
751	    State = {local, LocalTabs, L},
752	    restore_tables([Rec | Recs], Header, Schema, State)
753    end;
754restore_tables([Rec | Recs], Header, Schema, S = {not_local, LocalTabs, PrevTab}) ->
755    Tab = element(1, Rec),
756    if
757	Tab == PrevTab ->
758	    restore_tables(Recs, Header, Schema, S);
759	true ->
760	    State = {new, LocalTabs},
761	    restore_tables([Rec | Recs], Header, Schema, State)
762    end;
763restore_tables([Rec | Recs], Header, Schema, State = {local, LocalTabs, L}) ->
764    Tab = element(1, Rec),
765    if
766	Tab == L#local_tab.name ->
767	    Key = element(2, Rec),
768	    (L#local_tab.add)(Tab, Key, Rec, L),
769	    restore_tables(Recs, Header, Schema, State);
770	true ->
771	    (L#local_tab.close)(L),
772	    NState = {new, LocalTabs},
773	    restore_tables([Rec | Recs], Header, Schema, NState)
774    end;
775restore_tables([], _Header, _Schema, State) ->
776    State.
777
778%% Creates all necessary dat files and inserts
779%% the table definitions in the schema table
780%%
781%% Returns a list of local_tab tuples for all local tables
782init_dat_files(Schema, LocalTabs) ->
783    Fname = mnesia_lib:tab2tmp(schema),
784    Args = [{file, Fname}, {keypos, 2}, {type, set}],
785    case dets:open_file(schema, Args) of % Assume schema lock
786	{ok, _} ->
787	    create_dat_files(Schema, LocalTabs),
788	    dets:close(schema),
789	    LocalTab = #local_tab{name = schema,
790				  storage_type = disc_copies,
791				  dets_args = Args,
792				  open   = fun open_media/2,
793				  close  = fun close_media/1,
794				  add    = fun add_to_media/4,
795				  record_name = schema},
796	    ?ets_insert(LocalTabs, LocalTab);
797	{error, Reason} ->
798	    throw({error, {"Cannot open file", schema, Args, Reason}})
799    end.
800
801create_dat_files([{schema, schema, TabDef} | Tail], LocalTabs) ->
802    ok = dets:insert(schema, {schema, schema, TabDef}),
803    create_dat_files(Tail, LocalTabs);
804create_dat_files([{schema, Tab, TabDef} | Tail], LocalTabs) ->
805    Cs =  mnesia_schema:list2cs(TabDef),
806    ok = dets:insert(schema, {schema, Tab, TabDef}),
807    RecName = Cs#cstruct.record_name,
808    case mnesia_lib:cs_to_storage_type(node(), Cs) of
809	unknown ->
810	    cleanup_dat_file(Tab),
811	    create_dat_files(Tail, LocalTabs);
812	disc_only_copies ->
813	    Fname = mnesia_lib:tab2tmp(Tab),
814	    Args = [{file, Fname}, {keypos, 2},
815		    {type, mnesia_lib:disk_type(Tab, Cs#cstruct.type)}],
816	    case mnesia_lib:dets_sync_open(Tab, Args) of
817		{ok, _} ->
818		    mnesia_lib:dets_sync_close(Tab),
819		    LocalTab = #local_tab{name = Tab,
820					  storage_type = disc_only_copies,
821					  dets_args = Args,
822					  open   = fun open_media/2,
823					  close  = fun close_media/1,
824					  add    = fun add_to_media/4,
825					  record_name = RecName},
826		    ?ets_insert(LocalTabs, LocalTab),
827		    create_dat_files(Tail, LocalTabs);
828		{error, Reason} ->
829		    throw({error, {"Cannot open file", Tab, Args, Reason}})
830	    end;
831	ram_copies ->
832	    %% Create .DCD if needed in open_media in case any ram_copies
833	    %% are backed up.
834	    LocalTab = #local_tab{name = Tab,
835				  storage_type = ram_copies,
836				  dets_args = ignore,
837				  open   = fun open_media/2,
838				  close  = fun close_media/1,
839				  add    = fun add_to_media/4,
840				  record_name = RecName},
841	    ?ets_insert(LocalTabs, LocalTab),
842	    create_dat_files(Tail, LocalTabs);
843	Storage ->
844	    %% Create DCD
845	    Fname = mnesia_lib:tab2dcd(Tab),
846	    file:delete(Fname),
847	    Log = mnesia_log:open_log(fallback_tab, mnesia_log:dcd_log_header(),
848				      Fname, false),
849	    LocalTab = #local_tab{name = Tab,
850				  storage_type = Storage,
851				  dets_args = ignore,
852				  open   = fun open_media/2,
853				  close  = fun close_media/1,
854				  add    = fun add_to_media/4,
855				  record_name = RecName},
856	    mnesia_log:close_log(Log),
857	    ?ets_insert(LocalTabs, LocalTab),
858	    create_dat_files(Tail, LocalTabs)
859    end;
860create_dat_files([{schema, Tab} | Tail], LocalTabs) ->
861    cleanup_dat_file(Tab),
862    create_dat_files(Tail, LocalTabs);
863create_dat_files([], _LocalTabs) ->
864    ok.
865
866cleanup_dat_file(Tab) ->
867    ok = dets:delete(schema, {schema, Tab}),
868    mnesia_lib:cleanup_tmp_files([Tab]).
869
870open_media(Tab, LT) ->
871    case LT#local_tab.storage_type of
872	disc_only_copies ->
873	    Args = LT#local_tab.dets_args,
874	    case mnesia_lib:dets_sync_open(Tab, Args) of
875		{ok, _} -> ok;
876		{error, Reason} ->
877		    throw({error, {"Cannot open file", Tab, Args, Reason}})
878	    end;
879	ram_copies ->
880	    %% Create .DCD as ram_copies backed up.
881	    FnameDCD = mnesia_lib:tab2dcd(Tab),
882	    file:delete(FnameDCD),
883	    Log = mnesia_log:open_log(fallback_tab,
884				      mnesia_log:dcd_log_header(),
885				      FnameDCD, false),
886	    mnesia_log:close_log(Log),
887
888	    %% Create .DCL
889	    Fname = mnesia_lib:tab2dcl(Tab),
890	    file:delete(Fname),
891	    mnesia_log:open_log({?MODULE,Tab},
892				mnesia_log:dcl_log_header(),
893				Fname, false, false,
894				read_write);
895	_ ->
896	    Fname = mnesia_lib:tab2dcl(Tab),
897	    file:delete(Fname),
898	    mnesia_log:open_log({?MODULE,Tab},
899				mnesia_log:dcl_log_header(),
900				Fname, false, false,
901				read_write)
902    end.
903close_media(L) ->
904    Tab = L#local_tab.name,
905    case L#local_tab.storage_type of
906	disc_only_copies ->
907	    mnesia_lib:dets_sync_close(Tab);
908	_ ->
909	    mnesia_log:close_log({?MODULE,Tab})
910    end.
911
912add_to_media(Tab, Key, Rec, L) ->
913    RecName = L#local_tab.record_name,
914    case L#local_tab.storage_type of
915	disc_only_copies ->
916	    case Rec of
917		{Tab, Key} ->
918		    ok = dets:delete(Tab, Key);
919		(Rec) when Tab == RecName ->
920		    ok = dets:insert(Tab, Rec);
921		(Rec) ->
922		    Rec2 = setelement(1, Rec, RecName),
923		    ok = dets:insert(Tab, Rec2)
924	    end;
925	_ ->
926	    Log = {?MODULE, Tab},
927	    case Rec of
928		{Tab, Key} ->
929		    mnesia_log:append(Log, {{Tab, Key}, {Tab, Key}, delete});
930		(Rec) when Tab == RecName ->
931		    mnesia_log:append(Log, {{Tab, Key}, Rec, write});
932		(Rec) ->
933		    Rec2 = setelement(1, Rec, RecName),
934		    mnesia_log:append(Log, {{Tab, Key}, Rec2, write})
935	    end
936    end.
937
938uninstall_fallback() ->
939    uninstall_fallback([{scope, global}]).
940
941uninstall_fallback(Args) ->
942    case check_fallback_args(Args, #fallback_args{}) of
943	{ok, FA} ->
944	    do_uninstall_fallback(FA);
945	{error, Reason} ->
946	    {error, Reason}
947    end.
948
949do_uninstall_fallback(FA) ->
950    %% Ensure that we access the intended Mnesia
951    %% directory. This function may not be called
952    %% during startup since it will cause the
953    %% application_controller to get into deadlock
954    case mnesia_lib:ensure_loaded(?APPLICATION) of
955	ok ->
956	    Pid = spawn_link(?MODULE, uninstall_fallback_master, [self(), FA]),
957	    receive
958		{'EXIT', Pid, Reason} -> % if appl has trapped exit
959		    {error, {'EXIT', Reason}};
960		{Pid, Res} ->
961		    Res
962	    end;
963	{error, Reason} ->
964	    {error, Reason}
965    end.
966
967uninstall_fallback_master(ClientPid, FA) ->
968    process_flag(trap_exit, true),
969
970    FA2 = check_fallback_dir(ClientPid, FA), % May exit
971    Bup = FA2#fallback_args.fallback_bup,
972    case fallback_to_schema(Bup) of
973	{ok, fallback, List} ->
974	    Cs = mnesia_schema:list2cs(List),
975	    case catch get_fallback_nodes(FA, Cs#cstruct.disc_copies) of
976		Ns when list(Ns) ->
977		    do_uninstall(ClientPid, Ns, FA);
978		{error, Reason} ->
979		    local_fallback_error(ClientPid, Reason)
980	    end;
981	{error, Reason} ->
982	    local_fallback_error(ClientPid, Reason)
983    end.
984
985do_uninstall(ClientPid, Ns, FA) ->
986    Args = [self(), FA],
987    global:set_lock({{mnesia_table_lock, schema}, self()}, Ns, infinity),
988    Pids = [spawn_link(N, ?MODULE, local_uninstall_fallback, Args) || N <- Ns],
989    Res = do_uninstall(ClientPid, Pids, [], [], ok),
990    global:del_lock({{mnesia_table_lock, schema}, self()}, Ns),
991    ClientPid ! {self(), Res},
992    unlink(ClientPid),
993    exit(shutdown).
994
995do_uninstall(ClientPid, [Pid | Pids], GoodPids, BadNodes, Res) ->
996    receive
997	%% {'EXIT', ClientPid, _} ->
998	%% client_exit;
999	{'EXIT', Pid, Reason} ->
1000	    BadNode = node(Pid),
1001	    BadRes = {error, {"Uninstall fallback", BadNode, Reason}},
1002	    do_uninstall(ClientPid, Pids, GoodPids, [BadNode | BadNodes], BadRes);
1003	{Pid, {error, Reason}} ->
1004	    BadNode = node(Pid),
1005	    BadRes = {error, {"Uninstall fallback", BadNode, Reason}},
1006	    do_uninstall(ClientPid, Pids, GoodPids, [BadNode | BadNodes], BadRes);
1007	{Pid, started} ->
1008	    do_uninstall(ClientPid, Pids, [Pid | GoodPids], BadNodes, Res)
1009    end;
1010do_uninstall(ClientPid, [], GoodPids, [], ok) ->
1011    lists:foreach(fun(Pid) -> Pid ! {self(), do_uninstall} end, GoodPids),
1012    rec_uninstall(ClientPid, GoodPids, ok);
1013do_uninstall(_ClientPid, [], GoodPids, BadNodes, BadRes) ->
1014    lists:foreach(fun(Pid) -> exit(Pid, shutdown) end, GoodPids),
1015    {error, {node_not_running, BadNodes, BadRes}}.
1016
1017local_uninstall_fallback(Master, FA) ->
1018    %% Don't trap exit
1019
1020    register(mnesia_fallback, self()),        % May exit
1021    FA2 = check_fallback_dir(Master, FA), % May exit
1022    Master ! {self(), started},
1023
1024    receive
1025	{Master, do_uninstall} ->
1026	    ?eval_debug_fun({?MODULE, uninstall_fallback2, pre_delete}, []),
1027	    catch mnesia_lib:set(active_fallback, false),
1028	    Tmp = FA2#fallback_args.fallback_tmp,
1029	    Bup = FA2#fallback_args.fallback_bup,
1030	    file:delete(Tmp),
1031	    Res =
1032		case fallback_exists(Bup) of
1033		    true -> file:delete(Bup);
1034		    false -> ok
1035		end,
1036	    ?eval_debug_fun({?MODULE, uninstall_fallback2, post_delete}, []),
1037	    Master ! {self(), Res},
1038	    unlink(Master),
1039	    exit(normal)
1040    end.
1041
1042rec_uninstall(ClientPid, [Pid | Pids], AccRes) ->
1043    receive
1044	%% {'EXIT', ClientPid, _} ->
1045	%% exit(shutdown);
1046	{'EXIT', Pid, R} ->
1047	    Reason = {node_not_running, {node(Pid), R}},
1048	    rec_uninstall(ClientPid, Pids, {error, Reason});
1049	{Pid, ok} ->
1050	    rec_uninstall(ClientPid, Pids, AccRes);
1051	{Pid, BadRes} ->
1052	    rec_uninstall(ClientPid, Pids, BadRes)
1053    end;
1054rec_uninstall(ClientPid, [], Res) ->
1055    ClientPid ! {self(), Res},
1056    unlink(ClientPid),
1057    exit(normal).
1058
1059%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1060%% Backup traversal
1061
1062%% Iterate over a backup and produce a new backup.
1063%% Fun(BackupItem, Acc) is applied for each BackupItem.
1064%%
1065%% Valid BackupItems are:
1066%%
1067%%   {schema, Tab}		Table to be deleted
1068%%   {schema, Tab, CreateList}	Table to be created, CreateList may be empty
1069%%   {schema, db_nodes, DbNodes}List of nodes, defaults to [node()] OLD
1070%%   {schema, version, Version} Schema version                      OLD
1071%%   {schema, cookie, Cookie}	Unique schema cookie                OLD
1072%%   {Tab, Key}			Oid for record to be deleted
1073%%   Record			Record to be inserted.
1074%%
1075%% The Fun must return a tuple {BackupItems, NewAcc}
1076%% where BackupItems is a list of valid BackupItems and
1077%% NewAcc is a new accumulator value. Once BackupItems
1078%% that not are schema  related has been returned, no more schema
1079%% items may be  returned. The schema related items must always be
1080%% first in the backup.
1081%%
1082%% If TargetMod == read_only, no new backup will be created.
1083%%
1084%% Opening of the source media will be performed by
1085%% to SourceMod:open_read(Source)
1086%%
1087%% Opening of the target media will be performed by
1088%% to TargetMod:open_write(Target)
1089traverse_backup(Source, Target, Fun, Acc) ->
1090    Mod = mnesia_monitor:get_env(backup_module),
1091    traverse_backup(Source, Mod, Target, Mod, Fun, Acc).
1092
1093traverse_backup(Source, SourceMod, Target, TargetMod, Fun, Acc) ->
1094    Args = [self(), Source, SourceMod, Target, TargetMod, Fun, Acc],
1095    Pid = spawn_link(?MODULE, do_traverse_backup, Args),
1096    receive
1097	{'EXIT', Pid, Reason} ->
1098	    {error, {"Backup traversal crashed", Reason}};
1099	{iter_done, Pid, Res} ->
1100	    Res
1101    end.
1102
1103do_traverse_backup(ClientPid, Source, SourceMod, Target, TargetMod, Fun, Acc) ->
1104    process_flag(trap_exit, true),
1105    Iter =
1106	if
1107	    TargetMod /= read_only ->
1108		case catch do_apply(TargetMod, open_write, [Target], Target) of
1109		    {error, Error} ->
1110			unlink(ClientPid),
1111			ClientPid ! {iter_done, self(), {error, Error}},
1112			exit(Error);
1113		    Else -> Else
1114		end;
1115	    true ->
1116		ignore
1117	end,
1118    A = {start, Fun, Acc, TargetMod, Iter},
1119    Res =
1120	case iterate(SourceMod, fun trav_apply/4, Source, A) of
1121	    {ok, {iter, _, Acc2, _, Iter2}} when TargetMod /= read_only ->
1122		case catch do_apply(TargetMod, commit_write, [Iter2], Iter2) of
1123		    {error, Reason} ->
1124			{error, Reason};
1125		    _ ->
1126			{ok, Acc2}
1127		end;
1128	    {ok, {iter, _, Acc2, _, _}} ->
1129		{ok, Acc2};
1130	    {error, Reason} when TargetMod /= read_only->
1131		catch do_apply(TargetMod, abort_write, [Iter], Iter),
1132		{error, {"Backup traversal failed", Reason}};
1133	    {error, Reason} ->
1134		{error, {"Backup traversal failed", Reason}}
1135	end,
1136    unlink(ClientPid),
1137    ClientPid ! {iter_done, self(), Res}.
1138
1139trav_apply(Recs, _Header, _Schema, {iter, Fun, Acc, Mod, Iter}) ->
1140    {NewRecs, Acc2} = filter_foldl(Fun, Acc, Recs),
1141    if
1142	Mod /= read_only, NewRecs /= [] ->
1143	    Iter2 = do_apply(Mod, write, [Iter, NewRecs], Iter),
1144	    {iter, Fun, Acc2, Mod, Iter2};
1145	true ->
1146	    {iter, Fun, Acc2, Mod, Iter}
1147    end;
1148trav_apply(Recs, Header, Schema, {start, Fun, Acc, Mod, Iter}) ->
1149    Iter2 =
1150	if
1151	    Mod /= read_only ->
1152		do_apply(Mod, write, [Iter, [Header]], Iter);
1153	    true ->
1154		Iter
1155	end,
1156    TravAcc = trav_apply(Schema, Header, Schema, {iter, Fun, Acc, Mod, Iter2}),
1157    trav_apply(Recs, Header, Schema, TravAcc).
1158
1159filter_foldl(Fun, Acc, [Head|Tail]) ->
1160    case Fun(Head, Acc) of
1161	{HeadItems, HeadAcc} when list(HeadItems) ->
1162	    {TailItems, TailAcc} = filter_foldl(Fun, HeadAcc, Tail),
1163	    {HeadItems ++ TailItems, TailAcc};
1164	Other ->
1165	    throw({error, {"Fun must return a list", Other}})
1166    end;
1167filter_foldl(_Fun, Acc, []) ->
1168    {[], Acc}.
1169