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