aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/inets/src/ftp/ftp.erl53
-rw-r--r--lib/inets/test/ftp_SUITE.erl51
2 files changed, 97 insertions, 7 deletions
diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl
index 2a225e930e..e0430654eb 100644
--- a/lib/inets/src/ftp/ftp.erl
+++ b/lib/inets/src/ftp/ftp.erl
@@ -100,6 +100,12 @@
ftp_extension = ?FTP_EXT_DEFAULT
}).
+-record(recv_chunk_closing, {
+ dconn_closed = false,
+ pos_compl_received = false,
+ client_called_us = false
+ }).
+
-type shortage_reason() :: 'etnospc' | 'epnospc'.
-type restriction_reason() :: 'epath' | 'efnamena' | 'elogin' | 'enotbinary'.
@@ -1343,6 +1349,25 @@ handle_call({_,{recv_chunk_start, RemoteFile}}, From, #state{chunk = false}
handle_call({_, recv_chunk}, _, #state{chunk = false} = State) ->
{reply, {error, "ftp:recv_chunk_start/2 not called"}, State};
+handle_call({_, recv_chunk}, _From, #state{chunk = true,
+ caller = #recv_chunk_closing{dconn_closed = true,
+ pos_compl_received = true
+ }
+ } = State0) ->
+ %% The ftp:recv_chunk call was the last event we waited for, finnish and clean up
+ ?DBG("recv_chunk_closing ftp:recv_chunk, last event",[]),
+ activate_ctrl_connection(State0),
+ {reply, ok, State0#state{caller = undefined,
+ chunk = false,
+ client = undefined}};
+
+handle_call({_, recv_chunk}, From, #state{chunk = true,
+ caller = #recv_chunk_closing{} = R
+ } = State) ->
+ %% Waiting for more, don't care what
+ ?DBG("recv_chunk_closing ftp:recv_chunk, get more",[]),
+ {noreply, State#state{client = From, caller = R#recv_chunk_closing{client_called_us=true}}};
+
handle_call({_, recv_chunk}, From, #state{chunk = true} = State0) ->
State = activate_data_connection(State0),
{noreply, State#state{client = From, caller = recv_chunk}};
@@ -1480,19 +1505,24 @@ handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
file_close(Fd),
progress_report({transfer_size, 0}, State),
activate_ctrl_connection(State),
+ ?DBG("Data channel close",[]),
{noreply, State#state{dsock = undefined, data = <<>>}};
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket},
+ client = Client,
caller = recv_chunk} = State)
when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ ?DBG("Data channel close recv_chunk",[]),
activate_ctrl_connection(State),
- {noreply, State#state{dsock = undefined, data = <<>>,
- caller = recv_chunk_closed
+ {noreply, State#state{dsock = undefined,
+ caller = #recv_chunk_closing{dconn_closed = true,
+ client_called_us = Client =/= undefined}
}};
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin,
data = Data} = State)
when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ ?DBG("Data channel close",[]),
activate_ctrl_connection(State),
{noreply, State#state{dsock = undefined, data = <<>>,
caller = {recv_bin, Data}}};
@@ -1500,6 +1530,7 @@ handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, caller = recv_bin,
handle_info({Cls, Socket}, #state{dsock = {Trpt,Socket}, data = Data,
caller = {handle_dir_result, Dir}}
= State) when {Cls,Trpt}=={tcp_closed,tcp} ; {Cls,Trpt}=={ssl_closed,ssl} ->
+ ?DBG("Data channel close",[]),
activate_ctrl_connection(State),
{noreply, State#state{dsock = undefined,
caller = {handle_dir_result, Dir, Data},
@@ -2047,14 +2078,28 @@ handle_ctrl_result({pos_prel, _}, #state{client = From,
%%--------------------------------------------------------------------------
%% File handling - chunk_transfer complete
+
handle_ctrl_result({pos_compl, _}, #state{client = From,
- caller = recv_chunk_closed}
- = State0) ->
+ caller = #recv_chunk_closing{dconn_closed = true,
+ client_called_us = true,
+ pos_compl_received = false
+ }}
+ = State0) when From =/= undefined ->
+ %% The pos_compl was the last event we waited for, finnish and clean up
+ ?DBG("recv_chunk_closing pos_compl, last event",[]),
gen_server:reply(From, ok),
+ activate_ctrl_connection(State0),
{noreply, State0#state{caller = undefined,
chunk = false,
client = undefined}};
+handle_ctrl_result({pos_compl, _}, #state{caller = #recv_chunk_closing{}=R}
+ = State0) ->
+ %% Waiting for more, don't care what
+ ?DBG("recv_chunk_closing pos_compl, wait more",[]),
+ {noreply, State0#state{caller = R#recv_chunk_closing{pos_compl_received=true}}};
+
+
%%--------------------------------------------------------------------------
%% File handling - recv_file
handle_ctrl_result({pos_prel, _}, #state{caller = {recv_file, _}} = State0) ->
diff --git a/lib/inets/test/ftp_SUITE.erl b/lib/inets/test/ftp_SUITE.erl
index d37f2feb7e..1a79cb58ed 100644
--- a/lib/inets/test/ftp_SUITE.erl
+++ b/lib/inets/test/ftp_SUITE.erl
@@ -97,6 +97,7 @@ ftp_tests()->
recv_bin_twice,
recv_chunk,
recv_chunk_twice,
+ recv_chunk_three_times,
type,
quote,
error_elogin,
@@ -626,13 +627,57 @@ recv_chunk_twice(Config0) ->
find_diff(ReceivedContents1, Contents1),
find_diff(ReceivedContents2, Contents2).
+recv_chunk_three_times() ->
+ [{doc, "Receive two files using chunk-wise."},
+ {timetrap,{seconds,120}}].
+recv_chunk_three_times(Config0) ->
+ File1 = "big_file1.txt",
+ File2 = "big_file2.txt",
+ File3 = "big_file3.txt",
+ Contents1 = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ),
+ Contents2 = crypto:strong_rand_bytes(1200),
+ Contents3 = list_to_binary( lists:duplicate(1000, lists:seq(255,0,-1)) ),
+
+ Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}, {mkfile,File3,Contents3}], Config0),
+ Pid = proplists:get_value(ftp, Config),
+ {{error, "ftp:recv_chunk_start/2 not called"},_} = recv_chunk(Pid, <<>>),
+
+ ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)),
+ {ok, ReceivedContents1, Nchunks1} = recv_chunk(Pid, <<>>),
+
+ ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)),
+ {ok, ReceivedContents2, _Nchunks2} = recv_chunk(Pid, <<>>),
+
+ ok = ftp:recv_chunk_start(Pid, id2ftp(File3,Config)),
+ {ok, ReceivedContents3, _Nchunks3} = recv_chunk(Pid, <<>>, 10000, 0, Nchunks1),
+
+ find_diff(ReceivedContents1, Contents1),
+ find_diff(ReceivedContents2, Contents2),
+ find_diff(ReceivedContents3, Contents3).
+
+
+
recv_chunk(Pid, Acc) ->
- recv_chunk(Pid, Acc, 0).
+ recv_chunk(Pid, Acc, 0, 0, undefined).
+
+
+
+%% ExpectNchunks :: integer() | undefined
+recv_chunk(Pid, Acc, DelayMilliSec, N, ExpectNchunks) when N+1 < ExpectNchunks ->
+ %% for all I in integer(), I < undefined
+ recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks);
+
+recv_chunk(Pid, Acc, DelayMilliSec, N, ExpectNchunks) ->
+ %% N >= ExpectNchunks-1
+ timer:sleep(DelayMilliSec),
+ recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks).
+
-recv_chunk(Pid, Acc, N) ->
+recv_chunk1(Pid, Acc, DelayMilliSec, N, ExpectNchunks) ->
+ ct:log("Call ftp:recv_chunk",[]),
case ftp:recv_chunk(Pid) of
ok -> {ok, Acc, N};
- {ok, Bin} -> recv_chunk(Pid, <<Acc/binary, Bin/binary>>, N+1);
+ {ok, Bin} -> recv_chunk(Pid, <<Acc/binary, Bin/binary>>, DelayMilliSec, N+1, ExpectNchunks);
Error -> {Error, N}
end.