%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% This module contains one implementation of callback functions
%% used by Mnesia at backup and restore. The user may however
%% write an own module the same interface as mnesia_backup and
%% configure Mnesia so the alternate module performs the actual
%% accesses to the backup media. This means that the user may put
%% the backup on medias that Mnesia does not know about, possibly
%% on hosts where Erlang is not running.
%%
%% The OpaqueData argument is never interpreted by other parts of
%% Mnesia. It is the property of this module. Alternate implementations
%% of this module may have different interpretations of OpaqueData.
%% The OpaqueData argument given to open_write/1 and open_read/1
%% are forwarded directly from the user.
%%
%% All functions must return {ok, NewOpaqueData} or {error, Reason}.
%%
%% The NewOpaqueData arguments returned by backup callback functions will
%% be given as input when the next backup callback function is invoked.
%% If any return value does not match {ok, _} the backup will be aborted.
%%
%% The NewOpaqueData arguments returned by restore callback functions will
%% be given as input when the next restore callback function is invoked
%% If any return value does not match {ok, _} the restore will be aborted.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-module(mnesia_backup).
-include_lib("kernel/include/file.hrl").
-export([
%% Write access
open_write/1,
write/2,
commit_write/1,
abort_write/1,
%% Read access
open_read/1,
read/1,
close_read/1
]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Backup callback interface
-record(backup, {tmp_file, file, file_desc}).
%% Opens backup media for write
%%
%% Returns {ok, OpaqueData} or {error, Reason}
open_write(OpaqueData) ->
File = OpaqueData,
Tmp = lists:concat([File,".BUPTMP"]),
file:delete(Tmp),
case disk_log:open([{name, make_ref()},
{file, Tmp},
{repair, false},
{linkto, self()}]) of
{ok, Fd} ->
{ok, #backup{tmp_file = Tmp, file = File, file_desc = Fd}};
{error, Reason} ->
{error, Reason}
end.
%% Writes BackupItems to the backup media
%%
%% Returns {ok, OpaqueData} or {error, Reason}
write(OpaqueData, BackupItems) ->
B = OpaqueData,
case disk_log:log_terms(B#backup.file_desc, BackupItems) of
ok ->
{ok, B};
{error, Reason} ->
abort_write(B),
{error, Reason}
end.
%% Closes the backup media after a successful backup
%%
%% Returns {ok, ReturnValueToUser} or {error, Reason}
commit_write(OpaqueData) ->
B = OpaqueData,
case disk_log:sync(B#backup.file_desc) of
ok ->
case disk_log:close(B#backup.file_desc) of
ok ->
file:delete(B#backup.file),
case file:rename(B#backup.tmp_file, B#backup.file) of
ok ->
{ok, B#backup.file};
{error, Reason} ->
{error, Reason}
end;
{error, Reason} ->
{error, Reason}
end;
{error, Reason} ->
{error, Reason}
end.
%% Closes the backup media after an interrupted backup
%%
%% Returns {ok, ReturnValueToUser} or {error, Reason}
abort_write(BackupRef) ->
Res = disk_log:close(BackupRef#backup.file_desc),
file:delete(BackupRef#backup.tmp_file),
case Res of
ok ->
{ok, BackupRef#backup.file};
{error, Reason} ->
{error, Reason}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Restore callback interface
-record(restore, {file, file_desc, cont}).
%% Opens backup media for read
%%
%% Returns {ok, OpaqueData} or {error, Reason}
open_read(OpaqueData) ->
File = OpaqueData,
case file:read_file_info(File) of
{error, Reason} ->
{error, Reason};
_FileInfo -> %% file exists
case disk_log:open([{file, File},
{name, make_ref()},
{repair, false},
{mode, read_only},
{linkto, self()}]) of
{ok, Fd} ->
{ok, #restore{file = File, file_desc = Fd, cont = start}};
{repaired, Fd, _, {badbytes, 0}} ->
{ok, #restore{file = File, file_desc = Fd, cont = start}};
{repaired, Fd, _, _} ->
{ok, #restore{file = File, file_desc = Fd, cont = start}};
{error, Reason} ->
{error, Reason}
end
end.
%% Reads BackupItems from the backup media
%%
%% Returns {ok, OpaqueData, BackupItems} or {error, Reason}
%%
%% BackupItems == [] is interpreted as eof
read(OpaqueData) ->
R = OpaqueData,
Fd = R#restore.file_desc,
case disk_log:chunk(Fd, R#restore.cont) of
{error, Reason} ->
{error, {"Possibly truncated", Reason}};
eof ->
{ok, R, []};
{Cont, []} ->
read(R#restore{cont = Cont});
{Cont, BackupItems, _BadBytes} ->
{ok, R#restore{cont = Cont}, BackupItems};
{Cont, BackupItems} ->
{ok, R#restore{cont = Cont}, BackupItems}
end.
%% Closes the backup media after restore
%%
%% Returns {ok, ReturnValueToUser} or {error, Reason}
close_read(OpaqueData) ->
R = OpaqueData,
case disk_log:close(R#restore.file_desc) of
ok -> {ok, R#restore.file};
{error, Reason} -> {error, Reason}
end.