Current section

Files

Jump to
grisp_updater src grisp_updater_checker.erl
Raw

src/grisp_updater_checker.erl

-module(grisp_updater_checker).
-behavior(gen_server).
%--- Includes ------------------------------------------------------------------
-include_lib("kernel/include/logger.hrl").
-include("grisp_updater.hrl").
%--- Exports -------------------------------------------------------------------
% API
-export([start_link/1]).
-export([schedule_check/2]).
-export([cancel_check/1]).
-export([abort/0]).
% Callbacks
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
%--- Records -------------------------------------------------------------------
-record(check, {
block :: #block{},
target :: #target{}
}).
-record(state, {
pending = #{} :: #{non_neg_integer() => #check{}},
schedule = queue:new() :: queue:queue()
}).
%--- Macros --------------------------------------------------------------------
-define(TIMEOUT, 10).
%--- API Functions -------------------------------------------------------------
start_link(Opts) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Opts, []).
schedule_check(Block, Target) ->
gen_server:cast(?MODULE, {schedule, Block, Target}).
cancel_check(BlockId) ->
gen_server:call(?MODULE, {cancel, BlockId}).
abort() ->
gen_server:call(?MODULE, abort).
%--- Callbacks -----------------------------------------------------------------
init(_Opts) ->
?LOG_INFO("Starting GRiSP updater's block checker ..."),
{ok, #state{}}.
handle_call(abort, _From, State) ->
{reply, ok, State#state{pending = #{}, schedule = queue:new()}};
handle_call({cancel, BlockId}, _From, #state{pending = Map} = State) ->
State2 = State#state{pending = maps:remove(BlockId, Map)},
{reply, ok, State2, timeout(State2)};
handle_call(Request, From, State) ->
?LOG_WARNING("Unexpected call from ~p: ~p", [From, Request]),
{reply, {error, unexpected_call}, State, timeout(State)}.
handle_cast({schedule, #block{id = Id} = Block, Target},
#state{pending = M, schedule = Q} = State) ->
Check = #check{block = Block, target = Target},
case maps:find(Id, M) of
{ok, _} ->
grisp_updater_manager:checker_error(Id, already_scheduled),
{noreply, State, timeout(State)};
error ->
M2 = M#{Id => Check},
Q2 = queue:in(Id, Q),
State2 = State#state{pending = M2, schedule = Q2},
{noreply, State2, timeout(State2)}
end;
handle_cast(Request, State) ->
?LOG_WARNING("Unexpected cast: ~p", [Request]),
{noreply, State, timeout(State)}.
handle_info(timeout, #state{pending = M, schedule = Q} = State) ->
FinalState = case queue:out(Q) of
{empty, Q2} -> State#state{schedule = Q2};
{{value, Id}, Q2} ->
case maps:take(Id, M) of
error -> State#state{schedule = Q2};
{Check, M2} ->
State2 = State#state{pending = M2, schedule = Q2},
do_check(State2, Check)
end
end,
{noreply, FinalState, timeout(FinalState)};
handle_info(Info, State) ->
?LOG_WARNING("Unexpected message: ~p", [Info]),
{noreply, State, timeout(State)}.
%--- Internal ------------------------------------------------------------------
timeout(#state{pending = M}) when map_size(M) > 0 -> ?TIMEOUT;
timeout(_State) -> infinity.
do_check(State, Check) ->
#check{
block = #block{
id = Id,
data_offset = DataOffset,
data_size = DataSize,
data_crc = ExpectedCrc
},
target = #target{device = Device, offset = DeviceOffset}
} = Check,
?LOG_DEBUG("Checking block ~b [~b+~b=~b:~b] from ~s",
[Id, DeviceOffset, DataOffset,
DeviceOffset + DataOffset, DataSize, Device]),
Offset = DeviceOffset + DataOffset,
%TODO: Mabe do some device boundary checks ?
case grisp_updater_storage:digest(crc32, Device, Offset, DataSize) of
{error, eof} ->
% Needed to check files that may be smaller than they updated version
%TODO: Maybe only allow that for regular files, not for devices
?LOG_DEBUG("Block ~b check error: eof", [Id]),
grisp_updater_manager:checker_done(Id, false),
State;
{error, Reason} ->
?LOG_DEBUG("Block ~b check error: ~w", [Id, Reason]),
grisp_updater_manager:checker_error(Id, Reason),
State;
{ok, ExpectedCrc} ->
?LOG_DEBUG("Block ~b check passed (~b)", [Id, ExpectedCrc]),
grisp_updater_manager:checker_done(Id, true),
State;
{ok, BlockCrc} ->
?LOG_DEBUG("Block ~b check failed, got ~b and expecting ~b",
[Id, BlockCrc, ExpectedCrc]),
grisp_updater_manager:checker_done(Id, false),
State
end.