%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(raw_file_io_deflate). -behavior(gen_statem). -export([init/1, callback_mode/0, terminate/3]). -export([opening/3, opened/3]). -include("file_int.hrl"). -define(GZIP_WBITS, 16 + 15). callback_mode() -> state_functions. init({Owner, Secret, [compressed]}) -> Monitor = monitor(process, Owner), Z = zlib:open(), ok = zlib:deflateInit(Z, default, deflated, ?GZIP_WBITS, 8, default), Data = #{ owner => Owner, monitor => Monitor, secret => Secret, position => 0, zlib => Z }, {ok, opening, Data}. opening({call, From}, {'$open', Secret, Filename, Modes}, #{ secret := Secret } = Data) -> case raw_file_io:open(Filename, Modes) of {ok, PrivateFd} -> NewData = Data#{ handle => PrivateFd }, {next_state, opened, NewData, [{reply, From, ok}]}; Other -> {stop_and_reply, normal, [{reply, From, Other}]} end; opening(_Event, _Contents, _Data) -> {keep_state_and_data, [postpone]}. %% opened(info, {'DOWN', Monitor, process, _Owner, Reason}, #{ monitor := Monitor } = Data) -> if Reason =/= kill -> flush_deflate_state(Data); Reason =:= kill -> ignored end, {stop, shutdown}; opened(info, _Message, _Data) -> keep_state_and_data; opened({call, {Owner, _Tag} = From}, [close], #{ owner := Owner } = Data) -> #{ handle := PrivateFd } = Data, Response = case flush_deflate_state(Data) of ok -> ?CALL_FD(PrivateFd, close, []); Other -> Other end, {stop_and_reply, normal, [{reply, From, Response}]}; opened({call, {Owner, _Tag} = From}, [position, Mark], #{ owner := Owner } = Data) -> case position(Data, Mark) of {ok, NewData, Result} -> Response = {ok, Result}, {keep_state, NewData, [{reply, From, Response}]}; Other -> {keep_state_and_data, [{reply, From, Other}]} end; opened({call, {Owner, _Tag} = From}, [write, IOVec], #{ owner := Owner } = Data) -> case write(Data, IOVec) of {ok, NewData} -> {keep_state, NewData, [{reply, From, ok}]}; Other -> {keep_state_and_data, [{reply, From, Other}]} end; opened({call, {Owner, _Tag} = From}, [read, _Size], #{ owner := Owner }) -> Response = {error, ebadf}, {keep_state_and_data, [{reply, From, Response}]}; opened({call, {Owner, _Tag} = From}, [read_line], #{ owner := Owner }) -> Response = {error, ebadf}, {keep_state_and_data, [{reply, From, Response}]}; opened({call, {Owner, _Tag} = From}, _Command, #{ owner := Owner }) -> Response = {error, enotsup}, {keep_state_and_data, [{reply, From, Response}]}; opened({call, _From}, _Command, _Data) -> %% The client functions filter this out, so we'll crash if the user does %% anything stupid on purpose. {shutdown, protocol_violation}; opened(_Event, _Request, _Data) -> keep_state_and_data. write(Data, IOVec) -> #{ handle := PrivateFd, position := Position, zlib := Z } = Data, UncompressedSize = iolist_size(IOVec), case ?CALL_FD(PrivateFd, write, [zlib:deflate(Z, IOVec)]) of ok -> {ok, Data#{ position := (Position + UncompressedSize) }}; Other -> Other end. %% %% We support "seeking" forward as long as it isn't relative to EOF. %% %% Seeking is a bit of a misnomer as it's really just compressing zeroes until %% we reach the desired point, but it has always behaved like this. %% position(Data, Mark) when is_atom(Mark) -> position(Data, {Mark, 0}); position(Data, Offset) when is_integer(Offset) -> position(Data, {bof, Offset}); position(Data, {bof, Offset}) when is_integer(Offset) -> position_1(Data, Offset); position(Data, {cur, Offset}) when is_integer(Offset) -> #{ position := Position } = Data, position_1(Data, Position + Offset); position(_Data, {eof, Offset}) when is_integer(Offset) -> {error, einval}; position(_Data, _Any) -> {error, badarg}. position_1(#{ position := Desired } = Data, Desired) -> {ok, Data, Desired}; position_1(#{ position := Current } = Data, Desired) when Current < Desired -> BytesToWrite = min(Desired - Current, 4 bsl 20), case write(Data, <<0:(BytesToWrite)/unit:8>>) of {ok, NewData} -> position_1(NewData, Desired); Other -> Other end; position_1(#{ position := Current }, Desired) when Current > Desired -> {error, einval}. flush_deflate_state(#{ handle := PrivateFd, zlib := Z }) -> case ?CALL_FD(PrivateFd, write, [zlib:deflate(Z, [], finish)]) of ok -> ok; Other -> Other end. terminate(_Reason, _State, _Data) -> ok.