Packages
ra
2.16.6
3.1.9
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.2
3.0.1
3.0.0
3.0.0-beta.1
2.17.3
2.17.2
2.17.1
2.17.0
2.16.13
2.16.12
2.16.11
2.16.10
2.16.9
2.16.8
2.16.7
2.16.6
2.16.5
2.16.4
2.16.3
2.16.2
2.16.1
2.16.0
2.16.0-pre.12
2.16.0-pre.11
2.16.0-pre.10
2.16.0-pre.9
2.16.0-pre.8
2.16.0-pre.7
2.16.0-pre.6
2.16.0-pre.5
2.16.0-pre.4
2.16.0-pre.3
2.16.0-pre.2
2.16.0-pre.1
2.15.4
2.15.3
2.15.2
2.15.1
2.15.0
2.14.0
2.13.6
2.13.5
2.13.4
2.13.3
2.13.2
2.13.1
2.13.0
2.13.0-pre.1
2.12.0
2.11.0
2.11.0-pre.1
2.10.2-pre.2
2.10.2-pre.1
2.10.1
2.10.0
2.10.0-pre.3
2.10.0-pre.2
2.10.0-pre.1
2.9.10-pre.1
2.9.1
2.9.1-pre.2
2.9.1-pre.1
2.9.0
2.8.0
retired
2.7.3
2.7.2
2.7.1
2.7.0
2.7.0-pre.3
2.7.0-pre.2
2.7.0-pre.1
2.6.3
2.6.2
2.6.1
2.6.0-pre.1
2.5.1
2.5.1-pre.1
2.5.0
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
retired
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.13
2.0.12
2.0.11
2.0.10
2.0.9
2.0.8
2.0.7
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.9.6
0.9.5
0.9.4
0.9.2
0.3.3
retired
0.3.2
retired
0.3.1
retired
Raft library
Current section
Files
Jump to
Current section
Files
src/ra_log_snapshot.erl
%% This Source Code Form is subject to the terms of the Mozilla Public
%% License, v. 2.0. If a copy of the MPL was not distributed with this
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
%%
%% Copyright (c) 2017-2023 Broadcom. All Rights Reserved. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries.
%%
%% @hidden
-module(ra_log_snapshot).
-behaviour(ra_snapshot).
-export([
prepare/2,
write/4,
sync/1,
begin_accept/2,
accept_chunk/2,
complete_accept/2,
begin_read/2,
read_chunk/3,
recover/1,
validate/1,
read_meta/1,
context/0
]).
-define(MAGIC, "RASN").
-define(VERSION, 1).
-type file_err() :: ra_snapshot:file_err().
-type meta() :: ra_snapshot:meta().
%% DO nothing. There is no preparation for snapshotting
prepare(_Index, State) -> State.
%% @doc
%% Snapshot file format:
%% "RASN"
%% Version (byte)
%% Checksum (unsigned 32)
%% MetaData Len (unsigned 32)
%% MetaData (binary)
%% Snapshot Data (binary)
%% @end
-spec write(file:filename(), meta(), term(), Sync :: boolean()) ->
{ok, non_neg_integer()} | {error, file_err()}.
write(Dir, Meta, MacState, Sync) ->
%% no compression on meta data to make sure reading it is as fast
%% as possible
MetaBin = term_to_binary(Meta),
IOVec = term_to_iovec(MacState),
Data = [<<(size(MetaBin)):32/unsigned>>, MetaBin | IOVec],
Checksum = erlang:crc32(Data),
File = filename(Dir),
Bytes = 9 + iolist_size(Data),
case ra_lib:write_file(File, [<<?MAGIC,
?VERSION:8/unsigned,
Checksum:32/integer>>,
Data], Sync) of
ok ->
{ok, Bytes};
Err ->
Err
end.
-spec sync(file:filename()) ->
ok | {error, file_err()}.
sync(Dir) ->
File = filename(Dir),
ra_lib:sync_file(File).
begin_accept(SnapDir, Meta) ->
File = filename(SnapDir),
{ok, Fd} = file:open(File, [write, binary, raw]),
MetaBin = term_to_binary(Meta),
Data = [<<(size(MetaBin)):32/unsigned>>, MetaBin],
PartialCrc = erlang:crc32(Data),
ok = file:write(Fd, [<<?MAGIC,
?VERSION:8/unsigned,
0:32/integer>>,
Data]),
{ok, {PartialCrc, Fd}}.
accept_chunk(<<?MAGIC, ?VERSION:8/unsigned, Crc:32/integer,
Rest/binary>> = Chunk, {_PartialCrc, Fd}) ->
% ensure we overwrite the existing header when we are receiving the
% full file
PartialCrc = erlang:crc32(Rest),
{ok, 0} = file:position(Fd, 0),
ok = file:write(Fd, Chunk),
{ok, {PartialCrc, Crc, Fd}};
accept_chunk(Chunk, {PartialCrc, Fd}) ->
%% compatibility clause where we did not receive the full file
%% do not validate Crc due to OTP 26 map key ordering changes
<<_Crc:32/integer, Rest/binary>> = Chunk,
accept_chunk(Rest, {PartialCrc, undefined, Fd});
accept_chunk(Chunk, {PartialCrc0, Crc, Fd}) ->
ok = file:write(Fd, Chunk),
PartialCrc = erlang:crc32(PartialCrc0, Chunk),
{ok, {PartialCrc, Crc, Fd}}.
complete_accept(Chunk, St0) ->
{ok, {CalculatedCrc, Crc, Fd}} = accept_chunk(Chunk, St0),
CrcToWrite = case Crc of
undefined ->
CalculatedCrc;
_ ->
Crc
end,
ok = file:pwrite(Fd, 5, <<CrcToWrite:32/integer>>),
ok = ra_file:sync(Fd),
ok = file:close(Fd),
CalculatedCrc = CrcToWrite,
ok.
begin_read(Dir, Context) ->
File = filename(Dir),
case file:open(File, [read, binary, raw]) of
{ok, Fd} ->
case read_meta_internal(Fd) of
{ok, Meta, _Crc}
when map_get(can_accept_full_file, Context) ->
{ok, Eof} = file:position(Fd, eof),
{ok, Meta, {0, Eof, Fd}};
{ok, Meta, Crc} ->
{ok, Cur} = file:position(Fd, cur),
{ok, Eof} = file:position(Fd, eof),
{ok, Meta, {Crc, {Cur, Eof, Fd}}};
{error, _} = Err ->
_ = file:close(Fd),
Err
end;
Err ->
Err
end.
read_chunk({Crc, ReadState}, Size, Dir) when is_integer(Crc) ->
%% this the compatibility read mode for old snapshot receivers
case read_chunk(ReadState, Size - 4, Dir) of
{ok, Data, ReadState1} ->
{ok, <<Crc:32/integer, Data/binary>>, ReadState1};
{error, _} = Err ->
Err
end;
read_chunk({Pos, Eof, Fd}, Size, _Dir) ->
case file:pread(Fd, Pos, Size) of
{ok, Data} ->
case Pos + Size >= Eof of
true ->
_ = file:close(Fd),
{ok, Data, last};
false ->
{ok, Data, {next, {Pos + Size, Eof, Fd}}}
end;
{error, _} = Err ->
Err;
eof ->
{error, unexpected_eof}
end.
-spec recover(file:filename()) ->
{ok, meta(), term()} |
{error, invalid_format |
{invalid_version, integer()} |
checksum_error |
file_err()}.
recover(Dir) ->
File = filename(Dir),
case prim_file:read_file(File) of
{ok, <<?MAGIC, ?VERSION:8/unsigned, Crc:32/integer, Data/binary>>} ->
validate(Crc, Data);
{ok, <<?MAGIC, Version:8/unsigned, _:32/integer, _/binary>>} ->
{error, {invalid_version, Version}};
{ok, _} ->
{error, invalid_format};
{error, _} = Err ->
Err
end.
validate(Dir) ->
case recover(Dir) of
{ok, _, _} -> ok;
Err -> Err
end.
%% @doc reads the index and term from the snapshot file without reading the
%% entire binary body. NB: this does not do checksum validation.
-spec read_meta(file:filename()) ->
{ok, meta()} | {error, invalid_format |
{invalid_version, integer()} |
checksum_error |
file_err()}.
read_meta(Dir) ->
File = filename(Dir),
case file:open(File, [read, binary, raw]) of
{ok, Fd} ->
case read_meta_internal(Fd) of
{ok, Meta, _Crc} ->
_ = file:close(Fd),
{ok, Meta};
Err ->
_ = file:close(Fd),
Err
end
end.
-spec context() -> map().
context() ->
#{can_accept_full_file => true}.
%% Internal
read_meta_internal(Fd) ->
HeaderSize = 9 + 4,
case file:read(Fd, HeaderSize) of
{ok, <<?MAGIC, ?VERSION:8/unsigned, Crc:32/integer,
MetaSize:32/unsigned>>} ->
case file:read(Fd, MetaSize) of
{ok, MetaBin} ->
{ok, binary_to_term(MetaBin), Crc};
Err ->
Err
end;
{ok, <<?MAGIC, Version:8/unsigned, _:32/integer, _/binary>>} ->
{error, {invalid_version, Version}};
{ok, _} ->
{error, invalid_format};
eof ->
{error, unexpected_eof_when_parsing_header};
Err ->
Err
end.
validate(Crc, Data) ->
case erlang:crc32(Data) of
Crc ->
parse_snapshot(Data);
_ ->
{error, checksum_error}
end.
parse_snapshot(<<MetaSize:32/unsigned, MetaBin:MetaSize/binary,
Rest/binary>>) ->
Meta = binary_to_term(MetaBin),
{ok, Meta, binary_to_term(Rest)}.
filename(Dir) ->
filename:join(Dir, "snapshot.dat").
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.