~ubuntu-branches/debian/squeeze/erlang/squeeze

« back to all changes in this revision

Viewing changes to lib/inets/src/tftp/tftp_engine.erl

  • Committer: Bazaar Package Importer
  • Author(s): Erlang Packagers, Sergei Golovan
  • Date: 2006-12-03 17:07:44 UTC
  • mfrom: (2.1.11 feisty)
  • Revision ID: james.westby@ubuntu.com-20061203170744-rghjwupacqlzs6kv
Tags: 1:11.b.2-4
[ Sergei Golovan ]
Fixed erlang-base and erlang-base-hipe prerm scripts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
%%%-------------------------------------------------------------------
 
2
%%% File    : tftp_engine.erl
 
3
%%% Author  : Hakan Mattsson <hakan@erix.ericsson.se>
 
4
%%% Description : Protocol engine for trivial FTP
 
5
%%%
 
6
%%% Created : 18 May 2004 by Hakan Mattsson <hakan@erix.ericsson.se>
 
7
%%%-------------------------------------------------------------------
 
8
 
 
9
-module(tftp_engine).
 
10
 
 
11
%%%-------------------------------------------------------------------
 
12
%%% Interface
 
13
%%%-------------------------------------------------------------------
 
14
 
 
15
%% application internal functions
 
16
-export([
 
17
         daemon_start/1,
 
18
         client_start/4,
 
19
         info/1
 
20
        ]).
 
21
 
 
22
%% module internal
 
23
-export([
 
24
         daemon_init/1, 
 
25
         server_init/2, 
 
26
         client_init/2,
 
27
         wait_for_msg/3
 
28
        ]).
 
29
 
 
30
%% sys callback functions
 
31
-export([
 
32
         system_continue/3,
 
33
         system_terminate/4,
 
34
         system_code_change/4
 
35
        ]).
 
36
 
 
37
-include("tftp.hrl").
 
38
 
 
39
%%%-------------------------------------------------------------------
 
40
%%% Info
 
41
%%%-------------------------------------------------------------------
 
42
 
 
43
info(ToPid) when pid(ToPid) ->
 
44
    Type = process,
 
45
    Ref = erlang:monitor(Type, ToPid),
 
46
    ToPid ! {info, self()},
 
47
    receive
 
48
        {info, FromPid, Info} when FromPid == ToPid ->
 
49
            erlang:demonitor(Ref),
 
50
            Info;
 
51
        {'DOWN', Ref, Type, FromPid, _Reason} when FromPid == ToPid ->
 
52
            undefined
 
53
    after timer:seconds(10) ->
 
54
            timeout
 
55
    end.
 
56
 
 
57
%%%-------------------------------------------------------------------
 
58
%%% Daemon
 
59
%%%-------------------------------------------------------------------
 
60
 
 
61
%% Returns {ok, Port}
 
62
daemon_start(Options) when list(Options) ->
 
63
    Config = tftp_lib:parse_config(Options),
 
64
    proc_lib:start_link(?MODULE, daemon_init, [Config], infinity).
 
65
 
 
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
 
70
        {ok, Socket} ->
 
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, []);
 
77
        {error, Reason} ->
 
78
            exit({gen_udp_open, Reason});
 
79
        Reason ->
 
80
            exit({gen_udp_open, Reason})
 
81
    end.
 
82
 
 
83
daemon_loop(Config, N, Servers) ->
 
84
    receive
 
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),
 
97
            case Msg of
 
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);
 
111
                Req  ->
 
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)
 
117
            end;
 
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),
 
123
            exit(Reason);
 
124
        {'EXIT', Pid, _Reason} = Info ->
 
125
            case lists:member(Pid, Servers) of
 
126
                true ->
 
127
                    daemon_loop(Config, N - 1, Servers -- [Pid]);
 
128
                false ->
 
129
                    error("Daemon received: ~p", [Info]),
 
130
                    daemon_loop(Config, N, Servers)
 
131
            end;
 
132
        Info ->
 
133
            error("Daemon received: ~p", [Info]),
 
134
            daemon_loop(Config, N, Servers)
 
135
    end.
 
136
 
 
137
%%%-------------------------------------------------------------------
 
138
%%% Server
 
139
%%%-------------------------------------------------------------------
 
140
 
 
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
 
150
        {ok, Config3} ->
 
151
            Filename = Req#tftp_msg_req.filename,
 
152
            case match_callback(Filename, Config#config.callbacks) of
 
153
                {ok, Callback} ->
 
154
                    print_debug_info(Config3, server, match, Callback),
 
155
                    case pre_verify_options(Config2, Req2) of
 
156
                        ok ->
 
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),
 
166
                                            BlockNo =
 
167
                                                case LocalAccess of
 
168
                                                    read  -> 0;
 
169
                                                    write -> 1
 
170
                                                end,
 
171
                                            common_loop(Config5, Callback3, Req3, Next, LocalAccess, BlockNo);
 
172
                                        {ok, Config4, Req3} when LocalAccess == write ->
 
173
                                            BlockNo = 0,
 
174
                                            common_ack(Config4, Callback2, Req3, LocalAccess, BlockNo, undefined);
 
175
                                        {ok, Config4, Req3} when LocalAccess == read ->
 
176
                                            BlockNo = 0,
 
177
                                            common_read(Config4, Callback2, Req3, LocalAccess, BlockNo, BlockNo, undefined);
 
178
                                        {error, {Code, Text}} ->
 
179
                                            {undefined, Error} =
 
180
                                                callback({abort, {Code, Text}}, Config3, Callback2, Req2),
 
181
                                            send_msg(Config3, Req, Error),
 
182
                                            terminate(Config3, Req2, {error, {post_verify_options, Code, Text}})
 
183
                                    end;
 
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}})
 
187
                            end;
 
188
                        {error, {Code, Text}} ->
 
189
                            {undefined, Error} =
 
190
                                callback({abort, {Code, Text}}, Config2, Callback, Req2),
 
191
                            send_msg(Config2, Req, Error),
 
192
                            terminate(Config2, Req2, {error, {pre_verify_options, Code, Text}})
 
193
                    end;
 
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}})
 
197
            end;
 
198
        {error, Reason} ->
 
199
            terminate(Config2, Req, {error, {gen_udp_open, Reason}})
 
200
    end.
 
201
 
 
202
%%%-------------------------------------------------------------------
 
203
%%% Client
 
204
%%%-------------------------------------------------------------------
 
205
 
 
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};
 
221
        {error, Error} ->
 
222
            {error, Error}
 
223
    end.
 
224
 
 
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
 
230
        {ok, Config2} ->
 
231
            Req2 =
 
232
                case Config2#config.use_tsize of
 
233
                    true ->
 
234
                        SuggestedOptions = Req#tftp_msg_req.options,
 
235
                        SuggestedOptions2 = tftp_lib:replace_val("tsize", "0", SuggestedOptions),
 
236
                        Req#tftp_msg_req{options = SuggestedOptions2};
 
237
                    false ->
 
238
                        Req
 
239
                end,
 
240
            LocalFilename = Req2#tftp_msg_req.local_filename,
 
241
            case match_callback(LocalFilename, Config2#config.callbacks) of
 
242
                {ok, Callback} ->
 
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}})
 
247
            end;
 
248
        {error, Reason} ->
 
249
            terminate(Config, Req, {error, {gen_udp_open, Reason}})
 
250
    end.
 
251
 
 
252
client_prepare(Config, Callback, Req) ->
 
253
    case pre_verify_options(Config, Req) of
 
254
        ok ->
 
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}})
 
267
                    end;
 
268
                {undefined, #tftp_msg_error{code = Code, text = Text}} ->
 
269
                    terminate(Config, Req, {error, {client_prepare, Code, Text}})
 
270
            end;
 
271
        {error, {Code, Text}} ->
 
272
            callback({abort, {Code, Text}}, Config, Callback, Req),
 
273
            terminate(Config, Req, {error, {pre_verify_options, Code, Text}})
 
274
    end.
 
275
 
 
276
client_open(Config, Callback, Req, Next) ->
 
277
    {LocalAccess, _} = local_file_access(Req),
 
278
    case Next of
 
279
        {ok, DecodedMsg, undefined} ->
 
280
            case DecodedMsg of
 
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),
 
288
                            case LocalAccess of
 
289
                                read ->
 
290
                                    BlockNo = 0,
 
291
                                    common_read(Config3, Callback2, Req3, LocalAccess, BlockNo, BlockNo, undefined);
 
292
                                write ->
 
293
                                    BlockNo = 0,
 
294
                                    common_ack(Config3, Callback2, Req3, LocalAccess, BlockNo, undefined)
 
295
                            end;
 
296
                        {error, {Code, Text}} ->
 
297
                            {undefined, Error} =
 
298
                                callback({abort, {Code, Text}}, Config, Callback, Req),
 
299
                            send_msg(Config, Req, Error),
 
300
                            terminate(Config, Req, {error, {verify_server_options, Code, Text}})
 
301
                    end;
 
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),
 
305
                    ExpectedBlockNo = 0,
 
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),
 
310
                    ExpectedBlockNo = 1,
 
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) ->
 
324
                    Code = badop,
 
325
                    Text = "Illegal TFTP operation",
 
326
                    {undefined, Error} =
 
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)}})
 
330
            end;
 
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}})
 
334
    end.
 
335
 
 
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}} ->
 
344
                    {undefined, Error} =
 
345
                        callback({abort, {Code, Text}}, Config, Callback, Req),
 
346
                    send_msg(Config, Req, Error),
 
347
                    terminate(Config, Req, {error, {post_verify_options, Code, Text}})
 
348
            end;
 
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}})
 
352
    end.
 
353
 
 
354
%%%-------------------------------------------------------------------
 
355
%%% Common loop for both client and server
 
356
%%%-------------------------------------------------------------------
 
357
 
 
358
common_loop(Config, Callback, Req, Next, LocalAccess, ExpectedBlockNo) ->
 
359
    case Next of
 
360
        {ok, DecodedMsg, Prepared} ->
 
361
            case DecodedMsg of
 
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) ->
 
374
                    Code = badop,
 
375
                    Text = "Illegal TFTP operation",
 
376
                    {undefined, Error} =
 
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)}})
 
380
            end;
 
381
        {error, #tftp_msg_error{code = Code, text = Text} = Error} ->
 
382
            send_msg(Config, Req, Error),
 
383
            terminate(Config, Req, {error, {transfer, Code, Text}})
 
384
    end.
 
385
 
 
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}})
 
397
    end;
 
398
common_read(Config, Callback, Req, _LocalAccess, ExpectedBlockNo, ActualBlockNo, _Prepared) ->
 
399
    Code = badblk,
 
400
    Text = "Unknown transfer ID = " ++ 
 
401
        integer_to_list(ActualBlockNo) ++ "(" ++ integer_to_list(ExpectedBlockNo) ++ ")", 
 
402
    {undefined, Error} =
 
403
        callback({abort, {Code, Text}}, Config, Callback, Req),
 
404
    send_msg(Config, Req, Error),
 
405
    terminate(Config, Req, {error, {read, Code, Text}}).
 
406
 
 
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).
 
413
 
 
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
 
418
        {Callback2, more} ->
 
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}})
 
426
    end;
 
427
common_write(Config, Callback, Req, _, ExpectedBlockNo, ActualBlockNo, _, _) ->
 
428
    Code = badblk,
 
429
    Text = "Unknown transfer ID = " ++ 
 
430
        integer_to_list(ActualBlockNo) ++ "(" ++ integer_to_list(ExpectedBlockNo) ++ ")", 
 
431
    {undefined, Error} =
 
432
        callback({abort, {Code, Text}}, Config, Callback, Req),
 
433
    send_msg(Config, Req, Error),
 
434
    terminate(Config, Req, {error, {write, Code, Text}}).
 
435
 
 
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).
 
442
 
 
443
pre_terminate(Config, Req, Result) ->
 
444
    if
 
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};
 
450
        true ->
 
451
            Config#config{polite_ack = true}
 
452
    end.
 
453
 
 
454
terminate(Config, Req, Result) ->
 
455
    if
 
456
        Config#config.parent_pid == undefined ->
 
457
            close_port(Config, client),
 
458
            exit(normal);
 
459
        Req#tftp_msg_req.local_filename /= undefined  ->
 
460
            %% Client
 
461
            close_port(Config, client),
 
462
            proc_lib:init_ack(Result),
 
463
            unlink(Config#config.parent_pid),
 
464
            exit(normal);
 
465
        true ->
 
466
            %% Server
 
467
            close_port(Config, server),
 
468
            case Result of
 
469
                {ok, _} ->
 
470
                    exit(shutdown);
 
471
                {error, Reason} ->
 
472
                    exit(Reason)
 
473
            end
 
474
    end.
 
475
 
 
476
close_port(Config, Who) ->
 
477
    case Config#config.udp_socket of
 
478
        undefined -> 
 
479
            ignore;
 
480
        Socket    -> 
 
481
            print_debug_info(Config, Who, close, undefined),
 
482
            gen_udp:close(Socket)
 
483
    end.
 
484
 
 
485
open_free_port(Config, Who) when record(Config, config) ->
 
486
    PortOptions = Config#config.udp_options,
 
487
    case Config#config.port_policy of
 
488
        random ->
 
489
            %% BUGBUG: Should be a random port
 
490
            case catch gen_udp:open(0, PortOptions) of
 
491
                {ok, Socket} ->
 
492
                    Config2 = Config#config{udp_socket = Socket},
 
493
                    print_debug_info(Config2, Who, open, undefined),
 
494
                    {ok, Config2};
 
495
                {error, Reason} ->
 
496
                    {error, Reason};
 
497
                {'EXIT', _} = Reason->
 
498
                    {error, Reason}
 
499
            end;
 
500
        {range, Port, Max} when Port =< Max ->
 
501
            case catch gen_udp:open(Port, PortOptions) of
 
502
                {ok, Socket} ->
 
503
                    Config2 = Config#config{udp_socket = Socket},
 
504
                    print_debug_info(Config2, Who, open, undefined),
 
505
                    {ok, Config2};
 
506
                {error, eaddrinuse} ->
 
507
                    PortPolicy = {range, Port + 1, Max},
 
508
                    Config2 = Config#config{port_policy = PortPolicy},
 
509
                    open_free_port(Config2, Who);
 
510
                {error, Reason} ->
 
511
                    {error, Reason};
 
512
                {'EXIT', _} = Reason->
 
513
                    {error, Reason}
 
514
            end;
 
515
        {range, Port, _Max} ->
 
516
            {error, {port_range_exhausted, Port}}
 
517
    end.
 
518
 
 
519
%%-------------------------------------------------------------------
 
520
%% Transfer
 
521
%%-------------------------------------------------------------------
 
522
 
 
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).
 
528
 
 
529
do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, Prepared, Retry) ->
 
530
    case do_send_msg(Config, Req, Msg, IoList) of
 
531
        ok ->
 
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 ->
 
539
                    Retry2 = false,
 
540
                    do_transfer(Config, Callback2, Req, Msg, IoList, LocalAccess, Prepared2, Retry2);
 
541
                timeout ->
 
542
                    Code = undef,
 
543
                    Text = "Transfer timed out.",
 
544
                    Error = #tftp_msg_error{code = Code, text = Text},
 
545
                    {Config, Callback, {error, Error}};
 
546
                {Config2, Reply} ->
 
547
                    {Config2, Callback2, {ok, Reply, Prepared2}}
 
548
            end;
 
549
        {error, _Reason} when Retry == true ->
 
550
            do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, Prepared, false);
 
551
        {error, _Reason} ->
 
552
            Code = undef,
 
553
            Text = "Internal error. gen_udp:send/4 failed",
 
554
            {Config, Callback, {error, #tftp_msg_error{code = Code, text = Text}}}
 
555
    end.
 
556
 
 
557
send_msg(Config, Req, Msg) ->
 
558
    case catch tftp_lib:encode_msg(Msg) of
 
559
        {'EXIT', Reason} ->
 
560
            Code = undef,
 
561
            Text = "Internal error. Encode failed",
 
562
            Msg2 = #tftp_msg_error{code = Code, text = Text, details = Reason},
 
563
            send_msg(Config, Req, Msg2);
 
564
        IoList ->
 
565
            do_send_msg(Config, Req, Msg, IoList)
 
566
    end.
 
567
 
 
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,
 
573
                 IoList).
 
574
 
 
575
wait_for_msg(Config, Callback, Req) ->
 
576
    receive
 
577
        {info, Pid} when pid(Pid) ->
 
578
            Type =
 
579
                case Req#tftp_msg_req.local_filename /= undefined of
 
580
                    true  -> client;
 
581
                    false -> server
 
582
                end,
 
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 ->
 
587
            %% Client prepare
 
588
            inet:setopts(Socket, [{active, once}]),
 
589
            Config2 = Config#config{udp_host = Host,
 
590
                                    udp_port = Port},
 
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 ->
 
605
            Code = undef,
 
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),
 
616
            timeout
 
617
    end.
 
618
 
 
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}.
 
624
 
 
625
%%-------------------------------------------------------------------
 
626
%% Callback
 
627
%%-------------------------------------------------------------------
 
628
 
 
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}),
 
633
    {Callback2, Result}.
 
634
 
 
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, 
 
645
                                          block_no = BlockNo,
 
646
                                          count    = Count},
 
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}};
 
652
        Details ->
 
653
            Code = undef,
 
654
            Text = "Internal error. File handler error.",
 
655
            callback({abort, {Code, Text, Details}}, Config, Callback, Req)
 
656
    end;
 
657
do_callback({write, Bin}, Config, Callback, Req)
 
658
  when record(Config, config),
 
659
       record(Callback, callback),
 
660
       record(Req, tftp_msg_req),
 
661
       binary(Bin) ->
 
662
    Args =  [Bin, Callback#callback.state],
 
663
    case catch apply(Callback#callback.module, write, Args) of
 
664
        {more, NewState} ->
 
665
            BlockNo = Callback#callback.block_no + 1,
 
666
            Count   = Callback#callback.count + size(Bin),
 
667
            Callback2 = Callback#callback{state    = NewState, 
 
668
                                          block_no = BlockNo,
 
669
                                          count    = Count}, 
 
670
            verify_count(Config, Callback2, Req, more);
 
671
        {last, Result} ->
 
672
            {undefined, {last, Result}};
 
673
        {error, {Code, Text}} ->
 
674
            {undefined, #tftp_msg_error{code = Code, text = Text}};
 
675
        Details ->
 
676
            Code = undef,
 
677
            Text = "Internal error. File handler error.",
 
678
            callback({abort, {Code, Text, Details}}, Config, Callback, Req)
 
679
    end;
 
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),
 
685
    {Oper, BlockNo} =
 
686
        case Type of
 
687
            client_prepare -> {prepare, undefined};
 
688
            client_open    -> {open, 0};
 
689
            server_open    -> {open, 0}
 
690
        end,
 
691
    Args = [Access,
 
692
            Filename,
 
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, 
 
699
                                          block_no = BlockNo, 
 
700
                                          count    = 0}, 
 
701
            {Callback2, {ok, AcceptedOptions}};
 
702
        {error, {Code, Text}} ->
 
703
            {undefined, #tftp_msg_error{code = Code, text = Text}};
 
704
        Details ->
 
705
            Code = undef,
 
706
            Text = "Internal error. File handler error.",
 
707
            callback({abort, {Code, Text, Details}}, Config, Callback, Req)
 
708
    end;
 
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),
 
721
    {undefined, Error};
 
722
do_callback({abort, Error}, _Config, undefined, _Req) when record(Error, tftp_msg_error) ->
 
723
    {undefined, Error}.
 
724
 
 
725
match_callback(Filename, Callbacks) ->
 
726
    if
 
727
        Filename == binary ->
 
728
            {ok, #callback{regexp   = "", 
 
729
                           internal = "", 
 
730
                           module   = tftp_binary,
 
731
                           state    = []}};
 
732
        binary(Filename) ->
 
733
            {ok, #callback{regexp   = "", 
 
734
                           internal = "", 
 
735
                           module   = tftp_binary, 
 
736
                           state    = []}};  
 
737
        Callbacks == []  ->
 
738
            {ok, #callback{regexp   = "", 
 
739
                           internal = "",
 
740
                           module   = tftp_file, 
 
741
                           state    = []}};
 
742
        true ->
 
743
            do_match_callback(Filename, Callbacks)
 
744
    end.
 
745
 
 
746
do_match_callback(Filename, [C | Tail]) when record(C, callback) ->
 
747
    case catch regexp:match(Filename, C#callback.internal) of
 
748
        {match, _, _} ->
 
749
            {ok, C};
 
750
        nomatch ->
 
751
            do_match_callback(Filename, Tail);
 
752
        Details ->
 
753
            Code = baduser,
 
754
            Text = "Internal error. File handler not found",
 
755
            {error, #tftp_msg_error{code = Code, text = Text, details = Details}}
 
756
    end;
 
757
do_match_callback(Filename, []) ->
 
758
    Code = baduser,
 
759
    Text = "Internal error. File handler not found",
 
760
    {error, #tftp_msg_error{code = Code, text = Text, details = Filename}}.
 
761
 
 
762
verify_count(Config, Callback, Req, Result) ->
 
763
    case Config#config.max_tsize of
 
764
        infinity ->
 
765
            {Callback, Result};
 
766
        Max when Callback#callback.count =< Max ->
 
767
            {Callback, Result};
 
768
        _Max ->
 
769
            Code = enospc,
 
770
            Text = "Too large file.",
 
771
            callback({abort, {Code, Text}}, Config, Callback, Req)
 
772
    end.
 
773
 
 
774
%%-------------------------------------------------------------------
 
775
%% Miscellaneous
 
776
%%-------------------------------------------------------------------
 
777
 
 
778
internal_info(Config, Type) ->
 
779
    {ok, ActualPort} = inet:port(Config#config.udp_socket),
 
780
    [
 
781
     {type, Type},
 
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.
 
796
 
 
797
local_file_access(#tftp_msg_req{access = Access, 
 
798
                                local_filename = Local, 
 
799
                                filename = Filename}) ->
 
800
    case Local == undefined of
 
801
        true ->
 
802
            %% Server side
 
803
            {Access, Filename};
 
804
        false ->
 
805
            %% Client side
 
806
            case Access of
 
807
                read ->
 
808
                    {write, Local};
 
809
                write ->
 
810
                    {read, Local}
 
811
            end
 
812
    end.
 
813
 
 
814
pre_verify_options(Config, Req) ->
 
815
    Options = Req#tftp_msg_req.options,
 
816
    case catch verify_reject(Config, Req, Options) of
 
817
        ok ->
 
818
            case verify_integer("tsize", 0, Config#config.max_tsize, Options) of
 
819
                true ->
 
820
                    case verify_integer("blksize", 0, 65464, Options) of
 
821
                        true ->
 
822
                            ok;
 
823
                        false ->
 
824
                            {error, {badopt, "Too large blksize"}}
 
825
                    end;
 
826
                false ->
 
827
                    {error, {badopt, "Too large tsize"}}
 
828
            end;
 
829
        {error, Reason} ->
 
830
            {error, Reason}
 
831
    end.
 
832
    
 
833
post_verify_options(Config, Req, NewOptions, Text) ->
 
834
    OldOptions = Req#tftp_msg_req.options,
 
835
    BadOptions  = 
 
836
        [Key || {Key, _Val} <- NewOptions, 
 
837
                not lists:keymember(Key, 1, OldOptions)],
 
838
    case BadOptions == [] of
 
839
        true ->
 
840
            {ok,
 
841
             Config#config{timeout = lookup_timeout(NewOptions)},
 
842
             Req#tftp_msg_req{options = NewOptions}};
 
843
        false ->
 
844
            {error, {badopt, Text}}
 
845
    end.
 
846
 
 
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
 
851
        true ->
 
852
            {error, {eacces, atom_to_list(Access) ++ " mode not allowed"}};
 
853
        false ->
 
854
            [throw({error, {badopt, Key ++ " not allowed"}}) ||
 
855
                {Key, _} <- Options, lists:member(Key, Rejected)],
 
856
            ok
 
857
    end.
 
858
 
 
859
lookup_timeout(Options) ->
 
860
    case lists:keysearch("timeout", 1, Options) of
 
861
        {value, {_, Val}} ->
 
862
            list_to_integer(Val);
 
863
        false ->
 
864
            3
 
865
    end.
 
866
 
 
867
lookup_mode(Options) ->
 
868
    case lists:keysearch("mode", 1, Options) of
 
869
        {value, {_, Val}} ->
 
870
            Val;
 
871
        false ->
 
872
            "octet"
 
873
    end.
 
874
 
 
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
 
879
                {'EXIT', _} ->
 
880
                    false;
 
881
                Int when Int >= Min, integer(Min),
 
882
                         Max == infinity ->
 
883
                    true;
 
884
                Int when Int >= Min, integer(Min),
 
885
                         Int =< Max, integer(Max) ->
 
886
                    true;
 
887
                _ ->
 
888
                    false
 
889
            end;
 
890
        false ->
 
891
            true
 
892
    end.
 
893
error(F, A) ->
 
894
    ok = error_logger:format("~p(~p): " ++ F ++ "~n", [?MODULE, self() | A]).
 
895
 
 
896
print_debug_info(#config{debug_level = Level} = Config, Who, What, Data) ->
 
897
    if
 
898
        Level == none ->
 
899
            ok;
 
900
        Level == all ->
 
901
            do_print_debug_info(Config, Who, What, Data);
 
902
        What == open ->
 
903
            do_print_debug_info(Config, Who, What, Data);
 
904
        What == close ->
 
905
            do_print_debug_info(Config, Who, What, Data);
 
906
        Level == brief ->
 
907
            ok; 
 
908
        What /= recv, What /= send ->
 
909
            ok;
 
910
        record(Data, tftp_msg_data), Level == normal ->
 
911
            ok;  
 
912
        record(Data, tftp_msg_ack), Level == normal ->
 
913
            ok;
 
914
        true ->
 
915
            do_print_debug_info(Config, Who, What, Data)
 
916
    end.
 
917
 
 
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,
 
927
    Side = 
 
928
        if
 
929
            record(Who, tftp_msg_req),
 
930
            Who#tftp_msg_req.local_filename /= undefined ->
 
931
                client;
 
932
            record(Who, tftp_msg_req),
 
933
            Who#tftp_msg_req.local_filename == undefined ->
 
934
                server;
 
935
            atom(Who) ->
 
936
                Who
 
937
        end,
 
938
    case What of
 
939
        open ->
 
940
            io:format("~p(~p): open ~p ->  ~p\n", [Side, Local, self(), Config#config.udp_host]);
 
941
        close ->
 
942
            io:format("~p(~p): close ~p ->  ~p\n", [Side, Local, self(), Config#config.udp_host]);
 
943
        recv ->
 
944
            io:format("~p(~p): recv  ~p <- ~p\n", [Side, Local, Remote, Data]);
 
945
        send ->
 
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]);
 
951
        call ->
 
952
            case Data of
 
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])
 
959
            end
 
960
    end.
 
961
 
 
962
 
 
963
%%-------------------------------------------------------------------
 
964
%% System upgrade
 
965
%%-------------------------------------------------------------------
 
966
 
 
967
system_continue(_Parent, _Debug, {Fun, Args}) ->
 
968
    apply(?MODULE, Fun, Args).
 
969
 
 
970
system_terminate(Reason, _Parent, _Debug, {_Fun, _Args}) ->
 
971
    exit(Reason).
 
972
 
 
973
system_code_change({Fun, Args}, _Module, _OldVsn, _Extra) ->
 
974
    {ok, {Fun, Args}}.