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