# 9 Appendix A: Backup Callback Interface

###
9.1 mnesia_backup Callback Behavior

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%
    %% 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),
        file:delete(File),
        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 ->
    		    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.