1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2005-2018. 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%%-------------------------------------------------------------------
21%% Protocol engine for trivial FTP
22%%-------------------------------------------------------------------
23
24-module(tftp_engine).
25
26%%%-------------------------------------------------------------------
27%%% Interface
28%%%-------------------------------------------------------------------
29
30%% application internal functions
31-export([
32         daemon_start/1,
33         daemon_loop/1,
34         daemon_loop/3,    %% Handle upgrade from old releases. Please, remove this function in next release.
35         client_start/4,
36         common_loop/6,
37         info/1,
38         change_config/2
39        ]).
40
41%% module internal
42-export([
43         daemon_init/1,
44         server_init/2,
45         client_init/2,
46         wait_for_msg/3,
47         callback/4
48        ]).
49
50%% sys callback functions
51-export([
52         system_continue/3,
53         system_terminate/4,
54         system_code_change/4
55        ]).
56
57-include("tftp.hrl").
58
59-type prep_status() :: 'error' | 'last' | 'more' | 'terminate'.
60
61-record(daemon_state, {config, n_servers, server_tab, file_tab}).
62-record(server_info, {pid, req, peer}).
63-record(file_info, {peer_req, pid}).
64-record(sys_misc, {module, function, arguments}).
65-record(error, {where, code, text, filename}).
66-record(prepared, {status :: prep_status() | 'undefined',
67                   result, block_no, next_data, prev_data}).
68-record(transfer_res, {status, decoded_msg, prepared}).
69-define(ERROR(Where, Code, Text, Filename),
70        #error{where = Where, code = Code, text = Text, filename = Filename}).
71
72%%%-------------------------------------------------------------------
73%%% Info
74%%%-------------------------------------------------------------------
75
76info(daemons) ->
77    Daemons = supervisor:which_children(tftp_sup),
78    [{Pid, info(Pid)} || {_, Pid, _, _} <- Daemons];
79info(servers) ->
80    [{Pid, info(Pid)} || {_, {ok, DeamonInfo}} <- info(daemons),
81                         {server, Pid}   <- DeamonInfo];
82info(ToPid) when is_pid(ToPid) ->
83    call(info, ToPid, timer:seconds(10)).
84
85change_config(daemons, Options) ->
86    Daemons = supervisor:which_children(tftp_sup),
87    [{Pid, change_config(Pid, Options)} || {_, Pid, _, _} <- Daemons];
88change_config(servers, Options) ->
89    [{Pid, change_config(Pid, Options)} || {_, {ok, DeamonInfo}} <- info(daemons),
90                                           {server, Pid}   <- DeamonInfo];
91change_config(ToPid, Options) when is_pid(ToPid) ->
92    BadKeys = [host, port, udp],
93    BadOptions = [{Key, Val} || {Key, Val} <- Options,
94                                BadKey <- BadKeys,
95                                Key =:= BadKey],
96    case BadOptions of
97        [] ->
98            call({change_config, Options}, ToPid, timer:seconds(10));
99        [{Key, Val} | _] ->
100            {error, {badarg, {Key, Val}}}
101    end.
102
103call(Req, ToPid, Timeout) when is_pid(ToPid) ->
104    Type = process,
105    Ref = erlang:monitor(Type, ToPid),
106    ToPid ! {Req, Ref, self()},
107    receive
108        {Reply, Ref, FromPid} when FromPid =:= ToPid ->
109            erlang:demonitor(Ref, [flush]),
110            Reply;
111        {'DOWN', Ref, Type, FromPid, _Reason} when FromPid =:= ToPid ->
112            {error, timeout}
113    after Timeout ->
114            {error, timeout}
115    end.
116
117reply(Reply, Ref, ToPid) ->
118    ToPid ! {Reply, Ref, self()}.
119
120%%%-------------------------------------------------------------------
121%%% Daemon
122%%%-------------------------------------------------------------------
123
124%% Returns {ok, Port}
125daemon_start(Options) when is_list(Options) ->
126    Config = tftp_lib:parse_config(Options),
127    proc_lib:start_link(?MODULE, daemon_init, [Config], infinity).
128
129daemon_init(Config) when is_record(Config, config),
130                         is_pid(Config#config.parent_pid) ->
131    process_flag(trap_exit, true),
132    {Port, UdpOptions} = prepare_daemon_udp(Config),
133    case catch gen_udp:open(Port, UdpOptions) of
134        {ok, Socket} ->
135            {ok, ActualPort} = inet:port(Socket),
136            proc_lib:init_ack({ok, self()}),
137            Config2 = Config#config{udp_socket = Socket,
138                                    udp_port   = ActualPort},
139            print_debug_info(Config2, daemon, open, #tftp_msg_req{filename = ""}),
140            ServerTab = ets:new(tftp_daemon_servers, [{keypos, 2}]),
141            FileTab = ets:new(tftp_daemon_files, [{keypos, 2}]),
142            State = #daemon_state{config = Config2,
143                                  n_servers = 0,
144                                  server_tab = ServerTab,
145                                  file_tab = FileTab},
146            daemon_loop(State);
147        {error, Reason} ->
148            Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [UdpOptions, Reason])),
149            print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")),
150            exit({gen_udp_open, UdpOptions, Reason});
151        Reason ->
152            Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [UdpOptions, Reason])),
153            print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")),
154            exit({gen_udp_open, UdpOptions, Reason})
155    end.
156
157prepare_daemon_udp(#config{udp_port = Port, udp_options = UdpOptions} = Config) ->
158    case lists:keymember(fd, 1, UdpOptions) of
159        true ->
160            %% Use explicit fd
161            {Port, UdpOptions};
162        false ->
163            %% Use fd from setuid_socket_wrap, such as -tftpd_69
164            InitArg = list_to_atom("tftpd_" ++ integer_to_list(Port)),
165            case init:get_argument(InitArg) of
166                {ok, [[FdStr]] = Badarg} when is_list(FdStr) ->
167                    case catch list_to_integer(FdStr) of
168                        Fd when is_integer(Fd) ->
169                            {0, [{fd, Fd} | lists:keydelete(ip, 1, UdpOptions)]};
170                        {'EXIT', _} ->
171                            Text = lists:flatten(io_lib:format("Illegal prebound fd ~p: ~p", [InitArg, Badarg])),
172                            print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")),
173                            exit({badarg, {prebound_fd, InitArg, Badarg}})
174                    end;
175                {ok, Badarg} ->
176                    Text = lists:flatten(io_lib:format("Illegal prebound fd ~p: ~p", [InitArg, Badarg])),
177                    print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")),
178                    exit({badarg, {prebound_fd, InitArg, Badarg}});
179                error ->
180                    {Port, UdpOptions}
181            end
182    end.
183
184daemon_loop(DaemonConfig, N, Servers) when is_list(Servers) ->
185    %% Handle upgrade from old releases. Please, remove this function in next release.
186    ServerTab = ets:new(tftp_daemon_servers, [{keypos, 2}]),
187    FileTab = ets:new(tftp_daemon_files, [{keypos, 2}]),
188    State = #daemon_state{config = DaemonConfig,
189                          n_servers = N,
190                          server_tab = ServerTab,
191                          file_tab = FileTab},
192    Req = #tftp_msg_req{filename = dummy},
193    [ets:insert(ServerTab, #server_info{pid = Pid, req = Req, peer = dummy}) || Pid <- Servers],
194    daemon_loop(State).
195
196daemon_loop(#daemon_state{config = DaemonConfig,
197                          n_servers = N,
198                          server_tab = ServerTab,
199                          file_tab = FileTab} = State) when is_record(DaemonConfig, config) ->
200    %% info_msg(DaemonConfig, "=====> TFTP: Daemon #~p\n", [N]), %% XXX
201    receive
202        {info, Ref, FromPid} when is_pid(FromPid) ->
203            Fun = fun(#server_info{pid = Pid}, Acc) -> [{server, Pid} | Acc] end,
204            ServerInfo = ets:foldl(Fun, [], ServerTab),
205            Info = internal_info(DaemonConfig, daemon) ++ [{n_conn, N}] ++ ServerInfo,
206            _ = reply({ok, Info}, Ref, FromPid),
207            ?MODULE:daemon_loop(State);
208        {{change_config, Options}, Ref, FromPid} when is_pid(FromPid) ->
209            case catch tftp_lib:parse_config(Options, DaemonConfig) of
210                {'EXIT', Reason} ->
211                    _ = reply({error, Reason}, Ref, FromPid),
212                    ?MODULE:daemon_loop(State);
213                DaemonConfig2 when is_record(DaemonConfig2, config) ->
214                    _ = reply(ok, Ref, FromPid),
215                    ?MODULE:daemon_loop(State#daemon_state{config = DaemonConfig2})
216            end;
217        {udp, Socket, RemoteHost, RemotePort, Bin} when is_binary(Bin) ->
218            _ = inet:setopts(Socket, [{active, once}]),
219            ServerConfig = DaemonConfig#config{parent_pid = self(),
220                                               udp_host   = RemoteHost,
221                                               udp_port   = RemotePort},
222            Msg = (catch tftp_lib:decode_msg(Bin)),
223            print_debug_info(ServerConfig, daemon, recv, Msg),
224            case Msg of
225                Req when is_record(Req, tftp_msg_req),
226                N =< DaemonConfig#config.max_conn ->
227                    Peer = peer_info(ServerConfig),
228                    PeerReq = {Peer, Req},
229                    PeerInfo = lists:flatten(io_lib:format("~p", [Peer])),
230                    case ets:lookup(FileTab, PeerReq) of
231                        [] ->
232                            Args = [ServerConfig, Req],
233                            Pid = proc_lib:spawn_link(?MODULE, server_init, Args),
234                            ets:insert(ServerTab, #server_info{pid = Pid, req = Req, peer = Peer}),
235                            ets:insert(FileTab, #file_info{peer_req = PeerReq, pid = Pid}),
236                            ?MODULE:daemon_loop(State#daemon_state{n_servers = N + 1});
237                        [#file_info{pid = Pid}] ->
238                            %% Yet another request of the file from same peer
239                            warning_msg(DaemonConfig, "~p Reuse connection for ~s\n\t~p\n",
240                                        [Pid, PeerInfo, Req#tftp_msg_req.filename]),
241                            ?MODULE:daemon_loop(State)
242                    end;
243                Req when is_record(Req, tftp_msg_req) ->
244                    Reply = #tftp_msg_error{code = enospc, text = "Too many connections"},
245                    Peer = peer_info(ServerConfig),
246                    PeerInfo = lists:flatten(io_lib:format("~p", [Peer])),
247                    warning_msg(DaemonConfig,
248				"Daemon has too many connections (~p)."
249				"\n\tRejecting request from ~s\n",
250				[N, PeerInfo]),
251                    send_msg(ServerConfig, daemon, Reply),
252                    ?MODULE:daemon_loop(State);
253                {'EXIT', Reply} when is_record(Reply, tftp_msg_error) ->
254                    send_msg(ServerConfig, daemon, Reply),
255                    ?MODULE:daemon_loop(State);
256                Req  ->
257                    Reply = #tftp_msg_error{code = badop,
258                                            text = "Illegal TFTP operation"},
259                    warning_msg(DaemonConfig, "Daemon received: ~p.\n\tfrom ~p:~p",
260                                [Req, RemoteHost, RemotePort]),
261                    send_msg(ServerConfig, daemon, Reply),
262                    ?MODULE:daemon_loop(State)
263            end;
264        {system, From, Msg} ->
265            Misc = #sys_misc{module = ?MODULE, function = daemon_loop, arguments = [State]},
266            sys:handle_system_msg(Msg, From, DaemonConfig#config.parent_pid, ?MODULE, [], Misc);
267        {'EXIT', Pid, Reason} when DaemonConfig#config.parent_pid =:= Pid ->
268            close_port(DaemonConfig, daemon, #tftp_msg_req{filename = ""}),
269            exit(Reason);
270        {'EXIT', Pid, _Reason} = Info ->
271            case ets:lookup(ServerTab, Pid) of
272                [] ->
273                    warning_msg(DaemonConfig, "Daemon received: ~p", [Info]),
274                    ?MODULE:daemon_loop(State);
275                [#server_info{req = Req, peer = Peer}] ->
276                    PeerReq = {Peer, Req},
277                    ets:delete(FileTab, PeerReq),
278                    ets:delete(ServerTab, Pid),
279                    ?MODULE:daemon_loop(State#daemon_state{n_servers = N - 1})
280            end;
281        Info ->
282            warning_msg(DaemonConfig, "Daemon received: ~p", [Info]),
283            ?MODULE:daemon_loop(State)
284    end;
285daemon_loop(#daemon_state{config = Config} = State) ->
286    %% Handle upgrade from old releases. Please, remove this clause in next release.
287    Config2 = upgrade_config(Config),
288    daemon_loop(State#daemon_state{config = Config2}).
289
290upgrade_config({config, ParentPid, UdpSocket, UdpOptions, UdpHost, UdpPort, PortPolicy,
291                UseTsize, MaxTsize, MaxConn, Rejected, PoliteAck, DebugLevel,
292                Timeout, UserOptions, Callbacks}) ->
293    Callbacks2  = tftp_lib:add_default_callbacks(Callbacks),
294    Logger = tftp_logger,
295    MaxRetries = 5,
296    {config, ParentPid, UdpSocket, UdpOptions, UdpHost, UdpPort, PortPolicy,
297     UseTsize, MaxTsize, MaxConn, Rejected, PoliteAck, DebugLevel,
298     Timeout, UserOptions, Callbacks2, Logger, MaxRetries}.
299
300%%%-------------------------------------------------------------------
301%%% Server
302%%%-------------------------------------------------------------------
303
304server_init(Config, Req) when is_record(Config, config),
305                              is_pid(Config#config.parent_pid),
306                              is_record(Req, tftp_msg_req) ->
307    process_flag(trap_exit, true),
308    %% Config =
309    %%     case os:getenv("TFTPDEBUG") of
310    %%         false ->
311    %%             Config0;
312    %%         DebugLevel ->
313    %%             Config0#config{debug_level = list_to_atom(DebugLevel)}
314    %%     end,
315    SuggestedOptions = Req#tftp_msg_req.options,
316    UdpOptions = Config#config.udp_options,
317    UdpOptions2 = lists:keydelete(fd, 1, UdpOptions),
318    Config1 = Config#config{udp_options = UdpOptions2},
319    Config2 = tftp_lib:parse_config(SuggestedOptions, Config1),
320    SuggestedOptions2 = Config2#config.user_options,
321    Req2 = Req#tftp_msg_req{options = SuggestedOptions2},
322    case open_free_port(Config2, server, Req2) of
323        {ok, Config3} ->
324            Filename = Req#tftp_msg_req.filename,
325            case match_callback(Filename, Config3#config.callbacks) of
326                {ok, Callback} ->
327                    print_debug_info(Config3, server, match, Callback),
328                    case pre_verify_options(Config3, Req2) of
329                        ok ->
330                            case callback({open, server_open}, Config3, Callback, Req2) of
331                                {Callback2, {ok, AcceptedOptions}} ->
332                                    {LocalAccess,  _} = local_file_access(Req2),
333                                    OptText = "Internal error. Not allowed to add new options.",
334                                    case post_verify_options(Config3, Req2, AcceptedOptions, OptText) of
335                                        {ok, Config4, Req3} when AcceptedOptions =/= [] ->
336                                            Reply = #tftp_msg_oack{options = AcceptedOptions},
337                                            BlockNo =
338                                                case LocalAccess of
339                                                    read  -> 0;
340                                                    write -> 1
341                                                end,
342                                            {Config5, Callback3, TransferRes} =
343                                                transfer(Config4, Callback2, Req3, Reply, LocalAccess, BlockNo, #prepared{}),
344                                            common_loop(Config5, Callback3, Req3, TransferRes, LocalAccess, BlockNo);
345                                        {ok, Config4, Req3} when LocalAccess =:= write ->
346                                            BlockNo = 0,
347                                            common_ack(Config4, Callback2, Req3, LocalAccess, BlockNo, #prepared{});
348                                        {ok, Config4, Req3} when LocalAccess =:= read ->
349                                            BlockNo = 0,
350                                            common_read(Config4, Callback2, Req3, LocalAccess, BlockNo, BlockNo, #prepared{});
351                                        {error, {Code, Text}} ->
352                                            {undefined, Error} =
353                                                callback({abort, {Code, Text}}, Config3, Callback2, Req2),
354                                            send_msg(Config3, Req, Error),
355                                            terminate(Config3, Req2, ?ERROR(post_verify_options, Code, Text, Req2#tftp_msg_req.filename))
356                                    end;
357                                {undefined, #tftp_msg_error{code = Code, text = Text} = Error} ->
358                                    send_msg(Config3, Req, Error),
359                                    terminate(Config3, Req, ?ERROR(server_open, Code, Text, Req2#tftp_msg_req.filename))
360                            end;
361                        {error, {Code, Text}} ->
362                            {undefined, Error} =
363                                callback({abort, {Code, Text}}, Config2, Callback, Req2),
364                            send_msg(Config2, Req, Error),
365                            terminate(Config2, Req2, ?ERROR(pre_verify_options, Code, Text, Req2#tftp_msg_req.filename))
366                    end;
367                {error, #tftp_msg_error{code = Code, text = Text} = Error} ->
368                    send_msg(Config3, Req, Error),
369                    terminate(Config3, Req, ?ERROR(match_callback, Code, Text, Req2#tftp_msg_req.filename))
370            end;
371        #error{} = Error ->
372            terminate(Config2, Req, Error)
373    end;
374server_init(Config, Req) when is_record(Req, tftp_msg_req) ->
375    Config2 = upgrade_config(Config),
376    server_init(Config2, Req).
377
378%%%-------------------------------------------------------------------
379%%% Client
380%%%-------------------------------------------------------------------
381
382%% LocalFilename = filename() | 'binary' | binary()
383%% Returns {ok, LastCallbackState} | {error, Reason}
384client_start(Access, RemoteFilename, LocalFilename, Options) ->
385    Config = tftp_lib:parse_config(Options),
386    Config2 = Config#config{parent_pid      = self(),
387                            udp_socket      = undefined},
388    Req = #tftp_msg_req{access         = Access,
389                        filename       = RemoteFilename,
390                        mode           = lookup_mode(Config2#config.user_options),
391                        options        = Config2#config.user_options,
392                        local_filename = LocalFilename},
393    Args = [Config2, Req],
394    case proc_lib:start_link(?MODULE, client_init, Args, infinity) of
395        {ok, LastCallbackState} ->
396            {ok, LastCallbackState};
397        {error, Error} ->
398            {error, Error}
399    end.
400
401client_init(Config, Req) when is_record(Config, config),
402                              is_pid(Config#config.parent_pid),
403                              is_record(Req, tftp_msg_req) ->
404    process_flag(trap_exit, true),
405    %% Config =
406    %%  case os:getenv("TFTPDEBUG") of
407    %%      false ->
408    %%          Config0;
409    %%      "none" ->
410    %%          Config0;
411    %%      DebugLevel ->
412    %%          info_msg(Config, "TFTPDEBUG: ~s\n", [DebugLevel]),
413    %%          Config0#config{debug_level = list_to_atom(DebugLevel)}
414    %%  end,
415    case open_free_port(Config, client, Req) of
416        {ok, Config2} ->
417            Req2 =
418                case Config2#config.use_tsize of
419                    true ->
420                        SuggestedOptions = Req#tftp_msg_req.options,
421                        SuggestedOptions2 = tftp_lib:replace_val("tsize", "0", SuggestedOptions),
422                        Req#tftp_msg_req{options = SuggestedOptions2};
423                    false ->
424                        Req
425                end,
426            LocalFilename = Req2#tftp_msg_req.local_filename,
427            case match_callback(LocalFilename, Config2#config.callbacks) of
428                {ok, Callback} ->
429                    print_debug_info(Config2, client, match, Callback),
430                    client_prepare(Config2, Callback, Req2);
431                {error, #tftp_msg_error{code = Code, text = Text}} ->
432                    terminate(Config, Req, ?ERROR(match, Code, Text, Req#tftp_msg_req.filename))
433            end;
434        #error{} = Error ->
435            terminate(Config, Req, Error)
436    end.
437
438client_prepare(Config, Callback, Req) when is_record(Req, tftp_msg_req) ->
439    case pre_verify_options(Config, Req) of
440        ok ->
441            case callback({open, client_prepare}, Config, Callback, Req) of
442                {Callback2, {ok, AcceptedOptions}} ->
443                    OptText = "Internal error. Not allowed to add new options.",
444                    case post_verify_options(Config, Req, AcceptedOptions, OptText) of
445                        {ok, Config2, Req2} ->
446                            {LocalAccess, _} = local_file_access(Req2),
447                            BlockNo = 0,
448                            {Config3, Callback3, TransferRes} =
449                                transfer(Config2, Callback2, Req2, Req2, LocalAccess, BlockNo, #prepared{}),
450                            client_open(Config3, Callback3, Req2, BlockNo, TransferRes);
451                        {error, {Code, Text}} ->
452                            _ = callback({abort, {Code, Text}}, Config, Callback2, Req),
453                            terminate(Config, Req, ?ERROR(post_verify_options, Code, Text, Req#tftp_msg_req.filename))
454                    end;
455                {undefined, #tftp_msg_error{code = Code, text = Text}} ->
456                    terminate(Config, Req, ?ERROR(client_prepare, Code, Text, Req#tftp_msg_req.filename))
457            end;
458        {error, {Code, Text}} ->
459            _ = callback({abort, {Code, Text}}, Config, Callback, Req),
460            terminate(Config, Req, ?ERROR(pre_verify_options, Code, Text, Req#tftp_msg_req.filename))
461    end.
462
463client_open(Config, Callback, Req, BlockNo, #transfer_res{status = Status, decoded_msg = DecodedMsg, prepared = Prepared}) ->
464    {LocalAccess, _} = local_file_access(Req),
465    case Status of
466        ok when is_record(Prepared, prepared) ->
467            case DecodedMsg of
468                Msg when is_record(Msg, tftp_msg_oack) ->
469                    ServerOptions = Msg#tftp_msg_oack.options,
470                    OptText = "Protocol violation. Server is not allowed new options",
471                    case post_verify_options(Config, Req, ServerOptions, OptText) of
472                        {ok, Config2, Req2} ->
473                            {Config3, Callback2, Req3} =
474                                do_client_open(Config2, Callback, Req2),
475                            case LocalAccess of
476                                read ->
477                                    common_read(Config3, Callback2, Req3, LocalAccess, BlockNo, BlockNo, Prepared);
478                                write ->
479                                    common_ack(Config3, Callback2, Req3, LocalAccess, BlockNo, Prepared)
480                            end;
481                        {error, {Code, Text}} ->
482                            {undefined, Error} =
483                                callback({abort, {Code, Text}}, Config, Callback, Req),
484                            send_msg(Config, Req, Error),
485                            terminate(Config, Req, ?ERROR(verify_server_options, Code, Text, Req#tftp_msg_req.filename))
486                    end;
487                #tftp_msg_ack{block_no = ActualBlockNo} when LocalAccess =:= read ->
488                    Req2 = Req#tftp_msg_req{options = []},
489                    {Config2, Callback2, Req2} = do_client_open(Config, Callback, Req2),
490                    ExpectedBlockNo = 0,
491                    common_read(Config2, Callback2, Req2, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared);
492                #tftp_msg_data{block_no = ActualBlockNo, data = Data} when LocalAccess =:= write ->
493                    Req2 = Req#tftp_msg_req{options = []},
494                    {Config2, Callback2, Req2} = do_client_open(Config, Callback, Req2),
495                    ExpectedBlockNo = 1,
496                    common_write(Config2, Callback2, Req2, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared);
497                %% #tftp_msg_error{code = Code, text = Text} when Req#tftp_msg_req.options =/= [] ->
498                %%     %% Retry without options
499                %%     callback({abort, {Code, Text}}, Config, Callback, Req),
500                %%     Req2 = Req#tftp_msg_req{options = []},
501                %%     client_prepare(Config, Callback, Req2);
502                #tftp_msg_error{code = Code, text = Text} ->
503                    _ = callback({abort, {Code, Text}}, Config, Callback, Req),
504                    terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename));
505                {'EXIT', #tftp_msg_error{code = Code, text = Text}} ->
506                    _ = callback({abort, {Code, Text}}, Config, Callback, Req),
507                    terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename));
508                Msg when is_tuple(Msg) ->
509                    Code = badop,
510                    Text = "Illegal TFTP operation",
511                    {undefined, Error} =
512                        callback({abort, {Code, Text}}, Config, Callback, Req),
513                    send_msg(Config, Req, Error),
514                    Text2 = lists:flatten([Text, ". ", io_lib:format("~p", [element(1, Msg)])]),
515                    terminate(Config, Req, ?ERROR(client_open, Code, Text2, Req#tftp_msg_req.filename))
516            end;
517        error when is_record(Prepared, tftp_msg_error) ->
518            #tftp_msg_error{code = Code, text = Text} = Prepared,
519            _ = callback({abort, {Code, Text}}, Config, Callback, Req),
520            terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename))
521    end.
522
523do_client_open(Config, Callback, Req) ->
524    case callback({open, client_open}, Config, Callback, Req) of
525        {Callback2, {ok, FinalOptions}} ->
526            OptText = "Internal error. Not allowed to change options.",
527            case post_verify_options(Config, Req, FinalOptions, OptText) of
528                {ok, Config2, Req2} ->
529                    {Config2, Callback2, Req2};
530                {error, {Code, Text}} ->
531                    {undefined, Error} =
532                        callback({abort, {Code, Text}}, Config, Callback2, Req),
533                    send_msg(Config, Req, Error),
534                    terminate(Config, Req, ?ERROR(post_verify_options, Code, Text, Req#tftp_msg_req.filename))
535            end;
536        {undefined, #tftp_msg_error{code = Code, text = Text} = Error} ->
537            send_msg(Config, Req, Error),
538            terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename))
539    end.
540
541%%%-------------------------------------------------------------------
542%%% Common loop for both client and server
543%%%-------------------------------------------------------------------
544
545common_loop(Config, Callback, Req, #transfer_res{status = Status, decoded_msg = DecodedMsg, prepared = Prepared}, LocalAccess, ExpectedBlockNo)
546  when is_record(Config, config)->
547    %%    Config =
548    %%  case os:getenv("TFTPMAX") of
549    %%      false ->
550    %%          Config0;
551    %%      MaxBlockNoStr when Config0#config.debug_level =/= none ->
552    %%          case list_to_integer(MaxBlockNoStr) of
553    %%              MaxBlockNo when ExpectedBlockNo > MaxBlockNo ->
554    %%                  info_msg(Config, "TFTPMAX: ~p\n", [MaxBlockNo]),
555    %%                  info_msg(Config, "TFTPDEBUG: none\n", []),
556    %%                  Config0#config{debug_level = none};
557    %%              _ ->
558    %%                  Config0
559    %%          end;
560    %%      _MaxBlockNoStr ->
561    %%          Config0
562    %%  end,
563    case Status of
564        ok when is_record(Prepared, prepared) ->
565            case DecodedMsg of
566                #tftp_msg_ack{block_no = ActualBlockNo} when LocalAccess =:= read ->
567                    common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared);
568                #tftp_msg_data{block_no = ActualBlockNo, data = Data} when LocalAccess =:= write ->
569                    common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared);
570                #tftp_msg_error{code = Code, text = Text} ->
571                    _ = callback({abort, {Code, Text}}, Config, Callback, Req),
572                    terminate(Config, Req, ?ERROR(common_loop, Code, Text, Req#tftp_msg_req.filename));
573                {'EXIT', #tftp_msg_error{code = Code, text = Text} = Error} ->
574                    _ = callback({abort, {Code, Text}}, Config, Callback, Req),
575                    send_msg(Config, Req, Error),
576                    terminate(Config, Req, ?ERROR(common_loop, Code, Text, Req#tftp_msg_req.filename));
577                Msg when is_tuple(Msg) ->
578                    Code = badop,
579                    Text = "Illegal TFTP operation",
580                    {undefined, Error} =
581                        callback({abort, {Code, Text}}, Config, Callback, Req),
582                    send_msg(Config, Req, Error),
583                    Text2 = lists:flatten([Text, ". ", io_lib:format("~p", [element(1, Msg)])]),
584                    terminate(Config, Req, ?ERROR(common_loop, Code, Text2, Req#tftp_msg_req.filename))
585            end;
586        error when is_record(Prepared, tftp_msg_error) ->
587            #tftp_msg_error{code = Code, text = Text} = Prepared,
588            send_msg(Config, Req, Prepared),
589            terminate(Config, Req, ?ERROR(transfer, Code, Text, Req#tftp_msg_req.filename))
590    end;
591common_loop(Config, Callback, Req, TransferRes, LocalAccess, ExpectedBlockNo) ->
592    %% Handle upgrade from old releases. Please, remove this clause in next release.
593    Config2 = upgrade_config(Config),
594    common_loop(Config2, Callback, Req, TransferRes, LocalAccess, ExpectedBlockNo).
595
596-spec common_read(#config{}, #callback{}, _, 'read', _, _, #prepared{}) -> no_return().
597
598common_read(Config, _, Req, _, _, _, #prepared{status = terminate, result = Result}) ->
599    terminate(Config, Req, {ok, Result});
600common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared)
601  when ActualBlockNo =:= ExpectedBlockNo, is_record(Prepared, prepared) ->
602    case early_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Prepared) of
603        {Callback2,  #prepared{status = more, next_data = Data} = Prepared2} when is_binary(Data) ->
604            Prepared3 = Prepared2#prepared{prev_data = Data, next_data = undefined},
605            do_common_read(Config, Callback2, Req, LocalAccess, ActualBlockNo, Data, Prepared3);
606        {undefined, #prepared{status = last, next_data = Data} = Prepared2} when is_binary(Data) ->
607            Prepared3 = Prepared2#prepared{status = terminate},
608            do_common_read(Config, undefined, Req, LocalAccess, ActualBlockNo, Data, Prepared3);
609        {undefined, #prepared{status = error, result = Error}} ->
610            #tftp_msg_error{code = Code, text = Text} = Error,
611            send_msg(Config, Req, Error),
612            terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename))
613    end;
614common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared)
615  when ActualBlockNo =:= (ExpectedBlockNo - 1), is_record(Prepared, prepared) ->
616    case Prepared of
617        #prepared{status = more, prev_data = Data} when is_binary(Data) ->
618            do_common_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Data, Prepared);
619        #prepared{status = last, prev_data = Data} when is_binary(Data) ->
620            do_common_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Data, Prepared);
621        #prepared{status = error, result = Error} ->
622            #tftp_msg_error{code = Code, text = Text} = Error,
623            send_msg(Config, Req, Error),
624            terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename))
625    end;
626common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared)
627  when ActualBlockNo =< ExpectedBlockNo, is_record(Prepared, prepared) ->
628    %% error_logger:error_msg("TFTP READ ~s: Expected block ~p but got block ~p - IGNORED\n",
629    %%                     [Req#tftp_msg_req.filename, ExpectedBlockNo, ActualBlockNo]),
630    case Prepared of
631        #prepared{status = more, prev_data = Data} when is_binary(Data) ->
632            Reply = #tftp_msg_data{block_no = ExpectedBlockNo, data = Data},
633            {Config2, Callback2, TransferRes} =
634                wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared),
635            ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo);
636        #prepared{status = last, prev_data = Data} when is_binary(Data) ->
637            Reply = #tftp_msg_data{block_no = ExpectedBlockNo, data = Data},
638            {Config2, Callback2, TransferRes} =
639                wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared),
640            ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo);
641        #prepared{status = error, result = Error} ->
642            #tftp_msg_error{code = Code, text = Text} = Error,
643            send_msg(Config, Req, Error),
644            terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename))
645    end;
646common_read(Config, Callback, Req, _LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared)
647  when is_record(Prepared, prepared) ->
648    Code = badblk,
649    Text = "Unknown transfer ID = " ++
650        integer_to_list(ActualBlockNo) ++ " (" ++ integer_to_list(ExpectedBlockNo) ++ ")",
651    {undefined, Error} =
652        callback({abort, {Code, Text}}, Config, Callback, Req),
653    send_msg(Config, Req, Error),
654    terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename)).
655
656-spec do_common_read(#config{}, #callback{} | undefined, _, 'read', integer(), binary(), #prepared{}) -> no_return().
657
658do_common_read(Config, Callback, Req, LocalAccess, BlockNo, Data, Prepared)
659  when is_binary(Data), is_record(Prepared, prepared) ->
660    NextBlockNo = (BlockNo + 1) rem 65536,
661    Reply = #tftp_msg_data{block_no = NextBlockNo, data = Data},
662    {Config2, Callback2, TransferRes} =
663        transfer(Config, Callback, Req, Reply, LocalAccess, NextBlockNo, Prepared),
664    ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, NextBlockNo).
665
666-spec common_write(#config{}, #callback{}, _, 'write', integer(), integer(), _, #prepared{}) -> no_return().
667
668common_write(Config, _, Req, _, _, _, _, #prepared{status = terminate, result = Result}) ->
669    terminate(Config, Req, {ok, Result});
670common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared)
671  when ActualBlockNo =:= ExpectedBlockNo, is_binary(Data), is_record(Prepared, prepared) ->
672    case callback({write, Data}, Config, Callback, Req) of
673        {Callback2, #prepared{status = more} = Prepared2} ->
674            common_ack(Config, Callback2, Req, LocalAccess, ActualBlockNo, Prepared2);
675        {undefined, #prepared{status = last, result = Result} = Prepared2} ->
676            Config2 = pre_terminate(Config, Req, {ok, Result}),
677            Prepared3 = Prepared2#prepared{status = terminate},
678            common_ack(Config2, undefined, Req, LocalAccess, ActualBlockNo, Prepared3);
679        {undefined, #prepared{status = error, result = Error}} ->
680            #tftp_msg_error{code = Code, text = Text} = Error,
681            send_msg(Config, Req, Error),
682            terminate(Config, Req, ?ERROR(write, Code, Text, Req#tftp_msg_req.filename))
683    end;
684common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared)
685  when ActualBlockNo =:= (ExpectedBlockNo - 1), is_binary(Data), is_record(Prepared, prepared) ->
686    common_ack(Config, Callback, Req, LocalAccess, ExpectedBlockNo - 1, Prepared);
687common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared)
688  when ActualBlockNo =< ExpectedBlockNo, is_binary(Data), is_record(Prepared, prepared) ->
689    %% error_logger:error_msg("TFTP WRITE ~s: Expected block ~p but got block ~p - IGNORED\n",
690    %% [Req#tftp_msg_req.filename, ExpectedBlockNo, ActualBlockNo]),
691    Reply = #tftp_msg_ack{block_no = ExpectedBlockNo},
692    {Config2, Callback2, TransferRes} =
693        wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared),
694    ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo);
695common_write(Config, Callback, Req, _, ExpectedBlockNo, ActualBlockNo, Data, Prepared)
696  when is_binary(Data), is_record(Prepared, prepared) ->
697    Code = badblk,
698    Text = "Unknown transfer ID = " ++
699        integer_to_list(ActualBlockNo) ++ " (" ++ integer_to_list(ExpectedBlockNo) ++ ")",
700    {undefined, Error} =
701        callback({abort, {Code, Text}}, Config, Callback, Req),
702    send_msg(Config, Req, Error),
703    terminate(Config, Req, ?ERROR(write, Code, Text, Req#tftp_msg_req.filename)).
704
705common_ack(Config, Callback, Req, LocalAccess, BlockNo, Prepared)
706  when is_record(Prepared, prepared) ->
707    Reply = #tftp_msg_ack{block_no = BlockNo},
708    NextBlockNo = (BlockNo + 1) rem 65536,
709    {Config2, Callback2, TransferRes} =
710        transfer(Config, Callback, Req, Reply, LocalAccess, NextBlockNo, Prepared),
711    ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, NextBlockNo).
712
713pre_terminate(Config, Req, Result) ->
714    if
715        Req#tftp_msg_req.local_filename =/= undefined,
716        Config#config.parent_pid =/= undefined ->
717            proc_lib:init_ack(Result),
718            unlink(Config#config.parent_pid),
719            Config#config{parent_pid = undefined, polite_ack = true};
720        true ->
721            Config#config{polite_ack = true}
722    end.
723
724-spec terminate(#config{}, #tftp_msg_req{}, {'ok', _} | #error{}) -> no_return().
725
726terminate(Config, Req, Result) ->
727    Result2 =
728        case Result of
729            {ok, _} ->
730                Result;
731            #error{where = Where, code = Code, text = Text} = Error ->
732                print_debug_info(Config, Req, Where, Error#error{filename = Req#tftp_msg_req.filename}),
733                {error, {Where, Code, Text}}
734        end,
735    if
736        Config#config.parent_pid =:= undefined ->
737            close_port(Config, client, Req),
738            exit(normal);
739        Req#tftp_msg_req.local_filename =/= undefined  ->
740            %% Client
741            close_port(Config, client, Req),
742            proc_lib:init_ack(Result2),
743            unlink(Config#config.parent_pid),
744            exit(normal);
745        true ->
746            %% Server
747            close_port(Config, server, Req),
748            exit(shutdown)
749    end.
750
751close_port(Config, Who, Req) when is_record(Req, tftp_msg_req) ->
752    case Config#config.udp_socket of
753        undefined ->
754            ignore;
755        Socket    ->
756            print_debug_info(Config, Who, close, Req),
757            gen_udp:close(Socket)
758    end.
759
760open_free_port(Config, Who, Req) when is_record(Config, config), is_record(Req, tftp_msg_req) ->
761    UdpOptions = Config#config.udp_options,
762    case Config#config.port_policy of
763        random ->
764            %% BUGBUG: Should be a random port
765            case catch gen_udp:open(0, UdpOptions) of
766                {ok, Socket} ->
767                    Config2 = Config#config{udp_socket = Socket},
768                    print_debug_info(Config2, Who, open, Req),
769                    {ok, Config2};
770                {error, Reason} ->
771                    Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[0 | UdpOptions], Reason])),
772                    ?ERROR(open, undef, Text, Req#tftp_msg_req.filename);
773                {'EXIT', _} = Reason ->
774                    Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[0 | UdpOptions], Reason])),
775                    ?ERROR(open, undef, Text, Req#tftp_msg_req.filename)
776            end;
777        {range, Port, Max} when Port =< Max ->
778            case catch gen_udp:open(Port, UdpOptions) of
779                {ok, Socket} ->
780                    Config2 = Config#config{udp_socket = Socket},
781                    print_debug_info(Config2, Who, open, Req),
782                    {ok, Config2};
783                {error, eaddrinuse} ->
784                    PortPolicy = {range, Port + 1, Max},
785                    Config2 = Config#config{port_policy = PortPolicy},
786                    open_free_port(Config2, Who, Req);
787                {error, Reason} ->
788                    Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])),
789                    ?ERROR(open, undef, Text, Req#tftp_msg_req.filename);
790                {'EXIT', _} = Reason->
791                    Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])),
792                    ?ERROR(open, undef, Text, Req#tftp_msg_req.filename)
793            end;
794        {range, Port, _Max} ->
795            Reason = "Port range exhausted",
796            Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])),
797            ?ERROR(Who, undef, Text, Req#tftp_msg_req.filename)
798    end.
799
800%%-------------------------------------------------------------------
801%% Transfer
802%%-------------------------------------------------------------------
803
804%% Returns {Config, Callback, #transfer_res{}}
805transfer(Config, Callback, Req, Msg, LocalAccess, NextBlockNo, Prepared)
806  when is_record(Prepared, prepared) ->
807    IoList = tftp_lib:encode_msg(Msg),
808    Retries = Config#config.max_retries + 1,
809    do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries).
810
811do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries)
812  when is_record(Prepared, prepared), is_integer(Retries), Retries >= 0 ->
813    case do_send_msg(Config, Req, Msg, IoList) of
814        ok ->
815            {Callback2, Prepared2} =
816                early_read(Config, Callback, Req, LocalAccess, NextBlockNo, Prepared),
817            do_wait_for_msg_and_handle_timeout(Config, Callback2, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared2, Retries);
818        {error, _Reason} when Retries > 0 ->
819            Retries2 = 0, % Just retry once when send fails
820            do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries2);
821        {error, Reason} ->
822            Code = undef,
823            Text = lists:flatten(io_lib:format("Transfer failed - giving up -> ~p", [Reason])),
824            Error = #tftp_msg_error{code = Code, text = Text},
825            {Config, Callback, #transfer_res{status = error, prepared = Error}}
826    end.
827
828wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, LocalAccess, NextBlockNo, Prepared) ->
829    IoList = tftp_lib:encode_msg(Msg),
830    Retries = Config#config.max_retries + 1,
831    do_wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries).
832
833do_wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries) ->
834    Code = undef,
835    Text = "Transfer timed out.",
836    case wait_for_msg(Config, Callback, Req) of
837        timeout when Config#config.polite_ack =:= true ->
838            do_send_msg(Config, Req, Msg, IoList),
839            case Prepared of
840                #prepared{status = terminate, result = Result} ->
841                    terminate(Config, Req, {ok, Result});
842                #prepared{} ->
843                    terminate(Config, Req, ?ERROR(transfer, Code, Text, Req#tftp_msg_req.filename))
844            end;
845        timeout when Retries > 0 ->
846            Retries2 = Retries - 1,
847            do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries2);
848        timeout ->
849            Error = #tftp_msg_error{code = Code, text = Text},
850            {Config, Callback, #transfer_res{status = error, prepared = Error}};
851        {Config2, DecodedMsg} ->
852            {Config2, Callback, #transfer_res{status = ok, decoded_msg = DecodedMsg, prepared = Prepared}}
853    end.
854
855send_msg(Config, Req, Msg) ->
856    case catch tftp_lib:encode_msg(Msg) of
857        {'EXIT', Reason} ->
858            Code = undef,
859            Text = "Internal error. Encode failed",
860            Msg2 = #tftp_msg_error{code = Code, text = Text, details = Reason},
861            send_msg(Config, Req, Msg2);
862        IoList ->
863            do_send_msg(Config, Req, Msg, IoList)
864    end.
865
866do_send_msg(#config{udp_socket = Socket, udp_host = RemoteHost, udp_port = RemotePort} = Config, Req, Msg, IoList) ->
867    %% {ok, LocalPort} = inet:port(Socket),
868    %% if
869    %%     LocalPort =/= ?TFTP_DEFAULT_PORT ->
870    %%         ok;
871    %%     true  ->
872    %%         print_debug_info(Config#config{debug_level = all}, Req, send, Msg),
873    %%         error(Config,
874    %%               "Daemon replies from the default port (~p)\n\t to ~p:~p\n\t¨~p\n",
875    %%               [LocalPort, RemoteHost, RemotePort, Msg])
876    %% end,
877
878    print_debug_info(Config, Req, send, Msg),
879
880    %% case os:getenv("TFTPDUMP") of
881    %%  false     ->
882    %%      ignore;
883    %%  DumpPath  ->
884    %%      trace_udp_send(Req, Msg, IoList, DumpPath)
885    %% end,
886    try
887        ok = gen_udp:send(Socket, RemoteHost, RemotePort, IoList)
888    catch
889        error:{badmatch,{error,einval=Reason}}:StackTrace ->
890            error_msg(Config,
891                      "Stacktrace; ~p\n gen_udp:send(~p, ~p, ~p, ~p) -> ~p\n",
892                      [StackTrace, Socket, RemoteHost, RemotePort, IoList, {error, Reason}]);
893        error:{badmatch,{error,Reason}} ->
894            {error, Reason}
895    end.
896
897%% trace_udp_send(#tftp_msg_req{filename = [$/ | RelFile]} = Req, Msg, IoList, DumpPath) ->
898%%     trace_udp_send(Req#tftp_msg_req{filename = RelFile}, Msg, IoList, DumpPath);
899%% trace_udp_send(#tftp_msg_req{filename = RelFile},
900%%             #tftp_msg_data{block_no = BlockNo, data = Data},
901%%             _IoList,
902%%             DumpPath) ->
903%%     File = filename:join([DumpPath, RelFile, "block" ++ string:right(integer_to_list(BlockNo), 5, $0)  ++ ".dump"]),
904%%     if
905%%      (BlockNo rem 1000) =:= 1 ->
906%%          info_msg(Config, "TFTPDUMP: Data    ~s\n", [File]);
907%%      true ->
908%%          ignore
909%%     end,
910%%     ok = filelib:ensure_dir(File),
911%%     ok = file:write_file(File, Data);
912%% trace_udp_send(#tftp_msg_req{filename = RelFile}, Msg, _IoList, _DumpPath) ->
913%%     info_msg(Config, "TFTPDUMP: No data  ~s -> ~p\n", [RelFile, element(1, Msg)]).
914
915wait_for_msg(Config, Callback, Req) ->
916    receive
917        {udp, Socket, RemoteHost, RemotePort, Bin}
918        when is_binary(Bin), Callback#callback.block_no =:= undefined ->
919            %% Client prepare
920            _ = inet:setopts(Socket, [{active, once}]),
921            Config2 = Config#config{udp_host = RemoteHost,
922                                    udp_port = RemotePort},
923            DecodedMsg = (catch tftp_lib:decode_msg(Bin)),
924            print_debug_info(Config2, Req, recv, DecodedMsg),
925            {Config2, DecodedMsg};
926        {udp, Socket, Host, Port, Bin} when is_binary(Bin),
927                                            Config#config.udp_host =:= Host,
928                                            Config#config.udp_port =:= Port ->
929            _ = inet:setopts(Socket, [{active, once}]),
930            DecodedMsg = (catch tftp_lib:decode_msg(Bin)),
931            print_debug_info(Config, Req, recv, DecodedMsg),
932            {Config, DecodedMsg};
933        {info, Ref, FromPid} when is_pid(FromPid) ->
934            Type =
935                case Req#tftp_msg_req.local_filename =/= undefined of
936                    true  -> client;
937                    false -> server
938                end,
939            Info = internal_info(Config, Type),
940            _ = reply({ok, Info}, Ref, FromPid),
941            wait_for_msg(Config, Callback, Req);
942        {{change_config, Options}, Ref, FromPid} when is_pid(FromPid) ->
943            case catch tftp_lib:parse_config(Options, Config) of
944                {'EXIT', Reason} ->
945                    _ = reply({error, Reason}, Ref, FromPid),
946                    wait_for_msg(Config, Callback, Req);
947                Config2 when is_record(Config2, config) ->
948                    _ = reply(ok, Ref, FromPid),
949                    wait_for_msg(Config2, Callback, Req)
950            end;
951        {system, From, Msg} ->
952            Misc = #sys_misc{module = ?MODULE, function = wait_for_msg, arguments = [Config, Callback, Req]},
953            sys:handle_system_msg(Msg, From, Config#config.parent_pid, ?MODULE, [], Misc);
954        {'EXIT', Pid, _Reason} when Config#config.parent_pid =:= Pid ->
955            Code = undef,
956            Text = "Parent exited.",
957            terminate(Config, Req, ?ERROR(wait_for_msg, Code, Text, Req#tftp_msg_req.filename));
958        Msg when Req#tftp_msg_req.local_filename =/= undefined ->
959            warning_msg(Config, "Client received : ~p", [Msg]),
960            wait_for_msg(Config, Callback, Req);
961        Msg when Req#tftp_msg_req.local_filename =:= undefined ->
962            warning_msg(Config, "Server received : ~p", [Msg]),
963            wait_for_msg(Config, Callback, Req)
964    after Config#config.timeout * 1000 ->
965            print_debug_info(Config, Req, recv, timeout),
966            timeout
967    end.
968
969early_read(Config, Callback, Req, LocalAccess, _NextBlockNo,
970           #prepared{status = Status, next_data = NextData, prev_data = PrevData} = Prepared) ->
971    if
972        Status =/= terminate,
973        LocalAccess =:= read,
974        Callback#callback.block_no =/= undefined,
975        NextData =:= undefined ->
976            case callback(read, Config, Callback, Req) of
977                {undefined, Error} when is_record(Error, tftp_msg_error) ->
978                    {undefined, Error};
979                {Callback2, Prepared2} when is_record(Prepared2, prepared)->
980                    {Callback2, Prepared2#prepared{prev_data = PrevData}}
981            end;
982        true ->
983            {Callback, Prepared}
984    end.
985
986%%-------------------------------------------------------------------
987%% Callback
988%%-------------------------------------------------------------------
989
990callback(Access, Config, Callback, Req) ->
991    {Callback2, Result} =
992        do_callback(Access, Config, Callback, Req),
993    print_debug_info(Config, Req, call, {Callback2, Result}),
994    {Callback2, Result}.
995
996do_callback(read = Fun, Config, Callback, Req)
997  when is_record(Config, config),
998       is_record(Callback, callback),
999       is_record(Req, tftp_msg_req) ->
1000    Args =  [Callback#callback.state],
1001    NextBlockNo = Callback#callback.block_no + 1,
1002    case catch safe_apply(Callback#callback.module, Fun, Args) of
1003        {more, Bin, NewState} when is_binary(Bin) ->
1004            Count = Callback#callback.count + size(Bin),
1005            Callback2 = Callback#callback{state    = NewState,
1006                                          block_no = NextBlockNo,
1007                                          count    = Count},
1008            Prepared = #prepared{status    = more,
1009                                 result    = undefined,
1010                                 block_no  = NextBlockNo,
1011                                 next_data = Bin},
1012            verify_count(Config, Callback2, Req, Prepared);
1013        {last, Bin, Result} when is_binary(Bin) ->
1014            Prepared = #prepared{status    = last,
1015                                 result    = Result,
1016                                 block_no  = NextBlockNo,
1017                                 next_data = Bin},
1018            {undefined, Prepared};
1019        {error, {Code, Text}} ->
1020            Error = #tftp_msg_error{code = Code, text = Text},
1021            Prepared = #prepared{status = error,
1022                                 result = Error},
1023            {undefined, Prepared};
1024        Illegal ->
1025            Code = undef,
1026            Text = "Internal error. File handler error.",
1027            callback({abort, {Code, Text, Illegal}}, Config, Callback, Req)
1028    end;
1029do_callback({write = Fun, Bin}, Config, Callback, Req)
1030  when is_record(Config, config),
1031       is_record(Callback, callback),
1032       is_record(Req, tftp_msg_req),
1033       is_binary(Bin) ->
1034    Args =  [Bin, Callback#callback.state],
1035    NextBlockNo = Callback#callback.block_no + 1,
1036    case catch safe_apply(Callback#callback.module, Fun, Args) of
1037        {more, NewState} ->
1038            Count = Callback#callback.count + size(Bin),
1039            Callback2 = Callback#callback{state    = NewState,
1040                                          block_no = NextBlockNo,
1041                                          count    = Count},
1042            Prepared = #prepared{status    = more,
1043                                 block_no  = NextBlockNo},
1044            verify_count(Config, Callback2, Req, Prepared);
1045        {last, Result} ->
1046            Prepared = #prepared{status   = last,
1047                                 result   = Result,
1048                                 block_no = NextBlockNo},
1049            {undefined, Prepared};
1050        {error, {Code, Text}} ->
1051            Error = #tftp_msg_error{code = Code, text = Text},
1052            Prepared = #prepared{status = error,
1053                                 result = Error},
1054            {undefined, Prepared};
1055        Illegal ->
1056            Code = undef,
1057            Text = "Internal error. File handler error.",
1058            callback({abort, {Code, Text, Illegal}}, Config, Callback, Req)
1059    end;
1060do_callback({open, Type}, Config, Callback, Req)
1061  when is_record(Config, config),
1062       is_record(Callback, callback),
1063       is_record(Req, tftp_msg_req) ->
1064    {Access, Filename} = local_file_access(Req),
1065    {Fun, BlockNo} =
1066        case Type of
1067            client_prepare -> {prepare, undefined};
1068            client_open    -> {open, 0};
1069            server_open    -> {open, 0}
1070        end,
1071    Mod = Callback#callback.module,
1072    Args = [Access,
1073            Filename,
1074            Req#tftp_msg_req.mode,
1075            Req#tftp_msg_req.options,
1076            Callback#callback.state],
1077    PeerInfo = peer_info(Config),
1078    _ = fast_ensure_loaded(Mod),
1079    Args2 =
1080        case erlang:function_exported(Mod, Fun, length(Args)) of
1081            true  -> Args;
1082            false -> [PeerInfo | Args]
1083        end,
1084    case catch safe_apply(Mod, Fun, Args2) of
1085        {ok, AcceptedOptions, NewState} ->
1086            Callback2 = Callback#callback{state    = NewState,
1087                                          block_no = BlockNo,
1088                                          count    = 0},
1089            {Callback2, {ok, AcceptedOptions}};
1090        {error, {Code, Text}} ->
1091            {undefined, #tftp_msg_error{code = Code, text = Text}};
1092        Illegal ->
1093            Code = undef,
1094            Text = "Internal error. File handler error.",
1095            callback({abort, {Code, Text, Illegal}}, Config, Callback, Req)
1096    end;
1097do_callback({abort, {Code, Text}}, Config, Callback, Req) ->
1098    Error = #tftp_msg_error{code = Code, text = Text},
1099    do_callback({abort, Error}, Config, Callback, Req);
1100do_callback({abort, {Code, Text, Details}}, Config, Callback, Req) ->
1101    Error = #tftp_msg_error{code = Code, text = Text, details = Details},
1102    do_callback({abort, Error}, Config, Callback, Req);
1103do_callback({abort = Fun, #tftp_msg_error{code = Code, text = Text} = Error}, Config, Callback, Req)
1104  when is_record(Config, config),
1105       is_record(Callback, callback),
1106       is_record(Req, tftp_msg_req) ->
1107    Args =  [Code, Text, Callback#callback.state],
1108    catch safe_apply(Callback#callback.module, Fun, Args),
1109    {undefined, Error};
1110do_callback({abort, Error}, _Config, undefined, _Req) when is_record(Error, tftp_msg_error) ->
1111    {undefined, Error}.
1112
1113peer_info(#config{udp_host = Host, udp_port = Port}) ->
1114    if
1115        is_tuple(Host), size(Host) =:= 4 ->
1116            {inet, tftp_lib:host_to_string(Host), Port};
1117        is_tuple(Host), size(Host) =:= 8 ->
1118            {inet6, tftp_lib:host_to_string(Host), Port};
1119        true ->
1120            {undefined, Host, Port}
1121    end.
1122
1123match_callback(Filename, Callbacks) ->
1124    if
1125        Filename =:= binary ->
1126            lookup_callback_mod(tftp_binary, Callbacks);
1127        is_binary(Filename) ->
1128            lookup_callback_mod(tftp_binary, Callbacks);
1129        true ->
1130            do_match_callback(Filename, Callbacks)
1131    end.
1132
1133do_match_callback(Filename, [C | Tail]) when is_record(C, callback) ->
1134    case catch re:run(Filename, C#callback.internal, [{capture, none}]) of
1135        match ->
1136            {ok, C};
1137        nomatch ->
1138            do_match_callback(Filename, Tail);
1139        Details ->
1140            Code = baduser,
1141            Text = "Internal error. File handler not found",
1142            {error, #tftp_msg_error{code = Code, text = Text, details = Details}}
1143    end;
1144do_match_callback(Filename, []) ->
1145    Code = baduser,
1146    Text = "Internal error. File handler not found",
1147    {error, #tftp_msg_error{code = Code, text = Text, details = Filename}}.
1148
1149lookup_callback_mod(Mod, Callbacks) ->
1150    {value, C} = lists:keysearch(Mod, #callback.module, Callbacks),
1151    {ok, C}.
1152
1153verify_count(Config, Callback, Req, Result) ->
1154    case Config#config.max_tsize of
1155        infinity ->
1156            {Callback, Result};
1157        Max when Callback#callback.count =< Max ->
1158            {Callback, Result};
1159        _Max ->
1160            Code = enospc,
1161            Text = "Too large file.",
1162            callback({abort, {Code, Text}}, Config, Callback, Req)
1163    end.
1164
1165%%-------------------------------------------------------------------
1166%% Miscellaneous
1167%%-------------------------------------------------------------------
1168
1169internal_info(Config, Type) when is_record(Config, config) ->
1170    {ok, ActualPort} = inet:port(Config#config.udp_socket),
1171    [
1172     {type, Type},
1173     {host, tftp_lib:host_to_string(Config#config.udp_host)},
1174     {port, Config#config.udp_port},
1175     {local_port, ActualPort},
1176     {port_policy, Config#config.port_policy},
1177     {udp, Config#config.udp_options},
1178     {use_tsize, Config#config.use_tsize},
1179     {max_tsize, Config#config.max_tsize},
1180     {max_conn, Config#config.max_conn},
1181     {rejected, Config#config.rejected},
1182     {timeout, Config#config.timeout},
1183     {polite_ack, Config#config.polite_ack},
1184     {debug, Config#config.debug_level},
1185     {parent_pid, Config#config.parent_pid}
1186    ] ++ Config#config.user_options ++ Config#config.callbacks.
1187
1188local_file_access(#tftp_msg_req{access = Access,
1189                                local_filename = Local,
1190                                filename = Filename}) ->
1191    case Local =:= undefined of
1192        true ->
1193            %% Server side
1194            {Access, Filename};
1195        false ->
1196            %% Client side
1197            case Access of
1198                read  -> {write, Local};
1199                write -> {read, Local}
1200            end
1201    end.
1202
1203pre_verify_options(Config, Req) ->
1204    Options = Req#tftp_msg_req.options,
1205    case catch verify_reject(Config, Req, Options) of
1206        ok ->
1207            case verify_integer("tsize", 0, Config#config.max_tsize, Options) of
1208                true ->
1209                    case verify_integer("blksize", 0, 65464, Options) of
1210                        true ->
1211                            ok;
1212                        false ->
1213                            {error, {badopt, "Too large blksize"}}
1214                    end;
1215                false ->
1216                    {error, {badopt, "Too large tsize"}}
1217            end;
1218        {error, Reason} ->
1219            {error, Reason}
1220    end.
1221
1222post_verify_options(Config, Req, NewOptions, Text) ->
1223    OldOptions = Req#tftp_msg_req.options,
1224    BadOptions  =
1225        [Key || {Key, _Val} <- NewOptions,
1226                not lists:keymember(Key, 1, OldOptions)],
1227    case BadOptions =:= [] of
1228        true ->
1229            Config2 = Config#config{timeout = lookup_timeout(NewOptions)},
1230            Req2 = Req#tftp_msg_req{options = NewOptions},
1231            {ok, Config2, Req2};
1232        false ->
1233            {error, {badopt, Text}}
1234    end.
1235
1236verify_reject(Config, Req, Options) ->
1237    Access = Req#tftp_msg_req.access,
1238    Rejected = Config#config.rejected,
1239    case lists:member(Access, Rejected) of
1240        true ->
1241            {error, {eacces, atom_to_list(Access) ++ " mode not allowed"}};
1242        false ->
1243            [throw({error, {badopt, Key ++ " not allowed"}}) ||
1244                {Key, _} <- Options, lists:member(Key, Rejected)],
1245            ok
1246    end.
1247
1248lookup_timeout(Options) ->
1249    case lists:keysearch("timeout", 1, Options) of
1250        {value, {_, Val}} ->
1251            list_to_integer(Val);
1252        false ->
1253            3
1254    end.
1255
1256lookup_mode(Options) ->
1257    case lists:keysearch("mode", 1, Options) of
1258        {value, {_, Val}} ->
1259            Val;
1260        false ->
1261            "octet"
1262    end.
1263
1264verify_integer(Key, Min, Max, Options) ->
1265    case lists:keysearch(Key, 1, Options) of
1266        {value, {_, Val}} when is_list(Val) ->
1267            case catch list_to_integer(Val) of
1268                {'EXIT', _} ->
1269                    false;
1270                Int when Int >= Min, is_integer(Min),
1271                         Max =:= infinity ->
1272                    true;
1273                Int when Int >= Min, is_integer(Min),
1274                         Int =< Max, is_integer(Max) ->
1275                    true;
1276                _ ->
1277                    false
1278            end;
1279        false ->
1280            true
1281    end.
1282
1283error_msg(#config{logger = Logger, debug_level = _Level}, F, A) ->
1284    safe_apply(Logger, error_msg, [F, A]).
1285
1286warning_msg(#config{logger = Logger, debug_level = Level}, F, A) ->
1287    case Level of
1288        none  -> ok;
1289        error -> ok;
1290        _     -> safe_apply(Logger, warning_msg, [F, A])
1291    end.
1292
1293info_msg(#config{logger = Logger}, F, A) ->
1294    safe_apply(Logger, info_msg, [F, A]).
1295
1296safe_apply(Mod, Fun, Args) ->
1297    _ = fast_ensure_loaded(Mod),
1298    apply(Mod, Fun, Args).
1299
1300fast_ensure_loaded(Mod) ->
1301    case erlang:function_exported(Mod, module_info, 0) of
1302        true  ->
1303            ok;
1304        false ->
1305            Res = code:load_file(Mod),
1306            %% io:format("tftp: code:load_file(~p) -> ~p\n", [Mod, Res]), %% XXX
1307            Res
1308    end.
1309
1310print_debug_info(#config{debug_level = Level} = Config, Who, Where, Data) ->
1311    if
1312        Level =:= none ->
1313            ok;
1314        is_record(Data, error) ->
1315            do_print_debug_info(Config, Who, Where, Data);
1316        Level =:= warning ->
1317            ok;
1318        Level =:= error ->
1319            ok;
1320        Level =:= all ->
1321            do_print_debug_info(Config, Who, Where, Data);
1322        Where =:= open ->
1323            do_print_debug_info(Config, Who, Where, Data);
1324        Where =:= close ->
1325            do_print_debug_info(Config, Who, Where, Data);
1326        Level =:= brief ->
1327            ok;
1328        Where =/= recv, Where =/= send ->
1329            ok;
1330        is_record(Data, tftp_msg_data), Level =:= normal ->
1331            ok;
1332        is_record(Data, tftp_msg_ack), Level =:= normal ->
1333            ok;
1334        true ->
1335            do_print_debug_info(Config, Who, Where, Data)
1336    end.
1337
1338do_print_debug_info(Config, Who, Where, #tftp_msg_data{data = Bin} = Msg) when is_binary(Bin) ->
1339    Msg2 = Msg#tftp_msg_data{data = {bytes, size(Bin)}},
1340    do_print_debug_info(Config, Who, Where, Msg2);
1341do_print_debug_info(Config, Who, Where, #tftp_msg_req{local_filename = Filename} = Msg) when is_binary(Filename) ->
1342    Msg2 = Msg#tftp_msg_req{local_filename = binary},
1343    do_print_debug_info(Config, Who, Where, Msg2);
1344do_print_debug_info(Config, Who, Where, Data) ->
1345    Local =
1346        case catch inet:port(Config#config.udp_socket) of
1347            {'EXIT', _Reason} ->
1348                0;
1349            {ok, Port} ->
1350                Port
1351        end,
1352    %% Remote = Config#config.udp_port,
1353    PeerInfo = lists:flatten(io_lib:format("~p", [peer_info(Config)])),
1354    Side =
1355        if
1356            is_record(Who, tftp_msg_req),
1357            Who#tftp_msg_req.local_filename =/= undefined ->
1358                client;
1359            is_record(Who, tftp_msg_req),
1360            Who#tftp_msg_req.local_filename =:= undefined ->
1361                server;
1362            is_atom(Who) ->
1363                Who
1364        end,
1365    case {Where, Data} of
1366        {_, #error{where = Where, code = Code, text = Text, filename = Filename}} ->
1367            do_format(Config, Side, Local, "error ~s ->\n\t~p ~p\n\t~p ~p: ~s\n",
1368                      [PeerInfo, self(), Filename, Where, Code, Text]);
1369        {open, #tftp_msg_req{filename = Filename}} ->
1370            do_format(Config, Side, Local, "open  ~s ->\n\t~p ~p\n",
1371                      [PeerInfo, self(), Filename]);
1372        {close, #tftp_msg_req{filename = Filename}} ->
1373            do_format(Config, Side, Local, "close ~s ->\n\t~p ~p\n",
1374                      [PeerInfo, self(), Filename]);
1375        {recv, _} ->
1376            do_format(Config, Side, Local, "recv  ~s <-\n\t~p\n",
1377                      [PeerInfo, Data]);
1378        {send, _} ->
1379            do_format(Config, Side, Local, "send  ~s ->\n\t~p\n",
1380                      [PeerInfo, Data]);
1381        {match, _} when is_record(Data, callback) ->
1382            Mod = Data#callback.module,
1383            State = Data#callback.state,
1384            do_format(Config, Side, Local, "match ~s ~p =>\n\t~p\n",
1385                      [PeerInfo, Mod, State]);
1386        {call, _} ->
1387            case Data of
1388                {Callback, _Result} when is_record(Callback, callback) ->
1389                    Mod   = Callback#callback.module,
1390                    State = Callback#callback.state,
1391                    do_format(Config, Side, Local, "call ~s ~p =>\n\t~p\n",
1392                              [PeerInfo, Mod, State]);
1393                {undefined, Result}  ->
1394                    do_format(Config, Side, Local, "call ~s result =>\n\t~p\n",
1395                              [PeerInfo, Result])
1396            end
1397    end.
1398
1399do_format(Config, Side, Local, Format, Args) ->
1400    info_msg(Config, "~p(~p): " ++ Format, [Side, Local | Args]).
1401
1402%%-------------------------------------------------------------------
1403%% System upgrade
1404%%-------------------------------------------------------------------
1405
1406system_continue(_Parent, _Debug, #sys_misc{module = Mod, function = Fun, arguments = Args}) ->
1407    apply(Mod, Fun, Args);
1408system_continue(Parent, Debug, {Fun, Args}) ->
1409    %% Handle upgrade from old releases. Please, remove this clause in next release.
1410    system_continue(Parent, Debug, #sys_misc{module = ?MODULE, function = Fun, arguments = Args}).
1411
1412-spec system_terminate(_, _, _, #sys_misc{} | {_, _}) -> no_return().
1413
1414system_terminate(Reason, _Parent, _Debug, #sys_misc{}) ->
1415    exit(Reason);
1416system_terminate(Reason, Parent, Debug, {Fun, Args}) ->
1417    %% Handle upgrade from old releases. Please, remove this clause in next release.
1418    system_terminate(Reason, Parent, Debug, #sys_misc{module = ?MODULE, function = Fun, arguments = Args}).
1419
1420system_code_change({Fun, Args}, _Module, _OldVsn, _Extra) ->
1421    {ok, {Fun, Args}}.
1422