1
%%%-------------------------------------------------------------------
2
%%% File : tftp_engine.erl
3
%%% Author : Hakan Mattsson <hakan@erix.ericsson.se>
4
%%% Description : Protocol engine for trivial FTP
6
%%% Created : 18 May 2004 by Hakan Mattsson <hakan@erix.ericsson.se>
7
%%%-------------------------------------------------------------------
11
%%%-------------------------------------------------------------------
13
%%%-------------------------------------------------------------------
15
%% application internal functions
30
%% sys callback functions
39
%%%-------------------------------------------------------------------
41
%%%-------------------------------------------------------------------
43
info(ToPid) when pid(ToPid) ->
45
Ref = erlang:monitor(Type, ToPid),
46
ToPid ! {info, self()},
48
{info, FromPid, Info} when FromPid == ToPid ->
49
erlang:demonitor(Ref),
51
{'DOWN', Ref, Type, FromPid, _Reason} when FromPid == ToPid ->
53
after timer:seconds(10) ->
57
%%%-------------------------------------------------------------------
59
%%%-------------------------------------------------------------------
62
daemon_start(Options) when list(Options) ->
63
Config = tftp_lib:parse_config(Options),
64
proc_lib:start_link(?MODULE, daemon_init, [Config], infinity).
66
daemon_init(Config) when record(Config, config),
67
pid(Config#config.parent_pid) ->
68
process_flag(trap_exit, true),
69
case catch gen_udp:open(Config#config.udp_port, Config#config.udp_options) of
71
{ok, ActualPort} = inet:port(Socket),
72
proc_lib:init_ack({ok, self()}),
73
Config2 = Config#config{udp_socket = Socket,
74
udp_port = ActualPort},
75
print_debug_info(Config2, daemon, open, undefined),
76
daemon_loop(Config2, 0, []);
78
exit({gen_udp_open, Reason});
80
exit({gen_udp_open, Reason})
83
daemon_loop(Config, N, Servers) ->
85
{info, Pid} when pid(Pid) ->
86
ServerInfo = [{n_conn, N} | [{server, P} || P <- Servers]],
87
Info = internal_info(Config, daemon) ++ ServerInfo,
88
Pid ! {info, self(), Info},
89
daemon_loop(Config, N, Servers);
90
{udp, Socket, RemoteHost, RemotePort, Bin} when binary(Bin) ->
91
inet:setopts(Socket, [{active, once}]),
92
ServerConfig = Config#config{parent_pid = self(),
93
udp_host = RemoteHost,
94
udp_port = RemotePort},
95
Msg = (catch tftp_lib:decode_msg(Bin)),
96
print_debug_info(ServerConfig, daemon, recv, Msg),
98
Req when record(Req, tftp_msg_req),
99
N < Config#config.max_conn ->
100
Args = [ServerConfig, Req],
101
Pid = proc_lib:spawn_link(?MODULE, server_init, Args),
102
daemon_loop(Config, N + 1, [Pid | Servers]);
103
Req when record(Req, tftp_msg_req) ->
104
Reply = #tftp_msg_error{code = enospc,
105
text = "Too many connections"},
106
send_msg(ServerConfig, daemon, Reply),
107
daemon_loop(Config, N, Servers);
108
{'EXIT', Reply} when record(Reply, tftp_msg_error) ->
109
send_msg(ServerConfig, daemon, Reply),
110
daemon_loop(Config, N, Servers);
112
Reply = #tftp_msg_error{code = badop,
113
text = "Illegal TFTP operation"},
114
error("Daemon received: ~p", [Req]),
115
send_msg(ServerConfig, daemon, Reply),
116
daemon_loop(Config, N, Servers)
118
{system, From, Msg} ->
119
Misc = {daemon_loop, [Config, N, Servers]},
120
sys:handle_system_msg(Msg, From, Config#config.parent_pid, ?MODULE, [], Misc);
121
{'EXIT', Pid, Reason} when Config#config.parent_pid == Pid ->
122
close_port(Config, daemon),
124
{'EXIT', Pid, _Reason} = Info ->
125
case lists:member(Pid, Servers) of
127
daemon_loop(Config, N - 1, Servers -- [Pid]);
129
error("Daemon received: ~p", [Info]),
130
daemon_loop(Config, N, Servers)
133
error("Daemon received: ~p", [Info]),
134
daemon_loop(Config, N, Servers)
137
%%%-------------------------------------------------------------------
139
%%%-------------------------------------------------------------------
141
server_init(Config, Req) when record(Config, config),
142
pid(Config#config.parent_pid),
143
record(Req, tftp_msg_req) ->
144
process_flag(trap_exit, true),
145
SuggestedOptions = Req#tftp_msg_req.options,
146
Config2 = tftp_lib:parse_config(SuggestedOptions, Config),
147
SuggestedOptions2 = Config2#config.user_options,
148
Req2 = Req#tftp_msg_req{options = SuggestedOptions2},
149
case open_free_port(Config2, server) of
151
Filename = Req#tftp_msg_req.filename,
152
case match_callback(Filename, Config#config.callbacks) of
154
print_debug_info(Config3, server, match, Callback),
155
case pre_verify_options(Config2, Req2) of
157
case callback({open, server_open}, Config3, Callback, Req2) of
158
{Callback2, {ok, AcceptedOptions}} ->
159
{LocalAccess, _} = local_file_access(Req2),
160
OptText = "Internal error. Not allowed to add new options.",
161
case post_verify_options(Config3, Req2, AcceptedOptions, OptText) of
162
{ok, Config4, Req3} when AcceptedOptions /= [] ->
163
Reply = #tftp_msg_oack{options = AcceptedOptions},
164
{Config5, Callback3, Next} =
165
transfer(Config4, Callback2, Req3, Reply, LocalAccess, undefined),
171
common_loop(Config5, Callback3, Req3, Next, LocalAccess, BlockNo);
172
{ok, Config4, Req3} when LocalAccess == write ->
174
common_ack(Config4, Callback2, Req3, LocalAccess, BlockNo, undefined);
175
{ok, Config4, Req3} when LocalAccess == read ->
177
common_read(Config4, Callback2, Req3, LocalAccess, BlockNo, BlockNo, undefined);
178
{error, {Code, Text}} ->
180
callback({abort, {Code, Text}}, Config3, Callback2, Req2),
181
send_msg(Config3, Req, Error),
182
terminate(Config3, Req2, {error, {post_verify_options, Code, Text}})
184
{undefined, #tftp_msg_error{code = Code, text = Text} = Error} ->
185
send_msg(Config3, Req, Error),
186
terminate(Config3, Req, {error, {server_open, Code, Text}})
188
{error, {Code, Text}} ->
190
callback({abort, {Code, Text}}, Config2, Callback, Req2),
191
send_msg(Config2, Req, Error),
192
terminate(Config2, Req2, {error, {pre_verify_options, Code, Text}})
194
{error, #tftp_msg_error{code = Code, text = Text} = Error} ->
195
send_msg(Config3, Req, Error),
196
terminate(Config3, Req, {error, {match_callback, Code, Text}})
199
terminate(Config2, Req, {error, {gen_udp_open, Reason}})
202
%%%-------------------------------------------------------------------
204
%%%-------------------------------------------------------------------
206
%% LocalFilename = filename() | 'binary' | binary()
207
%% Returns {ok, LastCallbackState} | {error, Reason}
208
client_start(Access, RemoteFilename, LocalFilename, Options) ->
209
Config = tftp_lib:parse_config(Options),
210
Config2 = Config#config{parent_pid = self(),
211
udp_socket = undefined},
212
Req = #tftp_msg_req{access = Access,
213
filename = RemoteFilename,
214
mode = lookup_mode(Config2#config.user_options),
215
options = Config2#config.user_options,
216
local_filename = LocalFilename},
217
Args = [Config2, Req],
218
case proc_lib:start_link(?MODULE, client_init, Args, infinity) of
219
{ok, LastCallbackState} ->
220
{ok, LastCallbackState};
225
client_init(Config, Req) when record(Config, config),
226
pid(Config#config.parent_pid),
227
record(Req, tftp_msg_req) ->
228
process_flag(trap_exit, true),
229
case open_free_port(Config, client) of
232
case Config2#config.use_tsize of
234
SuggestedOptions = Req#tftp_msg_req.options,
235
SuggestedOptions2 = tftp_lib:replace_val("tsize", "0", SuggestedOptions),
236
Req#tftp_msg_req{options = SuggestedOptions2};
240
LocalFilename = Req2#tftp_msg_req.local_filename,
241
case match_callback(LocalFilename, Config2#config.callbacks) of
243
print_debug_info(Config2, client, match, Callback),
244
client_prepare(Config2, Callback, Req2);
245
{error, #tftp_msg_error{code = Code, text = Text}} ->
246
terminate(Config, Req, {error, {match, Code, Text}})
249
terminate(Config, Req, {error, {gen_udp_open, Reason}})
252
client_prepare(Config, Callback, Req) ->
253
case pre_verify_options(Config, Req) of
255
case callback({open, client_prepare}, Config, Callback, Req) of
256
{Callback2, {ok, AcceptedOptions}} ->
257
OptText = "Internal error. Not allowed to add new options.",
258
case post_verify_options(Config, Req, AcceptedOptions, OptText) of
259
{ok, Config2, Req2} ->
260
{LocalAccess, _} = local_file_access(Req2),
261
{Config3, Callback3, Next} =
262
transfer(Config2, Callback2, Req2, Req2, LocalAccess, undefined),
263
client_open(Config3, Callback3, Req2, Next);
264
{error, {Code, Text}} ->
265
callback({abort, {Code, Text}}, Config, Callback2, Req),
266
terminate(Config, Req, {error, {post_verify_options, Code, Text}})
268
{undefined, #tftp_msg_error{code = Code, text = Text}} ->
269
terminate(Config, Req, {error, {client_prepare, Code, Text}})
271
{error, {Code, Text}} ->
272
callback({abort, {Code, Text}}, Config, Callback, Req),
273
terminate(Config, Req, {error, {pre_verify_options, Code, Text}})
276
client_open(Config, Callback, Req, Next) ->
277
{LocalAccess, _} = local_file_access(Req),
279
{ok, DecodedMsg, undefined} ->
281
Msg when record(Msg, tftp_msg_oack) ->
282
ServerOptions = Msg#tftp_msg_oack.options,
283
OptText = "Protocol violation. Server is not allowed new options",
284
case post_verify_options(Config, Req, ServerOptions, OptText) of
285
{ok, Config2, Req2} ->
286
{Config3, Callback2, Req3} =
287
do_client_open(Config2, Callback, Req2),
291
common_read(Config3, Callback2, Req3, LocalAccess, BlockNo, BlockNo, undefined);
294
common_ack(Config3, Callback2, Req3, LocalAccess, BlockNo, undefined)
296
{error, {Code, Text}} ->
298
callback({abort, {Code, Text}}, Config, Callback, Req),
299
send_msg(Config, Req, Error),
300
terminate(Config, Req, {error, {verify_server_options, Code, Text}})
302
#tftp_msg_ack{block_no = ActualBlockNo} when LocalAccess == read ->
303
Req2 = Req#tftp_msg_req{options = []},
304
{Config2, Callback2, Req2} = do_client_open(Config, Callback, Req2),
306
common_read(Config2, Callback2, Req2, LocalAccess, ExpectedBlockNo, ActualBlockNo, undefined);
307
#tftp_msg_data{block_no = ActualBlockNo, data = Data} when LocalAccess == write ->
308
Req2 = Req#tftp_msg_req{options = []},
309
{Config2, Callback2, Req2} = do_client_open(Config, Callback, Req2),
311
common_write(Config2, Callback2, Req2, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, undefined);
312
%% #tftp_msg_error{code = Code, text = Text} when Req#tftp_msg_req.options /= [] ->
313
%% %% Retry without options
314
%% callback({abort, {Code, Text}}, Config, Callback, Req),
315
%% Req2 = Req#tftp_msg_req{options = []},
316
%% client_prepare(Config, Callback, Req2);
317
#tftp_msg_error{code = Code, text = Text} ->
318
callback({abort, {Code, Text}}, Config, Callback, Req),
319
terminate(Config, Req, {error, {client_open, Code, Text}});
320
{'EXIT', #tftp_msg_error{code = Code, text = Text}} ->
321
callback({abort, {Code, Text}}, Config, Callback, Req),
322
terminate(Config, Req, {error, {client_open, Code, Text}});
323
Msg when tuple(Msg) ->
325
Text = "Illegal TFTP operation",
327
callback({abort, {Code, Text}}, Config, Callback, Req),
328
send_msg(Config, Req, Error),
329
terminate(Config, Req, {error, {client_open, Code, Text, element(1, Msg)}})
331
{error, #tftp_msg_error{code = Code, text = Text}} ->
332
callback({abort, {Code, Text}}, Config, Callback, Req),
333
terminate(Config, Req, {error, {client_open, Code, Text}})
336
do_client_open(Config, Callback, Req) ->
337
case callback({open, client_open}, Config, Callback, Req) of
338
{Callback2, {ok, FinalOptions}} ->
339
OptText = "Internal error. Not allowed to change options.",
340
case post_verify_options(Config, Req, FinalOptions, OptText) of
341
{ok, Config2, Req2} ->
342
{Config2, Callback2, Req2};
343
{error, {Code, Text}} ->
345
callback({abort, {Code, Text}}, Config, Callback, Req),
346
send_msg(Config, Req, Error),
347
terminate(Config, Req, {error, {post_verify_options, Code, Text}})
349
{undefined, #tftp_msg_error{code = Code, text = Text} = Error} ->
350
send_msg(Config, Req, Error),
351
terminate(Config, Req, {error, {client_open, Code, Text}})
354
%%%-------------------------------------------------------------------
355
%%% Common loop for both client and server
356
%%%-------------------------------------------------------------------
358
common_loop(Config, Callback, Req, Next, LocalAccess, ExpectedBlockNo) ->
360
{ok, DecodedMsg, Prepared} ->
362
#tftp_msg_ack{block_no = ActualBlockNo} when LocalAccess == read ->
363
common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared);
364
#tftp_msg_data{block_no = ActualBlockNo, data = Data} when LocalAccess == write ->
365
common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared);
366
#tftp_msg_error{code = Code, text = Text} ->
367
callback({abort, {Code, Text}}, Config, Callback, Req),
368
terminate(Config, Req, {error, {common_loop, Code, Text}});
369
{'EXIT', #tftp_msg_error{code = Code, text = Text} = Error} ->
370
callback({abort, {Code, Text}}, Config, Callback, Req),
371
send_msg(Config, Req, Error),
372
terminate(Config, Req, {error, {common_loop, Code, Text}});
373
Msg when tuple(Msg) ->
375
Text = "Illegal TFTP operation",
377
callback({abort, {Code, Text}}, Config, Callback, Req),
378
send_msg(Config, Req, Error),
379
terminate(Config, Req, {error, {common_loop, Code, Text, element(1, Msg)}})
381
{error, #tftp_msg_error{code = Code, text = Text} = Error} ->
382
send_msg(Config, Req, Error),
383
terminate(Config, Req, {error, {transfer, Code, Text}})
386
common_read(Config, _, Req, _, _, _, {terminate, Result}) ->
387
terminate(Config, Req, {ok, Result});
388
common_read(Config, Callback, Req, LocalAccess, BlockNo, BlockNo, Prepared) ->
389
case early_read(Config, Callback, Req, LocalAccess, Prepared) of
390
{Callback2, {more, Data}} ->
391
do_common_read(Config, Callback2, Req, LocalAccess, BlockNo, Data, undefined);
392
{undefined, {last, Data, Result}} ->
393
do_common_read(Config, undefined, Req, LocalAccess, BlockNo, Data, {terminate, Result});
394
{undefined, #tftp_msg_error{code = Code, text = Text} = Reply} ->
395
send_msg(Config, Req, Reply),
396
terminate(Config, Req, {error, {read, Code, Text}})
398
common_read(Config, Callback, Req, _LocalAccess, ExpectedBlockNo, ActualBlockNo, _Prepared) ->
400
Text = "Unknown transfer ID = " ++
401
integer_to_list(ActualBlockNo) ++ "(" ++ integer_to_list(ExpectedBlockNo) ++ ")",
403
callback({abort, {Code, Text}}, Config, Callback, Req),
404
send_msg(Config, Req, Error),
405
terminate(Config, Req, {error, {read, Code, Text}}).
407
do_common_read(Config, Callback, Req, LocalAccess, BlockNo, Data, Prepared) ->
408
NextBlockNo = BlockNo + 1,
409
Reply = #tftp_msg_data{block_no = NextBlockNo, data = Data},
410
{Config2, Callback2, Next} =
411
transfer(Config, Callback, Req, Reply, LocalAccess, Prepared),
412
common_loop(Config2, Callback2, Req, Next, LocalAccess, NextBlockNo).
414
common_write(Config, _, Req, _, _, _, _, {terminate, Result}) ->
415
terminate(Config, Req, {ok, Result});
416
common_write(Config, Callback, Req, LocalAccess, BlockNo, BlockNo, Data, undefined) ->
417
case callback({write, Data}, Config, Callback, Req) of
419
common_ack(Config, Callback2, Req, LocalAccess, BlockNo, undefined);
420
{undefined, {last, Result}} ->
421
Config2 = pre_terminate(Config, Req, {ok, Result}),
422
common_ack(Config2, undefined, Req, LocalAccess, BlockNo, {terminate, Result});
423
{undefined, #tftp_msg_error{code = Code, text = Text} = Reply} ->
424
send_msg(Config, Req, Reply),
425
terminate(Config, Req, {error, {write, Code, Text}})
427
common_write(Config, Callback, Req, _, ExpectedBlockNo, ActualBlockNo, _, _) ->
429
Text = "Unknown transfer ID = " ++
430
integer_to_list(ActualBlockNo) ++ "(" ++ integer_to_list(ExpectedBlockNo) ++ ")",
432
callback({abort, {Code, Text}}, Config, Callback, Req),
433
send_msg(Config, Req, Error),
434
terminate(Config, Req, {error, {write, Code, Text}}).
436
common_ack(Config, Callback, Req, LocalAccess, BlockNo, Prepared) ->
437
Reply = #tftp_msg_ack{block_no = BlockNo},
438
{Config2, Callback2, Next} =
439
transfer(Config, Callback, Req, Reply, LocalAccess, Prepared),
440
NextBlockNo = BlockNo + 1,
441
common_loop(Config2, Callback2, Req, Next, LocalAccess, NextBlockNo).
443
pre_terminate(Config, Req, Result) ->
445
Req#tftp_msg_req.local_filename /= undefined,
446
Config#config.parent_pid /= undefined ->
447
proc_lib:init_ack(Result),
448
unlink(Config#config.parent_pid),
449
Config#config{parent_pid = undefined, polite_ack = true};
451
Config#config{polite_ack = true}
454
terminate(Config, Req, Result) ->
456
Config#config.parent_pid == undefined ->
457
close_port(Config, client),
459
Req#tftp_msg_req.local_filename /= undefined ->
461
close_port(Config, client),
462
proc_lib:init_ack(Result),
463
unlink(Config#config.parent_pid),
467
close_port(Config, server),
476
close_port(Config, Who) ->
477
case Config#config.udp_socket of
481
print_debug_info(Config, Who, close, undefined),
482
gen_udp:close(Socket)
485
open_free_port(Config, Who) when record(Config, config) ->
486
PortOptions = Config#config.udp_options,
487
case Config#config.port_policy of
489
%% BUGBUG: Should be a random port
490
case catch gen_udp:open(0, PortOptions) of
492
Config2 = Config#config{udp_socket = Socket},
493
print_debug_info(Config2, Who, open, undefined),
497
{'EXIT', _} = Reason->
500
{range, Port, Max} when Port =< Max ->
501
case catch gen_udp:open(Port, PortOptions) of
503
Config2 = Config#config{udp_socket = Socket},
504
print_debug_info(Config2, Who, open, undefined),
506
{error, eaddrinuse} ->
507
PortPolicy = {range, Port + 1, Max},
508
Config2 = Config#config{port_policy = PortPolicy},
509
open_free_port(Config2, Who);
512
{'EXIT', _} = Reason->
515
{range, Port, _Max} ->
516
{error, {port_range_exhausted, Port}}
519
%%-------------------------------------------------------------------
521
%%-------------------------------------------------------------------
523
%% Returns {Config, Callback, Next}
524
%% Next = {ok, Reply, Next} | {error, Error}
525
transfer(Config, Callback, Req, Msg, LocalAccess, Prepared) ->
526
IoList = tftp_lib:encode_msg(Msg),
527
do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, Prepared, true).
529
do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, Prepared, Retry) ->
530
case do_send_msg(Config, Req, Msg, IoList) of
532
{Callback2, Prepared2} =
533
early_read(Config, Callback, Req, LocalAccess, Prepared),
534
case wait_for_msg(Config, Callback, Req) of
535
timeout when Config#config.polite_ack == true ->
536
do_send_msg(Config, Req, Msg, IoList),
537
terminate(Config, Req, Prepared2);
538
timeout when Retry == true ->
540
do_transfer(Config, Callback2, Req, Msg, IoList, LocalAccess, Prepared2, Retry2);
543
Text = "Transfer timed out.",
544
Error = #tftp_msg_error{code = Code, text = Text},
545
{Config, Callback, {error, Error}};
547
{Config2, Callback2, {ok, Reply, Prepared2}}
549
{error, _Reason} when Retry == true ->
550
do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, Prepared, false);
553
Text = "Internal error. gen_udp:send/4 failed",
554
{Config, Callback, {error, #tftp_msg_error{code = Code, text = Text}}}
557
send_msg(Config, Req, Msg) ->
558
case catch tftp_lib:encode_msg(Msg) of
561
Text = "Internal error. Encode failed",
562
Msg2 = #tftp_msg_error{code = Code, text = Text, details = Reason},
563
send_msg(Config, Req, Msg2);
565
do_send_msg(Config, Req, Msg, IoList)
568
do_send_msg(Config, Req, Msg, IoList) ->
569
print_debug_info(Config, Req, send, Msg),
570
gen_udp:send(Config#config.udp_socket,
571
Config#config.udp_host,
572
Config#config.udp_port,
575
wait_for_msg(Config, Callback, Req) ->
577
{info, Pid} when pid(Pid) ->
579
case Req#tftp_msg_req.local_filename /= undefined of
583
Pid ! {info, self(), internal_info(Config, Type)},
584
wait_for_msg(Config, Callback, Req);
585
{udp, Socket, Host, Port, Bin} when binary(Bin),
586
Callback#callback.block_no == undefined ->
588
inet:setopts(Socket, [{active, once}]),
589
Config2 = Config#config{udp_host = Host,
591
DecodedMsg = (catch tftp_lib:decode_msg(Bin)),
592
print_debug_info(Config2, Req, recv, DecodedMsg),
593
{Config2, DecodedMsg};
594
{udp, Socket, Host, Port, Bin} when binary(Bin),
595
Config#config.udp_host == Host,
596
Config#config.udp_port == Port ->
597
inet:setopts(Socket, [{active, once}]),
598
DecodedMsg = (catch tftp_lib:decode_msg(Bin)),
599
print_debug_info(Config, Req, recv, DecodedMsg),
600
{Config, DecodedMsg};
601
{system, From, Msg} ->
602
Misc = {wait_for_msg, [Config, Callback, Req]},
603
sys:handle_system_msg(Msg, From, Config#config.parent_pid, ?MODULE, [], Misc);
604
{'EXIT', Pid, Reason} when Config#config.parent_pid == Pid ->
606
Text = "Parent exited.",
607
terminate(Config, Req, {error, {wait_for_msg, Code, Text, Reason}});
608
Msg when Req#tftp_msg_req.local_filename /= undefined ->
609
error("Client received : ~p", [Msg]),
610
wait_for_msg(Config, Callback, Req);
611
Msg when Req#tftp_msg_req.local_filename == undefined ->
612
error("Server received : ~p", [Msg]),
613
wait_for_msg(Config, Callback, Req)
614
after Config#config.timeout * 1000 ->
615
print_debug_info(Config, Req, recv, timeout),
619
early_read(Config, Callback, Req, read, undefined)
620
when Callback#callback.block_no /= undefined ->
621
callback(read, Config, Callback, Req);
622
early_read(_Config, Callback, _Req, _LocalAccess, Prepared) ->
623
{Callback, Prepared}.
625
%%-------------------------------------------------------------------
627
%%-------------------------------------------------------------------
629
callback(Access, Config, Callback, Req) ->
630
{Callback2, Result} =
631
do_callback(Access, Config, Callback, Req),
632
print_debug_info(Config, Req, call, {Callback2, Result}),
635
do_callback(read, Config, Callback, Req)
636
when record(Config, config),
637
record(Callback, callback),
638
record(Req, tftp_msg_req) ->
639
Args = [Callback#callback.state],
640
case catch apply(Callback#callback.module, read, Args) of
641
{more, Bin, NewState} when binary(Bin) ->
642
BlockNo = Callback#callback.block_no + 1,
643
Count = Callback#callback.count + size(Bin),
644
Callback2 = Callback#callback{state = NewState,
647
verify_count(Config, Callback2, Req, {more, Bin});
648
{last, Data, Result} ->
649
{undefined, {last, Data, Result}};
650
{error, {Code, Text}} ->
651
{undefined, #tftp_msg_error{code = Code, text = Text}};
654
Text = "Internal error. File handler error.",
655
callback({abort, {Code, Text, Details}}, Config, Callback, Req)
657
do_callback({write, Bin}, Config, Callback, Req)
658
when record(Config, config),
659
record(Callback, callback),
660
record(Req, tftp_msg_req),
662
Args = [Bin, Callback#callback.state],
663
case catch apply(Callback#callback.module, write, Args) of
665
BlockNo = Callback#callback.block_no + 1,
666
Count = Callback#callback.count + size(Bin),
667
Callback2 = Callback#callback{state = NewState,
670
verify_count(Config, Callback2, Req, more);
672
{undefined, {last, Result}};
673
{error, {Code, Text}} ->
674
{undefined, #tftp_msg_error{code = Code, text = Text}};
677
Text = "Internal error. File handler error.",
678
callback({abort, {Code, Text, Details}}, Config, Callback, Req)
680
do_callback({open, Type}, Config, Callback, Req)
681
when record(Config, config),
682
record(Callback, callback),
683
record(Req, tftp_msg_req) ->
684
{Access, Filename} = local_file_access(Req),
687
client_prepare -> {prepare, undefined};
688
client_open -> {open, 0};
689
server_open -> {open, 0}
693
Req#tftp_msg_req.mode,
694
Req#tftp_msg_req.options,
695
Callback#callback.state],
696
case catch apply(Callback#callback.module, Oper, Args) of
697
{ok, AcceptedOptions, NewState} ->
698
Callback2 = Callback#callback{state = NewState,
701
{Callback2, {ok, AcceptedOptions}};
702
{error, {Code, Text}} ->
703
{undefined, #tftp_msg_error{code = Code, text = Text}};
706
Text = "Internal error. File handler error.",
707
callback({abort, {Code, Text, Details}}, Config, Callback, Req)
709
do_callback({abort, {Code, Text}}, Config, Callback, Req) ->
710
Error = #tftp_msg_error{code = Code, text = Text},
711
do_callback({abort, Error}, Config, Callback, Req);
712
do_callback({abort, {Code, Text, Details}}, Config, Callback, Req) ->
713
Error = #tftp_msg_error{code = Code, text = Text, details = Details},
714
do_callback({abort, Error}, Config, Callback, Req);
715
do_callback({abort, #tftp_msg_error{code = Code, text = Text} = Error}, Config, Callback, Req)
716
when record(Config, config),
717
record(Callback, callback),
718
record(Req, tftp_msg_req) ->
719
Args = [Code, Text, Callback#callback.state],
720
catch apply(Callback#callback.module, abort, Args),
722
do_callback({abort, Error}, _Config, undefined, _Req) when record(Error, tftp_msg_error) ->
725
match_callback(Filename, Callbacks) ->
727
Filename == binary ->
728
{ok, #callback{regexp = "",
730
module = tftp_binary,
733
{ok, #callback{regexp = "",
735
module = tftp_binary,
738
{ok, #callback{regexp = "",
743
do_match_callback(Filename, Callbacks)
746
do_match_callback(Filename, [C | Tail]) when record(C, callback) ->
747
case catch regexp:match(Filename, C#callback.internal) of
751
do_match_callback(Filename, Tail);
754
Text = "Internal error. File handler not found",
755
{error, #tftp_msg_error{code = Code, text = Text, details = Details}}
757
do_match_callback(Filename, []) ->
759
Text = "Internal error. File handler not found",
760
{error, #tftp_msg_error{code = Code, text = Text, details = Filename}}.
762
verify_count(Config, Callback, Req, Result) ->
763
case Config#config.max_tsize of
766
Max when Callback#callback.count =< Max ->
770
Text = "Too large file.",
771
callback({abort, {Code, Text}}, Config, Callback, Req)
774
%%-------------------------------------------------------------------
776
%%-------------------------------------------------------------------
778
internal_info(Config, Type) ->
779
{ok, ActualPort} = inet:port(Config#config.udp_socket),
782
{host, Config#config.udp_host},
783
{port, Config#config.udp_port},
784
{local_port, ActualPort},
785
{port_policy, Config#config.port_policy},
786
{udp, Config#config.udp_options},
787
{use_tsize, Config#config.use_tsize},
788
{max_tsize, Config#config.max_tsize},
789
{max_conn, Config#config.max_conn},
790
{rejected, Config#config.rejected},
791
{timeout, Config#config.timeout},
792
{polite_ack, Config#config.polite_ack},
793
{debug_level, Config#config.debug_level},
794
{parent_pid, Config#config.parent_pid}
795
] ++ Config#config.user_options ++ Config#config.callbacks.
797
local_file_access(#tftp_msg_req{access = Access,
798
local_filename = Local,
799
filename = Filename}) ->
800
case Local == undefined of
814
pre_verify_options(Config, Req) ->
815
Options = Req#tftp_msg_req.options,
816
case catch verify_reject(Config, Req, Options) of
818
case verify_integer("tsize", 0, Config#config.max_tsize, Options) of
820
case verify_integer("blksize", 0, 65464, Options) of
824
{error, {badopt, "Too large blksize"}}
827
{error, {badopt, "Too large tsize"}}
833
post_verify_options(Config, Req, NewOptions, Text) ->
834
OldOptions = Req#tftp_msg_req.options,
836
[Key || {Key, _Val} <- NewOptions,
837
not lists:keymember(Key, 1, OldOptions)],
838
case BadOptions == [] of
841
Config#config{timeout = lookup_timeout(NewOptions)},
842
Req#tftp_msg_req{options = NewOptions}};
844
{error, {badopt, Text}}
847
verify_reject(Config, Req, Options) ->
848
Access = Req#tftp_msg_req.access,
849
Rejected = Config#config.rejected,
850
case lists:member(Access, Rejected) of
852
{error, {eacces, atom_to_list(Access) ++ " mode not allowed"}};
854
[throw({error, {badopt, Key ++ " not allowed"}}) ||
855
{Key, _} <- Options, lists:member(Key, Rejected)],
859
lookup_timeout(Options) ->
860
case lists:keysearch("timeout", 1, Options) of
862
list_to_integer(Val);
867
lookup_mode(Options) ->
868
case lists:keysearch("mode", 1, Options) of
875
verify_integer(Key, Min, Max, Options) ->
876
case lists:keysearch(Key, 1, Options) of
877
{value, {_, Val}} when list(Val) ->
878
case catch list_to_integer(Val) of
881
Int when Int >= Min, integer(Min),
884
Int when Int >= Min, integer(Min),
885
Int =< Max, integer(Max) ->
894
ok = error_logger:format("~p(~p): " ++ F ++ "~n", [?MODULE, self() | A]).
896
print_debug_info(#config{debug_level = Level} = Config, Who, What, Data) ->
901
do_print_debug_info(Config, Who, What, Data);
903
do_print_debug_info(Config, Who, What, Data);
905
do_print_debug_info(Config, Who, What, Data);
908
What /= recv, What /= send ->
910
record(Data, tftp_msg_data), Level == normal ->
912
record(Data, tftp_msg_ack), Level == normal ->
915
do_print_debug_info(Config, Who, What, Data)
918
do_print_debug_info(Config, Who, What, #tftp_msg_data{data = Bin} = Msg) when binary(Bin) ->
919
Msg2 = Msg#tftp_msg_data{data = {bytes, size(Bin)}},
920
do_print_debug_info(Config, Who, What, Msg2);
921
do_print_debug_info(Config, Who, What, #tftp_msg_req{local_filename = Filename} = Msg) when binary(Filename) ->
922
Msg2 = Msg#tftp_msg_req{local_filename = binary},
923
do_print_debug_info(Config, Who, What, Msg2);
924
do_print_debug_info(Config, Who, What, Data) ->
925
{ok, Local} = inet:port(Config#config.udp_socket),
926
Remote = Config#config.udp_port,
929
record(Who, tftp_msg_req),
930
Who#tftp_msg_req.local_filename /= undefined ->
932
record(Who, tftp_msg_req),
933
Who#tftp_msg_req.local_filename == undefined ->
940
io:format("~p(~p): open ~p -> ~p\n", [Side, Local, self(), Config#config.udp_host]);
942
io:format("~p(~p): close ~p -> ~p\n", [Side, Local, self(), Config#config.udp_host]);
944
io:format("~p(~p): recv ~p <- ~p\n", [Side, Local, Remote, Data]);
946
io:format("~p(~p): send ~p -> ~p\n", [Side, Local, Remote, Data]);
947
match when record(Data, callback) ->
948
Mod = Data#callback.module,
949
State = Data#callback.state,
950
io:format("~p(~p): match ~p ~p => ~p\n", [Side, Local, Remote, Mod, State]);
953
{Callback, _Result} when record(Callback, callback) ->
954
Mod = Callback#callback.module,
955
State = Callback#callback.state,
956
io:format("~p(~p): call ~p ~p => ~p\n", [Side, Local, Remote, Mod, State]);
957
{undefined, Result} ->
958
io:format("~p(~p): call ~p result => ~p\n", [Side, Local, Remote, Result])
963
%%-------------------------------------------------------------------
965
%%-------------------------------------------------------------------
967
system_continue(_Parent, _Debug, {Fun, Args}) ->
968
apply(?MODULE, Fun, Args).
970
system_terminate(Reason, _Parent, _Debug, {_Fun, _Args}) ->
973
system_code_change({Fun, Args}, _Module, _OldVsn, _Extra) ->