diff options
author | Péter Dimitrov <[email protected]> | 2018-03-20 11:26:01 +0100 |
---|---|---|
committer | Péter Dimitrov <[email protected]> | 2018-03-28 10:19:38 +0200 |
commit | 09ccfa2a6a8f8df55c7d808f5ad26324ac1e81b6 (patch) | |
tree | db6e9a1172ee8bb761041e352b6dc637fd765d28 /lib/inets | |
parent | 3c41882115f2cd9bfda8318925b4352cd1ec06b7 (diff) | |
download | otp-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')
27 files changed, 53 insertions, 5386 deletions
diff --git a/lib/inets/doc/src/Makefile b/lib/inets/doc/src/Makefile index d66ef31ba6..90c1258d4a 100644 --- a/lib/inets/doc/src/Makefile +++ b/lib/inets/doc/src/Makefile @@ -47,7 +47,6 @@ XML_CHAPTER_FILES = \ XML_REF3_FILES = \ inets.xml \ - tftp.xml \ http_uri.xml\ httpc.xml\ httpd.xml \ diff --git a/lib/inets/doc/src/inets.xml b/lib/inets/doc/src/inets.xml index 9b17567fd4..eb4e51584f 100644 --- a/lib/inets/doc/src/inets.xml +++ b/lib/inets/doc/src/inets.xml @@ -189,8 +189,8 @@ <section> <title>SEE ALSO</title> <p><seealso marker="httpc">httpc(3)</seealso>, - <seealso marker="httpd">httpd(3)</seealso>, - <seealso marker="tftp">tftp(3)</seealso></p> + <seealso marker="httpd">httpd(3)</seealso> + </p> </section> </erlref> diff --git a/lib/inets/doc/src/introduction.xml b/lib/inets/doc/src/introduction.xml index c006599a21..faf911f188 100644 --- a/lib/inets/doc/src/introduction.xml +++ b/lib/inets/doc/src/introduction.xml @@ -37,7 +37,6 @@ <p><c>Inets</c> is a container for Internet clients and servers including the following:</p> <list type="bulleted"> - <item>A TFTP client and server</item> <item>An <term id="HTTP"></term> client and server</item> </list> <p>The HTTP client and server are HTTP 1.1 compliant as @@ -49,7 +48,7 @@ <title>Prerequisites</title> <p>It is assumed that the reader is familiar with the Erlang programming language, concepts of OTP, and has a basic - understanding of the TFTP and HTTP protocols.</p> + understanding of and HTTP protocol.</p> </section> </chapter> diff --git a/lib/inets/doc/src/part.xml b/lib/inets/doc/src/part.xml index e7a786d47b..b9c8ed674c 100644 --- a/lib/inets/doc/src/part.xml +++ b/lib/inets/doc/src/part.xml @@ -33,7 +33,6 @@ <p>The <c>Inets</c> application provides a set of Internet-related services as follows:</p> <list type="bulleted"> - <item>A TFTP client and server</item> <item>An <term id="HTTP"></term> client and server</item> </list> <p>The HTTP client and server are HTTP 1.1 compliant as diff --git a/lib/inets/doc/src/ref_man.xml b/lib/inets/doc/src/ref_man.xml index 33886d1a41..58c1a651f9 100644 --- a/lib/inets/doc/src/ref_man.xml +++ b/lib/inets/doc/src/ref_man.xml @@ -30,12 +30,9 @@ <file>ref_man.xml</file> </header> <description> - <p><c>Inets</c> is a container for Internet clients and - servers. An HTTP client and server, and - a TFTP client and server are incorporated in <c>Inets</c>.</p> + <p><c>Inets</c> is a container for an HTTP client and server.</p> </description> <xi:include href="inets.xml"/> - <xi:include href="tftp.xml"/> <xi:include href="httpc.xml"/> <xi:include href="httpd.xml"/> <xi:include href="httpd_custom_api.xml"/> diff --git a/lib/inets/doc/src/tftp.xml b/lib/inets/doc/src/tftp.xml deleted file mode 100644 index 10398f5088..0000000000 --- a/lib/inets/doc/src/tftp.xml +++ /dev/null @@ -1,647 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<!DOCTYPE erlref SYSTEM "erlref.dtd"> - -<erlref> - <header> - <copyright> - <year>2006</year><year>2015</year> - <holder>Ericsson AB. All Rights Reserved.</holder> - </copyright> - <legalnotice> - 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. - - </legalnotice> - - <title>tftp</title> - <prepared></prepared> - <docno></docno> - <date></date> - <rev></rev> - </header> - <module>tftp</module> - <modulesummary>Trivial FTP.</modulesummary> - <description> - <p>This is a complete implementation of the following IETF standards:</p> - <list type="bulleted"> - <item>RFC 1350, The TFTP Protocol (revision 2)</item> - <item>RFC 2347, TFTP Option Extension</item> - <item>RFC 2348, TFTP Blocksize Option</item> - <item>RFC 2349, TFTP Timeout Interval and Transfer Size Options</item> - </list> - <p>The only feature that not is implemented is - the "netascii" transfer mode.</p> - <p>The <seealso marker="#start/1">start/1</seealso> function starts - a daemon process listening for UDP packets on a port. When it - receives a request for read or write, it spawns a temporary server - process handling the transfer.</p> - <p>On the client side, - function <seealso marker="#read_file/3">read_file/3</seealso> - and <seealso marker="#write_file/3">write_file/3</seealso> - spawn a temporary client process establishing - contact with a TFTP daemon and perform the file transfer.</p> - <p><c>tftp</c> uses a callback module to handle the file - transfer. Two such callback modules are provided, - <c>tftp_binary</c> and <c>tftp_file</c>. See - <seealso marker="#read_file/3">read_file/3</seealso> and - <seealso marker="#write_file/3">write_file/3</seealso> for details. - You can also implement your own callback modules, see - <seealso marker="#tftp_callback">CALLBACK FUNCTIONS</seealso>. - A callback module provided by - the user is registered using option <c>callback</c>, see - <seealso marker="#options">DATA TYPES</seealso>.</p> - </description> - - <section> - <title>TFTP SERVER SERVICE START/STOP</title> - - <p>A TFTP server can be configured to start statically when starting - the <c>Inets</c> application. Alternatively, it can be started dynamically - (when <c>Inets</c> is already started) by calling the <c>Inets</c> application - API <c>inets:start(tftpd, ServiceConfig)</c> or - <c>inets:start(tftpd, ServiceConfig, How)</c>, - see <seealso marker="inets">inets(3)</seealso> for details. - The <c>ServiceConfig</c> for TFTP is described in - the <seealso marker="#options">DATA TYPES</seealso> - section.</p> - - <p>The TFTP server can be stopped using <c>inets:stop(tftpd, Pid)</c>, - see <seealso marker="inets">inets(3)</seealso> for details.</p> - - <p>The TPFT client is of such a temporary nature that it is not - handled as a service in the <c>Inets</c> service framework.</p> - - </section> - - <section> - <marker id="options"></marker> - <title>DATA TYPES</title> - <p><c>ServiceConfig = Options</c></p> - <p><c>Options = [option()]</c></p> - <p>Most of the options are common for both the client and the server - side, but some of them differs a little. - The available <c>option()</c>s are as follows:</p> - <taglist> - <tag><c>{debug, Level}</c></tag> - <item> - <p><c>Level = none | error | warning | brief | normal | verbose | all</c></p> - <p>Controls the level of debug printouts. - Default is <c>none</c>.</p> - </item> - <tag><c>{host, Host}</c></tag> - <item> - <p><c>Host = hostname()</c>, see - <seealso marker="kernel:inet">inet(3)</seealso>.</p> - <p>The name or IP address of the host where the TFTP daemon - resides. This option is only used by the client.</p> - </item> - <tag><c>{port, Port}</c></tag> - <item> - <p><c>Port = int()</c></p> - <p>The TFTP port where the daemon listens. Defaults is - the standardized number 69. On the server side, it can - sometimes make sense to set it to 0, meaning that - the daemon just picks a free port (which one is - returned by function <c>info/1</c>).</p> - <p>If a socket is connected already, option - <c>{udp, [{fd, integer()}]}</c> can be used to pass the - open file descriptor to <c>gen_udp</c>. This can be automated - by using a command-line argument stating the - prebound file descriptor number. For example, if the - port is 69 and file descriptor 22 is opened by - <c>setuid_socket_wrap</c>, the command-line argument - "-tftpd_69 22" triggers the prebound file - descriptor 22 to be used instead of opening port 69. - The UDP option <c>{udp, [{fd, 22}]}</c> is automatically added. - See <c>init:get_argument/</c> about command-line arguments and - <c>gen_udp:open/2</c> about UDP options.</p> - </item> - <tag><c>{port_policy, Policy}</c></tag> - <item> - <p><c>Policy = random | Port | {range, MinPort, MaxPort}</c></p> - <p><c>Port = MinPort = MaxPort = int()</c></p> - <p>Policy for the selection of the temporary port that is used - by the server/client during the file transfer. Default is - <c>random</c>, which is the standardized policy. With this - policy a randomized free port is used. A single port or a range - of ports can be useful if the protocol passes through a - firewall.</p> - </item> - <tag><c>{udp, Options}</c></tag> - <item> - <p><c>Options = [Opt]</c>, see - <seealso marker="kernel:gen_udp#open/1">gen_udp:open/2</seealso>.</p> - </item> - <tag><c>{use_tsize, Bool}</c></tag> - <item> - <p><c>Bool = bool()</c></p> - <p>Flag for automated use of option <c>tsize</c>. With - this set to <c>true</c>, the <c>write_file/3</c> client - determines the filesize and sends it to the server as - the standardized <c>tsize</c> option. A <c>read_file/3</c> - client acquires only a filesize from the server by sending - a zero <c>tsize</c>.</p> - </item> - <tag><c>{max_tsize, MaxTsize}</c></tag> - <item> - <p><c>MaxTsize = int() | infinity</c></p> - <p>Threshold for the maximal filesize in bytes. The transfer - is aborted if the limit is exceeded. - Default is <c>infinity</c>.</p> - </item> - <tag><c>{max_conn, MaxConn}</c></tag> - <item> - <p><c>MaxConn = int() | infinity</c></p> - <p>Threshold for the maximal number of active connections. - The daemon rejects the setup of new connections if - the limit is exceeded. Default is <c>infinity</c>.</p> - </item> - <tag><c>{TftpKey, TftpVal}</c></tag> - <item> - <p><c>TftpKey = string()</c> <br></br> -<c>TftpVal = string()</c></p> - <p>Name and value of a TFTP option.</p> - </item> - <tag><c>{reject, Feature}</c></tag> - <item> - <p><c>Feature = Mode | TftpKey</c> <br></br> -<c> Mode = read | write</c> <br></br> -<c> TftpKey = string()</c></p> - <p>Controls which features to reject. This is - mostly useful for the server as it can restrict the use - of certain TFTP options or read/write access.</p> - </item> - <tag><c>{callback, {RegExp, Module, State}}</c></tag> - <item> - <p><c>RegExp = string()</c> <br></br> -<c>Module = atom()</c> <br></br> -<c>State = term()</c></p> - <p>Registration of a callback module. When a file is to be - transferred, its local filename is matched to the regular - expressions of the registered callbacks. The first matching - callback is used during the transfer. See - <seealso marker="#read_file/3">read_file/3</seealso> and - <seealso marker="#write_file/3">write_file/3</seealso>. - </p> - <p>The callback module must implement the <c>tftp</c> behavior, see - <seealso marker="#tftp_callback">CALLBACK FUNCTIONS</seealso>.</p> - </item> - - <tag><c>{logger, Module}</c></tag> - <item> - <p><c>Module = module()()</c></p> - - <p>Callback module for customized logging of errors, warnings, and - info messages. The callback module must implement the - <c>tftp_logger</c> behavior, see - <seealso marker="#tftp_logger">LOGGER FUNCTIONS</seealso>. - The default module is <c>tftp_logger</c>.</p> - </item> - - <tag><c>{max_retries, MaxRetries}</c></tag> - <item> - <p><c>MaxRetries = int()</c></p> - - <p>Threshold for the maximal number of retries. By default - the server/client tries to resend a message up to - five times when the time-out expires.</p> - </item> - </taglist> - </section> - - <funcs> - <func> - <name>change_config(daemons, Options) -> [{Pid, Result}]</name> - <fsummary>Changes configuration for all daemons. - </fsummary> - <type> - <v>Options = [option()]</v> - <v>Pid = pid()</v> - <v>Result = ok | {error, Reason}</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Changes configuration for all TFTP daemon processes. </p> - </desc> - </func> - - <func> - <name>change_config(servers, Options) -> [{Pid, Result}]</name> - <fsummary>Changes configuration for all servers. - </fsummary> - <type> - <v>Options = [option()]</v> - <v>Pid = pid()</v> - <v>Result = ok | {error, Reason}</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Changes configuration for all TFTP server processes.</p> - </desc> - </func> - - <func> - <name>change_config(Pid, Options) -> Result</name> - <fsummary>Changes configuration for a TFTP daemon, server, - or client process.</fsummary> - <type> - <v>Pid = pid()</v> - <v>Options = [option()]</v> - <v>Result = ok | {error, Reason}</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Changes configuration for a TFTP daemon, server, or client process.</p> - </desc> - </func> - - <func> - <name>info(daemons) -> [{Pid, Options}]</name> - <fsummary>Returns information about all daemons.</fsummary> - <type> - <v>Pid = [pid()()]</v> - <v>Options = [option()]</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Returns information about all TFTP daemon processes.</p> - </desc> - </func> - - <func> - <name>info(servers) -> [{Pid, Options}]</name> - <fsummary>Returns information about all servers.</fsummary> - <type> - <v>Pid = [pid()()]</v> - <v>Options = [option()]</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Returns information about all TFTP server processes. </p> - </desc> - </func> - - <func> - <name>info(Pid) -> {ok, Options} | {error, Reason}</name> - <fsummary>Returns information about a daemon, server, or client process.</fsummary> - <type> - <v>Options = [option()]</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Returns information about a TFTP daemon, server, or client process.</p> - </desc> - </func> - - <func> - <name>read_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name> - <fsummary>Reads a (virtual) file from a TFTP server.</fsummary> - <type> - <v>RemoteFilename = string()</v> - <v>LocalFilename = binary | string()</v> - <v>Options = [option()]</v> - <v>LastCallbackState = term()</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Reads a (virtual) file <c>RemoteFilename</c> from a TFTP - server.</p> - <p>If <c>LocalFilename</c> is the atom <c>binary</c>, - <c>tftp_binary</c> is used as callback module. It concatenates - all transferred blocks and returns them as one single binary - in <c>LastCallbackState</c>.</p> - <p>If <c>LocalFilename</c> is a string and there are no - registered callback modules, <c>tftp_file</c> is used as - callback module. It writes each transferred block to the file - named <c>LocalFilename</c> and returns the number of - transferred bytes in <c>LastCallbackState</c>.</p> - <p>If <c>LocalFilename</c> is a string and there are registered - callback modules, <c>LocalFilename</c> is tested against - the regexps of these and the callback module corresponding to - the first match is used, or an error tuple is returned if no - matching regexp is found.</p> - </desc> - </func> - - <func> - <name>start(Options) -> {ok, Pid} | {error, Reason}</name> - <fsummary>Starts a daemon process.</fsummary> - <type> - <v>Options = [option()]</v> - <v>Pid = pid()</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Starts a daemon process listening for UDP packets on a - port. When it receives a request for read or write, it spawns - a temporary server process handling the actual transfer - of the (virtual) file.</p> - </desc> - </func> - - <func> - <name>write_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name> - <fsummary>Writes a (virtual) file to a TFTP server.</fsummary> - <type> - <v>RemoteFilename = string()</v> - <v>LocalFilename = binary() | string()</v> - <v>Options = [option()]</v> - <v>LastCallbackState = term()</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Writes a (virtual) file <c>RemoteFilename</c> to a TFTP - server.</p> - <p>If <c>LocalFilename</c> is a binary, <c>tftp_binary</c> is - used as callback module. The binary is transferred block by - block and the number of transferred bytes is returned in - <c>LastCallbackState</c>.</p> - <p>If <c>LocalFilename</c> is a string and there are no - registered callback modules, <c>tftp_file</c> is used as - callback module. It reads the file named <c>LocalFilename</c> - block by block and returns the number of transferred bytes - in <c>LastCallbackState</c>.</p> - <p>If <c>LocalFilename</c> is a string and there are registered - callback modules, <c>LocalFilename</c> is tested against - the regexps of these and the callback module corresponding to - the first match is used, or an error tuple is returned if no - matching regexp is found.</p> - </desc> - </func> - </funcs> - - <section> - <marker id="tftp_callback"></marker> - <title>CALLBACK FUNCTIONS</title> - <p>A <c>tftp</c> callback module is to be implemented as a - <c>tftp</c> behavior and export the functions listed - in the following.</p> - <p>On the server side, the callback interaction starts with a call to - <c>open/5</c> with the registered initial callback state. - <c>open/5</c> is expected to open the (virtual) file. Then either - function <c>read/1</c> or <c>write/2</c> is invoked - repeatedly, once per transferred block. At each function call, - the state returned from the previous call is obtained. When - the last block is encountered, function <c>read/1</c> or - <c>write/2</c> is expected to close the (virtual) file - and return its last state. Function <c>abort/3</c> is only - used in error situations. Function <c>prepare/5</c> is not used on - the server side.</p> - <p>On the client side, the callback interaction is the same, but it - starts and ends a bit differently. It starts with a call to - <c>prepare/5</c> with the same arguments as <c>open/5</c> takes. - <c>prepare/5</c> is expected to validate the TFTP options - suggested by the user and to return the subset of them that it - accepts. Then the options are sent to the server, which performs - the same TFTP option negotiation procedure. The options that are - accepted by the server are forwarded to function <c>open/5</c> - on the client side. On the client side, function <c>open/5</c> - must accept all option as-is or reject the transfer. Then - the callback interaction follows the same pattern as described - for the server side. When the last block is encountered in - <c>read/1</c> or <c>write/2</c>, the returned state is forwarded to - the user and returned from <c>read_file</c>/3 or - <c>write_file/3</c>.</p> - - <p> If a callback (performing the file access - in the TFTP server) takes too long time (more than - the double TFTP time-out), the server aborts the - connection and sends an error reply to the client. - This implies that the server releases resources - attached to the connection faster than before. The - server simply assumes that the client has given - up.</p> - - <p>If the TFTP server receives yet another request from - the same client (same host and port) while it - already has an active connection to the client, it - ignores the new request if the request is - equal to the first one (same filename and options). - This implies that the (new) client will be served - by the already ongoing connection on the server - side. By not setting up yet another connection, in - parallel with the ongoing one, the server - consumes less resources.</p> - - <marker id="prepare"></marker> - </section> - - <funcs> - <func> - <name>Module:abort(Code, Text, State) -> ok</name> - <fsummary>Aborts the file transfer.</fsummary> - <type> - <v>Code = undef | enoent | eacces | enospc</v> - <v> | badop | eexist | baduser | badopt</v> - <v> | int()</v> - <v>Text = string()</v> - <v>State = term()</v> - </type> - <desc> - <p>Invoked when the file transfer is aborted.</p> - <p>The callback function is expected to clean - up its used resources after the aborted file - transfer, such as closing open file - descriptors and so on. The function is not - invoked if any of the other callback - functions returns an error, as it is - expected that they already have cleaned up - the necessary resources. However, it is - invoked if the functions fail (crash).</p> - </desc> - </func> - - <func> - <name>Module:open(Peer, Access, Filename, Mode, SuggestedOptions, State) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name> - <fsummary>Opens a file for read or write access.</fsummary> - <type> - <v>Peer = {PeerType, PeerHost, PeerPort}</v> - <v>PeerType = inet | inet6</v> - <v>PeerHost = ip_address()</v> - <v>PeerPort = integer()</v> - <v>Access = read | write</v> - <v>Filename = string()</v> - <v>Mode = string()</v> - <v>SuggestedOptions = AcceptedOptions = [{Key, Value}]</v> - <v> Key = Value = string()</v> - <v>State = InitialState | term()</v> - <v> InitialState = [] | [{root_dir, string()}]</v> - <v>NewState = term()</v> - <v>Code = undef | enoent | eacces | enospc</v> - <v> | badop | eexist | baduser | badopt</v> - <v> | int()</v> - <v>Text = string()</v> - </type> - <desc> - <p>Opens a file for read or write access.</p> - <p>On the client side, where the <c>open/5</c> call has been - preceded by a call to <c>prepare/5</c>, all options must be - accepted or rejected.</p> - <p>On the server side, where there is no preceding - <c>prepare/5</c> call, no new options can be added, but - those present in <c>SuggestedOptions</c> can be - omitted or replaced with new values in <c>AcceptedOptions</c>.</p> - - <marker id="read"></marker> - </desc> - </func> - - <func> - <name>Module:prepare(Peer, Access, Filename, Mode, SuggestedOptions, InitialState) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name> - <fsummary>Prepares to open a file on the client side.</fsummary> - <type> - <v>Peer = {PeerType, PeerHost, PeerPort}</v> - <v>PeerType = inet | inet6</v> - <v>PeerHost = ip_address()</v> - <v>PeerPort = integer()</v> - <v>Access = read | write</v> - <v>Filename = string()</v> - <v>Mode = string()</v> - <v>SuggestedOptions = AcceptedOptions = [{Key, Value}]</v> - <v> Key = Value = string()</v> - <v>InitialState = [] | [{root_dir, string()}]</v> - <v>NewState = term()</v> - <v>Code = undef | enoent | eacces | enospc</v> - <v> | badop | eexist | baduser | badopt</v> - <v> | int()</v> - <v>Text = string()</v> - </type> - <desc> - <p>Prepares to open a file on the client side.</p> - <p>No new options can be added, but those present in - <c>SuggestedOptions</c> can be omitted or replaced with new - values in <c>AcceptedOptions</c>.</p> - <p>This is followed by a call to <c>open/4</c> before any - read/write access is performed. <c>AcceptedOptions</c> is - sent to the server, which replies with the options that it - accepts. These are then forwarded to <c>open/4</c> as - <c>SuggestedOptions</c>.</p> - - <marker id="open"></marker> - </desc> - </func> - - <func> - <name>Module:read(State) -> {more, Bin, NewState} | {last, Bin, FileSize} | {error, {Code, Text}}</name> - <fsummary>Reads a chunk from the file.</fsummary> - <type> - <v>State = NewState = term()</v> - <v>Bin = binary()</v> - <v>FileSize = int()</v> - <v>Code = undef | enoent | eacces | enospc</v> - <v> | badop | eexist | baduser | badopt</v> - <v> | int()</v> - <v>Text = string()</v> - </type> - <desc> - <p>Reads a chunk from the file.</p> - <p>The callback function is expected to close - the file when the last file chunk is - encountered. When an error is encountered, - the callback function is expected to clean - up after the aborted file transfer, such as - closing open file descriptors, and so on. In both - cases there will be no more calls to any of - the callback functions.</p> - - <marker id="write"></marker> - </desc> - </func> - - <func> - <name>Module:write(Bin, State) -> {more, NewState} | {last, FileSize} | {error, {Code, Text}}</name> - <fsummary>Writes a chunk to the file.</fsummary> - <type> - <v>Bin = binary()</v> - <v>State = NewState = term()</v> - <v>FileSize = int()</v> - <v>Code = undef | enoent | eacces | enospc</v> - <v> | badop | eexist | baduser | badopt</v> - <v> | int()</v> - <v>Text = string()</v> - </type> - <desc> - <p>Writes a chunk to the file.</p> - <p>The callback function is expected to close - the file when the last file chunk is - encountered. When an error is encountered, - the callback function is expected to clean - up after the aborted file transfer, such as - closing open file descriptors, and so on. In both - cases there will be no more calls to any of - the callback functions.</p> - - <marker id="abort"></marker> - </desc> - </func> - </funcs> - - <section> - <marker id="tftp_logger"></marker> - <title>LOGGER FUNCTIONS</title> - - <p>A <c>tftp_logger</c> callback module is to be implemented as a - <c>tftp_logger</c> behavior and export the following functions:</p> - - <marker id="error_msg"></marker> - </section> - - <funcs> - <func> - <name>Logger:error_msg(Format, Data) -> ok | exit(Reason)</name> - <fsummary>Logs an error message.</fsummary> - <type> - <v>Format = string()</v> - <v>Data = [term()]</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Logs an error message. - See <c>error_logger:error_msg/2</c> for details.</p> - - <marker id="warning_msg"></marker> - </desc> - </func> - - <func> - <name>Logger:info_msg(Format, Data) -> ok | exit(Reason)</name> - <fsummary>Logs an info message.</fsummary> - <type> - <v>Format = string()</v> - <v>Data = [term()]</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Logs an info message. - See <c>error_logger:info_msg/2</c> for details.</p> - </desc> - </func> - - <func> - <name>Logger:warning_msg(Format, Data) -> ok | exit(Reason)</name> - <fsummary>Logs a warning message.</fsummary> - <type> - <v>Format = string()</v> - <v>Data = [term()]</v> - <v>Reason = term()</v> - </type> - <desc> - <p>Logs a warning message. - See <c>error_logger:warning_msg/2</c> for details.</p> - - <marker id="info_msg"></marker> - </desc> - </func> - </funcs> -</erlref> - - diff --git a/lib/inets/src/inets_app/Makefile b/lib/inets/src/inets_app/Makefile index 450e49dcc3..fad2fefe2f 100644 --- a/lib/inets/src/inets_app/Makefile +++ b/lib/inets/src/inets_app/Makefile @@ -49,7 +49,8 @@ MODULES = \ inets_sup \ inets_trace \ inets_lib \ - inets_ftp_wrapper + inets_ftp_wrapper \ + inets_tftp_wrapper INTERNAL_HRL_FILES = inets_internal.hrl EXTERNAL_HRL_FILES = ../../include/httpd.hrl \ diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src index 0af44e39ab..dfe773e7fe 100644 --- a/lib/inets/src/inets_app/inets.app.src +++ b/lib/inets/src/inets_app/inets.app.src @@ -98,13 +98,7 @@ mod_trace, %% TFTP - tftp, - tftp_binary, - tftp_engine, - tftp_file, - tftp_lib, - tftp_logger, - tftp_sup + inets_tftp_wrapper ]}, {registered,[inets_sup, httpc_manager]}, %% If the "new" ssl is used then 'crypto' must be started before inets. diff --git a/lib/inets/src/inets_app/inets.erl b/lib/inets/src/inets_app/inets.erl index 615a4cf635..9005e73f5d 100644 --- a/lib/inets/src/inets_app/inets.erl +++ b/lib/inets/src/inets_app/inets.erl @@ -465,11 +465,13 @@ call_service(Service, Call, Args) -> exit:{noproc, _} -> {error, inets_not_started} end. - + +%% Obsolete! Kept for backward compatiblity! +%% TFTP application has been moved out from inets service_module(tftpd) -> - tftp; + inets_tftp_wrapper; service_module(tftpc) -> - tftp; + inets_tftp_wrapper; %% Obsolete! Kept for backward compatiblity! %% FTP application has been moved out from inets service_module(ftpc) -> diff --git a/lib/inets/src/inets_app/inets_sup.erl b/lib/inets/src/inets_app/inets_sup.erl index 8f4d806529..22c928f9f9 100644 --- a/lib/inets/src/inets_app/inets_sup.erl +++ b/lib/inets/src/inets_app/inets_sup.erl @@ -61,9 +61,7 @@ children() -> Services = get_services(), HttpdServices = [Service || Service <- Services, is_httpd(Service)], HttpcServices = [Service || Service <- Services, is_httpc(Service)], - TftpdServices = [Service || Service <- Services, is_tftpd(Service)], - [httpc_child_spec(HttpcServices), - httpd_child_spec(HttpdServices), tftpd_child_spec(TftpdServices)]. + [httpc_child_spec(HttpcServices), httpd_child_spec(HttpdServices)]. httpc_child_spec(HttpcServices0) -> HttpcServices = default_profile(HttpcServices0, []), @@ -84,15 +82,6 @@ httpd_child_spec(HttpdServices) -> Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -tftpd_child_spec(TftpServices) -> - Name = tftp_sup, - StartFunc = {tftp_sup, start_link, [TftpServices]}, - Restart = permanent, - Shutdown = infinity, - Modules = [tftp_sup], - Type = supervisor, - {Name, StartFunc, Restart, Shutdown, Type, Modules}. - is_httpd({httpd, _}) -> true; is_httpd({httpd, _, _}) -> @@ -105,11 +94,6 @@ is_httpc({httpc, _}) -> is_httpc(_) -> false. -is_tftpd({tftpd, _}) -> - true; -is_tftpd(_) -> - false. - default_profile([], Acc) -> [{httpc, {default, only_session_cookies}} | Acc]; default_profile([{httpc, {default, _}} | _] = Profiles, Acc) -> diff --git a/lib/inets/test/tftp_test_lib.hrl b/lib/inets/src/inets_app/inets_tftp_wrapper.erl index e7a5a37d2c..1e5deb234b 100644 --- a/lib/inets/test/tftp_test_lib.hrl +++ b/lib/inets/src/inets_app/inets_tftp_wrapper.erl @@ -1,8 +1,8 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2006-2018. 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 @@ -14,31 +14,35 @@ %% 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(inets_tftp_wrapper). + + +-export([start_standalone/1, + start_service/1, + stop_service/1, + services/0, + service_info/1]). + + +start_standalone(Options) -> + tftp:start_standalone(Options). + + +start_service(Options) -> + application:ensure_started(tftp), + tftp:start_service(Options). + + +stop_service(Pid) -> + tftp:stop_service(Pid). + + +services() -> + []. + --record('REASON', {mod, line, desc}). - --define(LOG(Format, Args), - tftp_test_lib:log(Format, Args, ?MODULE, ?LINE)). - --define(ERROR(Reason), - tftp_test_lib:error(Reason, ?MODULE, ?LINE)). - --define(VERIFY(Expected, Expr), - fun() -> - AcTuAlReS = (catch (Expr)), - case AcTuAlReS of - Expected -> ?LOG("Ok, ~p\n", [AcTuAlReS]); - _ -> ?ERROR(AcTuAlReS) - end, - AcTuAlReS - end()). - --define(IGNORE(Expr), - fun() -> - AcTuAlReS = (catch (Expr)), - ?LOG("Ok, ~p\n", [AcTuAlReS]), - AcTuAlReS - end()). +service_info(_) -> + []. diff --git a/lib/inets/src/subdirs.mk b/lib/inets/src/subdirs.mk index 8c18691aef..e9f4de959c 100644 --- a/lib/inets/src/subdirs.mk +++ b/lib/inets/src/subdirs.mk @@ -1,3 +1,3 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SUB_DIRECTORIES = inets_app http_lib http_client http_server tftp +SUB_DIRECTORIES = inets_app http_lib http_client http_server 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. diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile index 4dab4e14c6..0e33b72095 100644 --- a/lib/inets/test/Makefile +++ b/lib/inets/test/Makefile @@ -138,8 +138,6 @@ MODULES = \ httpd_test_lib \ inets_sup_SUITE \ inets_SUITE \ - tftp_test_lib \ - tftp_SUITE \ uri_SUITE \ inets_socketwrap_SUITE @@ -149,8 +147,7 @@ EBIN = . HRL_FILES = inets_test_lib.hrl \ inets_internal.hrl \ httpc_internal.hrl \ - http_internal.hrl \ - tftp_test_lib.hrl + http_internal.hrl ERL_FILES = $(MODULES:%=%.erl) @@ -165,8 +162,7 @@ INETS_FILES = inets.config $(INETS_SPECS) # SUB_SUITES = \ # inets_sup_suite \ # inets_httpd_suite \ -# inets_httpc_suite \ -# inets_tftp_suite +# inets_httpc_suite INETS_DATADIRS = inets_SUITE_data inets_socketwrap_SUITE_data @@ -299,10 +295,3 @@ info: @echo "INETS_PRIV_DIR = $(INETS_PRIV_DIR)" @echo "INETS_ROOT = $(INETS_ROOT)" @echo "INETS_FLAGS = $(INETS_FLAGS)" - -tftp: - erlc $(ERL_COMPILE_FLAGS) tftp_test_lib.erl tftp_SUITE.erl && erl -pa ../../inets/ebin -s tftp_SUITE t -s erlang halt - -tftp_work: - echo "tftp_test_lib:t([{tftp_SUITE, all}])." - erlc $(ERL_COMPILE_FLAGS) tftp_test_lib.erl tftp_SUITE.erl && erl -pa ../../inets/ebin diff --git a/lib/inets/test/inets_SUITE.erl b/lib/inets/test/inets_SUITE.erl index 12db2479d2..07ce594a1d 100644 --- a/lib/inets/test/inets_SUITE.erl +++ b/lib/inets/test/inets_SUITE.erl @@ -41,8 +41,7 @@ groups() -> [{services_test, [], [start_inets, start_httpc, - start_httpd, - start_tftpd + start_httpd ]}, {app_test, [], [app, appup]}]. @@ -297,41 +296,6 @@ start_httpd(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -start_tftpd() -> - [{doc, "Start/stop of tfpd service"}]. -start_tftpd(Config) when is_list(Config) -> - process_flag(trap_exit, true), - ok = inets:start(), - {ok, Pid0} = inets:start(tftpd, [{host, "localhost"}, {port, 0}]), - Pids0 = [ServicePid || {_, ServicePid} <- inets:services()], - true = lists:member(Pid0, Pids0), - [_|_] = inets:services_info(), - inets:stop(tftpd, Pid0), - ct:sleep(100), - Pids1 = [ServicePid || {_, ServicePid} <- inets:services()], - false = lists:member(Pid0, Pids1), - {ok, Pid1} = - inets:start(tftpd, [{host, "localhost"}, {port, 0}], stand_alone), - Pids2 = [ServicePid || {_, ServicePid} <- inets:services()], - false = lists:member(Pid1, Pids2), - ok = inets:stop(stand_alone, Pid1), - receive - {'EXIT', Pid1, shutdown} -> - ok - after 100 -> - ct:fail(stand_alone_not_shutdown) - end, - ok = inets:stop(), - application:load(inets), - application:set_env(inets, services, [{tftpd,[{host, "localhost"}, - {port, 0}]}]), - ok = inets:start(), - (?NUM_DEFAULT_SERVICES + 1) = length(inets:services()), - application:unset_env(inets, services), - ok = inets:stop(). - -%%------------------------------------------------------------------------- - httpd_reload() -> [{doc, "Reload httpd configuration without restarting service"}]. httpd_reload(Config) when is_list(Config) -> diff --git a/lib/inets/test/inets_socketwrap_SUITE.erl b/lib/inets/test/inets_socketwrap_SUITE.erl index 7ea7e08ed1..fc87c595a9 100644 --- a/lib/inets/test/inets_socketwrap_SUITE.erl +++ b/lib/inets/test/inets_socketwrap_SUITE.erl @@ -30,7 +30,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [start_httpd_fd, start_tftpd_fd]. + [start_httpd_fd]. init_per_suite(Config) -> case os:type() of @@ -90,37 +90,7 @@ start_httpd_fd(Config) when is_list(Config) -> ct:fail(open_port_failed) end end. -%%------------------------------------------------------------------------- -start_tftpd_fd() -> - [{doc, "Start/stop of tfpd service with socket wrapper"}]. -start_tftpd_fd(Config) when is_list(Config) -> - DataDir = proplists:get_value(data_dir, Config), - case setup_node_info(node()) of - {skip, _} = Skip -> - Skip; - {Node, NodeArg} -> - InetPort = inets_test_lib:inet_port(node()), - ct:pal("Node: ~p~n", [Node]), - Wrapper = filename:join(DataDir, "setuid_socket_wrap"), - Cmd = Wrapper ++ - " -s -tftpd_69,0:" ++ integer_to_list(InetPort) - ++ " -p " ++ os:find_executable("erl") ++ - " -- " ++ NodeArg, - ct:pal("cmd: ~p~n", [Cmd]), - case open_port({spawn, Cmd}, [stderr_to_stdout]) of - Port when is_port(Port) -> - wait_node_up(Node, 10), - ct:pal("~p", [rpc:call(Node, init, get_argument, [tftpd_69])]), - ok = rpc:call(Node, inets, start, []), - {ok, Pid} = rpc:call(Node, inets, start, - [tftpd,[{host, "localhost"}]]), - {ok, Info} = rpc:call(Node, tftp, info, [Pid]), - {value,{port, InetPort}} = lists:keysearch(port, 1, Info), - rpc:call(Node, erlang, halt, []); - _ -> - ct:fail(open_port_failed) - end - end. + %%------------------------------------------------------------------------- %% Internal functions %%------------------------------------------------------------------------- diff --git a/lib/inets/test/inets_sup_SUITE.erl b/lib/inets/test/inets_sup_SUITE.erl index d36d4dc53e..727e91e987 100644 --- a/lib/inets/test/inets_sup_SUITE.erl +++ b/lib/inets/test/inets_sup_SUITE.erl @@ -32,8 +32,7 @@ suite() -> ]. all() -> - [default_tree, tftpd_worker, - httpd_config, httpd_subtree, httpd_subtree_profile, + [default_tree, httpd_config, httpd_subtree, httpd_subtree_profile, httpc_subtree]. groups() -> @@ -147,13 +146,11 @@ default_tree() -> "in the default case."}]. default_tree(Config) when is_list(Config) -> TopSupChildren = supervisor:which_children(inets_sup), - 3 = length(TopSupChildren), + 2 = length(TopSupChildren), {value, {httpd_sup, _, supervisor,[httpd_sup]}} = lists:keysearch(httpd_sup, 1, TopSupChildren), {value, {httpc_sup, _,supervisor,[httpc_sup]}} = lists:keysearch(httpc_sup, 1, TopSupChildren), - {value, {tftp_sup,_,supervisor,[tftp_sup]}} = - lists:keysearch(tftp_sup, 1, TopSupChildren), HttpcSupChildren = supervisor:which_children(httpc_sup), {value, {httpc_profile_sup,_, supervisor, [httpc_profile_sup]}} = @@ -168,24 +165,7 @@ default_tree(Config) when is_list(Config) -> = supervisor:which_children(httpc_profile_sup), [] = supervisor:which_children(httpc_handler_sup), - - [] = supervisor:which_children(tftp_sup), - - ok. -tftpd_worker() -> - [{doc, "Makes sure the tftp sub tree is correct."}]. -tftpd_worker(Config) when is_list(Config) -> - [] = supervisor:which_children(tftp_sup), - {ok, Pid0} = inets:start(tftpd, [{host, inets_test_lib:hostname()}, - {port, 0}]), - {ok, _Pid1} = inets:start(tftpd, [{host, inets_test_lib:hostname()}, - {port, 0}], stand_alone), - - [{_,Pid0, worker, _}] = supervisor:which_children(tftp_sup), - inets:stop(tftpd, Pid0), - ct:sleep(5000), - [] = supervisor:which_children(tftp_sup), ok. httpd_config() -> diff --git a/lib/inets/test/tftp_SUITE.erl b/lib/inets/test/tftp_SUITE.erl deleted file mode 100644 index 09049e36af..0000000000 --- a/lib/inets/test/tftp_SUITE.erl +++ /dev/null @@ -1,949 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-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_SUITE). - --compile(export_all). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Includes and defines -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --include("tftp_test_lib.hrl"). - --define(START_DAEMON(PortX, OptionsX), - fun(Port, Options) -> - {ok, Pid} = ?VERIFY({ok, _Pid}, tftp:start([{port, Port} | Options])), - if - Port == 0 -> - {ok, ActualOptions} = ?IGNORE(tftp:info(Pid)), - {value, {port, ActualPort}} = - lists:keysearch(port, 1, ActualOptions), - {ActualPort, Pid}; - true -> - {Port, Pid} - end - end(PortX, OptionsX)). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% API -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -t() -> - tftp_test_lib:t([{?MODULE, all}]). - -t(Cases) -> - tftp_test_lib:t(Cases, default_config()). - -t(Cases, Config) -> - tftp_test_lib:t(Cases, Config). - -default_config() -> - []. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Test server callbacks -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -init_per_testcase(Case, Config) -> - tftp_test_lib:init_per_testcase(Case, Config). - -end_per_testcase(Case, Config) when is_list(Config) -> - tftp_test_lib:end_per_testcase(Case, Config). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Top test case -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -suite() -> [{ct_hooks,[ts_install_cth]}]. - -all() -> - [simple, extra, reuse_connection, resend_client, - resend_server, large_file]. - -groups() -> - []. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Simple -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -simple(doc) -> - ["Start the daemon and perform simple a read and write."]; -simple(suite) -> - []; -simple(Config) when is_list(Config) -> - ?VERIFY(ok, application:start(inets)), - - {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, brief}])), - - %% Read fail - RemoteFilename = "tftp_temporary_remote_test_file.txt", - LocalFilename = "tftp_temporary_local_test_file.txt", - Blob = list_to_binary(lists:duplicate(2000, $1)), - %% Blob = <<"Some file contents\n">>, - Size = size(Blob), - ?IGNORE(file:delete(RemoteFilename)), - ?VERIFY({error, {client_open, enoent, _}}, - tftp:read_file(RemoteFilename, binary, [{port, Port}])), - - %% Write and read - ?VERIFY({ok, Size}, tftp:write_file(RemoteFilename, Blob, [{port, Port}])), - ?VERIFY({ok, Blob}, tftp:read_file(RemoteFilename, binary, [{port, Port}])), - ?IGNORE(file:delete(LocalFilename)), - ?VERIFY({ok, Size}, tftp:read_file(RemoteFilename, LocalFilename, [{port, Port}])), - - %% Cleanup - unlink(DaemonPid), - exit(DaemonPid, kill), - ?VERIFY(ok, file:delete(LocalFilename)), - ?VERIFY(ok, file:delete(RemoteFilename)), - ?VERIFY(ok, application:stop(inets)), - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Extra -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -extra(doc) -> - ["Verify new stuff for IS 1.2."]; -extra(suite) -> - []; -extra(Config) when is_list(Config) -> - ?VERIFY({'EXIT', {badarg,{fake_key, fake_flag}}}, - tftp:start([{port, 0}, {fake_key, fake_flag}])), - - {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, brief}])), - - RemoteFilename = "tftp_extra_temporary_remote_test_file.txt", - LocalFilename = "tftp_extra_temporary_local_test_file.txt", - Blob = <<"Some file contents\n">>, - Size = size(Blob), - Host = "127.0.0.1", - Peer = {inet, Host, Port}, - Generic = - [ - {state, []}, - {prepare, fun extra_prepare/6}, - {open, fun extra_open/6}, - {read, fun extra_read/1}, - {write, fun extra_write/2}, - {abort, fun extra_abort/3 } - ], - Options = [{host, Host}, - {port, Port}, - %%{ debug,all}, - {callback, {".*", tftp_test_lib, Generic}}], - ?VERIFY(ok, file:write_file(LocalFilename, Blob)), - ?VERIFY({ok, [{count, Size}, Peer]}, - tftp:write_file(RemoteFilename, LocalFilename, Options)), - ?VERIFY(ok, file:delete(LocalFilename)), - - ?VERIFY({ok,[{bin, Blob}, Peer]}, - tftp:read_file(RemoteFilename, LocalFilename, Options)), - - %% Cleanup - unlink(DaemonPid), - exit(DaemonPid, kill), - ?VERIFY(ok, file:delete(LocalFilename)), - ?VERIFY(ok, file:delete(RemoteFilename)), - ok. - --record(extra_state, {file, blksize, count, acc, peer}). - -%%------------------------------------------------------------------- -%% Prepare -%%------------------------------------------------------------------- - -extra_prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) -> - %% Client side - BlkSize = list_to_integer(tftp_test_lib:lookup_option("blksize", "512", SuggestedOptions)), - State = #extra_state{blksize = BlkSize, peer = Peer}, - extra_open(Peer, Access, LocalFilename, Mode, SuggestedOptions, State), - {ok, SuggestedOptions, State}; -extra_prepare(_Peer, _Access, _Bin, _Mode, _SuggestedOptions, _Initial) -> - {error, {undef, "Illegal callback options."}}. - -%%------------------------------------------------------------------- -%% Open -%%------------------------------------------------------------------- - -extra_open(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) -> - %% Server side - case extra_prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, []) of - {ok, AcceptedOptions, []} -> - BlkSize = list_to_integer(tftp_test_lib:lookup_option("blksize", "512", AcceptedOptions)), - State = #extra_state{blksize = BlkSize, peer = Peer}, - extra_open(Peer, Access, LocalFilename, Mode, AcceptedOptions, State); - {error, {Code, Text}} -> - {error, {Code, Text}} - end; -extra_open(_Peer, Access, LocalFilename, _Mode, NegotiatedOptions, #extra_state{} = State) -> - {File, Acc} = - case Access of - read -> - if - is_binary(LocalFilename) -> - {undefined, LocalFilename}; - is_list(LocalFilename) -> - {ok, Bin} = file:read_file(LocalFilename), - {LocalFilename, Bin} - end; - write -> - {LocalFilename, []} - end, - %% Both sides - State2 = State#extra_state{file = File, acc = Acc, count = 0}, - {ok, NegotiatedOptions, State2}. - -%%------------------------------------------------------------------- -%% Read -%%------------------------------------------------------------------- - -extra_read(#extra_state{acc = Bin} = State) when is_binary(Bin) -> - BlkSize = State#extra_state.blksize, - Count = State#extra_state.count + size(Bin), - if - size(Bin) >= BlkSize -> - <<Block:BlkSize/binary, Bin2/binary>> = Bin, - State2 = State#extra_state{acc = Bin2, count = Count}, - {more, Block, State2}; - size(Bin) < BlkSize -> - Res = [{count, Count}, State#extra_state.peer], - {last, Bin, Res} - end. - -%%------------------------------------------------------------------- -%% Write -%%------------------------------------------------------------------- - -extra_write(Bin, #extra_state{acc = List} = State) when is_binary(Bin), is_list(List) -> - Size = size(Bin), - BlkSize = State#extra_state.blksize, - if - Size == BlkSize -> - {more, State#extra_state{acc = [Bin | List]}}; - Size < BlkSize -> - Bin2 = list_to_binary(lists:reverse([Bin | List])), - Res = [{bin, Bin2}, State#extra_state.peer], - file:write_file(State#extra_state.file, Bin2), - {last, Res} - end. - -%%------------------------------------------------------------------- -%% Abort -%%------------------------------------------------------------------- - -extra_abort(_Code, _Text, #extra_state{}) -> - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Re-send client -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -resend_client(doc) -> - ["Verify that the server behaves correctly when the client re-sends packets."]; -resend_client(suite) -> - []; -resend_client(Config) when is_list(Config) -> - Host = {127, 0, 0, 1}, - {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, all}])), - - ?VERIFY(ok, resend_read_client(Host, Port, 10)), - ?VERIFY(ok, resend_read_client(Host, Port, 512)), - ?VERIFY(ok, resend_read_client(Host, Port, 1025)), - - ?VERIFY(ok, resend_write_client(Host, Port, 10)), - ?VERIFY(ok, resend_write_client(Host, Port, 512)), - ?VERIFY(ok, resend_write_client(Host, Port, 1025)), - - %% Cleanup - unlink(DaemonPid), - exit(DaemonPid, kill), - ok. - -resend_read_client(Host, Port, BlkSize) -> - RemoteFilename = "tftp_resend_read_client.tmp", - Block1 = lists:duplicate(BlkSize, $1), - Block2 = lists:duplicate(BlkSize, $2), - Block3 = lists:duplicate(BlkSize, $3), - Block4 = lists:duplicate(BlkSize, $4), - Block5 = lists:duplicate(BlkSize, $5), - Blocks = [Block1, Block2, Block3, Block4, Block5], - Blob = list_to_binary(Blocks), - ?VERIFY(ok, file:write_file(RemoteFilename, Blob)), - - Timeout = timer:seconds(3), - ?VERIFY(timeout, recv(0)), - - %% Open socket - {ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])), - - ReadList = [0, 1, RemoteFilename, 0, "octet", 0], - Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]), - NewPort = - if - BlkSize =:= 512 -> - %% Send READ - ReadBin = list_to_binary(ReadList), - ?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)), - - %% Sleep a while in order to provoke the server to re-send the packet - timer:sleep(Timeout + timer:seconds(1)), - - %% Recv DATA #1 (the packet that the server think that we have lost) - {udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, Data1Bin}, recv(Timeout)), - NewPort0; - true -> - %% Send READ - BlkSizeList = integer_to_list(BlkSize), - Options = ["blksize", 0, BlkSizeList, 0], - ReadBin = list_to_binary([ReadList | Options]), - ?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)), - - %% Recv OACK - OptionAckBin = list_to_binary([0, 6 | Options]), - {udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)), - - %% Send ACK #0 - Ack0Bin = <<0, 4, 0, 0>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort0, Ack0Bin)), - - %% Send ACK #0 AGAIN (pretend that we timed out) - timer:sleep(timer:seconds(1)), - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort0, Ack0Bin)), - - %% Recv DATA #1 (the packet that the server think that we have lost) - ?VERIFY({udp, Socket, Host, NewPort0, Data1Bin}, recv(Timeout)), - NewPort0 - end, - - %% Recv DATA #1 AGAIN (the re-sent package) - ?VERIFY({udp, Socket, Host, NewPort, Data1Bin}, recv(Timeout)), - - %% Send ACK #1 - Ack1Bin = <<0, 4, 0, 1>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack1Bin)), - - %% Recv DATA #2 - Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]), - ?VERIFY({udp, Socket, Host, NewPort, Data2Bin}, recv(Timeout)), - - %% Send ACK #2 - Ack2Bin = <<0, 4, 0, 2>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)), - - %% Recv DATA #3 - Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]), - ?VERIFY({udp, Socket, Host, NewPort, Data3Bin}, recv(Timeout)), - - %% Send ACK #3 - Ack3Bin = <<0, 4, 0, 3>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack3Bin)), - - %% Send ACK #3 AGAIN (pretend that we timed out) - timer:sleep(timer:seconds(1)), - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack3Bin)), - - %% Recv DATA #4 (the packet that the server think that we have lost) - Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]), - ?VERIFY({udp, Socket, Host, NewPort, Data4Bin}, recv(Timeout)), - - %% Recv DATA #4 AGAIN (the re-sent package) - ?VERIFY({udp, Socket, Host, NewPort, Data4Bin}, recv(Timeout)), - - %% Send ACK #2 which is out of range - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)), - - %% Send ACK #4 - Ack4Bin = <<0, 4, 0, 4>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack4Bin)), - - %% Recv DATA #5 - Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]), - ?VERIFY({udp, Socket, Host, NewPort, Data5Bin}, recv(Timeout)), - - %% Send ACK #5 - Ack5Bin = <<0, 4, 0, 5>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack5Bin)), - - %% Close socket - ?VERIFY(ok, gen_udp:close(Socket)), - - ?VERIFY(timeout, recv(Timeout)), - ?VERIFY(ok, file:delete(RemoteFilename)), - ok. - -resend_write_client(Host, Port, BlkSize) -> - RemoteFilename = "tftp_resend_write_client.tmp", - Block1 = lists:duplicate(BlkSize, $1), - Block2 = lists:duplicate(BlkSize, $2), - Block3 = lists:duplicate(BlkSize, $3), - Block4 = lists:duplicate(BlkSize, $4), - Block5 = lists:duplicate(BlkSize, $5), - Blocks = [Block1, Block2, Block3, Block4, Block5], - Blob = list_to_binary(Blocks), - ?IGNORE(file:delete(RemoteFilename)), - ?VERIFY({error, enoent}, file:read_file(RemoteFilename)), - - Timeout = timer:seconds(3), - ?VERIFY(timeout, recv(0)), - - %% Open socket - {ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])), - - WriteList = [0, 2, RemoteFilename, 0, "octet", 0], - NewPort = - if - BlkSize =:= 512 -> - %% Send WRITE - WriteBin = list_to_binary(WriteList), - ?VERIFY(ok, gen_udp:send(Socket, Host, Port, WriteBin)), - - %% Sleep a while in order to provoke the server to re-send the packet - timer:sleep(Timeout + timer:seconds(1)), - - %% Recv ACK #0 (the packet that the server think that we have lost) - Ack0Bin = <<0, 4, 0, 0>>, - ?VERIFY({udp, Socket, Host, _, Ack0Bin}, recv(Timeout)), - - %% Recv ACK #0 AGAIN (the re-sent package) - {udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, Ack0Bin}, recv(Timeout)), - NewPort0; - true -> - %% Send WRITE - BlkSizeList = integer_to_list(BlkSize), - WriteBin = list_to_binary([WriteList, "blksize", 0, BlkSizeList, 0]), - ?VERIFY(ok, gen_udp:send(Socket, Host, Port, WriteBin)), - - %% Sleep a while in order to provoke the server to re-send the packet - timer:sleep(timer:seconds(1)), - - %% Recv OACK (the packet that the server think that we have lost) - OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]), - ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)), - - %% Recv OACK AGAIN (the re-sent package) - {udp, _, _, NewPort0, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)), - NewPort0 - end, - - %% Send DATA #1 - Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]), - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data1Bin)), - - %% Recv ACK #1 - Ack1Bin = <<0, 4, 0, 1>>, - ?VERIFY({udp, Socket, Host, NewPort, Ack1Bin}, recv(Timeout)), - - %% Send DATA #2 - Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]), - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data2Bin)), - - %% Recv ACK #2 - Ack2Bin = <<0, 4, 0, 2>>, - ?VERIFY({udp, Socket, Host, NewPort, Ack2Bin}, recv(Timeout)), - - %% Send DATA #3 - Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]), - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data3Bin)), - - %% Recv ACK #3 - Ack3Bin = <<0, 4, 0, 3>>, - ?VERIFY({udp, Socket, Host, NewPort, Ack3Bin}, recv(Timeout)), - - %% Send DATA #3 AGAIN (pretend that we timed out) - timer:sleep(timer:seconds(1)), - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data3Bin)), - - %% Recv ACK #3 AGAIN (the packet that the server think that we have lost) - ?VERIFY({udp, Socket, Host, NewPort, Ack3Bin}, recv(Timeout)), - - %% Send DATA #2 which is out of range - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data2Bin)), - - %% Send DATA #4 - Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]), - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data4Bin)), - - %% Recv ACK #4 - Ack4Bin = <<0, 4, 0, 4>>, - ?VERIFY({udp, Socket, Host, NewPort, Ack4Bin}, recv(Timeout)), - - %% Send DATA #5 - Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]), - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Data5Bin)), - - %% Recv ACK #5 - Ack5Bin = <<0, 4, 0, 5>>, - ?VERIFY({udp, Socket, Host, NewPort, Ack5Bin}, recv(Timeout)), - - %% Close socket - ?VERIFY(ok, gen_udp:close(Socket)), - - ?VERIFY(timeout, recv(Timeout)), - ?VERIFY({ok, Blob}, file:read_file(RemoteFilename)), - ?VERIFY(ok, file:delete(RemoteFilename)), - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Re-send server -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -resend_server(doc) -> - ["Verify that the server behaves correctly when the server re-sends packets."]; -resend_server(suite) -> - []; -resend_server(Config) when is_list(Config) -> - Host = {127, 0, 0, 1}, - - ?VERIFY(ok, resend_read_server(Host, 10)), - ?VERIFY(ok, resend_read_server(Host, 512)), - ?VERIFY(ok, resend_read_server(Host, 1025)), - - ?VERIFY(ok, resend_write_server(Host, 10)), - ?VERIFY(ok, resend_write_server(Host, 512)), - ?VERIFY(ok, resend_write_server(Host, 1025)), - ok. - -resend_read_server(Host, BlkSize) -> - RemoteFilename = "tftp_resend_read_server.tmp", - Block1 = lists:duplicate(BlkSize, $1), - Block2 = lists:duplicate(BlkSize, $2), - Block3 = lists:duplicate(BlkSize, $3), - Block4 = lists:duplicate(BlkSize, $4), - Block5 = lists:duplicate(BlkSize, $5), - Block6 = [], - Blocks = [Block1, Block2, Block3, Block4, Block5, Block6], - Blob = list_to_binary(Blocks), - - Timeout = timer:seconds(3), - ?VERIFY(timeout, recv(0)), - - %% Open daemon socket - {ok, DaemonSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])), - {ok, DaemonPort} = ?IGNORE(inet:port(DaemonSocket)), - - %% Open server socket - {ok, ServerSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])), - ?IGNORE(inet:port(ServerSocket)), - - %% Prepare client process - ReplyTo = self(), - ClientFun = - fun(Extra) -> - Options = [{port, DaemonPort}, {debug, brief}] ++ Extra, - Res = ?VERIFY({ok, Blob}, tftp:read_file(RemoteFilename, binary, Options)), - ReplyTo ! {self(), {tftp_client_reply, Res}}, - exit(normal) - end, - - ReadList = [0, 1, RemoteFilename, 0, "octet", 0], - Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]), - Ack1Bin = <<0, 4, 0, 1>>, - {ClientPort, ClientPid} = - if - BlkSize =:= 512 -> - %% Start client process - ClientPid0 = spawn_link(fun() -> ClientFun([]) end), - - %% Recv READ - ReadBin = list_to_binary(ReadList), - {udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, ReadBin}, recv(Timeout)), - - %% Send DATA #1 - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Data1Bin)), - - %% Sleep a while in order to provoke the client to re-send the packet - timer:sleep(Timeout + timer:seconds(1)), - - %% Recv ACK #1 (the packet that the server think that we have lost) - ?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack1Bin}, recv(Timeout)), - - %% Recv ACK #1 AGAIN (the re-sent package) - ?VERIFY({udp, ServerSocket, Host, _, Ack1Bin}, recv(Timeout)), - {ClientPort0, ClientPid0}; - true -> - %% Start client process - BlkSizeList = integer_to_list(BlkSize), - ClientPid0 = spawn_link(fun() -> ClientFun([{"blksize", BlkSizeList}]) end), - - %% Recv READ - Options = ["blksize", 0, BlkSizeList, 0], - ReadBin = list_to_binary([ReadList | Options]), - {udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, ReadBin}, recv(Timeout)), - - %% Send OACK - BlkSizeList = integer_to_list(BlkSize), - OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, OptionAckBin)), - - %% Sleep a while in order to provoke the client to re-send the packet - timer:sleep(Timeout + timer:seconds(1)), - - %% Recv ACK #0 (the packet that the server think that we have lost) - Ack0Bin = <<0, 4, 0, 0>>, - ?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack0Bin}, recv(Timeout)), - - %% Recv ACK #0 AGAIN (the re-sent package) - ?VERIFY({udp, ServerSocket, Host, ClientPort0, Ack0Bin}, recv(Timeout)), - - %% Send DATA #1 - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Data1Bin)), - - %% Recv ACK #1 - ?VERIFY({udp, ServerSocket, Host, _, Ack1Bin}, recv(Timeout)), - {ClientPort0, ClientPid0} - end, - - %% Send DATA #2 - Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data2Bin)), - - %% Recv ACK #2 - Ack2Bin = <<0, 4, 0, 2>>, - ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack2Bin}, recv(Timeout)), - - %% Send DATA #3 - Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)), - - %% Recv ACK #3 - Ack3Bin = <<0, 4, 0, 3>>, - ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack3Bin}, recv(Timeout)), - - %% Send DATA #3 AGAIN (pretend that we timed out) - timer:sleep(timer:seconds(1)), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)), - - %% Recv ACK #3 AGAIN (the packet that the server think that we have lost) - ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack3Bin}, recv(Timeout)), - - %% Send DATA #4 - Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data4Bin)), - - %% Recv ACK #4 - Ack4Bin = <<0, 4, 0, 4>>, - ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack4Bin}, recv(Timeout)), - - %% Send DATA #3 which is out of range - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data3Bin)), - - %% Send DATA #5 - Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data5Bin)), - - %% Recv ACK #5 - Ack5Bin = <<0, 4, 0, 5>>, - ?VERIFY({udp, ServerSocket, Host, ClientPort, Ack5Bin}, recv(Timeout)), - - %% Send DATA #6 - Data6Bin = list_to_binary([0, 3, 0, 6 | Block6]), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Data6Bin)), - - %% Close daemon and server sockets - ?VERIFY(ok, gen_udp:close(ServerSocket)), - ?VERIFY(ok, gen_udp:close(DaemonSocket)), - - ?VERIFY({ClientPid, {tftp_client_reply, {ok, Blob}}}, recv(Timeout)), - - ?VERIFY(timeout, recv(Timeout)), - ok. - -resend_write_server(Host, BlkSize) -> - RemoteFilename = "tftp_resend_write_server.tmp", - Block1 = lists:duplicate(BlkSize, $1), - Block2 = lists:duplicate(BlkSize, $2), - Block3 = lists:duplicate(BlkSize, $3), - Block4 = lists:duplicate(BlkSize, $4), - Block5 = lists:duplicate(BlkSize, $5), - Block6 = [], - Blocks = [Block1, Block2, Block3, Block4, Block5, Block6], - Blob = list_to_binary(Blocks), - Size = size(Blob), - - Timeout = timer:seconds(3), - ?VERIFY(timeout, recv(0)), - - %% Open daemon socket - {ok, DaemonSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])), - {ok, DaemonPort} = ?IGNORE(inet:port(DaemonSocket)), - - %% Open server socket - {ok, ServerSocket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])), - ?IGNORE(inet:port(ServerSocket)), - - %% Prepare client process - ReplyTo = self(), - ClientFun = - fun(Extra) -> - Options = [{port, DaemonPort}, {debug, brief}] ++ Extra, - Res = ?VERIFY({ok, Size}, tftp:write_file(RemoteFilename, Blob, Options)), - ReplyTo ! {self(), {tftp_client_reply, Res}}, - exit(normal) - end, - - WriteList = [0, 2, RemoteFilename, 0, "octet", 0], - Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]), - {ClientPort, ClientPid} = - if - BlkSize =:= 512 -> - %% Start client process - ClientPid0 = spawn_link(fun() -> ClientFun([]) end), - - %% Recv WRITE - WriteBin = list_to_binary(WriteList), - io:format("WriteBin ~p\n", [WriteBin]), - {udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, WriteBin}, recv(Timeout)), - - %% Send ACK #1 - Ack0Bin = <<0, 4, 0, 0>>, - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, Ack0Bin)), - - %% Sleep a while in order to provoke the client to re-send the packet - timer:sleep(Timeout + timer:seconds(1)), - - %% Recv DATA #1 (the packet that the server think that we have lost) - ?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)), - - %% Recv DATA #1 AGAIN (the re-sent package) - ?VERIFY({udp, ServerSocket, Host, _, Data1Bin}, recv(Timeout)), - {ClientPort0, ClientPid0}; - true -> - %% Start client process - BlkSizeList = integer_to_list(BlkSize), - ClientPid0 = spawn_link(fun() -> ClientFun([{"blksize", BlkSizeList}]) end), - - %% Recv WRITE - Options = ["blksize", 0, BlkSizeList, 0], - WriteBin = list_to_binary([WriteList | Options]), - {udp, _, _, ClientPort0, _} = ?VERIFY({udp, DaemonSocket, Host, _, WriteBin}, recv(Timeout)), - - %% Send OACK - BlkSizeList = integer_to_list(BlkSize), - OptionAckBin = list_to_binary([0, 6, "blksize",0, BlkSizeList, 0]), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort0, OptionAckBin)), - - %% Sleep a while in order to provoke the client to re-send the packet - timer:sleep(Timeout + timer:seconds(1)), - - %% Recv DATA #1 (the packet that the server think that we have lost) - ?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)), - - %% Recv DATA #1 AGAIN (the re-sent package) - ?VERIFY({udp, ServerSocket, Host, ClientPort0, Data1Bin}, recv(Timeout)), - {ClientPort0, ClientPid0} - end, - - %% Send ACK #1 - Ack1Bin = <<0, 4, 0, 1>>, - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack1Bin)), - - %% Recv DATA #2 - Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]), - ?VERIFY({udp, ServerSocket, Host, ClientPort, Data2Bin}, recv(Timeout)), - - %% Send ACK #2 - Ack2Bin = <<0, 4, 0, 2>>, - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack2Bin)), - - %% Recv DATA #3 - Data3Bin = list_to_binary([0, 3, 0, 3 | Block3]), - ?VERIFY({udp, ServerSocket, Host, ClientPort, Data3Bin}, recv(Timeout)), - - %% Send ACK #3 - Ack3Bin = <<0, 4, 0, 3>>, - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)), - - %% Send ACK #3 AGAIN (pretend that we timed out) - timer:sleep(timer:seconds(1)), - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)), - - %% Recv DATA #4 (the packet that the server think that we have lost) - Data4Bin = list_to_binary([0, 3, 0, 4 | Block4]), - ?VERIFY({udp, ServerSocket, Host, ClientPort, Data4Bin}, recv(Timeout)), - - %% Recv DATA #4 AGAIN (the re-sent package) - ?VERIFY({udp, ServerSocket, Host, ClientPort, Data4Bin}, recv(Timeout)), - - %% Send ACK #4 - Ack4Bin = <<0, 4, 0, 4>>, - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack4Bin)), - - %% Recv DATA #5 - Data5Bin = list_to_binary([0, 3, 0, 5 | Block5]), - ?VERIFY({udp, ServerSocket, Host, ClientPort, Data5Bin}, recv(Timeout)), - - %% Send ACK #3 which is out of range - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack3Bin)), - - %% Send ACK #5 - Ack5Bin = <<0, 4, 0, 5>>, - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack5Bin)), - - %% Recv DATA #6 - Data6Bin = list_to_binary([0, 3, 0, 6 | Block6]), - ?VERIFY({udp, ServerSocket, Host, ClientPort, Data6Bin}, recv(Timeout)), - - %% Send ACK #6 - Ack6Bin = <<0, 4, 0, 6>>, - ?VERIFY(ok, gen_udp:send(ServerSocket, Host, ClientPort, Ack6Bin)), - - %% Close daemon and server sockets - ?VERIFY(ok, gen_udp:close(ServerSocket)), - ?VERIFY(ok, gen_udp:close(DaemonSocket)), - - ?VERIFY({ClientPid, {tftp_client_reply, {ok, Size}}}, recv(Timeout)), - - ?VERIFY(timeout, recv(Timeout)), - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -reuse_connection(doc) -> - ["Verify that the server can reuse an ongiong connection when same client resends request."]; -reuse_connection(suite) -> - []; -reuse_connection(Config) when is_list(Config) -> - Host = {127, 0, 0, 1}, - {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, all}])), - - RemoteFilename = "reuse_connection.tmp", - BlkSize = 512, - Block1 = lists:duplicate(BlkSize, $1), - Block2 = lists:duplicate(BlkSize div 2, $2), - Blocks = [Block1, Block2], - Blob = list_to_binary(Blocks), - ?VERIFY(ok, file:write_file(RemoteFilename, Blob)), - - Seconds = 3, - Timeout = timer:seconds(Seconds), - ?VERIFY(timeout, recv(0)), - - %% Open socket - {ok, Socket} = ?VERIFY({ok, _}, gen_udp:open(0, [binary, {reuseaddr, true}, {active, true}])), - - ReadList = [0, 1, RemoteFilename, 0, "octet", 0], - Data1Bin = list_to_binary([0, 3, 0, 1 | Block1]), - - %% Send READ - TimeoutList = integer_to_list(Seconds), - Options = ["timeout", 0, TimeoutList, 0], - ReadBin = list_to_binary([ReadList | Options]), - ?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)), - - %% Send yet another READ for same file - ?VERIFY(ok, gen_udp:send(Socket, Host, Port, ReadBin)), - - %% Recv OACK - OptionAckBin = list_to_binary([0, 6 | Options]), - {udp, _, _, NewPort, _} = ?VERIFY({udp, Socket, Host, _, OptionAckBin}, recv(Timeout)), - - %% Send ACK #0 - Ack0Bin = <<0, 4, 0, 0>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack0Bin)), - - %% Recv DATA #1 - ?VERIFY({udp, Socket, Host, NewPort, Data1Bin}, recv(Timeout)), - - %% Send ACK #1 - Ack1Bin = <<0, 4, 0, 1>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack1Bin)), - - %% Recv DATA #2 - Data2Bin = list_to_binary([0, 3, 0, 2 | Block2]), - ?VERIFY({udp, Socket, Host, NewPort, Data2Bin}, recv(Timeout)), - - %% Send ACK #2 - Ack2Bin = <<0, 4, 0, 2>>, - ?VERIFY(ok, gen_udp:send(Socket, Host, NewPort, Ack2Bin)), - - %% Close socket - ?VERIFY(ok, gen_udp:close(Socket)), - - ?VERIFY(timeout, recv(Timeout)), - ?VERIFY(ok, file:delete(RemoteFilename)), - - %% Cleanup - unlink(DaemonPid), - exit(DaemonPid, kill), - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Large file: transfer > 65535 blocks -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -large_file(doc) -> - ["Start the daemon and test transfer of files greater than 32M."]; -large_file(suite) -> - []; -large_file(Config) when is_list(Config) -> - ?VERIFY(ok, application:start(inets)), - - {Port, DaemonPid} = ?IGNORE(?START_DAEMON(0, [{debug, brief}])), - - %% Read fail - RemoteFilename = "tftp_temporary_large_file_remote_test_file.txt", - LocalFilename = "tftp_temporary_large_file_local_test_file.txt", - - {ok, FH} = file:open(LocalFilename, [write,exclusive]), - {ok, Size} = file:position(FH, {eof, 2*512*65535}), - ok = file:truncate(FH), - ?IGNORE(file:close(FH)), - - %% Write and read - ?VERIFY({ok, Size}, tftp:write_file(RemoteFilename, LocalFilename, [{port, Port}])), - ?IGNORE(file:delete(LocalFilename)), - ?VERIFY({ok, Size}, tftp:read_file(RemoteFilename, LocalFilename, [{port, Port}])), - - %% Cleanup - unlink(DaemonPid), - exit(DaemonPid, kill), - ?VERIFY(ok, file:delete(LocalFilename)), - ?VERIFY(ok, file:delete(RemoteFilename)), - ?VERIFY(ok, application:stop(inets)), - ok. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Goodies -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -recv(Timeout) -> - receive - Msg -> - Msg - after Timeout -> - timeout - end. diff --git a/lib/inets/test/tftp_test_lib.erl b/lib/inets/test/tftp_test_lib.erl deleted file mode 100644 index f07795324f..0000000000 --- a/lib/inets/test/tftp_test_lib.erl +++ /dev/null @@ -1,308 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-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_test_lib). - --compile(export_all). - --include("tftp_test_lib.hrl"). - -%% -%% ----- -%% - -init_per_testcase(_Case, Config) when is_list(Config) -> - io:format("\n ", []), - ?IGNORE(application:stop(inets)), - Config. - -end_per_testcase(_Case, Config) when is_list(Config) -> - ?IGNORE(application:stop(inets)), - Config. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Infrastructure for test suite -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -error(Actual, Mod, Line) -> - (catch global:send(tftp_global_logger, {failed, Mod, Line})), - log("<ERROR> Bad result: ~p\n", [Actual], Mod, Line), - Label = lists:concat([Mod, "(", Line, ") unexpected result"]), - et:report_event(60, Mod, Mod, Label, - [{line, Mod, Line}, {error, Actual}]), - case global:whereis_name(tftp_test_case_sup) of - undefined -> - ignore; - Pid -> - Fail = #'REASON'{mod = Mod, line = Line, desc = Actual}, - Pid ! {fail, self(), Fail} - end, - Actual. - -log(Format, Args, Mod, Line) -> - case global:whereis_name(tftp_global_logger) of - undefined -> - io:format(user, "~p(~p): " ++ Format, - [Mod, Line] ++ Args); - Pid -> - io:format(Pid, "~p(~p): " ++ Format, - [Mod, Line] ++ Args) - end. - -default_config() -> - []. - -t() -> - t([{?MODULE, all}]). - -t(Cases) -> - t(Cases, default_config()). - -t(Cases, Config) -> - process_flag(trap_exit, true), - Res = lists:flatten(do_test(Cases, Config)), - io:format("Res: ~p\n", [Res]), - display_result(Res), - Res. - -do_test({Mod, Fun}, Config) when is_atom(Mod), is_atom(Fun) -> - case catch apply(Mod, Fun, [suite]) of - [] -> - io:format("Eval: ~p:", [{Mod, Fun}]), - Res = eval(Mod, Fun, Config), - {R, _, _} = Res, - io:format(" ~p\n", [R]), - Res; - - Cases when is_list(Cases) -> - io:format("Expand: ~p ...\n", [{Mod, Fun}]), - Map = fun(Case) when is_atom(Case)-> {Mod, Case}; - (Case) -> Case - end, - do_test(lists:map(Map, Cases), Config); - - {req, _, {conf, Init, Cases, Finish}} -> - case (catch apply(Mod, Init, [Config])) of - Conf when is_list(Conf) -> - io:format("Expand: ~p ...\n", [{Mod, Fun}]), - Map = fun(Case) when is_atom(Case)-> {Mod, Case}; - (Case) -> Case - end, - Res = do_test(lists:map(Map, Cases), Conf), - (catch apply(Mod, Finish, [Conf])), - Res; - - {'EXIT', {skipped, Reason}} -> - io:format(" => skipping: ~p\n", [Reason]), - [{skipped, {Mod, Fun}, Reason}]; - - Error -> - io:format(" => failed: ~p\n", [Error]), - [{failed, {Mod, Fun}, Error}] - end; - - {'EXIT', {undef, _}} -> - io:format("Undefined: ~p\n", [{Mod, Fun}]), - [{nyi, {Mod, Fun}, ok}]; - - Error -> - io:format("Ignoring: ~p: ~p\n", [{Mod, Fun}, Error]), - [{failed, {Mod, Fun}, Error}] - end; -do_test(Mod, Config) when is_atom(Mod) -> - Res = do_test({Mod, all}, Config), - Res; -do_test(Cases, Config) when is_list(Cases) -> - [do_test(Case, Config) || Case <- Cases]; -do_test(Bad, _Config) -> - [{badarg, Bad, ok}]. - -eval(Mod, Fun, Config) -> - TestCase = {?MODULE, Mod, Fun}, - Label = lists:concat(["TEST CASE: ", Fun]), - et:report_event(40, ?MODULE, Mod, Label ++ " started", - [TestCase, Config]), - global:register_name(tftp_test_case_sup, self()), - Flag = process_flag(trap_exit, true), - Config2 = Mod:init_per_testcase(Fun, Config), - Pid = spawn_link(?MODULE, do_eval, [self(), Mod, Fun, Config2]), - R = wait_for_evaluator(Pid, Mod, Fun, Config2, []), - Mod:end_per_testcase(Fun, Config2), - global:unregister_name(tftp_test_case_sup), - process_flag(trap_exit, Flag), - R. - -wait_for_evaluator(Pid, Mod, Fun, Config, Errors) -> - TestCase = {?MODULE, Mod, Fun}, - Label = lists:concat(["TEST CASE: ", Fun]), - receive - {done, Pid, ok} when Errors == [] -> - et:report_event(40, Mod, ?MODULE, Label ++ " ok", - [TestCase, Config]), - {ok, {Mod, Fun}, Errors}; - {done, Pid, {ok, _}} when Errors == [] -> - et:report_event(40, Mod, ?MODULE, Label ++ " ok", - [TestCase, Config]), - {ok, {Mod, Fun}, Errors}; - {done, Pid, Fail} -> - et:report_event(20, Mod, ?MODULE, Label ++ " failed", - [TestCase, Config, {return, Fail}, Errors]), - {failed, {Mod,Fun}, Fail}; - {'EXIT', Pid, {skipped, Reason}} -> - et:report_event(20, Mod, ?MODULE, Label ++ " skipped", - [TestCase, Config, {skipped, Reason}]), - {skipped, {Mod, Fun}, Errors}; - {'EXIT', Pid, Reason} -> - et:report_event(20, Mod, ?MODULE, Label ++ " crashed", - [TestCase, Config, {'EXIT', Reason}]), - {crashed, {Mod, Fun}, [{'EXIT', Reason} | Errors]}; - {fail, Pid, Reason} -> - wait_for_evaluator(Pid, Mod, Fun, Config, Errors ++ [Reason]) - end. - -do_eval(ReplyTo, Mod, Fun, Config) -> - case (catch apply(Mod, Fun, [Config])) of - {'EXIT', {skipped, Reason}} -> - ReplyTo ! {'EXIT', self(), {skipped, Reason}}; - Other -> - ReplyTo ! {done, self(), Other} - end, - unlink(ReplyTo), - exit(shutdown). - -display_result([]) -> - io:format("OK\n", []); -display_result(Res) when is_list(Res) -> - Ok = [MF || {ok, MF, _} <- Res], - Nyi = [MF || {nyi, MF, _} <- Res], - Skipped = [{MF, Reason} || {skipped, MF, Reason} <- Res], - Failed = [{MF, Reason} || {failed, MF, Reason} <- Res], - Crashed = [{MF, Reason} || {crashed, MF, Reason} <- Res], - display_summary(Ok, Nyi, Skipped, Failed, Crashed), - display_skipped(Skipped), - display_failed(Failed), - display_crashed(Crashed). - -display_summary(Ok, Nyi, Skipped, Failed, Crashed) -> - io:format("\nTest case summary:\n", []), - display_summary(Ok, "successful"), - display_summary(Nyi, "not yet implemented"), - display_summary(Skipped, "skipped"), - display_summary(Failed, "failed"), - display_summary(Crashed, "crashed"), - io:format("\n", []). - -display_summary(Res, Info) -> - io:format(" ~w test cases ~s\n", [length(Res), Info]). - -display_skipped([]) -> - ok; -display_skipped(Skipped) -> - io:format("Skipped test cases:\n", []), - F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end, - lists:foreach(F, Skipped), - io:format("\n", []). - - -display_failed([]) -> - ok; -display_failed(Failed) -> - io:format("Failed test cases:\n", []), - F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end, - lists:foreach(F, Failed), - io:format("\n", []). - -display_crashed([]) -> - ok; -display_crashed(Crashed) -> - io:format("Crashed test cases:\n", []), - F = fun({MF, Reason}) -> io:format(" ~p => ~p\n", [MF, Reason]) end, - lists:foreach(F, Crashed), - io:format("\n", []). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% generic callback -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - --record(generic_state, {state, prepare, open, read, write, abort}). - -prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) when is_list(Initial) -> - State = lookup_option(state, mandatory, Initial), - Prepare = lookup_option(prepare, mandatory, Initial), - Open = lookup_option(open, mandatory, Initial), - Read = lookup_option(read, mandatory, Initial), - Write = lookup_option(write, mandatory, Initial), - Abort = lookup_option(abort, mandatory, Initial), - case Prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, State) of - {ok, AcceptedOptions, NewState} -> - {ok, - AcceptedOptions, - #generic_state{state = NewState, - prepare = Prepare, - open = Open, - read = Read, - write = Write, - abort = Abort}}; - Other -> - Other - end. - -open(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) when is_list(Initial) -> - case prepare(Peer, Access, LocalFilename, Mode, SuggestedOptions, Initial) of - {ok, SuggestedOptions2, GenericState} -> - open(Peer, Access, LocalFilename, Mode, SuggestedOptions2, GenericState); - Other -> - Other - end; -open(Peer, Access, LocalFilename, Mode, SuggestedOptions, #generic_state{state = State, open = Open} = GenericState) -> - case Open(Peer, Access, LocalFilename, Mode, SuggestedOptions, State) of - {ok, SuggestedOptions2, NewState} -> - {ok, SuggestedOptions2, GenericState#generic_state{state = NewState}}; - Other -> - Other - end. - -read(#generic_state{state = State, read = Read} = GenericState) -> - case Read(State) of - {more, DataBlock, NewState} -> - {more, DataBlock, GenericState#generic_state{state = NewState}}; - Other -> - Other - end. - -write(DataBlock, #generic_state{state = State, write = Write} = GenericState) -> - case Write(DataBlock, State) of - {more, NewState} -> - {more, GenericState#generic_state{state = NewState}}; - Other -> - Other - end. - -abort(Code, Text, #generic_state{state = State, abort = Abort}) -> - Abort(Code, Text, State). - -lookup_option(Key, Default, Options) -> - case lists:keysearch(Key, 1, Options) of - {value, {_, Val}} -> - Val; - false -> - Default - end. - |