aboutsummaryrefslogtreecommitdiffstats
path: root/lib/inets/src/tftp
diff options
context:
space:
mode:
authorPéter Dimitrov <[email protected]>2018-03-20 11:26:01 +0100
committerPéter Dimitrov <[email protected]>2018-03-28 10:19:38 +0200
commit09ccfa2a6a8f8df55c7d808f5ad26324ac1e81b6 (patch)
treedb6e9a1172ee8bb761041e352b6dc637fd765d28 /lib/inets/src/tftp
parent3c41882115f2cd9bfda8318925b4352cd1ec06b7 (diff)
downloadotp-09ccfa2a6a8f8df55c7d808f5ad26324ac1e81b6.tar.gz
otp-09ccfa2a6a8f8df55c7d808f5ad26324ac1e81b6.tar.bz2
otp-09ccfa2a6a8f8df55c7d808f5ad26324ac1e81b6.zip
inets,tftp: Break out TFTP from inets
- Create directory structure - Move code, tests, documentation from inets - Add inets_tftp_wrapper - Add tftp app to run-dialyzer script Change-Id: I6a142ae66cecb9a1821cbf9ea6a45f66a836763d
Diffstat (limited to 'lib/inets/src/tftp')
-rw-r--r--lib/inets/src/tftp/Makefile109
-rw-r--r--lib/inets/src/tftp/tftp.erl398
-rw-r--r--lib/inets/src/tftp/tftp.hrl69
-rw-r--r--lib/inets/src/tftp/tftp_binary.erl239
-rw-r--r--lib/inets/src/tftp/tftp_engine.erl1422
-rw-r--r--lib/inets/src/tftp/tftp_file.erl390
-rw-r--r--lib/inets/src/tftp/tftp_lib.erl474
-rw-r--r--lib/inets/src/tftp/tftp_logger.erl99
-rw-r--r--lib/inets/src/tftp/tftp_sup.erl111
9 files changed, 0 insertions, 3311 deletions
diff --git a/lib/inets/src/tftp/Makefile b/lib/inets/src/tftp/Makefile
deleted file mode 100644
index 4eaa959cce..0000000000
--- a/lib/inets/src/tftp/Makefile
+++ /dev/null
@@ -1,109 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2016. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# %CopyrightEnd%
-#
-#
-
-include $(ERL_TOP)/make/target.mk
-EBIN = ../../ebin
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-
-# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-include ../../vsn.mk
-
-VSN = $(INETS_VSN)
-
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
-
-
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-BEHAVIOUR_MODULES= \
- tftp
-
-MODULES = \
- tftp_binary \
- tftp_engine \
- tftp_file \
- tftp_lib \
- tftp_logger \
- tftp_sup
-
-HRL_FILES = tftp.hrl
-
-ERL_FILES= \
- $(MODULES:%=%.erl) \
- $(BEHAVIOUR_MODULES:%=%.erl)
-
-TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-
-BEHAVIOUR_TARGET_FILES= $(BEHAVIOUR_MODULES:%=$(EBIN)/%.$(EMULATOR))
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-
-include ../inets_app/inets.mk
-
-ERL_COMPILE_FLAGS += \
- $(INETS_FLAGS) \
- $(INETS_ERL_COMPILE_FLAGS) \
- -I../../include \
- -I../inets_app
-
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-$(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES)
-
-debug opt: $(TARGET_FILES)
-
-clean:
- rm -f $(TARGET_FILES) $(BEHAVIOUR_TARGET_FILES)
- rm -f core
-
-docs:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_spec: opt
- $(INSTALL_DIR) "$(RELSYSDIR)/src"
- $(INSTALL_DIR) "$(RELSYSDIR)/src/tftp"
- $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) "$(RELSYSDIR)/src/tftp"
- $(INSTALL_DIR) "$(RELSYSDIR)/ebin"
- $(INSTALL_DATA) $(TARGET_FILES) $(BEHAVIOUR_TARGET_FILES) "$(RELSYSDIR)/ebin"
-
-release_docs_spec:
-
-info:
- @echo "APPLICATION = $(APPLICATION)"
- @echo "INETS_DEBUG = $(INETS_DEBUG)"
- @echo "INETS_FLAGS = $(INETS_FLAGS)"
- @echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)"
diff --git a/lib/inets/src/tftp/tftp.erl b/lib/inets/src/tftp/tftp.erl
deleted file mode 100644
index c8804ea55f..0000000000
--- a/lib/inets/src/tftp/tftp.erl
+++ /dev/null
@@ -1,398 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
-
-%%%-------------------------------------------------------------------
-%%% File : tftp.erl
-%%% Author : Hakan Mattsson <[email protected]>
-%%% Description : Trivial FTP
-%%% Created : 18 May 2004 by Hakan Mattsson <[email protected]>
-%%%-------------------------------------------------------------------
-%%%
-%%% This is a complete implementation of the following IETF standards:
-%%%
-%%% RFC 1350, The TFTP Protocol (revision 2).
-%%% RFC 2347, TFTP Option Extension.
-%%% RFC 2348, TFTP Blocksize Option.
-%%% RFC 2349, TFTP Timeout Interval and Transfer Size Options.
-%%%
-%%% The only feature that not is implemented in this release is
-%%% the "netascii" transfer mode.
-%%%
-%%% The start/1 function starts a daemon process which, listens for
-%%% UDP packets on a port. When it receives a request for read or
-%%% write it spawns a temporary server process which handles the
-%%% actual transfer of the file. On the client side the read_file/3
-%%% and write_file/3 functions spawns a temporary client process which
-%%% establishes contact with a TFTP daemon and performs the actual
-%%% transfer of the file.
-%%%
-%%% Most of the options are common for both the client and the server
-%%% side, but some of them differs a little. Here are the available
-%%% options:
-%%%
-%%% {debug, Level}
-%%%
-%%% Level = none | error | warning brief | normal | verbose | all
-%%%
-%%% Controls the level of debug printouts. The default is none.
-%%%
-%%% {host, Host}
-%%%
-%%% The name or IP address of the host where the TFTP daemon
-%%% resides. This option is only used by the client. See
-%%% 'inet' about valid host names.
-%%%
-%%% {port, Port}
-%%%
-%%% Port = integer()
-%%%
-%%% The TFTP port where the daemon listens. It defaults to the
-%%% standardized number 69. On the server side it may sometimes
-%%% make sense to set it to 0, which means that the daemon just
-%%% will pick a free port (which is returned by the start/1
-%%% function).
-%%%
-%%% If a socket has somehow already has been connected, the
-%%% {udp, [{fd, integer()}]} option can be used to pass the
-%%% open file descriptor to gen_udp. This can be automated
-%%% a bit by using a command line argument stating the
-%%% prebound file descriptor number. For example, if the
-%%% Port is 69 and the file descriptor 22 has been opened by
-%%% setuid_socket_wrap. Then the command line argument
-%%% "-tftpd_69 22" will trigger the prebound file
-%%% descriptor 22 to be used instead of opening port 69.
-%%% The UDP option {udp, [{fd, 22}]} autmatically be added.
-%%% See init:get_argument/ about command line arguments and
-%%% gen_udp:open/2 about UDP options.
-%%%
-%%% {port_policy, Policy}
-%%%
-%%% Policy = random | Port | {range, MinPort, MaxPort}
-%%% Port = MinPort = MaxPort = integer()
-%%%
-%%% Policy for the selection of the temporary port which is used
-%%% by the server/client during the file transfer. It defaults to
-%%% 'random' which is the standardized policy. With this policy a
-%%% randomized free port used. A single port or a range of ports
-%%% can be useful if the protocol should pass thru a firewall.
-%%%
-%%% {prebound_fd, InitArgFlag}
-%%%
-%%% InitArgFlag = atom()
-%%%
-%%% If a socket has somehow already has been connected, the
-%%% {udp, [{fd, integer()}]} option can be used to pass the
-%%% open file descriptor to gen_udp.
-%%%
-%%% The prebound_fd option makes it possible to pass give the
-%%% file descriptor as a command line argument. The typical
-%%% usage is when used in conjunction with setuid_socket_wrap
-%%% to be able to open privileged sockets. For example if the
-%%% file descriptor 22 has been opened by setuid_socket_wrap
-%%% and you have choosen my_tftp_fd as init argument, the
-%%% command line should like this "erl -my_tftp_fd 22" and
-%%% FileDesc should be set to my_tftpd_fd. This would
-%%% automatically imply {fd, 22} to be set as UDP option.
-%%%
-%%% {udp, UdpOptions}
-%%%
-%%% Options to gen_udp:open/2.
-%%%
-%%% {use_tsize, Bool}
-%%%
-%%% Bool = boolean()
-%%%
-%%% Flag for automated usage of the "tsize" option. With this set
-%%% to true, the write_file/3 client will determine the filesize
-%%% and send it to the server as the standardized "tsize" option.
-%%% A read_file/3 client will just acquire filesize from the
-%%% server by sending a zero "tsize".
-%%%
-%%% {max_tsize, MaxTsize}
-%%%
-%%% MaxTsize = integer() | infinity
-%%%
-%%% Threshold for the maximal filesize in bytes. The transfer will
-%%% be aborted if the limit is exceeded. It defaults to
-%%% 'infinity'.
-%%%
-%%% {max_conn, MaxConn}
-%%%
-%%% MaxConn = integer() | infinity
-%%%
-%%% Threshold for the maximal number of active connections. The
-%%% daemon will reject the setup of new connections if the limit
-%%% is exceeded. It defaults to 'infinity'.
-%%%
-%%% {TftpKey, TftpVal}
-%%%
-%%% TftpKey = string()
-%%% TftpVal = string()
-%%%
-%%% The name and value of a TFTP option.
-%%%
-%%% {reject, Feature}
-%%%
-%%% Feature = Mode | TftpKey
-%%% Mode = read | write
-%%% TftpKey = string()
-%%%
-%%% Control which features that should be rejected.
-%%% This is mostly useful for the server as it may restrict
-%%% usage of certain TFTP options or read/write access.
-%%%
-%%% {callback, {RegExp, Module, State}}
-%%%
-%%% RegExp = string()
-%%% Module = atom()
-%%% State = term()
-%%%
-%%% Registration of a callback module. When a file is to be
-%%% transferred, its local filename will be matched to the
-%%% regular expressions of the registered callbacks. The first
-%%% matching callback will be used the during the transfer.The
-%%% callback module must implement the 'tftp' behaviour.
-%%%
-%%% On the server side the callback interaction starts with a
-%%% call to open/5 with the registered initial callback
-%%% state. open/5 is expected to open the (virtual) file. Then
-%%% either the read/1 or write/2 functions are invoked
-%%% repeatedly, once per transfererred block. At each function
-%%% call the state returned from the previous call is
-%%% obtained. When the last block has been encountered the read/1
-%%% or write/2 functions is expected to close the (virtual)
-%%% file.and return its last state. The abort/3 function is only
-%%% used in error situations. prepare/5 is not used on the server
-%%% side.
-%%%
-%%% On the client side the callback interaction is the same, but
-%%% it starts and ends a bit differently. It starts with a call
-%%% to prepare/5 with the same arguments as open/5
-%%% takes. prepare/5 is expected to validate the TFTP options,
-%%% suggested by the user and return the subset of them that it
-%%% accepts. Then the options is sent to the server which will
-%%% perform the same TFTP option negotiation procedure. The
-%%% options that are accepted by the server is forwarded to the
-%%% open/5 function on the client side. On the client side the
-%%% open/5 function must accept all option as is or reject the
-%%% transfer. Then the callback interaction follows the same
-%%% pattern as described above for the server side. When the last
-%%% block is encountered in read/1 or write/2 the returned stated
-%%% is forwarded to the user and returned from read_file/3 or
-%%% write_file/3.
-%%%-------------------------------------------------------------------
-
--module(tftp).
-
-%%-------------------------------------------------------------------
-%% Interface
-%%-------------------------------------------------------------------
-
-%% Public functions
--export([
- read_file/3,
- write_file/3,
- start/1,
- info/1,
- change_config/2,
- start/0
- ]).
-
-%% Application local functions
--export([
- start_standalone/1,
- start_service/1,
- stop_service/1,
- services/0,
- service_info/1
- ]).
-
-
--type peer() :: {PeerType :: inet | inet6,
- PeerHost :: inet:ip_address(),
- PeerPort :: port()}.
-
--type access() :: read | write.
-
--type options() :: [{Key :: string(), Value :: string()}].
-
--type error_code() :: undef | enoent | eacces | enospc |
- badop | eexist | baduser | badopt |
- integer().
-
--callback prepare(Peer :: peer(),
- Access :: access(),
- Filename :: file:name(),
- Mode :: string(),
- SuggestedOptions :: options(),
- InitialState :: [] | [{root_dir, string()}]) ->
- {ok, AcceptedOptions :: options(), NewState :: term()} |
- {error, {Code :: error_code(), string()}}.
-
--callback open(Peer :: peer(),
- Access :: access(),
- Filename :: file:name(),
- Mode :: string(),
- SuggestedOptions :: options(),
- State :: [] | [{root_dir, string()}] | term()) ->
- {ok, AcceptedOptions :: options(), NewState :: term()} |
- {error, {Code :: error_code(), string()}}.
-
--callback read(State :: term()) -> {more, binary(), NewState :: term()} |
- {last, binary(), integer()} |
- {error, {Code :: error_code(), string()}}.
-
--callback write(binary(), State :: term()) ->
- {more, NewState :: term()} |
- {last, FileSize :: integer()} |
- {error, {Code :: error_code(), string()}}.
-
--callback abort(Code :: error_code(), string(), State :: term()) -> 'ok'.
-
--include("tftp.hrl").
-
-
-%%-------------------------------------------------------------------
-%% read_file(RemoteFilename, LocalFilename, Options) ->
-%% {ok, LastCallbackState} | {error, Reason}
-%%
-%% RemoteFilename = string()
-%% LocalFilename = binary | string()
-%% Options = [option()]
-%% LastCallbackState = term()
-%% Reason = term()
-%%
-%% Reads a (virtual) file from a TFTP server
-%%
-%% If LocalFilename is the atom 'binary', tftp_binary will be used as
-%% callback module. It will concatenate all transferred blocks and
-%% return them as one single binary in the CallbackState.
-%%
-%% When LocalFilename is a string, it will be matched to the
-%% registered callback modules and hopefully one of them will be
-%% selected. By default, tftp_file will be used as callback module. It
-%% will write each transferred block to the file named
-%% LocalFilename. The number of transferred bytes will be returned as
-%% LastCallbackState.
-%%-------------------------------------------------------------------
-
-read_file(RemoteFilename, LocalFilename, Options) ->
- tftp_engine:client_start(read, RemoteFilename, LocalFilename, Options).
-
-%%-------------------------------------------------------------------
-%% write(RemoteFilename, LocalFilename, Options) ->
-%% {ok, LastCallbackState} | {error, Reason}
-%%
-%% RemoteFilename = string()
-%% LocalFilename = binary() | string()
-%% Options = [option()]
-%% LastCallbackState = term()
-%% Reason = term()
-%%
-%% Writes a (virtual) file to a TFTP server
-%%
-%% If LocalFilename is a binary, tftp_binary will be used as callback
-%% module. The binary will be transferred block by block and the number
-%% of transferred bytes will be returned as LastCallbackState.
-%%
-%% When LocalFilename is a string, it will be matched to the
-%% registered callback modules and hopefully one of them will be
-%% selected. By default, tftp_file will be used as callback module. It
-%% will read the file named LocalFilename block by block. The number
-%% of transferred bytes will be returned as LastCallbackState.
-%%-------------------------------------------------------------------
-
-write_file(RemoteFilename, LocalFilename, Options) ->
- tftp_engine:client_start(write, RemoteFilename, LocalFilename, Options).
-
-%%-------------------------------------------------------------------
-%% start(Options) -> {ok, Pid} | {error, Reason}
-%%
-%% Options = [option()]
-%% Pid = pid()
-%% Reason = term()
-%%
-%% Starts a daemon process which listens for udp packets on a
-%% port. When it receives a request for read or write it spawns
-%% a temporary server process which handles the actual transfer
-%% of the (virtual) file.
-%%-------------------------------------------------------------------
-
-start(Options) ->
- tftp_engine:daemon_start(Options).
-
-%%-------------------------------------------------------------------
-%% info(Pid) -> {ok, Options} | {error, Reason}
-%%
-%% Options = [option()]
-%% Reason = term()
-%%
-%% Returns info about a tftp daemon, server or client process
-%%-------------------------------------------------------------------
-
-info(Pid) ->
- tftp_engine:info(Pid).
-
-%%-------------------------------------------------------------------
-%% change_config(Pid, Options) -> ok | {error, Reason}
-%%
-%% Options = [option()]
-%% Reason = term()
-%%
-%% Changes config for a tftp daemon, server or client process
-%% Must be used with care.
-%%-------------------------------------------------------------------
-
-change_config(Pid, Options) ->
- tftp_engine:change_config(Pid, Options).
-
-%%-------------------------------------------------------------------
-%% start() -> ok | {error, Reason}
-%%
-%% Reason = term()
-%%
-%% Start the application
-%%-------------------------------------------------------------------
-
-start() ->
- application:start(inets).
-
-%%-------------------------------------------------------------------
-%% Inets service behavior
-%%-------------------------------------------------------------------
-
-start_standalone(Options) ->
- start(Options).
-
-start_service(Options) ->
- tftp_sup:start_child(Options).
-
-stop_service(Pid) ->
- tftp_sup:stop_child(Pid).
-
-services() ->
- tftp_sup:which_children().
-
-service_info(Pid) ->
- info(Pid).
-
-
-
diff --git a/lib/inets/src/tftp/tftp.hrl b/lib/inets/src/tftp/tftp.hrl
deleted file mode 100644
index 25543e0b9e..0000000000
--- a/lib/inets/src/tftp/tftp.hrl
+++ /dev/null
@@ -1,69 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
-
-%%%-------------------------------------------------------------------
-%%% Defines
-%%%-------------------------------------------------------------------
-
--define(TFTP_DEFAULT_PORT, 69).% Default server port
-
--define(TFTP_OPCODE_RRQ, 1). % Read request
--define(TFTP_OPCODE_WRQ, 2). % Write request
--define(TFTP_OPCODE_DATA, 3). % Data
--define(TFTP_OPCODE_ACK, 4). % Acknowledgement
--define(TFTP_OPCODE_ERROR, 5). % Error
--define(TFTP_OPCODE_OACK, 6). % Option acknowledgment
-
--define(TFTP_ERROR_UNDEF, 0). % Not defined, see error message (if any)
--define(TFTP_ERROR_ENOENT, 1). % File not found.
--define(TFTP_ERROR_EACCES, 2). % Access violation.
--define(TFTP_ERROR_ENOSPC, 3). % Disk full or allocation exceeded.
--define(TFTP_ERROR_BADOP, 4). % Illegal TFTP operation.
--define(TFTP_ERROR_BADBLK, 5). % Unknown transfer ID.
--define(TFTP_ERROR_EEXIST, 6). % File already exists.
--define(TFTP_ERROR_BADUSER, 7). % No such user.
--define(TFTP_ERROR_BADOPT, 8). % Unrequested or illegal option.
-
--record(tftp_msg_req, {access, filename, mode, options, local_filename}).
--record(tftp_msg_data, {block_no, data}).
--record(tftp_msg_ack, {block_no}).
--record(tftp_msg_error, {code, text, details}).
--record(tftp_msg_oack, {options}).
-
--record(config, {parent_pid = self(),
- udp_socket,
- udp_options = [binary, {reuseaddr, true}, {active, once}],
- udp_host = "localhost",
- udp_port = ?TFTP_DEFAULT_PORT,
- port_policy = random,
- use_tsize = false,
- max_tsize = infinity, % Filesize
- max_conn = infinity,
- rejected = [],
- polite_ack = false,
- debug_level = none,
- timeout,
- user_options = [],
- callbacks = [],
- logger = tftp_logger,
- max_retries = 5}).
-
--record(callback, {regexp, internal, module, state, block_no, count}).
diff --git a/lib/inets/src/tftp/tftp_binary.erl b/lib/inets/src/tftp/tftp_binary.erl
deleted file mode 100644
index 09adcfc41f..0000000000
--- a/lib/inets/src/tftp/tftp_binary.erl
+++ /dev/null
@@ -1,239 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
-
-%%%-------------------------------------------------------------------
-%%% File : tft_binary.erl
-%%% Author : Hakan Mattsson <[email protected]>
-%%% Description :
-%%%
-%%% Created : 24 May 2004 by Hakan Mattsson <[email protected]>
-%%%-------------------------------------------------------------------
-
--module(tftp_binary).
-
-%%%-------------------------------------------------------------------
-%%% Interface
-%%%-------------------------------------------------------------------
-
--behaviour(tftp).
-
--export([prepare/6, open/6, read/1, write/2, abort/3]).
-
--record(read_state, {options, blksize, bin, is_native_ascii, is_network_ascii, count}).
--record(write_state, {options, blksize, list, is_native_ascii, is_network_ascii}).
-
-%%-------------------------------------------------------------------
-%% Prepare
-%%-------------------------------------------------------------------
-
-prepare(_Peer, Access, Filename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
- %% Client side
- IsNativeAscii = is_native_ascii(Initial),
- case catch handle_options(Access, Filename, Mode, SuggestedOptions, IsNativeAscii) of
- {ok, IsNetworkAscii, AcceptedOptions} when Access =:= read, is_binary(Filename) ->
- State = #read_state{options = AcceptedOptions,
- blksize = lookup_blksize(AcceptedOptions),
- bin = Filename,
- is_network_ascii = IsNetworkAscii,
- count = size(Filename),
- is_native_ascii = IsNativeAscii},
- {ok, AcceptedOptions, State};
- {ok, IsNetworkAscii, AcceptedOptions} when Access =:= write, Filename =:= binary ->
- State = #write_state{options = AcceptedOptions,
- blksize = lookup_blksize(AcceptedOptions),
- list = [],
- is_network_ascii = IsNetworkAscii,
- is_native_ascii = IsNativeAscii},
- {ok, AcceptedOptions, State};
- {ok, _, _} ->
- {error, {undef, "Illegal callback usage. Mode and filename is incompatible."}};
- {error, {Code, Text}} ->
- {error, {Code, Text}}
- end;
-prepare(_Peer, _Access, _Bin, _Mode, _SuggestedOptions, _Initial) ->
- {error, {undef, "Illegal callback options."}}.
-
-%%-------------------------------------------------------------------
-%% Open
-%%-------------------------------------------------------------------
-
-open(Peer, Access, Filename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
- %% Server side
- case prepare(Peer, Access, Filename, Mode, SuggestedOptions, Initial) of
- {ok, AcceptedOptions, State} ->
- open(Peer, Access, Filename, Mode, AcceptedOptions, State);
- {error, {Code, Text}} ->
- {error, {Code, Text}}
- end;
-open(_Peer, Access, Filename, Mode, NegotiatedOptions, State) when is_record(State, read_state) ->
- %% Both sides
- case catch handle_options(Access, Filename, Mode, NegotiatedOptions, State#read_state.is_native_ascii) of
- {ok, IsNetworkAscii, Options}
- when Options =:= NegotiatedOptions,
- IsNetworkAscii =:= State#read_state.is_network_ascii ->
- {ok, NegotiatedOptions, State};
- {error, {Code, Text}} ->
- {error, {Code, Text}}
- end;
-open(_Peer, Access, Filename, Mode, NegotiatedOptions, State) when is_record(State, write_state) ->
- %% Both sides
- case catch handle_options(Access, Filename, Mode, NegotiatedOptions, State#write_state.is_native_ascii) of
- {ok, IsNetworkAscii, Options}
- when Options =:= NegotiatedOptions,
- IsNetworkAscii =:= State#write_state.is_network_ascii ->
- {ok, NegotiatedOptions, State};
- {error, {Code, Text}} ->
- {error, {Code, Text}}
- end;
-open(Peer, Access, Filename, Mode, NegotiatedOptions, State) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- State2 = upgrade_state(State),
- open(Peer, Access, Filename, Mode, NegotiatedOptions, State2).
-
-%%-------------------------------------------------------------------
-%% Read
-%%-------------------------------------------------------------------
-
-read(#read_state{bin = Bin} = State) when is_binary(Bin) ->
- BlkSize = State#read_state.blksize,
- if
- size(Bin) >= BlkSize ->
- <<Block:BlkSize/binary, Bin2/binary>> = Bin,
- State2 = State#read_state{bin = Bin2},
- {more, Block, State2};
- size(Bin) < BlkSize ->
- {last, Bin, State#read_state.count}
- end;
-read(State) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- State2 = upgrade_state(State),
- read(State2).
-
-%%-------------------------------------------------------------------
-%% Write
-%%-------------------------------------------------------------------
-
-write(Bin, #write_state{list = List} = State) when is_binary(Bin), is_list(List) ->
- Size = size(Bin),
- BlkSize = State#write_state.blksize,
- if
- Size =:= BlkSize ->
- {more, State#write_state{list = [Bin | List]}};
- Size < BlkSize ->
- Bin2 = list_to_binary(lists:reverse([Bin | List])),
- {last, Bin2}
- end;
-write(Bin, State) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- State2 = upgrade_state(State),
- write(Bin, State2).
-
-%%-------------------------------------------------------------------
-%% Abort
-%%-------------------------------------------------------------------
-
-abort(_Code, _Text, #read_state{bin = Bin} = State)
- when is_record(State, read_state), is_binary(Bin) ->
- ok;
-abort(_Code, _Text, #write_state{list = List} = State)
- when is_record(State, write_state), is_list(List) ->
- ok;
-abort(Code, Text, State) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- State2 = upgrade_state(State),
- abort(Code, Text, State2).
-
-%%-------------------------------------------------------------------
-%% Process options
-%%-------------------------------------------------------------------
-
-handle_options(Access, Bin, Mode, Options, IsNativeAscii) ->
- IsNetworkAscii = handle_mode(Mode, IsNativeAscii),
- Options2 = do_handle_options(Access, Bin, Options),
- {ok, IsNetworkAscii, Options2}.
-
-handle_mode(Mode, IsNativeAscii) ->
- case Mode of
- "netascii" when IsNativeAscii =:= true -> true;
- "octet" -> false;
- _ -> throw({error, {badop, "Illegal mode " ++ Mode}})
- end.
-
-do_handle_options(Access, Bin, [{Key, Val} | T]) ->
- case Key of
- "tsize" ->
- case Access of
- read when Val =:= "0", is_binary(Bin) ->
- Tsize = integer_to_list(size(Bin)),
- [{Key, Tsize} | do_handle_options(Access, Bin, T)];
- _ ->
- handle_integer(Access, Bin, Key, Val, T, 0, infinity)
- end;
- "blksize" ->
- handle_integer(Access, Bin, Key, Val, T, 8, 65464);
- "timeout" ->
- handle_integer(Access, Bin, Key, Val, T, 1, 255);
- _ ->
- do_handle_options(Access, Bin, T)
- end;
-do_handle_options(_Access, _Bin, []) ->
- [].
-
-
-handle_integer(Access, Bin, Key, Val, Options, Min, Max) ->
- case catch list_to_integer(Val) of
- {'EXIT', _} ->
- do_handle_options(Access, Bin, Options);
- Int when Int >= Min, Int =< Max ->
- [{Key, Val} | do_handle_options(Access, Bin, Options)];
- Int when Int >= Min, Max =:= infinity ->
- [{Key, Val} | do_handle_options(Access, Bin, Options)];
- _Int ->
- throw({error, {badopt, "Illegal " ++ Key ++ " value " ++ Val}})
- end.
-
-lookup_blksize(Options) ->
- case lists:keysearch("blksize", 1, Options) of
- {value, {_, Val}} ->
- list_to_integer(Val);
- false ->
- 512
- end.
-
-is_native_ascii([]) ->
- is_native_ascii();
-is_native_ascii([{native_ascii, Bool}]) ->
- case Bool of
- true -> true;
- false -> false
- end.
-
-is_native_ascii() ->
- case os:type() of
- {win32, _} -> true;
- _ -> false
- end.
-
-%% Handle upgrade from old releases. Please, remove this function in next release.
-upgrade_state({read_state, Options, Blksize, Bin, IsNetworkAscii, Count}) ->
- {read_state, Options, Blksize, Bin, false, IsNetworkAscii, Count};
-upgrade_state({write_state, Options, Blksize, List, IsNetworkAscii}) ->
- {write_state, Options, Blksize, List, false, IsNetworkAscii}.
diff --git a/lib/inets/src/tftp/tftp_engine.erl b/lib/inets/src/tftp/tftp_engine.erl
deleted file mode 100644
index fb2c9749e5..0000000000
--- a/lib/inets/src/tftp/tftp_engine.erl
+++ /dev/null
@@ -1,1422 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%-------------------------------------------------------------------
-%% Protocol engine for trivial FTP
-%%-------------------------------------------------------------------
-
--module(tftp_engine).
-
-%%%-------------------------------------------------------------------
-%%% Interface
-%%%-------------------------------------------------------------------
-
-%% application internal functions
--export([
- daemon_start/1,
- daemon_loop/1,
- daemon_loop/3, %% Handle upgrade from old releases. Please, remove this function in next release.
- client_start/4,
- common_loop/6,
- info/1,
- change_config/2
- ]).
-
-%% module internal
--export([
- daemon_init/1,
- server_init/2,
- client_init/2,
- wait_for_msg/3,
- callback/4
- ]).
-
-%% sys callback functions
--export([
- system_continue/3,
- system_terminate/4,
- system_code_change/4
- ]).
-
--include("tftp.hrl").
-
--type prep_status() :: 'error' | 'last' | 'more' | 'terminate'.
-
--record(daemon_state, {config, n_servers, server_tab, file_tab}).
--record(server_info, {pid, req, peer}).
--record(file_info, {peer_req, pid}).
--record(sys_misc, {module, function, arguments}).
--record(error, {where, code, text, filename}).
--record(prepared, {status :: prep_status() | 'undefined',
- result, block_no, next_data, prev_data}).
--record(transfer_res, {status, decoded_msg, prepared}).
--define(ERROR(Where, Code, Text, Filename),
- #error{where = Where, code = Code, text = Text, filename = Filename}).
-
-%%%-------------------------------------------------------------------
-%%% Info
-%%%-------------------------------------------------------------------
-
-info(daemons) ->
- Daemons = supervisor:which_children(tftp_sup),
- [{Pid, info(Pid)} || {_, Pid, _, _} <- Daemons];
-info(servers) ->
- [{Pid, info(Pid)} || {_, {ok, DeamonInfo}} <- info(daemons),
- {server, Pid} <- DeamonInfo];
-info(ToPid) when is_pid(ToPid) ->
- call(info, ToPid, timer:seconds(10)).
-
-change_config(daemons, Options) ->
- Daemons = supervisor:which_children(tftp_sup),
- [{Pid, change_config(Pid, Options)} || {_, Pid, _, _} <- Daemons];
-change_config(servers, Options) ->
- [{Pid, change_config(Pid, Options)} || {_, {ok, DeamonInfo}} <- info(daemons),
- {server, Pid} <- DeamonInfo];
-change_config(ToPid, Options) when is_pid(ToPid) ->
- BadKeys = [host, port, udp],
- BadOptions = [{Key, Val} || {Key, Val} <- Options,
- BadKey <- BadKeys,
- Key =:= BadKey],
- case BadOptions of
- [] ->
- call({change_config, Options}, ToPid, timer:seconds(10));
- [{Key, Val} | _] ->
- {error, {badarg, {Key, Val}}}
- end.
-
-call(Req, ToPid, Timeout) when is_pid(ToPid) ->
- Type = process,
- Ref = erlang:monitor(Type, ToPid),
- ToPid ! {Req, Ref, self()},
- receive
- {Reply, Ref, FromPid} when FromPid =:= ToPid ->
- erlang:demonitor(Ref, [flush]),
- Reply;
- {'DOWN', Ref, Type, FromPid, _Reason} when FromPid =:= ToPid ->
- {error, timeout}
- after Timeout ->
- {error, timeout}
- end.
-
-reply(Reply, Ref, ToPid) ->
- ToPid ! {Reply, Ref, self()}.
-
-%%%-------------------------------------------------------------------
-%%% Daemon
-%%%-------------------------------------------------------------------
-
-%% Returns {ok, Port}
-daemon_start(Options) when is_list(Options) ->
- Config = tftp_lib:parse_config(Options),
- proc_lib:start_link(?MODULE, daemon_init, [Config], infinity).
-
-daemon_init(Config) when is_record(Config, config),
- is_pid(Config#config.parent_pid) ->
- process_flag(trap_exit, true),
- {Port, UdpOptions} = prepare_daemon_udp(Config),
- case catch gen_udp:open(Port, UdpOptions) of
- {ok, Socket} ->
- {ok, ActualPort} = inet:port(Socket),
- proc_lib:init_ack({ok, self()}),
- Config2 = Config#config{udp_socket = Socket,
- udp_port = ActualPort},
- print_debug_info(Config2, daemon, open, #tftp_msg_req{filename = ""}),
- ServerTab = ets:new(tftp_daemon_servers, [{keypos, 2}]),
- FileTab = ets:new(tftp_daemon_files, [{keypos, 2}]),
- State = #daemon_state{config = Config2,
- n_servers = 0,
- server_tab = ServerTab,
- file_tab = FileTab},
- daemon_loop(State);
- {error, Reason} ->
- Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [UdpOptions, Reason])),
- print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")),
- exit({gen_udp_open, UdpOptions, Reason});
- Reason ->
- Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [UdpOptions, Reason])),
- print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")),
- exit({gen_udp_open, UdpOptions, Reason})
- end.
-
-prepare_daemon_udp(#config{udp_port = Port, udp_options = UdpOptions} = Config) ->
- case lists:keymember(fd, 1, UdpOptions) of
- true ->
- %% Use explicit fd
- {Port, UdpOptions};
- false ->
- %% Use fd from setuid_socket_wrap, such as -tftpd_69
- InitArg = list_to_atom("tftpd_" ++ integer_to_list(Port)),
- case init:get_argument(InitArg) of
- {ok, [[FdStr]] = Badarg} when is_list(FdStr) ->
- case catch list_to_integer(FdStr) of
- Fd when is_integer(Fd) ->
- {0, [{fd, Fd} | lists:keydelete(ip, 1, UdpOptions)]};
- {'EXIT', _} ->
- Text = lists:flatten(io_lib:format("Illegal prebound fd ~p: ~p", [InitArg, Badarg])),
- print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")),
- exit({badarg, {prebound_fd, InitArg, Badarg}})
- end;
- {ok, Badarg} ->
- Text = lists:flatten(io_lib:format("Illegal prebound fd ~p: ~p", [InitArg, Badarg])),
- print_debug_info(Config, daemon, open, ?ERROR(open, undef, Text, "")),
- exit({badarg, {prebound_fd, InitArg, Badarg}});
- error ->
- {Port, UdpOptions}
- end
- end.
-
-daemon_loop(DaemonConfig, N, Servers) when is_list(Servers) ->
- %% Handle upgrade from old releases. Please, remove this function in next release.
- ServerTab = ets:new(tftp_daemon_servers, [{keypos, 2}]),
- FileTab = ets:new(tftp_daemon_files, [{keypos, 2}]),
- State = #daemon_state{config = DaemonConfig,
- n_servers = N,
- server_tab = ServerTab,
- file_tab = FileTab},
- Req = #tftp_msg_req{filename = dummy},
- [ets:insert(ServerTab, #server_info{pid = Pid, req = Req, peer = dummy}) || Pid <- Servers],
- daemon_loop(State).
-
-daemon_loop(#daemon_state{config = DaemonConfig,
- n_servers = N,
- server_tab = ServerTab,
- file_tab = FileTab} = State) when is_record(DaemonConfig, config) ->
- %% info_msg(DaemonConfig, "=====> TFTP: Daemon #~p\n", [N]), %% XXX
- receive
- {info, Ref, FromPid} when is_pid(FromPid) ->
- Fun = fun(#server_info{pid = Pid}, Acc) -> [{server, Pid} | Acc] end,
- ServerInfo = ets:foldl(Fun, [], ServerTab),
- Info = internal_info(DaemonConfig, daemon) ++ [{n_conn, N}] ++ ServerInfo,
- reply({ok, Info}, Ref, FromPid),
- ?MODULE:daemon_loop(State);
- {{change_config, Options}, Ref, FromPid} when is_pid(FromPid) ->
- case catch tftp_lib:parse_config(Options, DaemonConfig) of
- {'EXIT', Reason} ->
- reply({error, Reason}, Ref, FromPid),
- ?MODULE:daemon_loop(State);
- DaemonConfig2 when is_record(DaemonConfig2, config) ->
- reply(ok, Ref, FromPid),
- ?MODULE:daemon_loop(State#daemon_state{config = DaemonConfig2})
- end;
- {udp, Socket, RemoteHost, RemotePort, Bin} when is_binary(Bin) ->
- inet:setopts(Socket, [{active, once}]),
- ServerConfig = DaemonConfig#config{parent_pid = self(),
- udp_host = RemoteHost,
- udp_port = RemotePort},
- Msg = (catch tftp_lib:decode_msg(Bin)),
- print_debug_info(ServerConfig, daemon, recv, Msg),
- case Msg of
- Req when is_record(Req, tftp_msg_req),
- N =< DaemonConfig#config.max_conn ->
- Peer = peer_info(ServerConfig),
- PeerReq = {Peer, Req},
- PeerInfo = lists:flatten(io_lib:format("~p", [Peer])),
- case ets:lookup(FileTab, PeerReq) of
- [] ->
- Args = [ServerConfig, Req],
- Pid = proc_lib:spawn_link(?MODULE, server_init, Args),
- ets:insert(ServerTab, #server_info{pid = Pid, req = Req, peer = Peer}),
- ets:insert(FileTab, #file_info{peer_req = PeerReq, pid = Pid}),
- ?MODULE:daemon_loop(State#daemon_state{n_servers = N + 1});
- [#file_info{pid = Pid}] ->
- %% Yet another request of the file from same peer
- warning_msg(DaemonConfig, "~p Reuse connection for ~s\n\t~p\n",
- [Pid, PeerInfo, Req#tftp_msg_req.filename]),
- ?MODULE:daemon_loop(State)
- end;
- Req when is_record(Req, tftp_msg_req) ->
- Reply = #tftp_msg_error{code = enospc, text = "Too many connections"},
- Peer = peer_info(ServerConfig),
- PeerInfo = lists:flatten(io_lib:format("~p", [Peer])),
- warning_msg(DaemonConfig,
- "Daemon has too many connections (~p)."
- "\n\tRejecting request from ~s\n",
- [N, PeerInfo]),
- send_msg(ServerConfig, daemon, Reply),
- ?MODULE:daemon_loop(State);
- {'EXIT', Reply} when is_record(Reply, tftp_msg_error) ->
- send_msg(ServerConfig, daemon, Reply),
- ?MODULE:daemon_loop(State);
- Req ->
- Reply = #tftp_msg_error{code = badop,
- text = "Illegal TFTP operation"},
- warning_msg(DaemonConfig, "Daemon received: ~p.\n\tfrom ~p:~p",
- [Req, RemoteHost, RemotePort]),
- send_msg(ServerConfig, daemon, Reply),
- ?MODULE:daemon_loop(State)
- end;
- {system, From, Msg} ->
- Misc = #sys_misc{module = ?MODULE, function = daemon_loop, arguments = [State]},
- sys:handle_system_msg(Msg, From, DaemonConfig#config.parent_pid, ?MODULE, [], Misc);
- {'EXIT', Pid, Reason} when DaemonConfig#config.parent_pid =:= Pid ->
- close_port(DaemonConfig, daemon, #tftp_msg_req{filename = ""}),
- exit(Reason);
- {'EXIT', Pid, _Reason} = Info ->
- case ets:lookup(ServerTab, Pid) of
- [] ->
- warning_msg(DaemonConfig, "Daemon received: ~p", [Info]),
- ?MODULE:daemon_loop(State);
- [#server_info{req = Req, peer = Peer}] ->
- PeerReq = {Peer, Req},
- ets:delete(FileTab, PeerReq),
- ets:delete(ServerTab, Pid),
- ?MODULE:daemon_loop(State#daemon_state{n_servers = N - 1})
- end;
- Info ->
- warning_msg(DaemonConfig, "Daemon received: ~p", [Info]),
- ?MODULE:daemon_loop(State)
- end;
-daemon_loop(#daemon_state{config = Config} = State) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- Config2 = upgrade_config(Config),
- daemon_loop(State#daemon_state{config = Config2}).
-
-upgrade_config({config, ParentPid, UdpSocket, UdpOptions, UdpHost, UdpPort, PortPolicy,
- UseTsize, MaxTsize, MaxConn, Rejected, PoliteAck, DebugLevel,
- Timeout, UserOptions, Callbacks}) ->
- Callbacks2 = tftp_lib:add_default_callbacks(Callbacks),
- Logger = tftp_logger,
- MaxRetries = 5,
- {config, ParentPid, UdpSocket, UdpOptions, UdpHost, UdpPort, PortPolicy,
- UseTsize, MaxTsize, MaxConn, Rejected, PoliteAck, DebugLevel,
- Timeout, UserOptions, Callbacks2, Logger, MaxRetries}.
-
-%%%-------------------------------------------------------------------
-%%% Server
-%%%-------------------------------------------------------------------
-
-server_init(Config, Req) when is_record(Config, config),
- is_pid(Config#config.parent_pid),
- is_record(Req, tftp_msg_req) ->
- process_flag(trap_exit, true),
- %% Config =
- %% case os:getenv("TFTPDEBUG") of
- %% false ->
- %% Config0;
- %% DebugLevel ->
- %% Config0#config{debug_level = list_to_atom(DebugLevel)}
- %% end,
- SuggestedOptions = Req#tftp_msg_req.options,
- UdpOptions = Config#config.udp_options,
- UdpOptions2 = lists:keydelete(fd, 1, UdpOptions),
- Config1 = Config#config{udp_options = UdpOptions2},
- Config2 = tftp_lib:parse_config(SuggestedOptions, Config1),
- SuggestedOptions2 = Config2#config.user_options,
- Req2 = Req#tftp_msg_req{options = SuggestedOptions2},
- case open_free_port(Config2, server, Req2) of
- {ok, Config3} ->
- Filename = Req#tftp_msg_req.filename,
- case match_callback(Filename, Config3#config.callbacks) of
- {ok, Callback} ->
- print_debug_info(Config3, server, match, Callback),
- case pre_verify_options(Config3, Req2) of
- ok ->
- case callback({open, server_open}, Config3, Callback, Req2) of
- {Callback2, {ok, AcceptedOptions}} ->
- {LocalAccess, _} = local_file_access(Req2),
- OptText = "Internal error. Not allowed to add new options.",
- case post_verify_options(Config3, Req2, AcceptedOptions, OptText) of
- {ok, Config4, Req3} when AcceptedOptions =/= [] ->
- Reply = #tftp_msg_oack{options = AcceptedOptions},
- BlockNo =
- case LocalAccess of
- read -> 0;
- write -> 1
- end,
- {Config5, Callback3, TransferRes} =
- transfer(Config4, Callback2, Req3, Reply, LocalAccess, BlockNo, #prepared{}),
- common_loop(Config5, Callback3, Req3, TransferRes, LocalAccess, BlockNo);
- {ok, Config4, Req3} when LocalAccess =:= write ->
- BlockNo = 0,
- common_ack(Config4, Callback2, Req3, LocalAccess, BlockNo, #prepared{});
- {ok, Config4, Req3} when LocalAccess =:= read ->
- BlockNo = 0,
- common_read(Config4, Callback2, Req3, LocalAccess, BlockNo, BlockNo, #prepared{});
- {error, {Code, Text}} ->
- {undefined, Error} =
- callback({abort, {Code, Text}}, Config3, Callback2, Req2),
- send_msg(Config3, Req, Error),
- terminate(Config3, Req2, ?ERROR(post_verify_options, Code, Text, Req2#tftp_msg_req.filename))
- end;
- {undefined, #tftp_msg_error{code = Code, text = Text} = Error} ->
- send_msg(Config3, Req, Error),
- terminate(Config3, Req, ?ERROR(server_open, Code, Text, Req2#tftp_msg_req.filename))
- end;
- {error, {Code, Text}} ->
- {undefined, Error} =
- callback({abort, {Code, Text}}, Config2, Callback, Req2),
- send_msg(Config2, Req, Error),
- terminate(Config2, Req2, ?ERROR(pre_verify_options, Code, Text, Req2#tftp_msg_req.filename))
- end;
- {error, #tftp_msg_error{code = Code, text = Text} = Error} ->
- send_msg(Config3, Req, Error),
- terminate(Config3, Req, ?ERROR(match_callback, Code, Text, Req2#tftp_msg_req.filename))
- end;
- #error{} = Error ->
- terminate(Config2, Req, Error)
- end;
-server_init(Config, Req) when is_record(Req, tftp_msg_req) ->
- Config2 = upgrade_config(Config),
- server_init(Config2, Req).
-
-%%%-------------------------------------------------------------------
-%%% Client
-%%%-------------------------------------------------------------------
-
-%% LocalFilename = filename() | 'binary' | binary()
-%% Returns {ok, LastCallbackState} | {error, Reason}
-client_start(Access, RemoteFilename, LocalFilename, Options) ->
- Config = tftp_lib:parse_config(Options),
- Config2 = Config#config{parent_pid = self(),
- udp_socket = undefined},
- Req = #tftp_msg_req{access = Access,
- filename = RemoteFilename,
- mode = lookup_mode(Config2#config.user_options),
- options = Config2#config.user_options,
- local_filename = LocalFilename},
- Args = [Config2, Req],
- case proc_lib:start_link(?MODULE, client_init, Args, infinity) of
- {ok, LastCallbackState} ->
- {ok, LastCallbackState};
- {error, Error} ->
- {error, Error}
- end.
-
-client_init(Config, Req) when is_record(Config, config),
- is_pid(Config#config.parent_pid),
- is_record(Req, tftp_msg_req) ->
- process_flag(trap_exit, true),
- %% Config =
- %% case os:getenv("TFTPDEBUG") of
- %% false ->
- %% Config0;
- %% "none" ->
- %% Config0;
- %% DebugLevel ->
- %% info_msg(Config, "TFTPDEBUG: ~s\n", [DebugLevel]),
- %% Config0#config{debug_level = list_to_atom(DebugLevel)}
- %% end,
- case open_free_port(Config, client, Req) of
- {ok, Config2} ->
- Req2 =
- case Config2#config.use_tsize of
- true ->
- SuggestedOptions = Req#tftp_msg_req.options,
- SuggestedOptions2 = tftp_lib:replace_val("tsize", "0", SuggestedOptions),
- Req#tftp_msg_req{options = SuggestedOptions2};
- false ->
- Req
- end,
- LocalFilename = Req2#tftp_msg_req.local_filename,
- case match_callback(LocalFilename, Config2#config.callbacks) of
- {ok, Callback} ->
- print_debug_info(Config2, client, match, Callback),
- client_prepare(Config2, Callback, Req2);
- {error, #tftp_msg_error{code = Code, text = Text}} ->
- terminate(Config, Req, ?ERROR(match, Code, Text, Req#tftp_msg_req.filename))
- end;
- #error{} = Error ->
- terminate(Config, Req, Error)
- end.
-
-client_prepare(Config, Callback, Req) when is_record(Req, tftp_msg_req) ->
- case pre_verify_options(Config, Req) of
- ok ->
- case callback({open, client_prepare}, Config, Callback, Req) of
- {Callback2, {ok, AcceptedOptions}} ->
- OptText = "Internal error. Not allowed to add new options.",
- case post_verify_options(Config, Req, AcceptedOptions, OptText) of
- {ok, Config2, Req2} ->
- {LocalAccess, _} = local_file_access(Req2),
- BlockNo = 0,
- {Config3, Callback3, TransferRes} =
- transfer(Config2, Callback2, Req2, Req2, LocalAccess, BlockNo, #prepared{}),
- client_open(Config3, Callback3, Req2, BlockNo, TransferRes);
- {error, {Code, Text}} ->
- callback({abort, {Code, Text}}, Config, Callback2, Req),
- terminate(Config, Req, ?ERROR(post_verify_options, Code, Text, Req#tftp_msg_req.filename))
- end;
- {undefined, #tftp_msg_error{code = Code, text = Text}} ->
- terminate(Config, Req, ?ERROR(client_prepare, Code, Text, Req#tftp_msg_req.filename))
- end;
- {error, {Code, Text}} ->
- callback({abort, {Code, Text}}, Config, Callback, Req),
- terminate(Config, Req, ?ERROR(pre_verify_options, Code, Text, Req#tftp_msg_req.filename))
- end.
-
-client_open(Config, Callback, Req, BlockNo, #transfer_res{status = Status, decoded_msg = DecodedMsg, prepared = Prepared}) ->
- {LocalAccess, _} = local_file_access(Req),
- case Status of
- ok when is_record(Prepared, prepared) ->
- case DecodedMsg of
- Msg when is_record(Msg, tftp_msg_oack) ->
- ServerOptions = Msg#tftp_msg_oack.options,
- OptText = "Protocol violation. Server is not allowed new options",
- case post_verify_options(Config, Req, ServerOptions, OptText) of
- {ok, Config2, Req2} ->
- {Config3, Callback2, Req3} =
- do_client_open(Config2, Callback, Req2),
- case LocalAccess of
- read ->
- common_read(Config3, Callback2, Req3, LocalAccess, BlockNo, BlockNo, Prepared);
- write ->
- common_ack(Config3, Callback2, Req3, LocalAccess, BlockNo, Prepared)
- end;
- {error, {Code, Text}} ->
- {undefined, Error} =
- callback({abort, {Code, Text}}, Config, Callback, Req),
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(verify_server_options, Code, Text, Req#tftp_msg_req.filename))
- end;
- #tftp_msg_ack{block_no = ActualBlockNo} when LocalAccess =:= read ->
- Req2 = Req#tftp_msg_req{options = []},
- {Config2, Callback2, Req2} = do_client_open(Config, Callback, Req2),
- ExpectedBlockNo = 0,
- common_read(Config2, Callback2, Req2, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared);
- #tftp_msg_data{block_no = ActualBlockNo, data = Data} when LocalAccess =:= write ->
- Req2 = Req#tftp_msg_req{options = []},
- {Config2, Callback2, Req2} = do_client_open(Config, Callback, Req2),
- ExpectedBlockNo = 1,
- common_write(Config2, Callback2, Req2, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared);
- %% #tftp_msg_error{code = Code, text = Text} when Req#tftp_msg_req.options =/= [] ->
- %% %% Retry without options
- %% callback({abort, {Code, Text}}, Config, Callback, Req),
- %% Req2 = Req#tftp_msg_req{options = []},
- %% client_prepare(Config, Callback, Req2);
- #tftp_msg_error{code = Code, text = Text} ->
- callback({abort, {Code, Text}}, Config, Callback, Req),
- terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename));
- {'EXIT', #tftp_msg_error{code = Code, text = Text}} ->
- callback({abort, {Code, Text}}, Config, Callback, Req),
- terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename));
- Msg when is_tuple(Msg) ->
- Code = badop,
- Text = "Illegal TFTP operation",
- {undefined, Error} =
- callback({abort, {Code, Text}}, Config, Callback, Req),
- send_msg(Config, Req, Error),
- Text2 = lists:flatten([Text, ". ", io_lib:format("~p", [element(1, Msg)])]),
- terminate(Config, Req, ?ERROR(client_open, Code, Text2, Req#tftp_msg_req.filename))
- end;
- error when is_record(Prepared, tftp_msg_error) ->
- #tftp_msg_error{code = Code, text = Text} = Prepared,
- callback({abort, {Code, Text}}, Config, Callback, Req),
- terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename))
- end.
-
-do_client_open(Config, Callback, Req) ->
- case callback({open, client_open}, Config, Callback, Req) of
- {Callback2, {ok, FinalOptions}} ->
- OptText = "Internal error. Not allowed to change options.",
- case post_verify_options(Config, Req, FinalOptions, OptText) of
- {ok, Config2, Req2} ->
- {Config2, Callback2, Req2};
- {error, {Code, Text}} ->
- {undefined, Error} =
- callback({abort, {Code, Text}}, Config, Callback2, Req),
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(post_verify_options, Code, Text, Req#tftp_msg_req.filename))
- end;
- {undefined, #tftp_msg_error{code = Code, text = Text} = Error} ->
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(client_open, Code, Text, Req#tftp_msg_req.filename))
- end.
-
-%%%-------------------------------------------------------------------
-%%% Common loop for both client and server
-%%%-------------------------------------------------------------------
-
-common_loop(Config, Callback, Req, #transfer_res{status = Status, decoded_msg = DecodedMsg, prepared = Prepared}, LocalAccess, ExpectedBlockNo)
- when is_record(Config, config)->
- %% Config =
- %% case os:getenv("TFTPMAX") of
- %% false ->
- %% Config0;
- %% MaxBlockNoStr when Config0#config.debug_level =/= none ->
- %% case list_to_integer(MaxBlockNoStr) of
- %% MaxBlockNo when ExpectedBlockNo > MaxBlockNo ->
- %% info_msg(Config, "TFTPMAX: ~p\n", [MaxBlockNo]),
- %% info_msg(Config, "TFTPDEBUG: none\n", []),
- %% Config0#config{debug_level = none};
- %% _ ->
- %% Config0
- %% end;
- %% _MaxBlockNoStr ->
- %% Config0
- %% end,
- case Status of
- ok when is_record(Prepared, prepared) ->
- case DecodedMsg of
- #tftp_msg_ack{block_no = ActualBlockNo} when LocalAccess =:= read ->
- common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared);
- #tftp_msg_data{block_no = ActualBlockNo, data = Data} when LocalAccess =:= write ->
- common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared);
- #tftp_msg_error{code = Code, text = Text} ->
- callback({abort, {Code, Text}}, Config, Callback, Req),
- terminate(Config, Req, ?ERROR(common_loop, Code, Text, Req#tftp_msg_req.filename));
- {'EXIT', #tftp_msg_error{code = Code, text = Text} = Error} ->
- callback({abort, {Code, Text}}, Config, Callback, Req),
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(common_loop, Code, Text, Req#tftp_msg_req.filename));
- Msg when is_tuple(Msg) ->
- Code = badop,
- Text = "Illegal TFTP operation",
- {undefined, Error} =
- callback({abort, {Code, Text}}, Config, Callback, Req),
- send_msg(Config, Req, Error),
- Text2 = lists:flatten([Text, ". ", io_lib:format("~p", [element(1, Msg)])]),
- terminate(Config, Req, ?ERROR(common_loop, Code, Text2, Req#tftp_msg_req.filename))
- end;
- error when is_record(Prepared, tftp_msg_error) ->
- #tftp_msg_error{code = Code, text = Text} = Prepared,
- send_msg(Config, Req, Prepared),
- terminate(Config, Req, ?ERROR(transfer, Code, Text, Req#tftp_msg_req.filename))
- end;
-common_loop(Config, Callback, Req, TransferRes, LocalAccess, ExpectedBlockNo) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- Config2 = upgrade_config(Config),
- common_loop(Config2, Callback, Req, TransferRes, LocalAccess, ExpectedBlockNo).
-
--spec common_read(#config{}, #callback{}, _, 'read', _, _, #prepared{}) -> no_return().
-
-common_read(Config, _, Req, _, _, _, #prepared{status = terminate, result = Result}) ->
- terminate(Config, Req, {ok, Result});
-common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared)
- when ActualBlockNo =:= ExpectedBlockNo, is_record(Prepared, prepared) ->
- case early_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Prepared) of
- {Callback2, #prepared{status = more, next_data = Data} = Prepared2} when is_binary(Data) ->
- Prepared3 = Prepared2#prepared{prev_data = Data, next_data = undefined},
- do_common_read(Config, Callback2, Req, LocalAccess, ActualBlockNo, Data, Prepared3);
- {undefined, #prepared{status = last, next_data = Data} = Prepared2} when is_binary(Data) ->
- Prepared3 = Prepared2#prepared{status = terminate},
- do_common_read(Config, undefined, Req, LocalAccess, ActualBlockNo, Data, Prepared3);
- {undefined, #prepared{status = error, result = Error}} ->
- #tftp_msg_error{code = Code, text = Text} = Error,
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename))
- end;
-common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared)
- when ActualBlockNo =:= (ExpectedBlockNo - 1), is_record(Prepared, prepared) ->
- case Prepared of
- #prepared{status = more, prev_data = Data} when is_binary(Data) ->
- do_common_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Data, Prepared);
- #prepared{status = last, prev_data = Data} when is_binary(Data) ->
- do_common_read(Config, Callback, Req, LocalAccess, ActualBlockNo, Data, Prepared);
- #prepared{status = error, result = Error} ->
- #tftp_msg_error{code = Code, text = Text} = Error,
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename))
- end;
-common_read(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared)
- when ActualBlockNo =< ExpectedBlockNo, is_record(Prepared, prepared) ->
- %% error_logger:error_msg("TFTP READ ~s: Expected block ~p but got block ~p - IGNORED\n",
- %% [Req#tftp_msg_req.filename, ExpectedBlockNo, ActualBlockNo]),
- case Prepared of
- #prepared{status = more, prev_data = Data} when is_binary(Data) ->
- Reply = #tftp_msg_data{block_no = ExpectedBlockNo, data = Data},
- {Config2, Callback2, TransferRes} =
- wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared),
- ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo);
- #prepared{status = last, prev_data = Data} when is_binary(Data) ->
- Reply = #tftp_msg_data{block_no = ExpectedBlockNo, data = Data},
- {Config2, Callback2, TransferRes} =
- wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared),
- ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo);
- #prepared{status = error, result = Error} ->
- #tftp_msg_error{code = Code, text = Text} = Error,
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename))
- end;
-common_read(Config, Callback, Req, _LocalAccess, ExpectedBlockNo, ActualBlockNo, Prepared)
- when is_record(Prepared, prepared) ->
- Code = badblk,
- Text = "Unknown transfer ID = " ++
- integer_to_list(ActualBlockNo) ++ " (" ++ integer_to_list(ExpectedBlockNo) ++ ")",
- {undefined, Error} =
- callback({abort, {Code, Text}}, Config, Callback, Req),
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(read, Code, Text, Req#tftp_msg_req.filename)).
-
--spec do_common_read(#config{}, #callback{} | undefined, _, 'read', integer(), binary(), #prepared{}) -> no_return().
-
-do_common_read(Config, Callback, Req, LocalAccess, BlockNo, Data, Prepared)
- when is_binary(Data), is_record(Prepared, prepared) ->
- NextBlockNo = (BlockNo + 1) rem 65536,
- Reply = #tftp_msg_data{block_no = NextBlockNo, data = Data},
- {Config2, Callback2, TransferRes} =
- transfer(Config, Callback, Req, Reply, LocalAccess, NextBlockNo, Prepared),
- ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, NextBlockNo).
-
--spec common_write(#config{}, #callback{}, _, 'write', integer(), integer(), _, #prepared{}) -> no_return().
-
-common_write(Config, _, Req, _, _, _, _, #prepared{status = terminate, result = Result}) ->
- terminate(Config, Req, {ok, Result});
-common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared)
- when ActualBlockNo =:= ExpectedBlockNo, is_binary(Data), is_record(Prepared, prepared) ->
- case callback({write, Data}, Config, Callback, Req) of
- {Callback2, #prepared{status = more} = Prepared2} ->
- common_ack(Config, Callback2, Req, LocalAccess, ActualBlockNo, Prepared2);
- {undefined, #prepared{status = last, result = Result} = Prepared2} ->
- Config2 = pre_terminate(Config, Req, {ok, Result}),
- Prepared3 = Prepared2#prepared{status = terminate},
- common_ack(Config2, undefined, Req, LocalAccess, ActualBlockNo, Prepared3);
- {undefined, #prepared{status = error, result = Error}} ->
- #tftp_msg_error{code = Code, text = Text} = Error,
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(write, Code, Text, Req#tftp_msg_req.filename))
- end;
-common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared)
- when ActualBlockNo =:= (ExpectedBlockNo - 1), is_binary(Data), is_record(Prepared, prepared) ->
- common_ack(Config, Callback, Req, LocalAccess, ExpectedBlockNo - 1, Prepared);
-common_write(Config, Callback, Req, LocalAccess, ExpectedBlockNo, ActualBlockNo, Data, Prepared)
- when ActualBlockNo =< ExpectedBlockNo, is_binary(Data), is_record(Prepared, prepared) ->
- %% error_logger:error_msg("TFTP WRITE ~s: Expected block ~p but got block ~p - IGNORED\n",
- %% [Req#tftp_msg_req.filename, ExpectedBlockNo, ActualBlockNo]),
- Reply = #tftp_msg_ack{block_no = ExpectedBlockNo},
- {Config2, Callback2, TransferRes} =
- wait_for_msg_and_handle_timeout(Config, Callback, Req, Reply, LocalAccess, ExpectedBlockNo, Prepared),
- ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, ExpectedBlockNo);
-common_write(Config, Callback, Req, _, ExpectedBlockNo, ActualBlockNo, Data, Prepared)
- when is_binary(Data), is_record(Prepared, prepared) ->
- Code = badblk,
- Text = "Unknown transfer ID = " ++
- integer_to_list(ActualBlockNo) ++ " (" ++ integer_to_list(ExpectedBlockNo) ++ ")",
- {undefined, Error} =
- callback({abort, {Code, Text}}, Config, Callback, Req),
- send_msg(Config, Req, Error),
- terminate(Config, Req, ?ERROR(write, Code, Text, Req#tftp_msg_req.filename)).
-
-common_ack(Config, Callback, Req, LocalAccess, BlockNo, Prepared)
- when is_record(Prepared, prepared) ->
- Reply = #tftp_msg_ack{block_no = BlockNo},
- NextBlockNo = (BlockNo + 1) rem 65536,
- {Config2, Callback2, TransferRes} =
- transfer(Config, Callback, Req, Reply, LocalAccess, NextBlockNo, Prepared),
- ?MODULE:common_loop(Config2, Callback2, Req, TransferRes, LocalAccess, NextBlockNo).
-
-pre_terminate(Config, Req, Result) ->
- if
- Req#tftp_msg_req.local_filename =/= undefined,
- Config#config.parent_pid =/= undefined ->
- proc_lib:init_ack(Result),
- unlink(Config#config.parent_pid),
- Config#config{parent_pid = undefined, polite_ack = true};
- true ->
- Config#config{polite_ack = true}
- end.
-
--spec terminate(#config{}, #tftp_msg_req{}, {'ok', _} | #error{}) -> no_return().
-
-terminate(Config, Req, Result) ->
- Result2 =
- case Result of
- {ok, _} ->
- Result;
- #error{where = Where, code = Code, text = Text} = Error ->
- print_debug_info(Config, Req, Where, Error#error{filename = Req#tftp_msg_req.filename}),
- {error, {Where, Code, Text}}
- end,
- if
- Config#config.parent_pid =:= undefined ->
- close_port(Config, client, Req),
- exit(normal);
- Req#tftp_msg_req.local_filename =/= undefined ->
- %% Client
- close_port(Config, client, Req),
- proc_lib:init_ack(Result2),
- unlink(Config#config.parent_pid),
- exit(normal);
- true ->
- %% Server
- close_port(Config, server, Req),
- exit(shutdown)
- end.
-
-close_port(Config, Who, Req) when is_record(Req, tftp_msg_req) ->
- case Config#config.udp_socket of
- undefined ->
- ignore;
- Socket ->
- print_debug_info(Config, Who, close, Req),
- gen_udp:close(Socket)
- end.
-
-open_free_port(Config, Who, Req) when is_record(Config, config), is_record(Req, tftp_msg_req) ->
- UdpOptions = Config#config.udp_options,
- case Config#config.port_policy of
- random ->
- %% BUGBUG: Should be a random port
- case catch gen_udp:open(0, UdpOptions) of
- {ok, Socket} ->
- Config2 = Config#config{udp_socket = Socket},
- print_debug_info(Config2, Who, open, Req),
- {ok, Config2};
- {error, Reason} ->
- Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[0 | UdpOptions], Reason])),
- ?ERROR(open, undef, Text, Req#tftp_msg_req.filename);
- {'EXIT', _} = Reason ->
- Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[0 | UdpOptions], Reason])),
- ?ERROR(open, undef, Text, Req#tftp_msg_req.filename)
- end;
- {range, Port, Max} when Port =< Max ->
- case catch gen_udp:open(Port, UdpOptions) of
- {ok, Socket} ->
- Config2 = Config#config{udp_socket = Socket},
- print_debug_info(Config2, Who, open, Req),
- {ok, Config2};
- {error, eaddrinuse} ->
- PortPolicy = {range, Port + 1, Max},
- Config2 = Config#config{port_policy = PortPolicy},
- open_free_port(Config2, Who, Req);
- {error, Reason} ->
- Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])),
- ?ERROR(open, undef, Text, Req#tftp_msg_req.filename);
- {'EXIT', _} = Reason->
- Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])),
- ?ERROR(open, undef, Text, Req#tftp_msg_req.filename)
- end;
- {range, Port, _Max} ->
- Reason = "Port range exhausted",
- Text = lists:flatten(io_lib:format("UDP open ~p -> ~p", [[Port | UdpOptions], Reason])),
- ?ERROR(Who, undef, Text, Req#tftp_msg_req.filename)
- end.
-
-%%-------------------------------------------------------------------
-%% Transfer
-%%-------------------------------------------------------------------
-
-%% Returns {Config, Callback, #transfer_res{}}
-transfer(Config, Callback, Req, Msg, LocalAccess, NextBlockNo, Prepared)
- when is_record(Prepared, prepared) ->
- IoList = tftp_lib:encode_msg(Msg),
- Retries = Config#config.max_retries + 1,
- do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries).
-
-do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries)
- when is_record(Prepared, prepared), is_integer(Retries), Retries >= 0 ->
- case do_send_msg(Config, Req, Msg, IoList) of
- ok ->
- {Callback2, Prepared2} =
- early_read(Config, Callback, Req, LocalAccess, NextBlockNo, Prepared),
- do_wait_for_msg_and_handle_timeout(Config, Callback2, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared2, Retries);
- {error, _Reason} when Retries > 0 ->
- Retries2 = 0, % Just retry once when send fails
- do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries2);
- {error, Reason} ->
- Code = undef,
- Text = lists:flatten(io_lib:format("Transfer failed - giving up -> ~p", [Reason])),
- Error = #tftp_msg_error{code = Code, text = Text},
- {Config, Callback, #transfer_res{status = error, prepared = Error}}
- end.
-
-wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, LocalAccess, NextBlockNo, Prepared) ->
- IoList = tftp_lib:encode_msg(Msg),
- Retries = Config#config.max_retries + 1,
- do_wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries).
-
-do_wait_for_msg_and_handle_timeout(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries) ->
- Code = undef,
- Text = "Transfer timed out.",
- case wait_for_msg(Config, Callback, Req) of
- timeout when Config#config.polite_ack =:= true ->
- do_send_msg(Config, Req, Msg, IoList),
- case Prepared of
- #prepared{status = terminate, result = Result} ->
- terminate(Config, Req, {ok, Result});
- #prepared{} ->
- terminate(Config, Req, ?ERROR(transfer, Code, Text, Req#tftp_msg_req.filename))
- end;
- timeout when Retries > 0 ->
- Retries2 = Retries - 1,
- do_transfer(Config, Callback, Req, Msg, IoList, LocalAccess, NextBlockNo, Prepared, Retries2);
- timeout ->
- Error = #tftp_msg_error{code = Code, text = Text},
- {Config, Callback, #transfer_res{status = error, prepared = Error}};
- {Config2, DecodedMsg} ->
- {Config2, Callback, #transfer_res{status = ok, decoded_msg = DecodedMsg, prepared = Prepared}}
- end.
-
-send_msg(Config, Req, Msg) ->
- case catch tftp_lib:encode_msg(Msg) of
- {'EXIT', Reason} ->
- Code = undef,
- Text = "Internal error. Encode failed",
- Msg2 = #tftp_msg_error{code = Code, text = Text, details = Reason},
- send_msg(Config, Req, Msg2);
- IoList ->
- do_send_msg(Config, Req, Msg, IoList)
- end.
-
-do_send_msg(#config{udp_socket = Socket, udp_host = RemoteHost, udp_port = RemotePort} = Config, Req, Msg, IoList) ->
- %% {ok, LocalPort} = inet:port(Socket),
- %% if
- %% LocalPort =/= ?TFTP_DEFAULT_PORT ->
- %% ok;
- %% true ->
- %% print_debug_info(Config#config{debug_level = all}, Req, send, Msg),
- %% error(Config,
- %% "Daemon replies from the default port (~p)\n\t to ~p:~p\n\t¨~p\n",
- %% [LocalPort, RemoteHost, RemotePort, Msg])
- %% end,
-
- print_debug_info(Config, Req, send, Msg),
-
- %% case os:getenv("TFTPDUMP") of
- %% false ->
- %% ignore;
- %% DumpPath ->
- %% trace_udp_send(Req, Msg, IoList, DumpPath)
- %% end,
- Res = gen_udp:send(Socket, RemoteHost, RemotePort, IoList),
- case Res of
- ok ->
- ok;
- {error, einval = Reason} ->
- error_msg(Config,
- "Stacktrace; ~p\n gen_udp:send(~p, ~p, ~p, ~p) -> ~p\n",
- [erlang:get_stacktrace(), Socket, RemoteHost, RemotePort, IoList, {error, Reason}]);
- {error, Reason} ->
- {error, Reason}
- end.
-
-%% trace_udp_send(#tftp_msg_req{filename = [$/ | RelFile]} = Req, Msg, IoList, DumpPath) ->
-%% trace_udp_send(Req#tftp_msg_req{filename = RelFile}, Msg, IoList, DumpPath);
-%% trace_udp_send(#tftp_msg_req{filename = RelFile},
-%% #tftp_msg_data{block_no = BlockNo, data = Data},
-%% _IoList,
-%% DumpPath) ->
-%% File = filename:join([DumpPath, RelFile, "block" ++ string:right(integer_to_list(BlockNo), 5, $0) ++ ".dump"]),
-%% if
-%% (BlockNo rem 1000) =:= 1 ->
-%% info_msg(Config, "TFTPDUMP: Data ~s\n", [File]);
-%% true ->
-%% ignore
-%% end,
-%% ok = filelib:ensure_dir(File),
-%% ok = file:write_file(File, Data);
-%% trace_udp_send(#tftp_msg_req{filename = RelFile}, Msg, _IoList, _DumpPath) ->
-%% info_msg(Config, "TFTPDUMP: No data ~s -> ~p\n", [RelFile, element(1, Msg)]).
-
-wait_for_msg(Config, Callback, Req) ->
- receive
- {udp, Socket, RemoteHost, RemotePort, Bin}
- when is_binary(Bin), Callback#callback.block_no =:= undefined ->
- %% Client prepare
- inet:setopts(Socket, [{active, once}]),
- Config2 = Config#config{udp_host = RemoteHost,
- udp_port = RemotePort},
- DecodedMsg = (catch tftp_lib:decode_msg(Bin)),
- print_debug_info(Config2, Req, recv, DecodedMsg),
- {Config2, DecodedMsg};
- {udp, Socket, Host, Port, Bin} when is_binary(Bin),
- Config#config.udp_host =:= Host,
- Config#config.udp_port =:= Port ->
- inet:setopts(Socket, [{active, once}]),
- DecodedMsg = (catch tftp_lib:decode_msg(Bin)),
- print_debug_info(Config, Req, recv, DecodedMsg),
- {Config, DecodedMsg};
- {info, Ref, FromPid} when is_pid(FromPid) ->
- Type =
- case Req#tftp_msg_req.local_filename =/= undefined of
- true -> client;
- false -> server
- end,
- Info = internal_info(Config, Type),
- reply({ok, Info}, Ref, FromPid),
- wait_for_msg(Config, Callback, Req);
- {{change_config, Options}, Ref, FromPid} when is_pid(FromPid) ->
- case catch tftp_lib:parse_config(Options, Config) of
- {'EXIT', Reason} ->
- reply({error, Reason}, Ref, FromPid),
- wait_for_msg(Config, Callback, Req);
- Config2 when is_record(Config2, config) ->
- reply(ok, Ref, FromPid),
- wait_for_msg(Config2, Callback, Req)
- end;
- {system, From, Msg} ->
- Misc = #sys_misc{module = ?MODULE, function = wait_for_msg, arguments = [Config, Callback, Req]},
- sys:handle_system_msg(Msg, From, Config#config.parent_pid, ?MODULE, [], Misc);
- {'EXIT', Pid, _Reason} when Config#config.parent_pid =:= Pid ->
- Code = undef,
- Text = "Parent exited.",
- terminate(Config, Req, ?ERROR(wait_for_msg, Code, Text, Req#tftp_msg_req.filename));
- Msg when Req#tftp_msg_req.local_filename =/= undefined ->
- warning_msg(Config, "Client received : ~p", [Msg]),
- wait_for_msg(Config, Callback, Req);
- Msg when Req#tftp_msg_req.local_filename =:= undefined ->
- warning_msg(Config, "Server received : ~p", [Msg]),
- wait_for_msg(Config, Callback, Req)
- after Config#config.timeout * 1000 ->
- print_debug_info(Config, Req, recv, timeout),
- timeout
- end.
-
-early_read(Config, Callback, Req, LocalAccess, _NextBlockNo,
- #prepared{status = Status, next_data = NextData, prev_data = PrevData} = Prepared) ->
- if
- Status =/= terminate,
- LocalAccess =:= read,
- Callback#callback.block_no =/= undefined,
- NextData =:= undefined ->
- case callback(read, Config, Callback, Req) of
- {undefined, Error} when is_record(Error, tftp_msg_error) ->
- {undefined, Error};
- {Callback2, Prepared2} when is_record(Prepared2, prepared)->
- {Callback2, Prepared2#prepared{prev_data = PrevData}}
- end;
- true ->
- {Callback, Prepared}
- end.
-
-%%-------------------------------------------------------------------
-%% Callback
-%%-------------------------------------------------------------------
-
-callback(Access, Config, Callback, Req) ->
- {Callback2, Result} =
- do_callback(Access, Config, Callback, Req),
- print_debug_info(Config, Req, call, {Callback2, Result}),
- {Callback2, Result}.
-
-do_callback(read = Fun, Config, Callback, Req)
- when is_record(Config, config),
- is_record(Callback, callback),
- is_record(Req, tftp_msg_req) ->
- Args = [Callback#callback.state],
- NextBlockNo = Callback#callback.block_no + 1,
- case catch safe_apply(Callback#callback.module, Fun, Args) of
- {more, Bin, NewState} when is_binary(Bin) ->
- Count = Callback#callback.count + size(Bin),
- Callback2 = Callback#callback{state = NewState,
- block_no = NextBlockNo,
- count = Count},
- Prepared = #prepared{status = more,
- result = undefined,
- block_no = NextBlockNo,
- next_data = Bin},
- verify_count(Config, Callback2, Req, Prepared);
- {last, Bin, Result} when is_binary(Bin) ->
- Prepared = #prepared{status = last,
- result = Result,
- block_no = NextBlockNo,
- next_data = Bin},
- {undefined, Prepared};
- {error, {Code, Text}} ->
- Error = #tftp_msg_error{code = Code, text = Text},
- Prepared = #prepared{status = error,
- result = Error},
- {undefined, Prepared};
- Illegal ->
- Code = undef,
- Text = "Internal error. File handler error.",
- callback({abort, {Code, Text, Illegal}}, Config, Callback, Req)
- end;
-do_callback({write = Fun, Bin}, Config, Callback, Req)
- when is_record(Config, config),
- is_record(Callback, callback),
- is_record(Req, tftp_msg_req),
- is_binary(Bin) ->
- Args = [Bin, Callback#callback.state],
- NextBlockNo = Callback#callback.block_no + 1,
- case catch safe_apply(Callback#callback.module, Fun, Args) of
- {more, NewState} ->
- Count = Callback#callback.count + size(Bin),
- Callback2 = Callback#callback{state = NewState,
- block_no = NextBlockNo,
- count = Count},
- Prepared = #prepared{status = more,
- block_no = NextBlockNo},
- verify_count(Config, Callback2, Req, Prepared);
- {last, Result} ->
- Prepared = #prepared{status = last,
- result = Result,
- block_no = NextBlockNo},
- {undefined, Prepared};
- {error, {Code, Text}} ->
- Error = #tftp_msg_error{code = Code, text = Text},
- Prepared = #prepared{status = error,
- result = Error},
- {undefined, Prepared};
- Illegal ->
- Code = undef,
- Text = "Internal error. File handler error.",
- callback({abort, {Code, Text, Illegal}}, Config, Callback, Req)
- end;
-do_callback({open, Type}, Config, Callback, Req)
- when is_record(Config, config),
- is_record(Callback, callback),
- is_record(Req, tftp_msg_req) ->
- {Access, Filename} = local_file_access(Req),
- {Fun, BlockNo} =
- case Type of
- client_prepare -> {prepare, undefined};
- client_open -> {open, 0};
- server_open -> {open, 0}
- end,
- Mod = Callback#callback.module,
- Args = [Access,
- Filename,
- Req#tftp_msg_req.mode,
- Req#tftp_msg_req.options,
- Callback#callback.state],
- PeerInfo = peer_info(Config),
- fast_ensure_loaded(Mod),
- Args2 =
- case erlang:function_exported(Mod, Fun, length(Args)) of
- true -> Args;
- false -> [PeerInfo | Args]
- end,
- case catch safe_apply(Mod, Fun, Args2) of
- {ok, AcceptedOptions, NewState} ->
- Callback2 = Callback#callback{state = NewState,
- block_no = BlockNo,
- count = 0},
- {Callback2, {ok, AcceptedOptions}};
- {error, {Code, Text}} ->
- {undefined, #tftp_msg_error{code = Code, text = Text}};
- Illegal ->
- Code = undef,
- Text = "Internal error. File handler error.",
- callback({abort, {Code, Text, Illegal}}, Config, Callback, Req)
- end;
-do_callback({abort, {Code, Text}}, Config, Callback, Req) ->
- Error = #tftp_msg_error{code = Code, text = Text},
- do_callback({abort, Error}, Config, Callback, Req);
-do_callback({abort, {Code, Text, Details}}, Config, Callback, Req) ->
- Error = #tftp_msg_error{code = Code, text = Text, details = Details},
- do_callback({abort, Error}, Config, Callback, Req);
-do_callback({abort = Fun, #tftp_msg_error{code = Code, text = Text} = Error}, Config, Callback, Req)
- when is_record(Config, config),
- is_record(Callback, callback),
- is_record(Req, tftp_msg_req) ->
- Args = [Code, Text, Callback#callback.state],
- catch safe_apply(Callback#callback.module, Fun, Args),
- {undefined, Error};
-do_callback({abort, Error}, _Config, undefined, _Req) when is_record(Error, tftp_msg_error) ->
- {undefined, Error}.
-
-peer_info(#config{udp_host = Host, udp_port = Port}) ->
- if
- is_tuple(Host), size(Host) =:= 4 ->
- {inet, tftp_lib:host_to_string(Host), Port};
- is_tuple(Host), size(Host) =:= 8 ->
- {inet6, tftp_lib:host_to_string(Host), Port};
- true ->
- {undefined, Host, Port}
- end.
-
-match_callback(Filename, Callbacks) ->
- if
- Filename =:= binary ->
- lookup_callback_mod(tftp_binary, Callbacks);
- is_binary(Filename) ->
- lookup_callback_mod(tftp_binary, Callbacks);
- true ->
- do_match_callback(Filename, Callbacks)
- end.
-
-do_match_callback(Filename, [C | Tail]) when is_record(C, callback) ->
- case catch re:run(Filename, C#callback.internal, [{capture, none}]) of
- match ->
- {ok, C};
- nomatch ->
- do_match_callback(Filename, Tail);
- Details ->
- Code = baduser,
- Text = "Internal error. File handler not found",
- {error, #tftp_msg_error{code = Code, text = Text, details = Details}}
- end;
-do_match_callback(Filename, []) ->
- Code = baduser,
- Text = "Internal error. File handler not found",
- {error, #tftp_msg_error{code = Code, text = Text, details = Filename}}.
-
-lookup_callback_mod(Mod, Callbacks) ->
- {value, C} = lists:keysearch(Mod, #callback.module, Callbacks),
- {ok, C}.
-
-verify_count(Config, Callback, Req, Result) ->
- case Config#config.max_tsize of
- infinity ->
- {Callback, Result};
- Max when Callback#callback.count =< Max ->
- {Callback, Result};
- _Max ->
- Code = enospc,
- Text = "Too large file.",
- callback({abort, {Code, Text}}, Config, Callback, Req)
- end.
-
-%%-------------------------------------------------------------------
-%% Miscellaneous
-%%-------------------------------------------------------------------
-
-internal_info(Config, Type) when is_record(Config, config) ->
- {ok, ActualPort} = inet:port(Config#config.udp_socket),
- [
- {type, Type},
- {host, tftp_lib:host_to_string(Config#config.udp_host)},
- {port, Config#config.udp_port},
- {local_port, ActualPort},
- {port_policy, Config#config.port_policy},
- {udp, Config#config.udp_options},
- {use_tsize, Config#config.use_tsize},
- {max_tsize, Config#config.max_tsize},
- {max_conn, Config#config.max_conn},
- {rejected, Config#config.rejected},
- {timeout, Config#config.timeout},
- {polite_ack, Config#config.polite_ack},
- {debug, Config#config.debug_level},
- {parent_pid, Config#config.parent_pid}
- ] ++ Config#config.user_options ++ Config#config.callbacks.
-
-local_file_access(#tftp_msg_req{access = Access,
- local_filename = Local,
- filename = Filename}) ->
- case Local =:= undefined of
- true ->
- %% Server side
- {Access, Filename};
- false ->
- %% Client side
- case Access of
- read -> {write, Local};
- write -> {read, Local}
- end
- end.
-
-pre_verify_options(Config, Req) ->
- Options = Req#tftp_msg_req.options,
- case catch verify_reject(Config, Req, Options) of
- ok ->
- case verify_integer("tsize", 0, Config#config.max_tsize, Options) of
- true ->
- case verify_integer("blksize", 0, 65464, Options) of
- true ->
- ok;
- false ->
- {error, {badopt, "Too large blksize"}}
- end;
- false ->
- {error, {badopt, "Too large tsize"}}
- end;
- {error, Reason} ->
- {error, Reason}
- end.
-
-post_verify_options(Config, Req, NewOptions, Text) ->
- OldOptions = Req#tftp_msg_req.options,
- BadOptions =
- [Key || {Key, _Val} <- NewOptions,
- not lists:keymember(Key, 1, OldOptions)],
- case BadOptions =:= [] of
- true ->
- Config2 = Config#config{timeout = lookup_timeout(NewOptions)},
- Req2 = Req#tftp_msg_req{options = NewOptions},
- {ok, Config2, Req2};
- false ->
- {error, {badopt, Text}}
- end.
-
-verify_reject(Config, Req, Options) ->
- Access = Req#tftp_msg_req.access,
- Rejected = Config#config.rejected,
- case lists:member(Access, Rejected) of
- true ->
- {error, {eacces, atom_to_list(Access) ++ " mode not allowed"}};
- false ->
- [throw({error, {badopt, Key ++ " not allowed"}}) ||
- {Key, _} <- Options, lists:member(Key, Rejected)],
- ok
- end.
-
-lookup_timeout(Options) ->
- case lists:keysearch("timeout", 1, Options) of
- {value, {_, Val}} ->
- list_to_integer(Val);
- false ->
- 3
- end.
-
-lookup_mode(Options) ->
- case lists:keysearch("mode", 1, Options) of
- {value, {_, Val}} ->
- Val;
- false ->
- "octet"
- end.
-
-verify_integer(Key, Min, Max, Options) ->
- case lists:keysearch(Key, 1, Options) of
- {value, {_, Val}} when is_list(Val) ->
- case catch list_to_integer(Val) of
- {'EXIT', _} ->
- false;
- Int when Int >= Min, is_integer(Min),
- Max =:= infinity ->
- true;
- Int when Int >= Min, is_integer(Min),
- Int =< Max, is_integer(Max) ->
- true;
- _ ->
- false
- end;
- false ->
- true
- end.
-
-error_msg(#config{logger = Logger, debug_level = _Level}, F, A) ->
- safe_apply(Logger, error_msg, [F, A]).
-
-warning_msg(#config{logger = Logger, debug_level = Level}, F, A) ->
- case Level of
- none -> ok;
- error -> ok;
- _ -> safe_apply(Logger, warning_msg, [F, A])
- end.
-
-info_msg(#config{logger = Logger}, F, A) ->
- safe_apply(Logger, info_msg, [F, A]).
-
-safe_apply(Mod, Fun, Args) ->
- fast_ensure_loaded(Mod),
- apply(Mod, Fun, Args).
-
-fast_ensure_loaded(Mod) ->
- case erlang:function_exported(Mod, module_info, 0) of
- true ->
- ok;
- false ->
- Res = code:load_file(Mod),
- %% io:format("tftp: code:load_file(~p) -> ~p\n", [Mod, Res]), %% XXX
- Res
- end.
-
-print_debug_info(#config{debug_level = Level} = Config, Who, Where, Data) ->
- if
- Level =:= none ->
- ok;
- is_record(Data, error) ->
- do_print_debug_info(Config, Who, Where, Data);
- Level =:= warning ->
- ok;
- Level =:= error ->
- ok;
- Level =:= all ->
- do_print_debug_info(Config, Who, Where, Data);
- Where =:= open ->
- do_print_debug_info(Config, Who, Where, Data);
- Where =:= close ->
- do_print_debug_info(Config, Who, Where, Data);
- Level =:= brief ->
- ok;
- Where =/= recv, Where =/= send ->
- ok;
- is_record(Data, tftp_msg_data), Level =:= normal ->
- ok;
- is_record(Data, tftp_msg_ack), Level =:= normal ->
- ok;
- true ->
- do_print_debug_info(Config, Who, Where, Data)
- end.
-
-do_print_debug_info(Config, Who, Where, #tftp_msg_data{data = Bin} = Msg) when is_binary(Bin) ->
- Msg2 = Msg#tftp_msg_data{data = {bytes, size(Bin)}},
- do_print_debug_info(Config, Who, Where, Msg2);
-do_print_debug_info(Config, Who, Where, #tftp_msg_req{local_filename = Filename} = Msg) when is_binary(Filename) ->
- Msg2 = Msg#tftp_msg_req{local_filename = binary},
- do_print_debug_info(Config, Who, Where, Msg2);
-do_print_debug_info(Config, Who, Where, Data) ->
- Local =
- case catch inet:port(Config#config.udp_socket) of
- {'EXIT', _Reason} ->
- 0;
- {ok, Port} ->
- Port
- end,
- %% Remote = Config#config.udp_port,
- PeerInfo = lists:flatten(io_lib:format("~p", [peer_info(Config)])),
- Side =
- if
- is_record(Who, tftp_msg_req),
- Who#tftp_msg_req.local_filename =/= undefined ->
- client;
- is_record(Who, tftp_msg_req),
- Who#tftp_msg_req.local_filename =:= undefined ->
- server;
- is_atom(Who) ->
- Who
- end,
- case {Where, Data} of
- {_, #error{where = Where, code = Code, text = Text, filename = Filename}} ->
- do_format(Config, Side, Local, "error ~s ->\n\t~p ~p\n\t~p ~p: ~s\n",
- [PeerInfo, self(), Filename, Where, Code, Text]);
- {open, #tftp_msg_req{filename = Filename}} ->
- do_format(Config, Side, Local, "open ~s ->\n\t~p ~p\n",
- [PeerInfo, self(), Filename]);
- {close, #tftp_msg_req{filename = Filename}} ->
- do_format(Config, Side, Local, "close ~s ->\n\t~p ~p\n",
- [PeerInfo, self(), Filename]);
- {recv, _} ->
- do_format(Config, Side, Local, "recv ~s <-\n\t~p\n",
- [PeerInfo, Data]);
- {send, _} ->
- do_format(Config, Side, Local, "send ~s ->\n\t~p\n",
- [PeerInfo, Data]);
- {match, _} when is_record(Data, callback) ->
- Mod = Data#callback.module,
- State = Data#callback.state,
- do_format(Config, Side, Local, "match ~s ~p =>\n\t~p\n",
- [PeerInfo, Mod, State]);
- {call, _} ->
- case Data of
- {Callback, _Result} when is_record(Callback, callback) ->
- Mod = Callback#callback.module,
- State = Callback#callback.state,
- do_format(Config, Side, Local, "call ~s ~p =>\n\t~p\n",
- [PeerInfo, Mod, State]);
- {undefined, Result} ->
- do_format(Config, Side, Local, "call ~s result =>\n\t~p\n",
- [PeerInfo, Result])
- end
- end.
-
-do_format(Config, Side, Local, Format, Args) ->
- info_msg(Config, "~p(~p): " ++ Format, [Side, Local | Args]).
-
-%%-------------------------------------------------------------------
-%% System upgrade
-%%-------------------------------------------------------------------
-
-system_continue(_Parent, _Debug, #sys_misc{module = Mod, function = Fun, arguments = Args}) ->
- apply(Mod, Fun, Args);
-system_continue(Parent, Debug, {Fun, Args}) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- system_continue(Parent, Debug, #sys_misc{module = ?MODULE, function = Fun, arguments = Args}).
-
--spec system_terminate(_, _, _, #sys_misc{} | {_, _}) -> no_return().
-
-system_terminate(Reason, _Parent, _Debug, #sys_misc{}) ->
- exit(Reason);
-system_terminate(Reason, Parent, Debug, {Fun, Args}) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- system_terminate(Reason, Parent, Debug, #sys_misc{module = ?MODULE, function = Fun, arguments = Args}).
-
-system_code_change({Fun, Args}, _Module, _OldVsn, _Extra) ->
- {ok, {Fun, Args}}.
diff --git a/lib/inets/src/tftp/tftp_file.erl b/lib/inets/src/tftp/tftp_file.erl
deleted file mode 100644
index 7664324808..0000000000
--- a/lib/inets/src/tftp/tftp_file.erl
+++ /dev/null
@@ -1,390 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
-
-%%%-------------------------------------------------------------------
-%%% File : tft_file.erl
-%%% Author : Hakan Mattsson <[email protected]>
-%%% Description :
-%%%
-%%% Created : 24 May 2004 by Hakan Mattsson <[email protected]>
-%%%-------------------------------------------------------------------
-
--module(tftp_file).
-
-%%%-------------------------------------------------------------------
-%%% Interface
-%%%-------------------------------------------------------------------
-
--behaviour(tftp).
-
--export([prepare/6, open/6, read/1, write/2, abort/3]).
-
-%%%-------------------------------------------------------------------
-%%% Defines
-%%%-------------------------------------------------------------------
-
--include_lib("kernel/include/file.hrl").
-
--record(initial,
- {filename,
- is_native_ascii}).
-
--record(state,
- {access,
- filename,
- is_native_ascii,
- is_network_ascii,
- root_dir,
- options,
- blksize,
- fd,
- count,
- buffer}).
-
-%%-------------------------------------------------------------------
-%% prepare(Peer, Access, Filename, Mode, SuggestedOptions, InitialState) ->
-%% {ok, AcceptedOptions, NewState} | {error, Code, Text}
-%%
-%% Peer = {PeerType, PeerHost, PeerPort}
-%% PeerType = inet | inet6
-%% PeerHost = ip_address()
-%% PeerPort = integer()
-%% Acess = read | write
-%% Filename = string()
-%% Mode = string()
-%% SuggestedOptions = [{Key, Value}]
-%% AcceptedOptions = [{Key, Value}]
-%% Key = string()
-%% Value = string()
-%% InitialState = [] | [{root_dir, string()}]
-%% NewState = term()
-%% Code = undef | enoent | eacces | enospc |
-%% badop | eexist | baduser | badopt |
-%% integer()
-%% Text = string()
-%%
-%% Prepares open of a file on the client side.
-%%
-%% Will be followed by a call to open/4 before any read/write access
-%% is performed. The AcceptedOptions will be sent to the server which
-%% will reply with those options that it accepts. The options that are
-%% accepted by the server will be forwarded to open/4 as SuggestedOptions.
-%%
-%% No new options may be added, but the ones that are present as
-%% SuggestedOptions may be omitted or replaced with new values
-%% in the AcceptedOptions.
-%%-------------------------------------------------------------------
-
-prepare(_Peer, Access, Filename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
- %% Client side
- case catch handle_options(Access, Filename, Mode, SuggestedOptions, Initial) of
- {ok, Filename2, IsNativeAscii, IsNetworkAscii, AcceptedOptions} ->
- State = #state{access = Access,
- filename = Filename2,
- is_native_ascii = IsNativeAscii,
- is_network_ascii = IsNetworkAscii,
- options = AcceptedOptions,
- blksize = lookup_blksize(AcceptedOptions),
- count = 0,
- buffer = []},
- {ok, AcceptedOptions, State};
- {error, {Code, Text}} ->
- {error, {Code, Text}}
- end.
-
-%% ---------------------------------------------------------
-%% open(Peer, Access, Filename, Mode, SuggestedOptions, State) ->
-%% {ok, AcceptedOptions, NewState} | {error, Code, Text}
-%%
-%% Peer = {PeerType, PeerHost, PeerPort}
-%% PeerType = inet | inet6
-%% PeerHost = ip_address()
-%% PeerPort = integer()
-%% Acess = read | write
-%% Filename = string()
-%% Mode = string()
-%% SuggestedOptions = [{Key, Value}]
-%% AcceptedOptions = [{Key, Value}]
-%% Key = string()
-%% Value = string()
-%% State = InitialState | #state{}
-%% InitialState = [] | [{root_dir, string()}]
-%% NewState = term()
-%% Code = undef | enoent | eacces | enospc |
-%% badop | eexist | baduser | badopt |
-%% integer()
-%% Text = string()
-%%
-%% Opens a file for read or write access.
-%%
-%% On the client side where the open/4 call has been preceeded by a
-%% call to prepare/4, all options must be accepted or rejected.
-%% On the server side, where there are no preceeding prepare/4 call,
-%% noo new options may be added, but the ones that are present as
-%% SuggestedOptions may be omitted or replaced with new values
-%% in the AcceptedOptions.
-%%-------------------------------------------------------------------
-
-open(Peer, Access, Filename, Mode, SuggestedOptions, Initial) when is_list(Initial) ->
- %% Server side
- case prepare(Peer, Access, Filename, Mode, SuggestedOptions, Initial) of
- {ok, AcceptedOptions, State} ->
- open(Peer, Access, Filename, Mode, AcceptedOptions, State);
- {error, {Code, Text}} ->
- {error, {Code, Text}}
- end;
-open(_Peer, Access, Filename, Mode, NegotiatedOptions, State) when is_record(State, state) ->
- %% Both sides
- case catch handle_options(Access, Filename, Mode, NegotiatedOptions, State) of
- {ok, _Filename2, _IsNativeAscii, _IsNetworkAscii, Options}
- when Options =:= NegotiatedOptions ->
- do_open(State);
- {error, {Code, Text}} ->
- {error, {Code, Text}}
- end;
-open(Peer, Access, Filename, Mode, NegotiatedOptions, State) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- State2 = upgrade_state(State),
- open(Peer, Access, Filename, Mode, NegotiatedOptions, State2).
-
-do_open(State) when is_record(State, state) ->
- case file:open(State#state.filename, file_options(State)) of
- {ok, Fd} ->
- {ok, State#state.options, State#state{fd = Fd}};
- {error, Reason} when is_atom(Reason) ->
- {error, file_error(Reason)}
- end.
-
-file_options(State) ->
- case State#state.access of
- read -> [read, read_ahead, raw, binary];
- write -> [write, delayed_write, raw, binary]
- end.
-
-file_error(Reason) when is_atom(Reason) ->
- Details = file:format_error(Reason),
- case Reason of
- eexist -> {Reason, Details};
- enoent -> {Reason, Details};
- eacces -> {Reason, Details};
- eperm -> {eacces, Details};
- enospc -> {Reason, Details};
- _ -> {undef, Details ++ " (" ++ atom_to_list(Reason) ++ ")"}
- end.
-
-%%-------------------------------------------------------------------
-%% read(State) ->
-%% {more, Bin, NewState} | {last, Bin, FileSize} | {error, {Code, Text}}
-%%
-%% State = term()
-%% NewState = term()
-%% Bin = binary()
-%% FileSize = integer()
-%% Code = undef | enoent | eacces | enospc |
-%% badop | eexist | baduser | badopt |
-%% integer()
-%% Text = string()
-%%
-%% Reads a chunk from the file
-%%
-%% The file is automatically closed when the last chunk is read.
-%%-------------------------------------------------------------------
-
-read(#state{access = read} = State) ->
- BlkSize = State#state.blksize,
- case file:read(State#state.fd, BlkSize) of
- {ok, Bin} when is_binary(Bin), size(Bin) =:= BlkSize ->
- Count = State#state.count + size(Bin),
- {more, Bin, State#state{count = Count}};
- {ok, Bin} when is_binary(Bin), size(Bin) < BlkSize ->
- file:close(State#state.fd),
- Count = State#state.count + size(Bin),
- {last, Bin, Count};
- eof ->
- {last, <<>>, State#state.count};
- {error, Reason} ->
- file:close(State#state.fd),
- {error, file_error(Reason)}
- end;
-read(State) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- State2 = upgrade_state(State),
- read(State2).
-
-%%-------------------------------------------------------------------
-%% write(Bin, State) ->
-%% {more, NewState} | {last, FileSize} | {error, {Code, Text}}
-%%
-%% State = term()
-%% NewState = term()
-%% Bin = binary()
-%% FileSize = integer()
-%% Code = undef | enoent | eacces | enospc |
-%% badop | eexist | baduser | badopt |
-%% integer()
-%% Text = string()
-%%
-%% Writes a chunk to the file
-%%
-%% The file is automatically closed when the last chunk is written
-%%-------------------------------------------------------------------
-
-write(Bin, #state{access = write} = State) when is_binary(Bin) ->
- Size = size(Bin),
- BlkSize = State#state.blksize,
- case file:write(State#state.fd, Bin) of
- ok when Size =:= BlkSize->
- Count = State#state.count + Size,
- {more, State#state{count = Count}};
- ok when Size < BlkSize->
- file:close(State#state.fd),
- Count = State#state.count + Size,
- {last, Count};
- {error, Reason} ->
- file:close(State#state.fd),
- file:delete(State#state.filename),
- {error, file_error(Reason)}
- end;
-write(Bin, State) ->
- %% Handle upgrade from old releases. Please, remove this clause in next release.
- State2 = upgrade_state(State),
- write(Bin, State2).
-
-%%-------------------------------------------------------------------
-%% abort(Code, Text, State) -> ok
-%%
-%% State = term()
-%% Code = undef | enoent | eacces | enospc |
-%% badop | eexist | baduser | badopt |
-%% badblk | integer()
-%% Text = string()
-%%
-%% Aborts the file transfer
-%%-------------------------------------------------------------------
-
-abort(_Code, _Text, #state{fd = Fd, access = Access} = State) ->
- file:close(Fd),
- case Access of
- write ->
- ok = file:delete(State#state.filename);
- read ->
- ok
- end.
-
-%%-------------------------------------------------------------------
-%% Process options
-%%-------------------------------------------------------------------
-
-handle_options(Access, Filename, Mode, Options, Initial) ->
- I = #initial{filename = Filename, is_native_ascii = is_native_ascii()},
- {Filename2, IsNativeAscii} = handle_initial(Initial, I),
- IsNetworkAscii = handle_mode(Mode, IsNativeAscii),
- Options2 = do_handle_options(Access, Filename2, Options),
- {ok, Filename2, IsNativeAscii, IsNetworkAscii, Options2}.
-
-handle_mode(Mode, IsNativeAscii) ->
- case Mode of
- "netascii" when IsNativeAscii =:= true -> true;
- "octet" -> false;
- _ -> throw({error, {badop, "Illegal mode " ++ Mode}})
- end.
-
-handle_initial([{root_dir, Dir} | Initial], I) ->
- case catch filename_join(Dir, I#initial.filename) of
- {'EXIT', _} ->
- throw({error, {badop, "Internal error. root_dir is not a string"}});
- Filename2 ->
- handle_initial(Initial, I#initial{filename = Filename2})
- end;
-handle_initial([{native_ascii, Bool} | Initial], I) ->
- case Bool of
- true -> handle_initial(Initial, I#initial{is_native_ascii = true});
- false -> handle_initial(Initial, I#initial{is_native_ascii = false})
- end;
-handle_initial([], I) when is_record(I, initial) ->
- {I#initial.filename, I#initial.is_native_ascii};
-handle_initial(State, _) when is_record(State, state) ->
- {State#state.filename, State#state.is_native_ascii}.
-
-filename_join(Dir, Filename) ->
- case filename:pathtype(Filename) of
- absolute ->
- [_ | RelFilename] = filename:split(Filename),
- filename:join([Dir, RelFilename]);
- _ ->
- filename:join([Dir, Filename])
- end.
-
-do_handle_options(Access, Filename, [{Key, Val} | T]) ->
- case Key of
- "tsize" ->
- case Access of
- read when Val =:= "0" ->
- case file:read_file_info(Filename) of
- {ok, FI} ->
- Tsize = integer_to_list(FI#file_info.size),
- [{Key, Tsize} | do_handle_options(Access, Filename, T)];
- {error, _} ->
- do_handle_options(Access, Filename, T)
- end;
- _ ->
- handle_integer(Access, Filename, Key, Val, T, 0, infinity)
- end;
- "blksize" ->
- handle_integer(Access, Filename, Key, Val, T, 8, 65464);
- "timeout" ->
- handle_integer(Access, Filename, Key, Val, T, 1, 255);
- _ ->
- do_handle_options(Access, Filename, T)
- end;
-do_handle_options(_Access, _Filename, []) ->
- [].
-
-
-handle_integer(Access, Filename, Key, Val, Options, Min, Max) ->
- case catch list_to_integer(Val) of
- {'EXIT', _} ->
- do_handle_options(Access, Filename, Options);
- Int when Int >= Min, Int =< Max ->
- [{Key, Val} | do_handle_options(Access, Filename, Options)];
- Int when Int >= Min, Max =:= infinity ->
- [{Key, Val} | do_handle_options(Access, Filename, Options)];
- _Int ->
- throw({error, {badopt, "Illegal " ++ Key ++ " value " ++ Val}})
- end.
-
-lookup_blksize(Options) ->
- case lists:keysearch("blksize", 1, Options) of
- {value, {_, Val}} ->
- list_to_integer(Val);
- false ->
- 512
- end.
-
-is_native_ascii() ->
- case os:type() of
- {win32, _} -> true;
- _ -> false
- end.
-
-%% Handle upgrade from old releases. Please, remove this function in next release.
-upgrade_state({state, Access, Filename, RootDir, Options, BlkSize, Fd, Count, Buffer}) ->
- {state, Access, Filename, false, false, RootDir, Options, BlkSize, Fd, Count, Buffer}.
diff --git a/lib/inets/src/tftp/tftp_lib.erl b/lib/inets/src/tftp/tftp_lib.erl
deleted file mode 100644
index 454754f0a3..0000000000
--- a/lib/inets/src/tftp/tftp_lib.erl
+++ /dev/null
@@ -1,474 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
-
-%%%-------------------------------------------------------------------
-%%% File : tftp_lib.erl
-%%% Author : Hakan Mattsson <[email protected]>
-%%% Description : Option parsing, decode, encode etc.
-%%%
-%%% Created : 18 May 2004 by Hakan Mattsson <[email protected]>
-%%%-------------------------------------------------------------------
-
--module(tftp_lib).
-
-%%-------------------------------------------------------------------
-%% Interface
-%%-------------------------------------------------------------------
-
-%% application internal functions
--export([
- parse_config/1,
- parse_config/2,
- decode_msg/1,
- encode_msg/1,
- replace_val/3,
- to_lower/1,
- host_to_string/1,
- add_default_callbacks/1
- ]).
-
-%%-------------------------------------------------------------------
-%% Defines
-%%-------------------------------------------------------------------
-
--include("tftp.hrl").
-
--define(LOWER(Char),
- if
- Char >= $A, Char =< $Z ->
- Char - ($A - $a);
- true ->
- Char
- end).
-
-%%-------------------------------------------------------------------
-%% Config
-%%-------------------------------------------------------------------
-
-parse_config(Options) ->
- parse_config(Options, #config{}).
-
-parse_config(Options, Config) ->
- do_parse_config(Options, Config).
-
-do_parse_config([{Key, Val} | Tail], Config) when is_record(Config, config) ->
- case Key of
- debug ->
- if
- Val =:= 0; Val =:= none ->
- do_parse_config(Tail, Config#config{debug_level = none});
- Val =:= 1; Val =:= error ->
- do_parse_config(Tail, Config#config{debug_level = error});
- Val =:= 2; Val =:= warning ->
- do_parse_config(Tail, Config#config{debug_level = warning});
- Val =:= 3; Val =:= brief ->
- do_parse_config(Tail, Config#config{debug_level = brief});
- Val =:= 4; Val =:= normal ->
- do_parse_config(Tail, Config#config{debug_level = normal});
- Val =:= 5; Val =:= verbose ->
- do_parse_config(Tail, Config#config{debug_level = verbose});
- Val =:= 6; Val =:= all ->
- do_parse_config(Tail, Config#config{debug_level = all});
- true ->
- exit({badarg, {Key, Val}})
- end;
- host ->
- if
- is_list(Val) ->
- do_parse_config(Tail, Config#config{udp_host = Val});
- is_tuple(Val), size(Val) =:= 4 ->
- do_parse_config(Tail, Config#config{udp_host = Val});
- is_tuple(Val), size(Val) =:= 8 ->
- do_parse_config(Tail, Config#config{udp_host = Val});
- true ->
- exit({badarg, {Key, Val}})
- end;
- port ->
- if
- is_integer(Val), Val >= 0 ->
- Config2 = Config#config{udp_port = Val, udp_options = Config#config.udp_options},
- do_parse_config(Tail, Config2);
- true ->
- exit({badarg, {Key, Val}})
- end;
- port_policy ->
- case Val of
- random ->
- do_parse_config(Tail, Config#config{port_policy = Val});
- 0 ->
- do_parse_config(Tail, Config#config{port_policy = random});
- MinMax when is_integer(MinMax), MinMax > 0 ->
- do_parse_config(Tail, Config#config{port_policy = {range, MinMax, MinMax}});
- {range, Min, Max} when Max >= Min,
- is_integer(Min), Min > 0,
- is_integer(Max), Max > 0 ->
- do_parse_config(Tail, Config#config{port_policy = Val});
- true ->
- exit({badarg, {Key, Val}})
- end;
- udp when is_list(Val) ->
- Fun =
- fun({K, V}, List) when K /= active ->
- replace_val(K, V, List);
- (V, List) when V /= list, V /= binary ->
- List ++ [V];
- (V, _List) ->
- exit({badarg, {udp, [V]}})
- end,
- UdpOptions = lists:foldl(Fun, Config#config.udp_options, Val),
- do_parse_config(Tail, Config#config{udp_options = UdpOptions});
- use_tsize ->
- case Val of
- true ->
- do_parse_config(Tail, Config#config{use_tsize = Val});
- false ->
- do_parse_config(Tail, Config#config{use_tsize = Val});
- _ ->
- exit({badarg, {Key, Val}})
- end;
- max_tsize ->
- if
- Val =:= infinity ->
- do_parse_config(Tail, Config#config{max_tsize = Val});
- is_integer(Val), Val >= 0 ->
- do_parse_config(Tail, Config#config{max_tsize = Val});
- true ->
- exit({badarg, {Key, Val}})
- end;
- max_conn ->
- if
- Val =:= infinity ->
- do_parse_config(Tail, Config#config{max_conn = Val});
- is_integer(Val), Val > 0 ->
- do_parse_config(Tail, Config#config{max_conn = Val});
- true ->
- exit({badarg, {Key, Val}})
- end;
- _ when is_list(Key), is_list(Val) ->
- Key2 = to_lower(Key),
- Val2 = to_lower(Val),
- TftpOptions = replace_val(Key2, Val2, Config#config.user_options),
- do_parse_config(Tail, Config#config{user_options = TftpOptions});
- reject ->
- case Val of
- read ->
- Rejected = [Val | Config#config.rejected],
- do_parse_config(Tail, Config#config{rejected = Rejected});
- write ->
- Rejected = [Val | Config#config.rejected],
- do_parse_config(Tail, Config#config{rejected = Rejected});
- _ when is_list(Val) ->
- Rejected = [Val | Config#config.rejected],
- do_parse_config(Tail, Config#config{rejected = Rejected});
- _ ->
- exit({badarg, {Key, Val}})
- end;
- callback ->
- case Val of
- {RegExp, Mod, State} when is_list(RegExp), is_atom(Mod) ->
- case re:compile(RegExp) of
- {ok, Internal} ->
- Callback = #callback{regexp = RegExp,
- internal = Internal,
- module = Mod,
- state = State},
- Callbacks = Config#config.callbacks ++ [Callback],
- do_parse_config(Tail, Config#config{callbacks = Callbacks});
- {error, Reason} ->
- exit({badarg, {Key, Val}, Reason})
- end;
- _ ->
- exit({badarg, {Key, Val}})
- end;
- logger ->
- if
- is_atom(Val) ->
- do_parse_config(Tail, Config#config{logger = Val});
- true ->
- exit({badarg, {Key, Val}})
- end;
- max_retries ->
- if
- is_integer(Val), Val > 0 ->
- do_parse_config(Tail, Config#config{max_retries = Val});
- true ->
- exit({badarg, {Key, Val}})
- end;
- _ ->
- exit({badarg, {Key, Val}})
- end;
-do_parse_config([], #config{udp_host = Host,
- udp_options = UdpOptions,
- user_options = UserOptions,
- callbacks = Callbacks} = Config) ->
- IsInet6 = lists:member(inet6, UdpOptions),
- IsInet = lists:member(inet, UdpOptions),
- Host2 =
- if
- (IsInet and not IsInet6); (not IsInet and not IsInet6) ->
- case inet:getaddr(Host, inet) of
- {ok, Addr} ->
- Addr;
- {error, Reason} ->
- exit({badarg, {host, Reason}})
- end;
- (IsInet6 and not IsInet) ->
- case inet:getaddr(Host, inet6) of
- {ok, Addr} ->
- Addr;
- {error, Reason} ->
- exit({badarg, {host, Reason}})
- end;
- true ->
- %% Conflicting options
- exit({badarg, {udp, [inet]}})
- end,
- UdpOptions2 = lists:reverse(UdpOptions),
- TftpOptions = lists:reverse(UserOptions),
- Callbacks2 = add_default_callbacks(Callbacks),
- Config#config{udp_host = Host2,
- udp_options = UdpOptions2,
- user_options = TftpOptions,
- callbacks = Callbacks2};
-do_parse_config(Options, Config) when is_record(Config, config) ->
- exit({badarg, Options}).
-
-add_default_callbacks(Callbacks) ->
- RegExp = "",
- {ok, Internal} = re:compile(RegExp),
- File = #callback{regexp = RegExp,
- internal = Internal,
- module = tftp_file,
- state = []},
- Bin = #callback{regexp = RegExp,
- internal = Internal,
- module = tftp_binary,
- state = []},
- Callbacks ++ [File, Bin].
-
-host_to_string(Host) ->
- case Host of
- String when is_list(String) ->
- String;
- {A1, A2, A3, A4} -> % inet
- lists:concat([A1, ".", A2, ".", A3, ".",A4]);
- {A1, A2, A3, A4, A5, A6, A7, A8} -> % inet6
- lists:concat([
- int16_to_hex(A1), "::",
- int16_to_hex(A2), "::",
- int16_to_hex(A3), "::",
- int16_to_hex(A4), "::",
- int16_to_hex(A5), "::",
- int16_to_hex(A6), "::",
- int16_to_hex(A7), "::",
- int16_to_hex(A8)
- ])
- end.
-
-int16_to_hex(0) ->
- [$0];
-int16_to_hex(I) ->
- N1 = ((I bsr 8) band 16#ff),
- N2 = (I band 16#ff),
- [code_character(N1 div 16), code_character(N1 rem 16),
- code_character(N2 div 16), code_character(N2 rem 16)].
-
-code_character(N) when N < 10 ->
- $0 + N;
-code_character(N) ->
- $A + (N - 10).
-
-%%-------------------------------------------------------------------
-%% Decode
-%%-------------------------------------------------------------------
-
-decode_msg(Bin) when is_binary(Bin) ->
- case Bin of
- <<?TFTP_OPCODE_RRQ:16/integer, Tail/binary>> ->
- case decode_strings(Tail, [keep_case, lower_case]) of
- [Filename, Mode | Strings] ->
- Options = decode_options(Strings),
- #tftp_msg_req{access = read,
- filename = Filename,
- mode = to_lower(Mode),
- options = Options};
- [_Filename | _Strings] ->
- exit(#tftp_msg_error{code = undef,
- text = "Missing mode"});
- _ ->
- exit(#tftp_msg_error{code = undef,
- text = "Missing filename"})
- end;
- <<?TFTP_OPCODE_WRQ:16/integer, Tail/binary>> ->
- case decode_strings(Tail, [keep_case, lower_case]) of
- [Filename, Mode | Strings] ->
- Options = decode_options(Strings),
- #tftp_msg_req{access = write,
- filename = Filename,
- mode = to_lower(Mode),
- options = Options};
- [_Filename | _Strings] ->
- exit(#tftp_msg_error{code = undef,
- text = "Missing mode"});
- _ ->
- exit(#tftp_msg_error{code = undef,
- text = "Missing filename"})
- end;
- <<?TFTP_OPCODE_DATA:16/integer, SeqNo:16/integer, Data/binary>> ->
- #tftp_msg_data{block_no = SeqNo, data = Data};
- <<?TFTP_OPCODE_ACK:16/integer, SeqNo:16/integer>> ->
- #tftp_msg_ack{block_no = SeqNo};
- <<?TFTP_OPCODE_ERROR:16/integer, ErrorCode:16/integer, Tail/binary>> ->
- case decode_strings(Tail, [keep_case]) of
- [ErrorText] ->
- ErrorCode2 = decode_error_code(ErrorCode),
- #tftp_msg_error{code = ErrorCode2,
- text = ErrorText};
- _ ->
- exit(#tftp_msg_error{code = undef,
- text = "Trailing garbage"})
- end;
- <<?TFTP_OPCODE_OACK:16/integer, Tail/binary>> ->
- Strings = decode_strings(Tail, [lower_case]),
- Options = decode_options(Strings),
- #tftp_msg_oack{options = Options};
- _ ->
- exit(#tftp_msg_error{code = undef,
- text = "Invalid syntax"})
- end.
-
-decode_strings(Bin, Cases) when is_binary(Bin), is_list(Cases) ->
- do_decode_strings(Bin, Cases, []).
-
-do_decode_strings(<<>>, _Cases, Strings) ->
- lists:reverse(Strings);
-do_decode_strings(Bin, [Case | Cases], Strings) ->
- {String, Tail} = decode_string(Bin, Case, []),
- if
- Cases =:= [] ->
- do_decode_strings(Tail, [Case], [String | Strings]);
- true ->
- do_decode_strings(Tail, Cases, [String | Strings])
- end.
-
-decode_string(<<Char:8/integer, Tail/binary>>, Case, String) ->
- if
- Char =:= 0 ->
- {lists:reverse(String), Tail};
- Case =:= keep_case ->
- decode_string(Tail, Case, [Char | String]);
- Case =:= lower_case ->
- Char2 = ?LOWER(Char),
- decode_string(Tail, Case, [Char2 | String])
- end;
-decode_string(<<>>, _Case, _String) ->
- exit(#tftp_msg_error{code = undef, text = "Trailing null missing"}).
-
-decode_options([Key, Value | Strings]) ->
- [{to_lower(Key), Value} | decode_options(Strings)];
-decode_options([]) ->
- [].
-
-decode_error_code(Int) ->
- case Int of
- ?TFTP_ERROR_UNDEF -> undef;
- ?TFTP_ERROR_ENOENT -> enoent;
- ?TFTP_ERROR_EACCES -> eacces;
- ?TFTP_ERROR_ENOSPC -> enospc;
- ?TFTP_ERROR_BADOP -> badop;
- ?TFTP_ERROR_BADBLK -> badblk;
- ?TFTP_ERROR_EEXIST -> eexist;
- ?TFTP_ERROR_BADUSER -> baduser;
- ?TFTP_ERROR_BADOPT -> badopt;
- Int when is_integer(Int), Int >= 0, Int =< 65535 -> Int;
- _ -> exit(#tftp_msg_error{code = undef, text = "Error code outside range."})
- end.
-
-%%-------------------------------------------------------------------
-%% Encode
-%%-------------------------------------------------------------------
-
-encode_msg(#tftp_msg_req{access = Access,
- filename = Filename,
- mode = Mode,
- options = Options}) ->
- OpCode = case Access of
- read -> ?TFTP_OPCODE_RRQ;
- write -> ?TFTP_OPCODE_WRQ
- end,
- [
- <<OpCode:16/integer>>,
- Filename,
- 0,
- Mode,
- 0,
- [[Key, 0, Val, 0] || {Key, Val} <- Options]
- ];
-encode_msg(#tftp_msg_data{block_no = BlockNo, data = Data}) when BlockNo =< 65535 ->
- [
- <<?TFTP_OPCODE_DATA:16/integer, BlockNo:16/integer>>,
- Data
- ];
-encode_msg(#tftp_msg_ack{block_no = BlockNo}) when BlockNo =< 65535 ->
- <<?TFTP_OPCODE_ACK:16/integer, BlockNo:16/integer>>;
-encode_msg(#tftp_msg_error{code = Code, text = Text}) ->
- IntCode = encode_error_code(Code),
- [
- <<?TFTP_OPCODE_ERROR:16/integer, IntCode:16/integer>>,
- Text,
- 0
- ];
-encode_msg(#tftp_msg_oack{options = Options}) ->
- [
- <<?TFTP_OPCODE_OACK:16/integer>>,
- [[Key, 0, Val, 0] || {Key, Val} <- Options]
- ].
-
-encode_error_code(Code) ->
- case Code of
- undef -> ?TFTP_ERROR_UNDEF;
- enoent -> ?TFTP_ERROR_ENOENT;
- eacces -> ?TFTP_ERROR_EACCES;
- enospc -> ?TFTP_ERROR_ENOSPC;
- badop -> ?TFTP_ERROR_BADOP;
- badblk -> ?TFTP_ERROR_BADBLK;
- eexist -> ?TFTP_ERROR_EEXIST;
- baduser -> ?TFTP_ERROR_BADUSER;
- badopt -> ?TFTP_ERROR_BADOPT;
- Int when is_integer(Int), Int >= 0, Int =< 65535 -> Int
- end.
-
-%%-------------------------------------------------------------------
-%% Miscellaneous
-%%-------------------------------------------------------------------
-
-replace_val(Key, Val, List) ->
- case lists:keysearch(Key, 1, List) of
- false ->
- List ++ [{Key, Val}];
- {value, {_, OldVal}} when OldVal =:= Val ->
- List;
- {value, {_, _}} ->
- lists:keyreplace(Key, 1, List, {Key, Val})
- end.
-
-to_lower(Chars) ->
- [?LOWER(Char) || Char <- Chars].
diff --git a/lib/inets/src/tftp/tftp_logger.erl b/lib/inets/src/tftp/tftp_logger.erl
deleted file mode 100644
index a869958484..0000000000
--- a/lib/inets/src/tftp/tftp_logger.erl
+++ /dev/null
@@ -1,99 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
--module(tftp_logger).
-
-%%-------------------------------------------------------------------
-%% Interface
-%%-------------------------------------------------------------------
-
-%% public functions
--export([
- error_msg/2,
- warning_msg/2,
- info_msg/2
- ]).
-
--export([behaviour_info/1]).
-
-behaviour_info(callbacks) ->
- [{error_msg, 2}, {warning_msg, 2}, {info_msg, 2}];
-behaviour_info(_) ->
- undefined.
-
-%%-------------------------------------------------------------------
-%% error_msg(Format, Data) -> ok | exit(Reason)
-%%
-%% Format = string()
-%% Data = [term()]
-%% Reason = term()
-%%
-%% Log an error message
-%%-------------------------------------------------------------------
-
-error_msg(Format, Data) ->
- {Format2, Data2} = add_timestamp(Format, Data),
- error_logger:error_msg(Format2, Data2).
-
-%%-------------------------------------------------------------------
-%% warning_msg(Format, Data) -> ok | exit(Reason)
-%%
-%% Format = string()
-%% Data = [term()]
-%% Reason = term()
-%%
-%% Log a warning message
-%%-------------------------------------------------------------------
-
-warning_msg(Format, Data) ->
- {Format2, Data2} = add_timestamp(Format, Data),
- error_logger:warning_msg(Format2, Data2).
-
-%%-------------------------------------------------------------------
-%% info_msg(Format, Data) -> ok | exit(Reason)
-%%
-%% Format = string()
-%% Data = [term()]
-%% Reason = term()
-%%
-%% Log an info message
-%%-------------------------------------------------------------------
-
-info_msg(Format, Data) ->
- {Format2, Data2} = add_timestamp(Format, Data),
- io:format(Format2, Data2).
-
-%%-------------------------------------------------------------------
-%% Add timestamp to log message
-%%-------------------------------------------------------------------
-
-add_timestamp(Format, Data) ->
- Time = erlang:timestamp(),
- {{_Y, _Mo, _D}, {H, Mi, S}} = calendar:now_to_universal_time(Time),
- %% {"~p-~s-~sT~s:~s:~sZ,~6.6.0w tftp: " ++ Format ++ "\n",
- %% [Y, t(Mo), t(D), t(H), t(Mi), t(S), MicroSecs | Data]}.
- {"~s:~s:~s tftp: " ++ Format, [t(H), t(Mi), t(S) | Data]}.
-
-%% Convert 9 to "09".
-t(Int) ->
- case integer_to_list(Int) of
- [Single] -> [$0, Single];
- Multi -> Multi
- end.
diff --git a/lib/inets/src/tftp/tftp_sup.erl b/lib/inets/src/tftp/tftp_sup.erl
deleted file mode 100644
index 40b67c499c..0000000000
--- a/lib/inets/src/tftp/tftp_sup.erl
+++ /dev/null
@@ -1,111 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2016. All Rights Reserved.
-%%
-%% Licensed under the Apache License, Version 2.0 (the "License");
-%% you may not use this file except in compliance with the License.
-%% You may obtain a copy of the License at
-%%
-%% http://www.apache.org/licenses/LICENSE-2.0
-%%
-%% Unless required by applicable law or agreed to in writing, software
-%% distributed under the License is distributed on an "AS IS" BASIS,
-%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-%% See the License for the specific language governing permissions and
-%% limitations under the License.
-%%
-%% %CopyrightEnd%
-%%
-%%
-%%----------------------------------------------------------------------
-%% Purpose: The top supervisor for tftp hangs under inets_sup.
-%%----------------------------------------------------------------------
-
--module(tftp_sup).
-
--behaviour(supervisor).
-
-%% API
--export([start_link/1,
- start_child/1,
- stop_child/1,
- which_children/0]).
-
-%% Supervisor callback
--export([init/1]).
-
-%%%=========================================================================
-%%% API
-%%%=========================================================================
-
-start_link(TftpServices) ->
- supervisor:start_link({local, ?MODULE}, ?MODULE, [TftpServices]).
-
-start_child(Options) ->
- KillAfter = default_kill_after(),
- ChildSpec = worker_spec(KillAfter, Options),
- supervisor:start_child(?MODULE, ChildSpec).
-
-stop_child(Pid) when is_pid(Pid) ->
- Children = supervisor:which_children(?MODULE),
- case [Id || {Id, P, _Type, _Modules} <- Children, P =:= Pid] of
- [] ->
- {error, not_found};
- [Id] ->
- case supervisor:terminate_child(?MODULE, Id) of
- ok ->
- supervisor:delete_child(?MODULE, Id);
- {error, not_found} ->
- supervisor:delete_child(?MODULE, Id);
- {error, Reason} ->
- {error, Reason}
- end
- end.
-
-which_children() ->
- Children = supervisor:which_children(?MODULE),
- [{tftpd, Pid} || {_Id, Pid, _Type, _Modules} <- Children, Pid =/= undefined].
-
-%%%=========================================================================
-%%% Supervisor callback
-%%%=========================================================================
-
-init([Services]) when is_list(Services) ->
- RestartStrategy = one_for_one,
- MaxR = 10,
- MaxT = 3600,
- KillAfter = default_kill_after(),
- Children = [worker_spec(KillAfter, Options) || {tftpd, Options} <- Services],
- {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
-
-%%%=========================================================================
-%%% Internal functions
-%%%=========================================================================
-
-worker_spec(KillAfter, Options) ->
- Modules = [proc_lib, tftp, tftp_engine],
- KA = supervisor_timeout(KillAfter),
- Name = unique_name(Options),
- {Name, {tftp, start, [Options]}, permanent, KA, worker, Modules}.
-
-unique_name(Options) ->
- case lists:keysearch(port, 1, Options) of
- {value, {_, Port}} when is_integer(Port), Port > 0 ->
- {tftpd, Port};
- _ ->
- {tftpd, erlang:unique_integer([positive])}
- end.
-
-default_kill_after() ->
- timer:seconds(3).
-
-%% supervisor_spec(Name) ->
-%% {Name, {Name, start, []}, permanent, infinity, supervisor,
-%% [Name, supervisor]}.
-
--ifdef(debug_shutdown).
-supervisor_timeout(_KillAfter) -> timer:hours(24).
--else.
-supervisor_timeout(KillAfter) -> KillAfter.
--endif.