1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2017. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(raw_file_io_deflate).
21
22-behavior(gen_statem).
23
24-export([init/1, callback_mode/0, terminate/3]).
25-export([opening/3, opened/3]).
26
27-include("file_int.hrl").
28
29-define(GZIP_WBITS, 16 + 15).
30
31callback_mode() -> state_functions.
32
33init({Owner, Secret, [compressed]}) ->
34    Monitor = monitor(process, Owner),
35    Z = zlib:open(),
36    ok = zlib:deflateInit(Z, default, deflated, ?GZIP_WBITS, 8, default),
37    Data =
38        #{ owner => Owner,
39           monitor => Monitor,
40           secret => Secret,
41           position => 0,
42           zlib => Z },
43    {ok, opening, Data}.
44
45opening({call, From}, {'$open', Secret, Filename, Modes}, #{ secret := Secret } = Data) ->
46    case raw_file_io:open(Filename, Modes) of
47        {ok, PrivateFd} ->
48            NewData = Data#{ handle => PrivateFd },
49            {next_state, opened, NewData, [{reply, From, ok}]};
50        Other ->
51            {stop_and_reply, normal, [{reply, From, Other}]}
52    end;
53opening(_Event, _Contents, _Data) ->
54    {keep_state_and_data, [postpone]}.
55
56%%
57
58opened(info, {'DOWN', Monitor, process, _Owner, Reason}, #{ monitor := Monitor } = Data) ->
59    if
60        Reason =/= kill -> flush_deflate_state(Data);
61        Reason =:= kill -> ignored
62    end,
63    {stop, shutdown};
64
65opened(info, _Message, _Data) ->
66    keep_state_and_data;
67
68opened({call, {Owner, _Tag} = From}, [close], #{ owner := Owner } = Data) ->
69    #{ handle := PrivateFd } = Data,
70    Response =
71        case flush_deflate_state(Data) of
72            ok -> ?CALL_FD(PrivateFd, close, []);
73            Other -> Other
74        end,
75    {stop_and_reply, normal, [{reply, From, Response}]};
76
77opened({call, {Owner, _Tag} = From}, [position, Mark], #{ owner := Owner } = Data) ->
78    case position(Data, Mark) of
79        {ok, NewData, Result} ->
80            Response = {ok, Result},
81            {keep_state, NewData, [{reply, From, Response}]};
82        Other ->
83            {keep_state_and_data, [{reply, From, Other}]}
84    end;
85
86opened({call, {Owner, _Tag} = From}, [write, IOVec], #{ owner := Owner } = Data) ->
87    case write(Data, IOVec) of
88        {ok, NewData} -> {keep_state, NewData, [{reply, From, ok}]};
89        Other -> {keep_state_and_data, [{reply, From, Other}]}
90    end;
91
92opened({call, {Owner, _Tag} = From}, [read, _Size], #{ owner := Owner }) ->
93    Response = {error, ebadf},
94    {keep_state_and_data, [{reply, From, Response}]};
95
96opened({call, {Owner, _Tag} = From}, [read_line], #{ owner := Owner }) ->
97    Response = {error, ebadf},
98    {keep_state_and_data, [{reply, From, Response}]};
99
100opened({call, {Owner, _Tag} = From}, _Command, #{ owner := Owner }) ->
101    Response = {error, enotsup},
102    {keep_state_and_data, [{reply, From, Response}]};
103
104opened({call, _From}, _Command, _Data) ->
105    %% The client functions filter this out, so we'll crash if the user does
106    %% anything stupid on purpose.
107    {shutdown, protocol_violation};
108
109opened(_Event, _Request, _Data) ->
110    keep_state_and_data.
111
112write(Data, IOVec) ->
113    #{ handle := PrivateFd, position := Position, zlib := Z } = Data,
114    UncompressedSize = iolist_size(IOVec),
115    case ?CALL_FD(PrivateFd, write, [zlib:deflate(Z, IOVec)]) of
116        ok -> {ok, Data#{ position := (Position + UncompressedSize) }};
117        Other -> Other
118    end.
119
120%%
121%% We support "seeking" forward as long as it isn't relative to EOF.
122%%
123%% Seeking is a bit of a misnomer as it's really just compressing zeroes until
124%% we reach the desired point, but it has always behaved like this.
125%%
126
127position(Data, Mark) when is_atom(Mark) ->
128    position(Data, {Mark, 0});
129position(Data, Offset) when is_integer(Offset) ->
130    position(Data, {bof, Offset});
131position(Data, {bof, Offset}) when is_integer(Offset) ->
132    position_1(Data, Offset);
133position(Data, {cur, Offset}) when is_integer(Offset) ->
134    #{ position := Position } = Data,
135    position_1(Data, Position + Offset);
136position(_Data, {eof, Offset}) when is_integer(Offset) ->
137    {error, einval};
138position(_Data, _Any) ->
139    {error, badarg}.
140
141position_1(#{ position := Desired } = Data, Desired) ->
142    {ok, Data, Desired};
143position_1(#{ position := Current } = Data, Desired) when Current < Desired ->
144    BytesToWrite = min(Desired - Current, 4 bsl 20),
145    case write(Data, <<0:(BytesToWrite)/unit:8>>) of
146        {ok, NewData} -> position_1(NewData, Desired);
147        Other -> Other
148    end;
149position_1(#{ position := Current }, Desired) when Current > Desired ->
150    {error, einval}.
151
152flush_deflate_state(#{ handle := PrivateFd, zlib := Z }) ->
153    case ?CALL_FD(PrivateFd, write, [zlib:deflate(Z, [], finish)]) of
154        ok -> ok;
155        Other -> Other
156    end.
157
158terminate(_Reason, _State, _Data) ->
159    ok.
160