1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1996-2018. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% 22%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 23%% 24%% This module administers three kinds of log files: 25%% 26%% 1 The transaction log 27%% mnesia_tm appends to the log (via mnesia_log) at the 28%% end of each transaction (or dirty write) and 29%% mnesia_dumper reads the log and performs the ops in 30%% the dat files. The dump_log is done at startup and 31%% at intervals controlled by the user. 32%% 33%% 2 The mnesia_down log 34%% mnesia_tm appends to the log (via mnesia_log) when it 35%% realizes that mnesia goes up or down on another node. 36%% mnesia_init reads the log (via mnesia_log) at startup. 37%% 38%% 3 The backup log 39%% mnesia_schema produces one tiny log when the schema is 40%% initially created. mnesia_schema also reads the log 41%% when the user wants tables (possibly incl the schema) 42%% to be restored. mnesia_log appends to the log when the 43%% user wants to produce a real backup. 44%% 45%% The actual access to the backup media is performed via the 46%% mnesia_backup module for both read and write. mnesia_backup 47%% uses the disk_log (*), BUT the user may write an own module 48%% with the same interface as mnesia_backup and configure 49%% Mnesia so the alternate module performs the actual accesses 50%% to the backup media. This means that the user may put the 51%% backup on medias that Mnesia does not know about possibly on 52%% hosts where Erlang is not running. 53%% 54%% All these logs have to some extent a common structure. 55%% They are all using the disk_log module (*) for the basic 56%% file structure. The disk_log has a repair feature that 57%% can be used to skip erroneous log records if one comes to 58%% the conclusion that it is more important to reuse some 59%% of the log records than the risque of obtaining inconsistent 60%% data. If the data becomes inconsistent it is solely up to the 61%% application to make it consistent again. The automatic 62%% reparation of the disk_log is very powerful, but use it 63%% with extreme care. 64%% 65%% First in all Mnesia's log file is a mnesia log header. 66%% It contains a list with a log_header record as single 67%% element. The structure of the log_header may never be 68%% changed since it may be written to very old backup files. 69%% By holding this record definition stable we can be 70%% able to comprahend backups from timepoint 0. It also 71%% allows us to use the backup format as an interchange 72%% format between Mnesia releases. 73%% 74%% An op-list is a list of tuples with arity 3. Each tuple 75%% has this structure: {Oid, Recs, Op} where Oid is the tuple 76%% {Tab, Key}, Recs is a (possibly empty) list of records and 77%% Op is an atom. 78%% 79%% The log file structure for the transaction log is as follows. 80%% 81%% After the mnesia log section follows an extended record section 82%% containing op-lists. There are several values that Op may 83%% have, such as write, delete, update_counter, delete_object, 84%% and replace. There is no special end of section marker. 85%% 86%% +-----------------+ 87%% | mnesia log head | 88%% +-----------------+ 89%% | extended record | 90%% | section | 91%% +-----------------+ 92%% 93%% The log file structure for the mnesia_down log is as follows. 94%% 95%% After the mnesia log section follows a mnesia_down section 96%% containg lists with yoyo records as single element. 97%% 98%% +-----------------+ 99%% | mnesia log head | 100%% +-----------------+ 101%% | mnesia_down | 102%% | section | 103%% +-----------------+ 104%% 105%% The log file structure for the backup log is as follows. 106%% 107%% After the mnesia log section follows a schema section 108%% containing record lists. A record list is a list of tuples 109%% where {schema, Tab} is interpreted as a delete_table(Tab) and 110%% {schema, Tab, CreateList} are interpreted as create_table. 111%% 112%% The record section also contains record lists. In this section 113%% {Tab, Key} is interpreted as delete({Tab, Key}) and other tuples 114%% as write(Tuple). There is no special end of section marker. 115%% 116%% +-----------------+ 117%% | mnesia log head | 118%% +-----------------+ 119%% | schema section | 120%% +-----------------+ 121%% | record section | 122%% +-----------------+ 123%% 124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 125 126-module(mnesia_log). 127 128-export([ 129 append/2, 130 backup/1, 131 backup/2, 132 backup_checkpoint/2, 133 backup_checkpoint/3, 134 backup_log_header/0, 135 backup_master/2, 136 chunk_decision_log/1, 137 chunk_decision_tab/1, 138 chunk_log/1, 139 chunk_log/2, 140 close_decision_log/0, 141 close_decision_tab/0, 142 close_log/1, 143 unsafe_close_log/1, 144 confirm_log_dump/1, 145 confirm_decision_log_dump/0, 146 previous_log_file/0, 147 previous_decision_log_file/0, 148 latest_log_file/0, 149 decision_log_version/0, 150 decision_log_file/0, 151 decision_tab_file/0, 152 decision_tab_version/0, 153 dcl_version/0, 154 dcd_version/0, 155 ets2dcd/1, 156 ets2dcd/2, 157 dcd2ets/1, 158 dcd2ets/2, 159 init/0, 160 init_log_dump/0, 161 log/1, 162 slog/1, 163 log_decision/1, 164 log_files/0, 165 open_decision_log/0, 166 trans_log_header/0, 167 open_decision_tab/0, 168 dcl_log_header/0, 169 dcd_log_header/0, 170 open_log/4, 171 open_log/6, 172 prepare_decision_log_dump/0, 173 prepare_log_dump/1, 174 save_decision_tab/1, 175 purge_all_logs/0, 176 purge_some_logs/0, 177 stop/0, 178 tab_copier/3, 179 version/0, 180 view/0, 181 view/1, 182 write_trans_log_header/0 183 ]). 184 185 186-compile({no_auto_import,[error/2]}). 187 188-include("mnesia.hrl"). 189-import(mnesia_lib, [val/1, dir/1]). 190-import(mnesia_lib, [exists/1, fatal/2, error/2, dbg_out/2]). 191 192trans_log_header() -> log_header(trans_log, version()). 193backup_log_header() -> log_header(backup_log, "1.2"). 194decision_log_header() -> log_header(decision_log, decision_log_version()). 195decision_tab_header() -> log_header(decision_tab, decision_tab_version()). 196dcl_log_header() -> log_header(dcl_log, dcl_version()). 197dcd_log_header() -> log_header(dcd_log, dcd_version()). 198 199log_header(Kind, Version) -> 200 #log_header{log_version=Version, 201 log_kind=Kind, 202 mnesia_version=mnesia:system_info(version), 203 node=node(), 204 now=erlang:timestamp()}. 205 206version() -> "4.3". 207 208decision_log_version() -> "3.0". 209 210decision_tab_version() -> "1.0". 211 212dcl_version() -> "1.0". 213dcd_version() -> "1.0". 214 215append(Log, Bin) when is_binary(Bin) -> 216 disk_log:balog(Log, Bin); 217append(Log, Term) -> 218 disk_log:alog(Log, Term). 219 220%% Synced append 221sappend(Log, Bin) when is_binary(Bin) -> 222 ok = disk_log:blog(Log, Bin); 223sappend(Log, Term) -> 224 ok = disk_log:log(Log, Term). 225 226%% Write commit records to the latest_log 227log(C) -> 228 case need_log(C) andalso mnesia_monitor:use_dir() of 229 true -> 230 if 231 is_record(C, commit) -> 232 append(latest_log, strip_snmp(C)); 233 true -> 234 %% Either a commit record as binary 235 %% or some decision related info 236 append(latest_log, C) 237 end, 238 mnesia_dumper:incr_log_writes(); 239 false -> 240 ignore 241 end. 242 243%% Synced 244 245slog(C) -> 246 case need_log(C) andalso mnesia_monitor:use_dir() of 247 true -> 248 if 249 is_record(C, commit) -> 250 sappend(latest_log, strip_snmp(C)); 251 true -> 252 %% Either a commit record as binary 253 %% or some decision related info 254 sappend(latest_log, C) 255 end, 256 mnesia_dumper:incr_log_writes(); 257 false -> 258 ignore 259 end. 260 261need_log(#commit{disc_copies=[], disc_only_copies=[], schema_ops=[], ext=Ext}) -> 262 lists:keymember(ext_copies, 1, Ext); 263need_log(_) -> true. 264 265strip_snmp(#commit{ext=[]}=CR) -> CR; 266strip_snmp(#commit{ext=Ext}=CR) -> 267 CR#commit{ext=lists:keydelete(snmp, 1, Ext)}. 268 269%% Stuff related to the file LOG 270 271%% Returns a list of logfiles. The oldest is first. 272log_files() -> [previous_log_file(), 273 latest_log_file(), 274 decision_tab_file() 275 ]. 276 277latest_log_file() -> dir(latest_log_name()). 278 279previous_log_file() -> dir("PREVIOUS.LOG"). 280 281decision_log_file() -> dir(decision_log_name()). 282 283decision_tab_file() -> dir(decision_tab_name()). 284 285previous_decision_log_file() -> dir("PDECISION.LOG"). 286 287latest_log_name() -> "LATEST.LOG". 288 289decision_log_name() -> "DECISION.LOG". 290 291decision_tab_name() -> "DECISION_TAB.LOG". 292 293init() -> 294 case mnesia_monitor:use_dir() of 295 true -> 296 Prev = previous_log_file(), 297 verify_no_exists(Prev), 298 299 Latest = latest_log_file(), 300 verify_no_exists(Latest), 301 302 Header = trans_log_header(), 303 open_log(latest_log, Header, Latest); 304 false -> 305 ok 306 end. 307 308verify_no_exists(Fname) -> 309 case exists(Fname) of 310 false -> 311 ok; 312 true -> 313 fatal("Log file exists: ~tp~n", [Fname]) 314 end. 315 316open_log(Name, Header, Fname) -> 317 Exists = exists(Fname), 318 open_log(Name, Header, Fname, Exists). 319 320open_log(Name, Header, Fname, Exists) -> 321 Repair = mnesia_monitor:get_env(auto_repair), 322 open_log(Name, Header, Fname, Exists, Repair). 323 324open_log(Name, Header, Fname, Exists, Repair) -> 325 case Name == previous_log of 326 true -> 327 open_log(Name, Header, Fname, Exists, Repair, read_only); 328 false -> 329 open_log(Name, Header, Fname, Exists, Repair, read_write) 330 end. 331 332open_log(Name, Header, Fname, Exists, Repair, Mode) -> 333 Args = [{file, Fname}, {name, Name}, {repair, Repair}, {mode, Mode}], 334%% io:format("~p:open_log: ~tp ~tp~n", [?MODULE, Name, Fname]), 335 case mnesia_monitor:open_log(Args) of 336 {ok, Log} when Exists == true -> 337 Log; 338 {ok, Log} -> 339 write_header(Log, Header), 340 Log; 341 {repaired, Log, _, {badbytes, 0}} when Exists == true -> 342 Log; 343 {repaired, Log, _, {badbytes, 0}} -> 344 write_header(Log, Header), 345 Log; 346 {repaired, Log, _Recover, BadBytes} -> 347 mnesia_lib:important("Data may be missing, log ~tp repaired: Lost ~p bytes~n", 348 [Fname, BadBytes]), 349 Log; 350 {error, Reason = {file_error, _Fname, emfile}} -> 351 fatal("Cannot open log file ~tp: ~tp~n", [Fname, Reason]); 352 {error, Reason} when Repair == true -> 353 file:delete(Fname), 354 mnesia_lib:important("Data may be missing, Corrupt logfile deleted: ~tp, ~tp ~n", 355 [Fname, Reason]), 356 %% Create a new 357 open_log(Name, Header, Fname, false, false, read_write); 358 {error, Reason} -> 359 fatal("Cannot open log file ~tp: ~tp~n", [Fname, Reason]) 360 end. 361 362write_header(Log, Header) -> 363 append(Log, Header). 364 365write_trans_log_header() -> 366 write_header(latest_log, trans_log_header()). 367 368stop() -> 369 case mnesia_monitor:use_dir() of 370 true -> 371 close_log(latest_log); 372 false -> 373 ok 374 end. 375 376close_log(Log) -> 377%% io:format("mnesia_log:close_log ~p~n", [Log]), 378%% io:format("mnesia_log:close_log ~p~n", [Log]), 379 case disk_log:sync(Log) of 380 ok -> ok; 381 {error, {read_only_mode, Log}} -> 382 ok; 383 {error, Reason} -> 384 mnesia_lib:important("Failed syncing ~tp to_disk reason ~tp ~n", 385 [Log, Reason]) 386 end, 387 mnesia_monitor:close_log(Log). 388 389unsafe_close_log(Log) -> 390%% io:format("mnesia_log:close_log ~p~n", [Log]), 391 mnesia_monitor:unsafe_close_log(Log). 392 393 394purge_some_logs() -> 395 mnesia_monitor:unsafe_close_log(latest_log), 396 _ = file:delete(latest_log_file()), 397 _ = file:delete(decision_tab_file()), 398 ok. 399 400purge_all_logs() -> 401 _ = file:delete(previous_log_file()), 402 _ = file:delete(latest_log_file()), 403 _ = file:delete(decision_tab_file()), 404 ok. 405 406%% Prepare dump by renaming the open logfile if possible 407%% Returns a tuple on the following format: {Res, OpenLog} 408%% where OpenLog is the file descriptor to log file, ready for append 409%% and Res is one of the following: already_dumped, needs_dump or {error, Reason} 410prepare_log_dump(InitBy) -> 411 Diff = mnesia_dumper:get_log_writes() - 412 mnesia_lib:read_counter(trans_log_writes_prev), 413 if 414 Diff == 0, InitBy /= startup -> 415 already_dumped; 416 true -> 417 case mnesia_monitor:use_dir() of 418 true -> 419 Prev = previous_log_file(), 420 prepare_prev(Diff, InitBy, Prev, exists(Prev)); 421 false -> 422 already_dumped 423 end 424 end. 425 426prepare_prev(Diff, _, _, true) -> 427 {needs_dump, Diff}; 428prepare_prev(Diff, startup, Prev, false) -> 429 Latest = latest_log_file(), 430 case exists(Latest) of 431 true -> 432 case file:rename(Latest, Prev) of 433 ok -> 434 {needs_dump, Diff}; 435 {error, Reason} -> 436 {error, Reason} 437 end; 438 false -> 439 already_dumped 440 end; 441prepare_prev(Diff, _InitBy, Prev, false) -> 442 Head = trans_log_header(), 443 case mnesia_monitor:reopen_log(latest_log, Prev, Head) of 444 ok -> 445 {needs_dump, Diff}; 446 {error, Reason} -> 447 Latest = latest_log_file(), 448 {error, {"Cannot rename log file", 449 [Latest, Prev, Reason]}} 450 end. 451 452%% Init dump and return PrevLogFileDesc or exit. 453init_log_dump() -> 454 Fname = previous_log_file(), 455 open_log(previous_log, trans_log_header(), Fname), 456 start. 457 458 459chunk_log(Cont) -> 460 chunk_log(previous_log, Cont). 461 462chunk_log(_Log, eof) -> 463 eof; 464chunk_log(Log, Cont) -> 465 case disk_log:chunk(Log, Cont) of 466 {error, Reason} -> 467 fatal("Possibly truncated ~tp file: ~tp~n", 468 [Log, Reason]); 469 {C2, Chunk, _BadBytes} -> 470 %% Read_only case, should we warn about the bad log file? 471 %% BUGBUG Should we crash if Repair == false ?? 472 %% We got to check this !! 473 mnesia_lib:important("~tp repaired, lost ~p bad bytes~n", [Log, _BadBytes]), 474 {C2, Chunk}; 475 Other -> 476 Other 477 end. 478 479%% Confirms the dump by closing prev log and delete the file 480confirm_log_dump(Updates) -> 481 case mnesia_monitor:close_log(previous_log) of 482 ok -> 483 file:delete(previous_log_file()), 484 mnesia_lib:incr_counter(trans_log_writes_prev, Updates), 485 dumped; 486 {error, Reason} -> 487 {error, Reason} 488 end. 489 490%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 491%% Decision log 492 493open_decision_log() -> 494 Latest = decision_log_file(), 495 open_log(decision_log, decision_log_header(), Latest), 496 start. 497 498prepare_decision_log_dump() -> 499 Prev = previous_decision_log_file(), 500 prepare_decision_log_dump(exists(Prev), Prev). 501 502prepare_decision_log_dump(false, Prev) -> 503 Head = decision_log_header(), 504 case mnesia_monitor:reopen_log(decision_log, Prev, Head) of 505 ok -> 506 prepare_decision_log_dump(true, Prev); 507 {error, Reason} -> 508 fatal("Cannot rename decision log file ~tp -> ~tp: ~tp~n", 509 [decision_log_file(), Prev, Reason]) 510 end; 511prepare_decision_log_dump(true, Prev) -> 512 open_log(previous_decision_log, decision_log_header(), Prev), 513 start. 514 515chunk_decision_log(Cont) -> 516 %% dbg_out("chunk log ~p~n", [Cont]), 517 chunk_log(previous_decision_log, Cont). 518 519%% Confirms dump of the decision log 520confirm_decision_log_dump() -> 521 case mnesia_monitor:close_log(previous_decision_log) of 522 ok -> 523 file:delete(previous_decision_log_file()); 524 {error, Reason} -> 525 fatal("Cannot confirm decision log dump: ~tp~n", 526 [Reason]) 527 end. 528 529save_decision_tab(Decisions) -> 530 Log = decision_tab, 531 Tmp = mnesia_lib:dir("DECISION_TAB.TMP"), 532 file:delete(Tmp), 533 open_log(Log, decision_tab_header(), Tmp), 534 append(Log, Decisions), 535 close_log(Log), 536 TabFile = decision_tab_file(), 537 ok = file:rename(Tmp, TabFile). 538 539open_decision_tab() -> 540 TabFile = decision_tab_file(), 541 open_log(decision_tab, decision_tab_header(), TabFile), 542 start. 543 544close_decision_tab() -> 545 close_log(decision_tab). 546 547chunk_decision_tab(Cont) -> 548 %% dbg_out("chunk tab ~p~n", [Cont]), 549 chunk_log(decision_tab, Cont). 550 551close_decision_log() -> 552 close_log(decision_log). 553 554log_decision(Decision) -> 555 append(decision_log, Decision). 556 557%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 558%% Debug functions 559 560view() -> 561 lists:foreach(fun(F) -> view(F) end, log_files()). 562 563view(File) -> 564 mnesia_lib:show("***** ~tp ***** ~n", [File]), 565 case exists(File) of 566 false -> 567 nolog; 568 true -> 569 N = view_only, 570 Args = [{file, File}, {name, N}, {mode, read_only}], 571 case disk_log:open(Args) of 572 {ok, N} -> 573 view_file(start, N); 574 {repaired, _, _, _} -> 575 view_file(start, N); 576 {error, Reason} -> 577 error("Cannot open log ~tp: ~tp~n", [File, Reason]) 578 end 579 end. 580 581view_file(C, Log) -> 582 case disk_log:chunk(Log, C) of 583 {error, Reason} -> 584 error("** Possibly truncated FILE ~tp~n", [Reason]), 585 error; 586 eof -> 587 disk_log:close(Log), 588 eof; 589 {C2, Terms, _BadBytes} -> 590 dbg_out("Lost ~p bytes in ~tp ~n", [_BadBytes, Log]), 591 lists:foreach(fun(X) -> mnesia_lib:show("~tp~n", [X]) end, 592 Terms), 593 view_file(C2, Log); 594 {C2, Terms} -> 595 lists:foreach(fun(X) -> mnesia_lib:show("~tp~n", [X]) end, 596 Terms), 597 view_file(C2, Log) 598 end. 599 600%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 601%% Backup 602 603-record(backup_args, {name, module, opaque, scope, prev_name, tables, cookie}). 604 605backup(Opaque) -> 606 backup(Opaque, []). 607 608backup(Opaque, Mod) when is_atom(Mod) -> 609 backup(Opaque, [{module, Mod}]); 610backup(Opaque, Args) when is_list(Args) -> 611 %% Backup all tables with max redundancy 612 CpArgs = [{ram_overrides_dump, false}, {max, val({schema, tables})}], 613 case mnesia_checkpoint:activate(CpArgs) of 614 {ok, Name, _Nodes} -> 615 Res = backup_checkpoint(Name, Opaque, Args), 616 mnesia_checkpoint:deactivate(Name), 617 Res; 618 {error, Reason} -> 619 {error, Reason} 620 end. 621 622backup_checkpoint(Name, Opaque) -> 623 backup_checkpoint(Name, Opaque, []). 624 625backup_checkpoint(Name, Opaque, Mod) when is_atom(Mod) -> 626 backup_checkpoint(Name, Opaque, [{module, Mod}]); 627backup_checkpoint(Name, Opaque, Args) when is_list(Args) -> 628 DefaultMod = mnesia_monitor:get_env(backup_module), 629 B = #backup_args{name = Name, 630 module = DefaultMod, 631 opaque = Opaque, 632 scope = global, 633 tables = all, 634 prev_name = Name}, 635 case check_backup_args(Args, B) of 636 {ok, B2} -> 637 %% Decentralized backup 638 %% Incremental 639 640 Self = self(), 641 Pid = spawn_link(?MODULE, backup_master, [Self, B2]), 642 receive 643 {Pid, Self, Res} -> Res 644 end; 645 {error, Reason} -> 646 {error, Reason} 647 end. 648 649check_backup_args([Arg | Tail], B) -> 650 try check_backup_arg_type(Arg, B) of 651 B2 -> 652 check_backup_args(Tail, B2) 653 catch error:_ -> 654 {error, {badarg, Arg}} 655 end; 656 657check_backup_args([], B) -> 658 {ok, B}. 659 660check_backup_arg_type(Arg, B) -> 661 case Arg of 662 {scope, global} -> 663 B#backup_args{scope = global}; 664 {scope, local} -> 665 B#backup_args{scope = local}; 666 {module, Mod} -> 667 Mod2 = mnesia_monitor:do_check_type(backup_module, Mod), 668 B#backup_args{module = Mod2}; 669 {incremental, Name} -> 670 B#backup_args{prev_name = Name}; 671 {tables, Tabs} when is_list(Tabs) -> 672 B#backup_args{tables = Tabs} 673 end. 674 675backup_master(ClientPid, B) -> 676 process_flag(trap_exit, true), 677 try do_backup_master(B) of 678 Res -> 679 ClientPid ! {self(), ClientPid, Res} 680 catch _:Reason -> 681 ClientPid ! {self(), ClientPid, {error, {'EXIT', Reason}}} 682 end, 683 unlink(ClientPid), 684 exit(normal). 685 686do_backup_master(B) -> 687 Name = B#backup_args.name, 688 B2 = safe_apply(B, open_write, [B#backup_args.opaque]), 689 B3 = safe_write(B2, [backup_log_header()]), 690 case mnesia_checkpoint:tables_and_cookie(Name) of 691 {ok, AllTabs, Cookie} -> 692 Tabs = select_tables(AllTabs, B3), 693 B4 = B3#backup_args{cookie = Cookie}, 694 %% Always put schema first in backup file 695 B5 = backup_schema(B4, Tabs), 696 B6 = lists:foldl(fun backup_tab/2, B5, Tabs -- [schema]), 697 safe_apply(B6, commit_write, [B6#backup_args.opaque]), 698 ok; 699 {error, Reason} -> 700 abort_write(B3, {?MODULE, backup_master}, [B], {error, Reason}) 701 end. 702 703select_tables(AllTabs, B) -> 704 Tabs = 705 case B#backup_args.tables of 706 all -> AllTabs; 707 SomeTabs when is_list(SomeTabs) -> SomeTabs 708 end, 709 case B#backup_args.scope of 710 global -> 711 Tabs; 712 local -> 713 Name = B#backup_args.name, 714 [T || T <- Tabs, mnesia_checkpoint:most_local_node(Name, T) == {ok, node()}] 715 end. 716 717safe_write(B, []) -> 718 B; 719safe_write(B, Recs) -> 720 safe_apply(B, write, [B#backup_args.opaque, Recs]). 721 722backup_schema(B, Tabs) -> 723 case lists:member(schema, Tabs) of 724 true -> 725 backup_tab(schema, B); 726 false -> 727 Defs = [{schema, T, mnesia_schema:get_create_list(T)} || T <- Tabs], 728 safe_write(B, Defs) 729 end. 730 731safe_apply(B, write, [_, Items]) when Items == [] -> 732 B; 733safe_apply(B, What, Args) -> 734 Abort = abort_write_fun(B, What, Args), 735 receive 736 {'EXIT', Pid, R} -> Abort({'EXIT', Pid, R}) 737 after 0 -> 738 Mod = B#backup_args.module, 739 try apply(Mod, What, Args) of 740 {ok, Opaque} -> B#backup_args{opaque=Opaque}; 741 {error, R} -> Abort(R) 742 catch _:R -> Abort(R) 743 end 744 end. 745 746-spec abort_write_fun(_, _, _) -> fun((_) -> no_return()). 747abort_write_fun(B, What, Args) -> 748 fun(R) -> abort_write(B, What, Args, R) end. 749 750abort_write(B, What, Args, Reason) -> 751 Mod = B#backup_args.module, 752 Opaque = B#backup_args.opaque, 753 dbg_out("Failed to perform backup. M=~p:F=~tp:A=~tp -> ~tp~n", 754 [Mod, What, Args, Reason]), 755 try {ok, _Res} = apply(Mod, abort_write, [Opaque]) of 756 _ -> throw({error, Reason}) 757 catch _:Other -> 758 error("Failed to abort backup. ~p:~tp~tp -> ~tp~n", 759 [Mod, abort_write, [Opaque], Other]), 760 throw({error, Reason}) 761 end. 762 763backup_tab(Tab, B) -> 764 Name = B#backup_args.name, 765 case mnesia_checkpoint:most_local_node(Name, Tab) of 766 {ok, Node} when Node == node() -> 767 tab_copier(self(), B, Tab); 768 {ok, Node} -> 769 RemoteB = B, 770 Pid = spawn_link(Node, ?MODULE, tab_copier, [self(), RemoteB, Tab]), 771 RecName = val({Tab, record_name}), 772 tab_receiver(Pid, B, Tab, RecName, 0); 773 {error, Reason} -> 774 abort_write(B, {?MODULE, backup_tab}, [Tab, B], {error, Reason}) 775 end. 776 777tab_copier(Pid, B, Tab) when is_record(B, backup_args) -> 778 %% Intentional crash at exit 779 Name = B#backup_args.name, 780 PrevName = B#backup_args.prev_name, 781 {FirstName, FirstSource} = select_source(Tab, Name, PrevName), 782 783 ?eval_debug_fun({?MODULE, tab_copier, pre}, [{name, Name}, {tab, Tab}]), 784 Res = handle_more(Pid, B, Tab, FirstName, FirstSource, Name), 785 ?eval_debug_fun({?MODULE, tab_copier, post}, [{name, Name}, {tab, Tab}]), 786 787 handle_last(Pid, Res). 788 789select_source(Tab, Name, PrevName) -> 790 if 791 Tab == schema -> 792 %% Always full backup of schema 793 {Name, table}; 794 Name == PrevName -> 795 %% Full backup 796 {Name, table}; 797 true -> 798 %% Wants incremental backup 799 case mnesia_checkpoint:most_local_node(PrevName, Tab) of 800 {ok, Node} when Node == node() -> 801 %% Accept incremental backup 802 {PrevName, retainer}; 803 _ -> 804 %% Do a full backup anyway 805 dbg_out("Incremental backup escalated to full backup: ~tp~n", [Tab]), 806 {Name, table} 807 end 808 end. 809 810handle_more(Pid, B, Tab, FirstName, FirstSource, Name) -> 811 Acc = {0, B}, 812 case {mnesia_checkpoint:really_retain(Name, Tab), 813 mnesia_checkpoint:really_retain(FirstName, Tab)} of 814 {true, true} -> 815 Acc2 = iterate(B, FirstName, Tab, Pid, FirstSource, latest, first, Acc), 816 iterate(B, Name, Tab, Pid, retainer, checkpoint, last, Acc2); 817 {false, false}-> 818 %% Put the dumped file in the backup 819 %% instead of the ram table. Does 820 %% only apply to ram_copies. 821 iterate(B, Name, Tab, Pid, retainer, checkpoint, last, Acc); 822 Bad -> 823 Reason = {"Checkpoints for incremental backup must have same " 824 "setting of ram_overrides_dump", 825 Tab, Name, FirstName, Bad}, 826 abort_write(B, {?MODULE, backup_tab}, [Tab, B], {error, Reason}) 827 end. 828 829handle_last(Pid, {_Count, B}) when Pid == self() -> 830 B; 831handle_last(Pid, _Acc) -> 832 unlink(Pid), 833 Pid ! {self(), {last, {ok, dummy}}}, 834 exit(normal). 835 836iterate(B, Name, Tab, Pid, Source, Age, Pass, Acc) -> 837 Fun = 838 if 839 Pid == self() -> 840 RecName = val({Tab, record_name}), 841 fun(Recs, A) -> copy_records(RecName, Tab, Recs, A) end; 842 true -> 843 fun(Recs, A) -> send_records(Pid, Tab, Recs, Pass, A) end 844 end, 845 case mnesia_checkpoint:iterate(Name, Tab, Fun, Acc, Source, Age) of 846 {ok, Acc2} -> 847 Acc2; 848 {error, Reason} -> 849 R = {error, {"Tab copier iteration failed", Reason}}, 850 abort_write(B, {?MODULE, iterate}, [self(), B, Tab], R) 851 end. 852 853copy_records(_RecName, _Tab, [], Acc) -> 854 Acc; 855copy_records(RecName, Tab, Recs, {Count, B}) -> 856 Recs2 = rec_filter(B, Tab, RecName, Recs), 857 B2 = safe_write(B, Recs2), 858 {Count + 1, B2}. 859 860send_records(Pid, Tab, Recs, Pass, {Count, B}) -> 861 receive 862 {Pid, more, Count} -> 863 if 864 Pass == last, Recs == [] -> 865 {Count, B}; 866 true -> 867 Next = Count + 1, 868 Pid ! {self(), {more, Next, Recs}}, 869 {Next, B} 870 end; 871 Msg -> 872 exit({send_records_unexpected_msg, Tab, Msg}) 873 end. 874 875tab_receiver(Pid, B, Tab, RecName, Slot) -> 876 Pid ! {self(), more, Slot}, 877 receive 878 {Pid, {more, Next, Recs}} -> 879 Recs2 = rec_filter(B, Tab, RecName, Recs), 880 B2 = safe_write(B, Recs2), 881 tab_receiver(Pid, B2, Tab, RecName, Next); 882 883 {Pid, {last, {ok,_}}} -> 884 B; 885 886 {'EXIT', Pid, {error, R}} -> 887 Reason = {error, {"Tab copier crashed", R}}, 888 abort_write(B, {?MODULE, remote_tab_sender}, [self(), B, Tab], Reason); 889 {'EXIT', Pid, R} -> 890 Reason = {error, {"Tab copier crashed", {'EXIT', R}}}, 891 abort_write(B, {?MODULE, remote_tab_sender}, [self(), B, Tab], Reason); 892 Msg -> 893 R = {error, {"Tab receiver got unexpected msg", Msg}}, 894 abort_write(B, {?MODULE, remote_tab_sender}, [self(), B, Tab], R) 895 end. 896 897rec_filter(B, schema, _RecName, Recs) -> 898 try mnesia_bup:refresh_cookie(Recs, B#backup_args.cookie) 899 catch throw:{error, _Reason} -> 900 %% No schema table cookie 901 Recs 902 end; 903rec_filter(_B, Tab, Tab, Recs) -> 904 Recs; 905rec_filter(_B, Tab, _RecName, Recs) -> 906 [setelement(1, Rec, Tab) || Rec <- Recs]. 907 908ets2dcd(Tab) -> 909 ets2dcd(Tab, dcd). 910 911ets2dcd(Tab, Ftype) -> 912 Fname = 913 case Ftype of 914 dcd -> mnesia_lib:tab2dcd(Tab); 915 dmp -> mnesia_lib:tab2dmp(Tab) 916 end, 917 TmpF = mnesia_lib:tab2tmp(Tab), 918 file:delete(TmpF), 919 Log = open_log({Tab, ets2dcd}, dcd_log_header(), TmpF, false), 920 mnesia_lib:db_fixtable(ram_copies, Tab, true), 921 ok = ets2dcd(mnesia_lib:db_init_chunk(ram_copies, Tab, 1000), Tab, Log), 922 mnesia_lib:db_fixtable(ram_copies, Tab, false), 923 close_log(Log), 924 ok = file:rename(TmpF, Fname), 925 %% Remove old log data which is now in the new dcd. 926 %% No one else should be accessing this file! 927 file:delete(mnesia_lib:tab2dcl(Tab)), 928 ok. 929 930ets2dcd('$end_of_table', _Tab, _Log) -> 931 ok; 932ets2dcd({Recs, Cont}, Tab, Log) -> 933 ok = disk_log:log_terms(Log, Recs), 934 ets2dcd(mnesia_lib:db_chunk(ram_copies, Cont), Tab, Log). 935 936dcd2ets(Tab) -> 937 dcd2ets(Tab, mnesia_monitor:get_env(auto_repair)). 938 939dcd2ets(Tab, Rep) -> 940 Dcd = mnesia_lib:tab2dcd(Tab), 941 case mnesia_lib:exists(Dcd) of 942 true -> 943 Log = open_log({Tab, dcd2ets}, dcd_log_header(), Dcd, 944 true, Rep, read_only), 945 Data = chunk_log(Log, start), 946 ok = insert_dcdchunk(Data, Log, Tab), 947 close_log(Log), 948 load_dcl(Tab, Rep); 949 false -> %% Handle old dets files, and conversion from disc_only to disc. 950 Fname = mnesia_lib:tab2dat(Tab), 951 Type = val({Tab, setorbag}), 952 case mnesia_lib:dets_to_ets(Tab, Tab, Fname, Type, Rep, yes) of 953 loaded -> 954 ets2dcd(Tab), 955 file:delete(Fname), 956 0; 957 {error, Error} -> 958 erlang:error({"Failed to load table from disc", [Tab, Error]}) 959 end 960 end. 961 962insert_dcdchunk({Cont, [LogH | Rest]}, Log, Tab) 963 when is_record(LogH, log_header), 964 LogH#log_header.log_kind == dcd_log, 965 LogH#log_header.log_version >= "1.0" -> 966 insert_dcdchunk({Cont, Rest}, Log, Tab); 967 968insert_dcdchunk({Cont, Recs}, Log, Tab) -> 969 true = ets:insert(Tab, Recs), 970 insert_dcdchunk(chunk_log(Log, Cont), Log, Tab); 971insert_dcdchunk(eof, _Log, _Tab) -> 972 ok. 973 974load_dcl(Tab, Rep) -> 975 FName = mnesia_lib:tab2dcl(Tab), 976 case mnesia_lib:exists(FName) of 977 true -> 978 Name = {load_dcl,Tab}, 979 open_log(Name, 980 dcl_log_header(), 981 FName, 982 true, 983 Rep, 984 read_only), 985 FirstChunk = chunk_log(Name, start), 986 N = insert_logchunk(FirstChunk, Name, 0), 987 close_log(Name), 988 N; 989 false -> 990 0 991 end. 992 993insert_logchunk({C2, Recs}, Tab, C) -> 994 N = add_recs(Recs, C), 995 insert_logchunk(chunk_log(Tab, C2), Tab, C+N); 996insert_logchunk(eof, _Tab, C) -> 997 C. 998 999add_recs([{{Tab, _Key}, Val, write} | Rest], N) -> 1000 true = ets:insert(Tab, Val), 1001 add_recs(Rest, N+1); 1002add_recs([{{Tab, Key}, _Val, delete} | Rest], N) -> 1003 true = ets:delete(Tab, Key), 1004 add_recs(Rest, N+1); 1005add_recs([{{Tab, _Key}, Val, delete_object} | Rest], N) -> 1006 true = ets:match_delete(Tab, Val), 1007 add_recs(Rest, N+1); 1008add_recs([{{Tab, Key}, Val, update_counter} | Rest], N) -> 1009 {RecName, Incr} = Val, 1010 try 1011 CounterVal = ets:update_counter(Tab, Key, Incr), 1012 true = (CounterVal >= 0) 1013 catch 1014 error:_ when Incr < 0 -> 1015 Zero = {RecName, Key, 0}, 1016 true = ets:insert(Tab, Zero); 1017 error:_ -> 1018 Zero = {RecName, Key, Incr}, 1019 true = ets:insert(Tab, Zero) 1020 end, 1021 add_recs(Rest, N+1); 1022add_recs([LogH|Rest], N) 1023 when is_record(LogH, log_header), 1024 LogH#log_header.log_kind == dcl_log, 1025 LogH#log_header.log_version >= "1.0" -> 1026 add_recs(Rest, N); 1027add_recs([{{Tab, _Key}, _Val, clear_table} | Rest], N) -> 1028 Size = ets:info(Tab, size), 1029 true = ets:delete_all_objects(Tab), 1030 add_recs(Rest, N+Size); 1031add_recs([], N) -> 1032 N. 1033