1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
16
%% $Id: mnesia_bup.erl,v 1.1 2008/12/17 09:53:37 mikpe Exp $
33
make_initial_backup/3,
41
install_fallback_master/2,
42
uninstall_fallback_master/2,
43
local_uninstall_fallback/2,
48
-include("mnesia.hrl").
49
-import(mnesia_lib, [verbose/2, dbg_out/2]).
51
-record(restore, {mode, bup_module, bup_data}).
53
-record(fallback_args, {opaque,
55
module = mnesia_monitor:get_env(backup_module),
56
use_default_dir = true,
62
default_op = keep_tables
65
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
68
%% Reads schema section and iterates over all records in a backup.
70
%% Fun(BunchOfRecords, Header, Schema, Acc) is applied when a suitable amount
71
%% of records has been collected.
73
%% BunchOfRecords will be [] when the iteration is done.
74
iterate(Mod, Fun, Opaque, Acc) ->
75
R = #restore{bup_module = Mod, bup_data = Opaque},
76
case catch read_schema_section(R) of
79
{R2, {Header, Schema, Rest}} ->
80
case catch iter(R2, Header, Schema, Fun, Acc, Rest) of
82
catch safe_apply(R3, close_read, [R3#restore.bup_data]),
85
catch safe_apply(R2, close_read, [R2#restore.bup_data]),
87
{'EXIT', Pid, Reason} ->
88
catch safe_apply(R2, close_read, [R2#restore.bup_data]),
89
{error, {'EXIT', Pid, Reason}};
91
catch safe_apply(R2, close_read, [R2#restore.bup_data]),
92
{error, {'EXIT', Reason}}
96
iter(R, Header, Schema, Fun, Acc, []) ->
97
case safe_apply(R, read, [R#restore.bup_data]) of
99
Res = Fun([], Header, Schema, Acc),
102
iter(R2, Header, Schema, Fun, Acc, BupItems)
104
iter(R, Header, Schema, Fun, Acc, BupItems) ->
105
Acc2 = Fun(BupItems, Header, Schema, Acc),
106
iter(R, Header, Schema, Fun, Acc2, []).
108
safe_apply(R, write, [_, Items]) when Items == [] ->
110
safe_apply(R, What, Args) ->
111
Abort = fun(Re) -> abort_restore(R, What, Args, Re) end,
113
{'EXIT', Pid, Re} -> Abort({'EXIT', Pid, Re})
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};
128
abort_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}).
136
fallback_to_schema() ->
137
Fname = fallback_bup(),
138
fallback_to_schema(Fname).
140
fallback_to_schema(Fname) ->
142
case read_schema(Mod, Fname) of
146
case catch lookup_schema(schema, Schema) of
148
{error, "No schema in fallback"};
154
%% Opens Opaque reads schema and then close
155
read_schema(Mod, Opaque) ->
156
R = #restore{bup_module = Mod, bup_data = Opaque},
157
case catch read_schema_section(R) of
160
{R2, {_Header, Schema, _}} ->
161
catch safe_apply(R2, close_read, [R2#restore.bup_data]),
165
%% Open backup media and extract schema
166
%% rewind backup media and leave it open
167
%% Returns {R, {Header, Schema}}
168
read_schema_section(R) ->
169
case catch do_read_schema_section(R) of
171
catch safe_apply(R, close_read, [R#restore.bup_data]),
172
{error, {'EXIT', Reason}};
174
catch safe_apply(R, close_read, [R#restore.bup_data]),
176
{R2, {H, Schema, Rest}} ->
177
Schema2 = convert_schema(H#log_header.log_version, Schema),
178
{R2, {H, Schema2, Rest}}
181
do_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), []).
186
do_read_schema_section(R, {ok, B, C, []}, Acc) ->
187
case safe_apply(R, read, [R#restore.bup_data]) of
191
do_read_schema_section(R2, {ok, B, C, RawSchema}, Acc)
194
do_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]);
198
do_read_schema_section(R, {ok, B, _C, Rest}, Acc) ->
201
do_read_schema_section(_R, {error, Reason}, _Acc) ->
204
verify_header([H | RawSchema]) when record(H, log_header) ->
205
Current = mnesia_log:backup_log_header(),
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
211
{ok, H, Current, RawSchema};
213
{error, {"Bad header version. Cannot be used as backup.", H}}
216
{error, {"Bad kind of header. Cannot be used as backup.", H}}
218
verify_header(RawSchema) ->
219
{error, {"Missing header. Cannot be used as backup.", catch hd(RawSchema)}}.
221
refresh_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);
230
Reason = "No schema found. Cannot be used as backup.",
231
throw({error, {Reason, Schema}})
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.
238
convert_schema("0.1", Schema) ->
240
convert_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);
244
convert_schema(Latest, Schema) ->
245
H = mnesia_log:backup_log_header(),
247
H#log_header.log_version == Latest ->
250
Reason = "Bad backup header version. Cannot convert schema.",
251
throw({error, {Reason, H}})
254
%% Backward compatibility for 0.1
255
convert_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);
262
List = mnesia_schema:get_initial_schema(disc_copies, [node()]),
263
Cs = mnesia_schema:list2cs(List),
264
convert_0_1(Schema, [], Cs)
267
convert_0_1([{schema, cookie, Cookie} | Schema], Acc, Cs) ->
268
convert_0_1(Schema, Acc, Cs#cstruct{cookie = Cookie});
269
convert_0_1([{schema, db_nodes, DbNodes} | Schema], Acc, Cs) ->
270
convert_0_1(Schema, Acc, Cs#cstruct{disc_copies = DbNodes});
271
convert_0_1([{schema, version, Version} | Schema], Acc, Cs) ->
272
convert_0_1(Schema, Acc, Cs#cstruct{version = Version});
273
convert_0_1([{schema, Tab, Def} | Schema], Acc, Cs) ->
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}),
284
convert_0_1(Schema, [Head | Acc], Cs);
285
convert_0_1([Head | Schema], Acc, Cs) ->
286
convert_0_1(Schema, [Head | Acc], Cs);
287
convert_0_1([], Acc, Cs) ->
288
[schema2bup({schema, schema, Cs}) | Acc].
290
%% Returns Val or throw error
291
lookup_schema(Key, Schema) ->
292
case lists:keysearch(Key, 2, Schema) of
293
{value, {schema, Key, Val}} -> Val;
294
false -> throw({error, {"Cannot lookup", Key}})
297
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
298
%% Backup compatibility
300
%% Convert internal schema items to backup dito
301
schema2bup({schema, Tab}) ->
303
schema2bup({schema, Tab, TableDef}) ->
304
{schema, Tab, mnesia_schema:cs2list(TableDef)}.
306
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
307
%% Create schema on the given nodes
308
%% Requires that old schemas has been deleted
309
%% Returns ok | {error, Reason}
311
create_schema([node()]);
312
create_schema(Ns) when list(Ns) ->
315
create_schema(Ns, mnesia_schema:ensure_no_schema(Ns));
317
{error, {combine_error, Ns}}
320
{error, {badarg, Ns}}.
322
is_set(List) when list(List) ->
323
ordsets:is_set(lists:sort(List));
327
create_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
334
case mnesia_monitor:get_env(schema_location) of
336
{error, {has_no_disc, node()}};
338
case mnesia_schema:opt_create_dir(true, mnesia_lib:dir()) of
344
File = mnesia_lib:dir(Str),
346
case catch make_initial_backup(Ns, File, Mod) of
348
case do_install_fallback(File, Mod) of
363
create_schema(_Ns, {error, Reason}) ->
365
create_schema(_Ns, Reason) ->
369
Now = [integer_to_list(I) || I <- tuple_to_list(now())],
370
lists:concat([node()] ++ Now ++ ".TMP").
372
make_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),
380
do_apply(_, write, [_, Items], Opaque) when Items == [] ->
382
do_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}})
389
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
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}
396
install_fallback(Opaque) ->
397
install_fallback(Opaque, []).
399
install_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
406
do_install_fallback(Opaque, Args);
411
do_install_fallback(Opaque, Mod) when atom(Mod) ->
412
do_install_fallback(Opaque, [{module, Mod}]);
413
do_install_fallback(Opaque, Args) when list(Args) ->
414
case check_fallback_args(Args, #fallback_args{opaque = Opaque}) of
416
do_install_fallback(FA);
420
do_install_fallback(_Opaque, Args) ->
421
{error, {badarg, Args}}.
423
check_fallback_args([Arg | Tail], FA) ->
424
case catch check_fallback_arg_type(Arg, FA) of
426
{error, {badarg, Arg}};
428
check_fallback_args(Tail, FA2)
430
check_fallback_args([], FA) ->
433
check_fallback_arg_type(Arg, FA) ->
436
FA#fallback_args{scope = global};
438
FA#fallback_args{scope = local};
440
Mod2 = mnesia_monitor:do_check_type(backup_module, Mod),
441
FA#fallback_args{module = Mod2};
443
FA#fallback_args{mnesia_dir = Dir,
444
use_default_dir = false};
445
{keep_tables, Tabs} ->
447
FA#fallback_args{keep_tables = Tabs};
448
{skip_tables, 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}
457
atom_list([H | T]) when atom(H) ->
462
do_install_fallback(FA) ->
463
Pid = spawn_link(?MODULE, install_fallback_master, [self(), FA]),
466
{'EXIT', Pid, Reason} -> % if appl has trapped exit
467
{error, {'EXIT', Reason}};
473
{error, {"Cannot install fallback", Reason}}
478
install_fallback_master(ClientPid, FA) ->
479
process_flag(trap_exit, true),
481
Opaque = FA#fallback_args.opaque,
482
Mod = FA#fallback_args.module,
483
Res = (catch iterate(Mod, fun restore_recs/4, Opaque, State)),
485
ClientPid ! {self(), Res},
488
restore_recs(_, _, _, stop) ->
489
throw({error, "restore_recs already stopped"});
491
restore_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
497
throw({error, {"Bad schema in restore_recs", Reason}});
499
Ns = get_fallback_nodes(FA, Cs#cstruct.disc_copies),
500
global:set_lock({{mnesia_table_lock, schema}, self()}, Ns, infinity),
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),
509
restore_recs([], _Header, _Schema, Pids) ->
510
send_fallback(Pids, swap),
511
send_fallback(Pids, stop),
514
restore_recs(Recs, _, _, Pids) ->
515
send_fallback(Pids, {records, Recs}),
518
get_fallback_nodes(FA, Ns) ->
520
case lists:member(This, Ns) of
522
case FA#fallback_args.scope of
527
throw({error, {"No disc resident schema on local node", Ns}})
530
send_fallback(Pids, Msg) when list(Pids), Pids /= [] ->
531
lists:foreach(fun(Pid) -> Pid ! {self(), Msg} end, Pids),
532
rec_answers(Pids, []).
534
rec_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}})
540
rec_answers(Pids, Acc) ->
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]);
549
Pids2 = lists:delete(Pid, Pids),
550
rec_answers(Pids2, [Reply|Acc])
554
Fname = fallback_bup(),
555
fallback_exists(Fname).
557
fallback_exists(Fname) ->
558
case mnesia_monitor:use_dir() of
560
mnesia_lib:exists(Fname);
562
case ?catch_val(active_fallback) of
563
{'EXIT', _} -> false;
568
fallback_name() -> "FALLBACK.BUP".
569
fallback_bup() -> mnesia_lib:dir(fallback_name()).
571
fallback_tmp_name() -> "FALLBACK.TMP".
572
%% fallback_full_tmp_name() -> mnesia_lib:dir(fallback_tmp_name()).
574
fallback_receiver(Master, FA) ->
575
process_flag(trap_exit, true),
577
case catch register(mnesia_fallback, self()) of
579
Reason = {already_exists, node()},
580
local_fallback_error(Master, Reason);
582
FA2 = check_fallback_dir(Master, FA),
583
Bup = FA2#fallback_args.fallback_bup,
584
case mnesia_lib:exists(Bup) of
586
Reason2 = {already_exists, node()},
587
local_fallback_error(Master, Reason2);
590
Tmp = FA2#fallback_args.fallback_tmp,
591
R = #restore{mode = replace,
595
case catch fallback_receiver_loop(Master, R, FA2, schema) of
597
local_fallback_error(Master, Reason);
604
local_fallback_error(Master, Reason) ->
605
Master ! {self(), {error, Reason}},
609
check_fallback_dir(Master, FA) ->
610
case mnesia:system_info(schema_location) of
612
Reason = {has_no_disc, node()},
613
local_fallback_error(Master, Reason);
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,
623
check_fallback_dir_arg(Master, FA) ->
624
case FA#fallback_args.use_default_dir of
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
631
Reason = {badarg, {dir, Dir}, node()},
632
local_fallback_error(Master, Reason);
636
false when FA#fallback_args.scope == global ->
637
Reason = {combine_error, global, dir, node()},
638
local_fallback_error(Master, Reason)
641
fallback_receiver_loop(Master, R, FA, State) ->
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);
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);
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);
669
{Master, stop} when State == stop ->
673
safe_apply(R, abort_write, [R#restore.bup_data]),
674
Tmp = FA#fallback_args.fallback_tmp,
676
throw({error, "Unexpected msg fallback_receiver_loop", Msg})
679
throw_bad_res(Expected, Expected) -> Expected;
680
throw_bad_res(_Expected, {error, Actual}) -> throw({error, Actual});
681
throw_bad_res(_Expected, Actual) -> throw({error, Actual}).
683
-record(local_tab, {name, storage_type, dets_args, open, close, add, record_name}).
685
tm_fallback_start(IgnoreFallback) ->
686
mnesia_schema:lock_schema(),
687
Res = do_fallback_start(fallback_exists(), IgnoreFallback),
688
mnesia_schema: unlock_schema(),
691
{error, Reason} -> exit(Reason)
694
do_fallback_start(false, _IgnoreFallback) ->
696
do_fallback_start(true, true) ->
697
verbose("Ignoring fallback at startup, but leaving it active...~n", []),
698
mnesia_lib:set(active_fallback, true),
700
do_fallback_start(true, false) ->
701
verbose("Starting from fallback...~n", []),
703
Fname = fallback_bup(),
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
709
{local, _, LT} -> %% Close the last file
710
(LT#local_tab.close)(LT);
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
727
{error, {"Cannot start from fallback. Rename error.", Reason}}
730
{error, {"Cannot start from fallback", Reason}};
732
{error, {"Cannot start from fallback", Reason}}
735
restore_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);
743
restore_tables([Rec | Recs], Header, Schema, {new, LocalTabs}) ->
744
Tab = element(1, Rec),
745
case ?ets_lookup(LocalTabs, Tab) of
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)
754
restore_tables([Rec | Recs], Header, Schema, S = {not_local, LocalTabs, PrevTab}) ->
755
Tab = element(1, Rec),
758
restore_tables(Recs, Header, Schema, S);
760
State = {new, LocalTabs},
761
restore_tables([Rec | Recs], Header, Schema, State)
763
restore_tables([Rec | Recs], Header, Schema, State = {local, LocalTabs, L}) ->
764
Tab = element(1, Rec),
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);
771
(L#local_tab.close)(L),
772
NState = {new, LocalTabs},
773
restore_tables([Rec | Recs], Header, Schema, NState)
775
restore_tables([], _Header, _Schema, State) ->
778
%% Creates all neccessary dat files and inserts
779
%% the table definitions in the schema table
781
%% Returns a list of local_tab tuples for all local tables
782
init_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
787
create_dat_files(Schema, LocalTabs),
789
LocalTab = #local_tab{name = schema,
790
storage_type = disc_copies,
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);
798
throw({error, {"Cannot open file", schema, Args, Reason}})
801
create_dat_files([{schema, schema, TabDef} | Tail], LocalTabs) ->
802
ok = dets:insert(schema, {schema, schema, TabDef}),
803
create_dat_files(Tail, LocalTabs);
804
create_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
810
cleanup_dat_file(Tab),
811
create_dat_files(Tail, LocalTabs);
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
818
mnesia_lib:dets_sync_close(Tab),
819
LocalTab = #local_tab{name = Tab,
820
storage_type = disc_only_copies,
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);
829
throw({error, {"Cannot open file", Tab, Args, Reason}})
832
%% Create .DCD if needed in open_media in case any ram_copies
834
LocalTab = #local_tab{name = Tab,
835
storage_type = ram_copies,
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);
845
Fname = mnesia_lib:tab2dcd(Tab),
847
Log = mnesia_log:open_log(fallback_tab, mnesia_log:dcd_log_header(),
849
LocalTab = #local_tab{name = Tab,
850
storage_type = Storage,
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)
860
create_dat_files([{schema, Tab} | Tail], LocalTabs) ->
861
cleanup_dat_file(Tab),
862
create_dat_files(Tail, LocalTabs);
863
create_dat_files([], _LocalTabs) ->
866
cleanup_dat_file(Tab) ->
867
ok = dets:delete(schema, {schema, Tab}),
868
mnesia_lib:cleanup_tmp_files([Tab]).
870
open_media(Tab, LT) ->
871
case LT#local_tab.storage_type of
873
Args = LT#local_tab.dets_args,
874
case mnesia_lib:dets_sync_open(Tab, Args) of
877
throw({error, {"Cannot open file", Tab, Args, Reason}})
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(),
886
mnesia_log:close_log(Log),
889
Fname = mnesia_lib:tab2dcl(Tab),
891
mnesia_log:open_log({?MODULE,Tab},
892
mnesia_log:dcl_log_header(),
896
Fname = mnesia_lib:tab2dcl(Tab),
898
mnesia_log:open_log({?MODULE,Tab},
899
mnesia_log:dcl_log_header(),
904
Tab = L#local_tab.name,
905
case L#local_tab.storage_type of
907
mnesia_lib:dets_sync_close(Tab);
909
mnesia_log:close_log({?MODULE,Tab})
912
add_to_media(Tab, Key, Rec, L) ->
913
RecName = L#local_tab.record_name,
914
case L#local_tab.storage_type of
918
ok = dets:delete(Tab, Key);
919
(Rec) when Tab == RecName ->
920
ok = dets:insert(Tab, Rec);
922
Rec2 = setelement(1, Rec, RecName),
923
ok = dets:insert(Tab, Rec2)
926
Log = {?MODULE, Tab},
929
mnesia_log:append(Log, {{Tab, Key}, {Tab, Key}, delete});
930
(Rec) when Tab == RecName ->
931
mnesia_log:append(Log, {{Tab, Key}, Rec, write});
933
Rec2 = setelement(1, Rec, RecName),
934
mnesia_log:append(Log, {{Tab, Key}, Rec2, write})
938
uninstall_fallback() ->
939
uninstall_fallback([{scope, global}]).
941
uninstall_fallback(Args) ->
942
case check_fallback_args(Args, #fallback_args{}) of
944
do_uninstall_fallback(FA);
949
do_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
956
Pid = spawn_link(?MODULE, uninstall_fallback_master, [self(), FA]),
958
{'EXIT', Pid, Reason} -> % if appl has trapped exit
959
{error, {'EXIT', Reason}};
967
uninstall_fallback_master(ClientPid, FA) ->
968
process_flag(trap_exit, true),
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
977
do_uninstall(ClientPid, Ns, FA);
979
local_fallback_error(ClientPid, Reason)
982
local_fallback_error(ClientPid, Reason)
985
do_uninstall(ClientPid, Ns, 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},
995
do_uninstall(ClientPid, [Pid | Pids], GoodPids, BadNodes, Res) ->
997
%% {'EXIT', ClientPid, _} ->
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);
1008
do_uninstall(ClientPid, Pids, [Pid | GoodPids], BadNodes, Res)
1010
do_uninstall(ClientPid, [], GoodPids, [], ok) ->
1011
lists:foreach(fun(Pid) -> Pid ! {self(), do_uninstall} end, GoodPids),
1012
rec_uninstall(ClientPid, GoodPids, ok);
1013
do_uninstall(_ClientPid, [], GoodPids, BadNodes, BadRes) ->
1014
lists:foreach(fun(Pid) -> exit(Pid, shutdown) end, GoodPids),
1015
{error, {node_not_running, BadNodes, BadRes}}.
1017
local_uninstall_fallback(Master, FA) ->
1020
register(mnesia_fallback, self()), % May exit
1021
FA2 = check_fallback_dir(Master, FA), % May exit
1022
Master ! {self(), started},
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,
1032
case fallback_exists(Bup) of
1033
true -> file:delete(Bup);
1036
?eval_debug_fun({?MODULE, uninstall_fallback2, post_delete}, []),
1037
Master ! {self(), Res},
1042
rec_uninstall(ClientPid, [Pid | Pids], AccRes) ->
1044
%% {'EXIT', ClientPid, _} ->
1047
Reason = {node_not_running, {node(Pid), R}},
1048
rec_uninstall(ClientPid, Pids, {error, Reason});
1050
rec_uninstall(ClientPid, Pids, AccRes);
1052
rec_uninstall(ClientPid, Pids, BadRes)
1054
rec_uninstall(ClientPid, [], Res) ->
1055
ClientPid ! {self(), Res},
1059
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1062
%% Iterate over a backup and produce a new backup.
1063
%% Fun(BackupItem, Acc) is applied for each BackupItem.
1065
%% Valid BackupItems are:
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.
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.
1082
%% If TargetMod == read_only, no new backup will be created.
1084
%% Opening of the source media will be performed by
1085
%% to SourceMod:open_read(Source)
1087
%% Opening of the target media will be performed by
1088
%% to TargetMod:open_write(Target)
1089
traverse_backup(Source, Target, Fun, Acc) ->
1090
Mod = mnesia_monitor:get_env(backup_module),
1091
traverse_backup(Source, Mod, Target, Mod, Fun, Acc).
1093
traverse_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),
1097
{'EXIT', Pid, Reason} ->
1098
{error, {"Backup traversal crashed", Reason}};
1099
{iter_done, Pid, Res} ->
1103
do_traverse_backup(ClientPid, Source, SourceMod, Target, TargetMod, Fun, Acc) ->
1104
process_flag(trap_exit, true),
1107
TargetMod /= read_only ->
1108
case catch do_apply(TargetMod, open_write, [Target], Target) of
1111
ClientPid ! {iter_done, self(), {error, Error}},
1118
A = {start, Fun, Acc, TargetMod, Iter},
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
1128
{ok, {iter, _, Acc2, _, _}} ->
1130
{error, Reason} when TargetMod /= read_only->
1131
catch do_apply(TargetMod, abort_write, [Iter], Iter),
1132
{error, {"Backup traversal failed", Reason}};
1134
{error, {"Backup traversal failed", Reason}}
1137
ClientPid ! {iter_done, self(), Res}.
1139
trav_apply(Recs, _Header, _Schema, {iter, Fun, Acc, Mod, Iter}) ->
1140
{NewRecs, Acc2} = filter_foldl(Fun, Acc, Recs),
1142
Mod /= read_only, NewRecs /= [] ->
1143
Iter2 = do_apply(Mod, write, [Iter, NewRecs], Iter),
1144
{iter, Fun, Acc2, Mod, Iter2};
1146
{iter, Fun, Acc2, Mod, Iter}
1148
trav_apply(Recs, Header, Schema, {start, Fun, Acc, Mod, Iter}) ->
1152
do_apply(Mod, write, [Iter, [Header]], Iter);
1156
TravAcc = trav_apply(Schema, Header, Schema, {iter, Fun, Acc, Mod, Iter2}),
1157
trav_apply(Recs, Header, Schema, TravAcc).
1159
filter_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};
1165
throw({error, {"Fun must return a list", Other}})
1167
filter_foldl(_Fun, Acc, []) ->