1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20%%
21-module(ftp_SUITE).
22
23-include_lib("kernel/include/file.hrl").
24-include_lib("common_test/include/ct.hrl").
25
26%% Note: This directive should only be used in test suites.
27-compile(export_all).
28
29-define(FTP_USER, "anonymous").
30-define(FTP_PASS(Cmnt), (fun({ok,__H}) -> "ftp_SUITE_"++Cmnt++"@" ++ __H;
31			    (_) -> "ftp_SUITE_"++Cmnt++"@localhost"
32			 end)(inet:gethostname())
33       ).
34
35-define(BAD_HOST, "badhostname").
36-define(BAD_USER, "baduser").
37-define(BAD_DIR,  "baddirectory").
38
39-record(progress, {
40	  current = 0,
41	  total
42	 }).
43
44%%--------------------------------------------------------------------
45%% Common Test interface functions -----------------------------------
46%%--------------------------------------------------------------------
47suite() ->
48    [{timetrap,{seconds,20}}].
49
50all() ->
51    [
52     {group, ftp_passive},
53     {group, ftp_active},
54     {group, ftps_passive},
55     {group, ftps_active},
56     {group, ftp_sup},
57     app,
58     appup,
59     error_ehost,
60     clean_shutdown
61    ].
62
63groups() ->
64    [
65     {ftp_passive, [], ftp_tests()},
66     {ftp_active, [], ftp_tests()},
67     {ftps_passive, [], ftp_tests()},
68     {ftps_active, [], ftp_tests()},
69     {ftp_sup, [], ftp_sup_tests()}
70    ].
71
72ftp_tests()->
73    [
74     user,
75     bad_user,
76     pwd,
77     cd,
78     lcd,
79     ls,
80     nlist,
81     rename,
82     delete,
83     mkdir,
84     rmdir,
85     send,
86     send_3,
87     send_bin,
88     send_chunk,
89     append,
90     append_bin,
91     append_chunk,
92     recv,
93     recv_3,
94     recv_bin,
95     recv_bin_twice,
96     recv_chunk,
97     recv_chunk_twice,
98     recv_chunk_three_times,
99     recv_chunk_delay,
100     type,
101     quote,
102     error_elogin,
103     progress_report_send,
104     progress_report_recv,
105     not_owner,
106     unexpected_call,
107     unexpected_cast,
108     unexpected_bang
109    ].
110
111ftp_sup_tests() ->
112    [
113     start_ftp,
114     ftp_worker
115    ].
116
117%%--------------------------------------------------------------------
118
119%%% Config
120%%% key			meaning
121%%% ................................................................
122%%% ftpservers		list of servers to check if they are available
123%%%			The element is:
124%%%			  {Name,         % string(). The os command name
125%%%                        Path,         % string(). The os PATH syntax, e.g "/bin:/usr/bin"
126%%%			   StartCommand, % fun()->{ok,start_result()} | {error,string()}.
127%%%			                 % The command to start the daemon with.
128%%%			   ChkUp,        % fun(start_result()) -> string(). Os command to check
129%%%			                 %       if the server is running. [] if not running.
130%%%			                 %       The string in string() is suitable for logging.
131%%%			   StopCommand,  % fun(start_result()) -> void(). The command to stop the daemon with.
132%%%			   AugmentFun,   % fun(config()) -> config() Adds two funs for transforming names of files
133%%%			                 %       and directories to the form they are returned from this server
134%%%			   ServerHost,   % string(). Mostly "localhost"
135%%%			   ServerPort    % pos_integer()
136%%%			  }
137%%%
138
139-define(default_ftp_servers,
140	[{"vsftpd",
141	  "/sbin:/usr/sbin:/usr/local/sbin",
142	  fun(__CONF__, AbsName) ->
143		  DataDir = proplists:get_value(data_dir,__CONF__),
144		  ConfFile = filename:join(DataDir, "vsftpd.conf"),
145		  PrivDir = proplists:get_value(priv_dir,__CONF__),
146		  AnonRoot = PrivDir,
147		  Cmd = [AbsName ++" "++filename:join(DataDir,"vsftpd.conf"),
148			 " -oftpd_banner=erlang_otp_testing",
149			 " -oanon_root=\"",AnonRoot,"\"",
150			 " -orsa_cert_file=\"",filename:join(DataDir,"server-cert.pem"),"\"",
151			 " -orsa_private_key_file=\"",filename:join(DataDir,"server-key.pem"),"\""
152			],
153		  Result = os:cmd(Cmd),
154		  ct:log("Config file:~n~s~n~nServer start command:~n  ~s~nResult:~n  ~p",
155			 [case file:read_file(ConfFile) of
156			      {ok,X} -> X;
157			      _ -> ""
158			  end,
159			  Cmd, Result
160			 ]),
161		  case Result of
162		      [] -> {ok,'dont care'};
163		      [Msg] -> {error,Msg}
164		  end
165	  end,
166	  fun(_StartResult) -> os:cmd("ps ax | grep erlang_otp_testing | grep -v grep")
167	  end,
168	  fun(_StartResult) -> os:cmd("kill `ps ax | grep erlang_otp_testing | awk '/vsftpd/{print $1}'`")
169	  end,
170	  fun(__CONF__) ->
171		  AnonRoot = proplists:get_value(priv_dir,__CONF__),
172		  [{id2ftp, fun(Id) -> filename:join(AnonRoot,Id) end},
173		   {id2ftp_result,fun(Id) -> filename:join(AnonRoot,Id) end} | __CONF__]
174	  end,
175	  "localhost",
176	  9999
177	 }
178	]
179       ).
180
181
182init_per_suite(Config) ->
183    case find_executable(Config) of
184	false ->
185	    {skip, "No ftp server found"};
186	{ok,Data} ->
187	    TstDir = filename:join(proplists:get_value(priv_dir,Config), "test"),
188	    file:make_dir(TstDir),
189	    %% make_cert_files(dsa, rsa, "server-", proplists:get_value(data_dir,Config)),
190            ftp_test_lib:make_cert_files(proplists:get_value(data_dir,Config)),
191	    start_ftpd([{test_dir,TstDir},
192			{ftpd_data,Data}
193			| Config])
194    end.
195
196end_per_suite(Config) ->
197    ps_ftpd(Config),
198    stop_ftpd(Config),
199    ps_ftpd(Config),
200    ok.
201
202%%--------------------------------------------------------------------
203init_per_group(Group, Config) when Group == ftps_active,
204                                   Group == ftps_passive ->
205    catch crypto:stop(),
206    try crypto:start() of
207        ok ->
208            Config
209    catch
210        _:_ ->
211            {skip, "Crypto did not start"}
212    end;
213init_per_group(ftp_sup, Config) ->
214    try ftp:start() of
215        ok ->
216            Config
217    catch
218        _:_ ->
219            {skip, "Ftp did not start"}
220    end;
221init_per_group(_Group, Config) ->
222    Config.
223
224
225end_per_group(ftp_sup, Config) ->
226    ftp:stop(),
227    Config;
228end_per_group(_Group, Config) ->
229    Config.
230
231%%--------------------------------------------------------------------
232init_per_testcase(T, Config0) when T =:= app; T =:= appup ->
233    Config0;
234init_per_testcase(Case, Config0) ->
235    Group = proplists:get_value(name, proplists:get_value(tc_group_properties,Config0)),
236
237    %% Workaround for interoperability issues with vsftpd =< 3.0.2:
238    %%
239    %% vsftpd =< 3.0.2 does not support ECDHE ciphers and the ssl application
240    %% removed ciphers with RSA key exchange from its default cipher list.
241    %% To allow interoperability with old versions of vsftpd, cipher suites
242    %% with RSA key exchange are appended to the default cipher list.
243    All = ssl:cipher_suites(all, 'tlsv1.2'),
244    Default = ssl:cipher_suites(default, 'tlsv1.2'),
245    RSASuites =
246        ssl:filter_cipher_suites(All, [{key_exchange, fun(rsa) -> true;
247                                                         (_) -> false end}]),
248    Suites = ssl:append_cipher_suites(RSASuites, Default),
249    TLS = [{tls,[{reuse_sessions,true},{ciphers, Suites}]}],
250    ACTIVE = [{mode,active}],
251    PASSIVE = [{mode,passive}],
252    CaseOpts = case Case of
253		   progress_report_send -> [{progress, {?MODULE,progress,#progress{}}}];
254		   progress_report_recv -> [{progress, {?MODULE,progress,#progress{}}}];
255		   _ -> []
256	       end,
257    ExtraOpts = [verbose | CaseOpts],
258    Config =
259	case Group of
260	    ftp_active   -> ftp__open(Config0,       ACTIVE  ++ ExtraOpts);
261	    ftps_active  -> ftp__open(Config0, TLS++ ACTIVE  ++ ExtraOpts);
262	    ftp_passive  -> ftp__open(Config0,      PASSIVE  ++ ExtraOpts);
263	    ftps_passive -> ftp__open(Config0, TLS++PASSIVE  ++ ExtraOpts);
264            ftp_sup      -> ftp_start_service(Config0, ACTIVE  ++ ExtraOpts);
265	    undefined    -> Config0
266	end,
267    case Case of
268	user           -> Config;
269	bad_user       -> Config;
270	error_elogin   -> Config;
271	error_ehost    -> Config;
272	clean_shutdown -> Config;
273	_ ->
274	    Pid = proplists:get_value(ftp,Config),
275	    ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS(atom_to_list(Group)++"-"++atom_to_list(Case)) ),
276	    ok = ftp:cd(Pid, proplists:get_value(priv_dir,Config)),
277	    Config
278    end.
279
280end_per_testcase(T, _Config) when  T =:= app; T =:= appup -> ok;
281end_per_testcase(user, _Config) -> ok;
282end_per_testcase(bad_user, _Config) -> ok;
283end_per_testcase(error_elogin, _Config) -> ok;
284end_per_testcase(error_ehost, _Config) -> ok;
285end_per_testcase(clean_shutdown, _Config) -> ok;
286end_per_testcase(_Case, Config) ->
287    case proplists:get_value(tc_status,Config) of
288	ok -> ok;
289	_ ->
290	    try ftp:latest_ctrl_response(proplists:get_value(ftp,Config))
291	    of
292		{ok,S} -> ct:log("***~n*** Latest ctrl channel response:~n***     ~p~n***",[S])
293	    catch
294		_:_ -> ok
295	    end
296    end,
297    Group = proplists:get_value(name, proplists:get_value(tc_group_properties,Config)),
298    case Group of
299        ftp_sup ->
300            ftp_stop_service(Config);
301        _Else ->
302            ftp__close(Config)
303    end.
304
305%%--------------------------------------------------------------------
306%% Test Cases --------------------------------------------------------
307%%--------------------------------------------------------------------
308app() ->
309    [{doc, "Test that the ftp app file is ok"}].
310app(Config) when is_list(Config) ->
311    ok = ?t:app_test(ftp).
312
313%%--------------------------------------------------------------------
314appup() ->
315    [{doc, "Test that the ftp appup file is ok"}].
316appup(Config) when is_list(Config) ->
317    ok = ?t:appup_test(ftp).
318
319%%--------------------------------------------------------------------
320
321user() -> [
322	   {doc, "Open an ftp connection to a host, and logon as anonymous ftp,"
323	    " then logoff"}].
324user(Config) ->
325    Pid = proplists:get_value(ftp, Config),
326    ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS("")),% logon
327    ok = ftp:close(Pid),			% logoff
328    {error,eclosed} = ftp:pwd(Pid),		% check logoff result
329    ok.
330
331%%-------------------------------------------------------------------------
332bad_user() ->
333    [{doc, "Open an ftp connection to a host, and logon with bad user."}].
334bad_user(Config) ->
335    Pid = proplists:get_value(ftp, Config),
336    {error, euser} = ftp:user(Pid, ?BAD_USER, ?FTP_PASS("")),
337    ok.
338
339%%-------------------------------------------------------------------------
340pwd() ->
341    [{doc, "Test ftp:pwd/1 & ftp:lpwd/1"}].
342pwd(Config0) ->
343    Config = set_state([reset], Config0),
344    Pid = proplists:get_value(ftp, Config),
345    {ok, PWD} = ftp:pwd(Pid),
346    {ok, PathLpwd} = ftp:lpwd(Pid),
347    PWD = id2ftp_result("", Config),
348    PathLpwd = id2ftp_result("", Config).
349
350%%-------------------------------------------------------------------------
351cd() ->
352    ["Open an ftp connection, log on as anonymous ftp, and cd to a"
353     "directory and to a non-existent directory."].
354cd(Config0) ->
355    Dir = "test",
356    Config = set_state([reset,{mkdir,Dir}], Config0),
357    Pid = proplists:get_value(ftp, Config),
358    ok = ftp:cd(Pid, id2ftp(Dir,Config)),
359    {ok, PWD} = ftp:pwd(Pid),
360    ExpectedPWD = id2ftp_result(Dir, Config),
361    PWD = ExpectedPWD,
362    {error, epath} = ftp:cd(Pid, ?BAD_DIR),
363    ok.
364
365%%-------------------------------------------------------------------------
366lcd() ->
367    [{doc, "Test api function ftp:lcd/2"}].
368lcd(Config0) ->
369    Dir = "test",
370    Config = set_state([reset,{mkdir,Dir}], Config0),
371    Pid = proplists:get_value(ftp, Config),
372    ok = ftp:lcd(Pid, id2ftp(Dir,Config)),
373    {ok, PWD} = ftp:lpwd(Pid),
374    ExpectedPWD = id2ftp_result(Dir, Config),
375    PWD = ExpectedPWD,
376    {error, epath} = ftp:lcd(Pid, ?BAD_DIR).
377
378%%-------------------------------------------------------------------------
379ls() ->
380    [{doc, "Open an ftp connection; ls the current directory, and the "
381      "\"test\" directory. We assume that ls never fails, since "
382      "it's output is meant to be read by humans. "}].
383ls(Config0) ->
384    Config = set_state([reset,{mkdir,"test"}], Config0),
385    Pid = proplists:get_value(ftp, Config),
386    {ok, _R1} = ftp:ls(Pid),
387    {ok, _R2} = ftp:ls(Pid, id2ftp("test",Config)),
388    %% neither nlist nor ls operates on a directory
389    %% they operate on a pathname, which *can* be a
390    %% directory, but can also be a filename or a group
391    %% of files (including wildcards).
392    case proplists:get_value(wildcard_support, Config) of
393	true ->
394	    {ok, _R3} = ftp:ls(Pid, id2ftp("te*",Config));
395	_ ->
396	    ok
397    end.
398
399%%-------------------------------------------------------------------------
400nlist() ->
401    [{doc,"Open an ftp connection; nlist the current directory, and the "
402	       "\"test\" directory. Nlist does not behave consistenly over "
403	       "operating systems. On some it is an error to have an empty "
404	       "directory."}].
405nlist(Config0) ->
406    Config = set_state([reset,{mkdir,"test"}], Config0),
407    Pid = proplists:get_value(ftp, Config),
408    {ok, _R1} = ftp:nlist(Pid),
409    {ok, _R2} = ftp:nlist(Pid, id2ftp("test",Config)),
410    %% neither nlist nor ls operates on a directory
411    %% they operate on a pathname, which *can* be a
412    %% directory, but can also be a filename or a group
413    %% of files (including wildcards).
414    case proplists:get_value(wildcard_support, Config) of
415	true ->
416	    {ok, _R3} = ftp:nlist(Pid, id2ftp("te*",Config));
417	_ ->
418	    ok
419    end.
420
421%%-------------------------------------------------------------------------
422rename() ->
423    [{doc, "Rename a file."}].
424rename(Config0) ->
425    Contents = <<"ftp_SUITE test ...">>,
426    OldFile = "old.txt",
427    NewFile = "new.txt",
428    Config = set_state([reset,{mkfile,OldFile,Contents}], Config0),
429    Pid = proplists:get_value(ftp, Config),
430
431    ok = ftp:rename(Pid,
432		    id2ftp(OldFile,Config),
433		    id2ftp(NewFile,Config)),
434
435    true = (chk_file(NewFile,Contents,Config)
436	    and chk_no_file([OldFile],Config)),
437    {error,epath} = ftp:rename(Pid,
438			       id2ftp("non_existing_file",Config),
439			       id2ftp(NewFile,Config)),
440    ok.
441
442%%-------------------------------------------------------------------------
443send() ->
444    [{doc, "Transfer a file with ftp using send/2."}].
445send(Config0) ->
446    Contents = <<"ftp_SUITE test ...">>,
447    SrcDir = "data",
448    File = "file.txt",
449    Config = set_state([reset,{mkfile,[SrcDir,File],Contents}], Config0),
450    Pid = proplists:get_value(ftp, Config),
451
452    chk_no_file([File],Config),
453    chk_file([SrcDir,File],Contents,Config),
454
455    ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)),
456    ok = ftp:cd(Pid, id2ftp("",Config)),
457    ok = ftp:send(Pid, File),
458    chk_file(File, Contents, Config),
459
460    {error,epath} = ftp:send(Pid, "non_existing_file"),
461    ok.
462
463%%-------------------------------------------------------------------------
464send_3() ->
465    [{doc, "Transfer a file with ftp using send/3."}].
466send_3(Config0) ->
467    Contents = <<"ftp_SUITE test ...">>,
468    Dir = "incoming",
469    File = "file.txt",
470    RemoteFile = "remfile.txt",
471    Config = set_state([reset,{mkfile,File,Contents},{mkdir,Dir}], Config0),
472    Pid = proplists:get_value(ftp, Config),
473
474    ok = ftp:cd(Pid, id2ftp(Dir,Config)),
475    ok = ftp:lcd(Pid, id2ftp("",Config)),
476    ok = ftp:send(Pid, File, RemoteFile),
477    chk_file([Dir,RemoteFile], Contents, Config),
478
479    {error,epath} = ftp:send(Pid, "non_existing_file", RemoteFile),
480    ok.
481
482%%-------------------------------------------------------------------------
483send_bin() ->
484    [{doc, "Send a binary."}].
485send_bin(Config0) ->
486    BinContents = <<"ftp_SUITE test ...">>,
487    File = "file.txt",
488    Config = set_state([reset], Config0),
489    Pid = proplists:get_value(ftp, Config),
490    {error, enotbinary} = ftp:send_bin(Pid, "some string", id2ftp(File,Config)),
491    ok = ftp:send_bin(Pid, BinContents, id2ftp(File,Config)),
492    chk_file(File, BinContents, Config),
493    {error, efnamena} = ftp:send_bin(Pid, BinContents, "/nothere"),
494    ok.
495
496%%-------------------------------------------------------------------------
497send_chunk() ->
498    [{doc, "Send a binary using chunks."}].
499send_chunk(Config0) ->
500    Contents1 = <<"1: ftp_SUITE test ...">>,
501    Contents2 = <<"2: ftp_SUITE test ...">>,
502    File = "file.txt",
503    Config = set_state([reset,{mkdir,"incoming"}], Config0),
504    Pid = proplists:get_value(ftp, Config),
505
506    ok = ftp:send_chunk_start(Pid, id2ftp(File,Config)),
507    {error, echunk} = ftp:send_chunk_start(Pid, id2ftp(File,Config)),
508    {error, echunk} = ftp:cd(Pid, "incoming"),
509    {error, enotbinary} = ftp:send_chunk(Pid, "some string"),
510    ok = ftp:send_chunk(Pid, Contents1),
511    ok = ftp:send_chunk(Pid, Contents2),
512    ok = ftp:send_chunk_end(Pid),
513    chk_file(File, <<Contents1/binary,Contents2/binary>>, Config),
514
515    {error, echunk} = ftp:send_chunk(Pid, Contents1),
516    {error, echunk} = ftp:send_chunk_end(Pid),
517    {error, efnamena} = ftp:send_chunk_start(Pid, "/"),
518    ok.
519
520%%-------------------------------------------------------------------------
521delete() ->
522    [{doc, "Delete a file."}].
523delete(Config0) ->
524    Contents = <<"ftp_SUITE test ...">>,
525    File = "file.txt",
526    Config = set_state([reset,{mkfile,File,Contents}], Config0),
527    Pid = proplists:get_value(ftp, Config),
528    ok = ftp:delete(Pid, id2ftp(File,Config)),
529    chk_no_file([File], Config),
530    {error,epath} = ftp:delete(Pid, id2ftp(File,Config)),
531    ok.
532
533%%-------------------------------------------------------------------------
534mkdir() ->
535    [{doc, "Make a remote directory."}].
536mkdir(Config0) ->
537    NewDir = "new_dir",
538    Config = set_state([reset], Config0),
539    Pid = proplists:get_value(ftp, Config),
540    ok = ftp:mkdir(Pid, id2ftp(NewDir,Config)),
541    chk_dir([NewDir], Config),
542    {error,epath} = ftp:mkdir(Pid, id2ftp(NewDir,Config)),
543    ok.
544
545%%-------------------------------------------------------------------------
546rmdir() ->
547    [{doc, "Remove a directory."}].
548rmdir(Config0) ->
549    Dir = "dir",
550    Config = set_state([reset,{mkdir,Dir}], Config0),
551    Pid = proplists:get_value(ftp, Config),
552    ok = ftp:rmdir(Pid, id2ftp(Dir,Config)),
553    chk_no_dir([Dir], Config),
554    {error,epath} = ftp:rmdir(Pid, id2ftp(Dir,Config)),
555    ok.
556
557%%-------------------------------------------------------------------------
558append() ->
559    [{doc, "Append a local file twice to a remote file"}].
560append(Config0) ->
561    SrcFile = "f_src.txt",
562    DstFile = "f_dst.txt",
563    Contents = <<"ftp_SUITE test ...">>,
564    Config = set_state([reset,{mkfile,SrcFile,Contents}], Config0),
565    Pid = proplists:get_value(ftp, Config),
566    ok = ftp:append(Pid, id2ftp(SrcFile,Config), id2ftp(DstFile,Config)),
567    ok = ftp:append(Pid, id2ftp(SrcFile,Config), id2ftp(DstFile,Config)),
568    chk_file(DstFile, <<Contents/binary,Contents/binary>>, Config),
569    {error,epath} = ftp:append(Pid, id2ftp("non_existing_file",Config), id2ftp(DstFile,Config)),
570    ok.
571
572%%-------------------------------------------------------------------------
573append_bin() ->
574    [{doc, "Append a local file twice to a remote file using append_bin"}].
575append_bin(Config0) ->
576    DstFile = "f_dst.txt",
577    Contents = <<"ftp_SUITE test ...">>,
578    Config = set_state([reset], Config0),
579    Pid = proplists:get_value(ftp, Config),
580    ok = ftp:append_bin(Pid, Contents, id2ftp(DstFile,Config)),
581    ok = ftp:append_bin(Pid, Contents, id2ftp(DstFile,Config)),
582    chk_file(DstFile, <<Contents/binary,Contents/binary>>, Config).
583
584%%-------------------------------------------------------------------------
585append_chunk() ->
586    [{doc, "Append chunks."}].
587append_chunk(Config0) ->
588    File = "f_dst.txt",
589    Contents = [<<"ER">>,<<"LE">>,<<"RL">>],
590    Config = set_state([reset], Config0),
591    Pid = proplists:get_value(ftp, Config),
592    ok = ftp:append_chunk_start(Pid, id2ftp(File,Config)),
593    {error, enotbinary} = ftp:append_chunk(Pid, binary_to_list(lists:nth(1,Contents))),
594    ok = ftp:append_chunk(Pid,lists:nth(1,Contents)),
595    ok = ftp:append_chunk(Pid,lists:nth(2,Contents)),
596    ok = ftp:append_chunk(Pid,lists:nth(3,Contents)),
597    ok = ftp:append_chunk_end(Pid),
598    chk_file(File, <<"ERLERL">>, Config).
599
600%%-------------------------------------------------------------------------
601recv() ->
602    [{doc, "Receive a file using recv/2"}].
603recv(Config0) ->
604    File1 = "f_dst1.txt",
605    File2 = "f_dst2.txt",
606    SrcDir = "a_dir",
607    Contents1 = <<"1 ftp_SUITE test ...">>,
608    Contents2 = <<"2 ftp_SUITE test ...">>,
609    Config = set_state([reset, {mkfile,[SrcDir,File1],Contents1}, {mkfile,[SrcDir,File2],Contents2}], Config0),
610    Pid = proplists:get_value(ftp, Config),
611    ok = ftp:cd(Pid, id2ftp(SrcDir,Config)),
612    ok = ftp:lcd(Pid, id2ftp("",Config)),
613    ok = ftp:recv(Pid, File1),
614    chk_file(File1, Contents1, Config),
615    ok = ftp:recv(Pid, File2),
616    chk_file(File2, Contents2, Config),
617    {error,epath} = ftp:recv(Pid, "non_existing_file"),
618    ok.
619
620%%-------------------------------------------------------------------------
621recv_3() ->
622    [{doc,"Receive a file using recv/3"}].
623recv_3(Config0) ->
624    DstFile = "f_src.txt",
625    SrcFile = "f_dst.txt",
626    Contents = <<"ftp_SUITE test ...">>,
627    Config = set_state([reset, {mkfile,SrcFile,Contents}], Config0),
628    Pid = proplists:get_value(ftp, Config),
629    ok = ftp:cd(Pid, id2ftp("",Config)),
630    ok = ftp:recv(Pid, SrcFile, id2abs(DstFile,Config)),
631    chk_file(DstFile, Contents, Config).
632
633%%-------------------------------------------------------------------------
634recv_bin() ->
635    [{doc, "Receive a file as a binary."}].
636recv_bin(Config0) ->
637    File = "f_dst.txt",
638    Contents = <<"ftp_SUITE test ...">>,
639    Config = set_state([reset, {mkfile,File,Contents}], Config0),
640    Pid = proplists:get_value(ftp, Config),
641    {ok,Received} = ftp:recv_bin(Pid, id2ftp(File,Config)),
642    find_diff(Received, Contents),
643    {error,epath} = ftp:recv_bin(Pid, id2ftp("non_existing_file",Config)),
644    ok.
645
646%%-------------------------------------------------------------------------
647recv_bin_twice() ->
648    [{doc, "Receive two files as a binaries."}].
649recv_bin_twice(Config0) ->
650    File1 = "f_dst1.txt",
651    File2 = "f_dst2.txt",
652    Contents1 = <<"1 ftp_SUITE test ...">>,
653    Contents2 = <<"2 ftp_SUITE test ...">>,
654    Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}], Config0),
655    ct:log("First transfer",[]),
656    Pid = proplists:get_value(ftp, Config),
657    {ok,Received1} = ftp:recv_bin(Pid, id2ftp(File1,Config)),
658    find_diff(Received1, Contents1),
659    ct:log("Second transfer",[]),
660    {ok,Received2} = ftp:recv_bin(Pid, id2ftp(File2,Config)),
661    find_diff(Received2, Contents2),
662    ct:log("Transfers ready!",[]),
663    {error,epath} = ftp:recv_bin(Pid, id2ftp("non_existing_file",Config)),
664    ok.
665%%-------------------------------------------------------------------------
666recv_chunk() ->
667    [{doc, "Receive a file using chunk-wise."}].
668recv_chunk(Config0) ->
669    File = "big_file.txt",
670    Contents = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ),
671    Config = set_state([reset, {mkfile,File,Contents}], Config0),
672    Pid = proplists:get_value(ftp, Config),
673    {error, "ftp:recv_chunk_start/2 not called"} = do_recv_chunk(Pid),
674    ok = ftp:recv_chunk_start(Pid, id2ftp(File,Config)),
675    {ok, ReceivedContents} = do_recv_chunk(Pid),
676    find_diff(ReceivedContents, Contents).
677
678recv_chunk_twice() ->
679    [{doc, "Receive two files using chunk-wise."}].
680recv_chunk_twice(Config0) ->
681    File1 = "big_file1.txt",
682    File2 = "big_file2.txt",
683    Contents1 = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ),
684    Contents2 = crypto:strong_rand_bytes(1200),
685    Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}], Config0),
686    Pid = proplists:get_value(ftp, Config),
687    {error, "ftp:recv_chunk_start/2 not called"} = do_recv_chunk(Pid),
688    ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)),
689    {ok, ReceivedContents1} = do_recv_chunk(Pid),
690    ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)),
691    {ok, ReceivedContents2} = do_recv_chunk(Pid),
692    find_diff(ReceivedContents1, Contents1),
693    find_diff(ReceivedContents2, Contents2).
694
695recv_chunk_three_times() ->
696    [{doc, "Receive two files using chunk-wise."},
697     {timetrap,{seconds,120}}].
698recv_chunk_three_times(Config0) ->
699    File1 = "big_file1.txt",
700    File2 = "big_file2.txt",
701    File3 = "big_file3.txt",
702    Contents1 = list_to_binary( lists:duplicate(1000, lists:seq(0,255)) ),
703    Contents2 = crypto:strong_rand_bytes(1200),
704    Contents3 = list_to_binary( lists:duplicate(1000, lists:seq(255,0,-1)) ),
705
706    Config = set_state([reset, {mkfile,File1,Contents1}, {mkfile,File2,Contents2}, {mkfile,File3,Contents3}], Config0),
707    Pid = proplists:get_value(ftp, Config),
708    {error, "ftp:recv_chunk_start/2 not called"} = do_recv_chunk(Pid),
709
710    ok = ftp:recv_chunk_start(Pid, id2ftp(File3,Config)),
711    {ok, ReceivedContents3} = do_recv_chunk(Pid),
712
713    ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)),
714    {ok, ReceivedContents1} = do_recv_chunk(Pid),
715
716    ok = ftp:recv_chunk_start(Pid, id2ftp(File2,Config)),
717    {ok, ReceivedContents2} = do_recv_chunk(Pid),
718
719    find_diff(ReceivedContents1, Contents1),
720    find_diff(ReceivedContents2, Contents2),
721    find_diff(ReceivedContents3, Contents3).
722
723
724do_recv_chunk(Pid) ->
725    recv_chunk(Pid, <<>>).
726recv_chunk(Pid, Acc) ->
727    case ftp:recv_chunk(Pid) of
728	ok ->
729            {ok, Acc};
730	{ok, Bin} ->
731            recv_chunk(Pid, <<Acc/binary, Bin/binary>>);
732	Error ->
733            Error
734    end.
735
736recv_chunk_delay(Config0) when is_list(Config0) ->
737    File1 = "big_file1.txt",
738    Contents = list_to_binary(lists:duplicate(1000, lists:seq(0,255))),
739    Config = set_state([reset, {mkfile,File1,Contents}], Config0),
740    Pid = proplists:get_value(ftp, Config),
741    ok = ftp:recv_chunk_start(Pid, id2ftp(File1,Config)),
742    {ok, ReceivedContents} = delay_recv_chunk(Pid),
743    find_diff(ReceivedContents, Contents).
744
745delay_recv_chunk(Pid) ->
746     delay_recv_chunk(Pid, <<>>).
747delay_recv_chunk(Pid, Acc) ->
748    ct:pal("Recived size ~p", [byte_size(Acc)]),
749    case ftp:recv_chunk(Pid) of
750 	ok ->
751             {ok, Acc};
752 	{ok, Bin} ->
753            ct:sleep(100),
754            delay_recv_chunk(Pid, <<Acc/binary, Bin/binary>>);
755	Error ->
756            Error
757     end.
758
759%%-------------------------------------------------------------------------
760type() ->
761    [{doc,"Test that we can change btween ASCCI and binary transfer mode"}].
762type(Config) ->
763    Pid = proplists:get_value(ftp, Config),
764    ok = ftp:type(Pid, ascii),
765    ok = ftp:type(Pid, binary),
766    ok = ftp:type(Pid, ascii),
767    {error, etype} = ftp:type(Pid, foobar).
768
769%%-------------------------------------------------------------------------
770quote(Config) ->
771    Pid = proplists:get_value(ftp, Config),
772    ["257 \""++_Rest] = ftp:quote(Pid, "pwd"), %% 257
773    [_| _] = ftp:quote(Pid, "help"),
774    %% This negativ test causes some ftp servers to hang. This test
775    %% is not important for the client, so we skip it for now.
776    %%["425 Can't build data connection: Connection refused."]
777    %% = ftp:quote(Pid, "list"),
778    ok.
779
780%%-------------------------------------------------------------------------
781progress_report_send() ->
782    [{doc, "Test the option progress for ftp:send/[2,3]"}].
783progress_report_send(Config) when is_list(Config) ->
784    ReportPid =
785	spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]),
786    send(Config),
787    receive
788	{ReportPid, ok} ->
789	    ok
790    end.
791
792%%-------------------------------------------------------------------------
793progress_report_recv() ->
794    [{doc, "Test the option progress for ftp:recv/[2,3]"}].
795progress_report_recv(Config) when is_list(Config) ->
796    ReportPid =
797 	spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]),
798    recv(Config),
799    receive
800 	{ReportPid, ok} ->
801 	    ok
802    end.
803
804%%-------------------------------------------------------------------------
805
806not_owner() ->
807    [{doc, "Test what happens if a process that not owns the connection tries "
808    "to use it"}].
809not_owner(Config) when is_list(Config) ->
810    Pid = proplists:get_value(ftp, Config),
811
812    Parent = self(),
813    OtherPid = spawn_link(
814		 fun() ->
815			 {error, not_connection_owner} = ftp:pwd(Pid),
816			 ftp:close(Pid),
817			 Parent ! {self(), ok}
818		 end),
819    receive
820	{OtherPid, ok} ->
821	    {ok, _} = ftp:pwd(Pid)
822    end.
823
824
825%%-------------------------------------------------------------------------
826
827
828unexpected_call()->
829    [{doc, "Test that behaviour of the ftp process if the api is abused"}].
830unexpected_call(Config) when is_list(Config) ->
831    Flag =  process_flag(trap_exit, true),
832    Pid = proplists:get_value(ftp, Config),
833
834    %% Serious programming fault, connetion will be shut down
835    case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of
836	{error, {connection_terminated, 'API_violation'}} ->
837	    ok;
838	Unexpected1 ->
839	    exit({unexpected_result, Unexpected1})
840    end,
841    ct:sleep(500),
842    undefined = process_info(Pid, status),
843    process_flag(trap_exit, Flag).
844%%-------------------------------------------------------------------------
845
846unexpected_cast()->
847    [{doc, "Test that behaviour of the ftp process if the api is abused"}].
848unexpected_cast(Config) when is_list(Config) ->
849    Flag = process_flag(trap_exit, true),
850    Pid = proplists:get_value(ftp, Config),
851    %% Serious programming fault, connetion will be shut down
852    gen_server:cast(Pid, {self(), foobar, 10}),
853    ct:sleep(500),
854    undefined = process_info(Pid, status),
855    process_flag(trap_exit, Flag).
856%%-------------------------------------------------------------------------
857
858unexpected_bang()->
859    [{doc, "Test that connection ignores unexpected bang"}].
860unexpected_bang(Config) when is_list(Config) ->
861    Flag = process_flag(trap_exit, true),
862    Pid = proplists:get_value(ftp, Config),
863    %% Could be an innocent misstake the connection lives.
864    Pid ! foobar,
865    ct:sleep(500),
866    {status, _} = process_info(Pid, status),
867    process_flag(trap_exit, Flag).
868
869%%-------------------------------------------------------------------------
870
871clean_shutdown() ->
872    [{doc, "Test that owning process that exits with reason "
873     "'shutdown' does not cause an error message. OTP 6035"}].
874
875clean_shutdown(Config) ->
876    Parent = self(),
877    HelperPid = spawn(
878		  fun() ->
879			  ftp__open(Config, [verbose]),
880			  Parent ! ok,
881			  receive
882			      nothing -> ok
883			  end
884		  end),
885    receive
886	ok ->
887	    PrivDir = proplists:get_value(priv_dir, Config),
888	    LogFile = filename:join([PrivDir,"ticket_6035.log"]),
889 	    error_logger:logfile({open, LogFile}),
890	    exit(HelperPid, shutdown),
891	    timer:sleep(2000),
892	    error_logger:logfile(close),
893	    case is_error_report_6035(LogFile) of
894		true ->  ok;
895		false -> {fail, "Bad logfile"}
896	    end
897    end.
898
899%%-------------------------------------------------------------------------
900start_ftp() ->
901    [{doc, "Start/stop of ftp service"}].
902start_ftp(Config) ->
903    Pid0 = proplists:get_value(ftp,Config),
904    Pids0 = [ServicePid || {_, ServicePid} <- ftp:services()],
905    true = lists:member(Pid0, Pids0),
906    {ok, [_|_]} = ftp:service_info(Pid0),
907    ftp:stop_service(Pid0),
908    ct:sleep(100),
909    Pids1 =  [ServicePid || {_, ServicePid} <- ftp:services()],
910    false = lists:member(Pid0, Pids1),
911
912    Host = proplists:get_value(ftpd_host,Config),
913    Port = proplists:get_value(ftpd_port,Config),
914
915    {ok, Pid1} = ftp:start_standalone([{host, Host},{port, Port}]),
916    Pids2 =  [ServicePid || {_, ServicePid} <- ftp:services()],
917    false = lists:member(Pid1, Pids2).
918
919%%-------------------------------------------------------------------------
920ftp_worker() ->
921    [{doc, "Makes sure the ftp worker processes are added and removed "
922      "appropriatly to/from the supervison tree."}].
923ftp_worker(Config) ->
924    Pid = proplists:get_value(ftp,Config),
925    case supervisor:which_children(ftp_sup) of
926        [{_,_, worker, [ftp]}] ->
927            ftp:stop_service(Pid),
928            ct:sleep(5000),
929            [] = supervisor:which_children(ftp_sup),
930            ok;
931        Children ->
932            ct:fail("Unexpected children: ~p",[Children])
933    end.
934
935
936%%%----------------------------------------------------------------
937%%% Error codes not tested elsewhere
938
939error_elogin(Config0) ->
940    Dir = "test",
941    OldFile = "old.txt",
942    NewFile = "new.txt",
943    SrcDir = "data",
944    File = "file.txt",
945    Config = set_state([reset,
946			{mkdir,Dir},
947			{mkfile,OldFile,<<"Contents..">>},
948			{mkfile,[SrcDir,File],<<"Contents..">>}], Config0),
949
950    Pid = proplists:get_value(ftp, Config),
951    ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)),
952    {error,elogin} = ftp:send(Pid, File),
953    ok = ftp:lcd(Pid, id2ftp("",Config)),
954    {error,elogin} = ftp:pwd(Pid),
955    {error,elogin} = ftp:cd(Pid, id2ftp(Dir,Config)),
956    {error,elogin} = ftp:rename(Pid,
957				id2ftp(OldFile,Config),
958				id2ftp(NewFile,Config)),
959    ok.
960
961error_ehost(_Config) ->
962    {error, ehost} = ftp:open("nohost.nodomain"),
963    ok.
964
965%%--------------------------------------------------------------------
966%% Internal functions  -----------------------------------------------
967%%--------------------------------------------------------------------
968
969chk_file(Path=[C|_], ExpectedContents, Config) when 0<C,C=<255 ->
970    chk_file([Path], ExpectedContents, Config);
971
972chk_file(PathList, ExpectedContents, Config) ->
973    Path = filename:join(PathList),
974    AbsPath = id2abs(Path,Config),
975    case file:read_file(AbsPath) of
976	{ok,ExpectedContents} ->
977	    true;
978	{ok,ReadContents} ->
979	    {error,{diff,Pos,RC,LC}} = find_diff(ReadContents, ExpectedContents, 1),
980	    ct:log("Bad contents of ~p.~nGot:~n~p~nExpected:~n~p~nDiff at pos ~p ~nRead: ~p~nExp : ~p",
981		   [AbsPath,ReadContents,ExpectedContents,Pos,RC,LC]),
982	    ct:fail("Bad contents of ~p", [Path]);
983	{error,Error} ->
984	    try begin
985		    {ok,CWD} = file:get_cwd(),
986		    ct:log("file:get_cwd()=~p~nfiles:~n~p",[CWD,file:list_dir(CWD)])
987		end
988	    of _ -> ok
989	    catch _:_ ->ok
990	    end,
991	    ct:fail("Error reading ~p: ~p",[Path,Error])
992    end.
993
994
995chk_no_file(Path=[C|_], Config) when 0<C,C=<255 ->
996    chk_no_file([Path], Config);
997
998chk_no_file(PathList, Config) ->
999    Path = filename:join(PathList),
1000    AbsPath = id2abs(Path,Config),
1001    case file:read_file(AbsPath) of
1002	{error,enoent} ->
1003	    true;
1004	{ok,Contents} ->
1005	    ct:log("File ~p exists although it shouldn't. Contents:~n~p",
1006		   [AbsPath,Contents]),
1007	    ct:fail("File exists: ~p", [Path]);
1008	{error,Error} ->
1009	    ct:fail("Unexpected error reading ~p: ~p",[Path,Error])
1010    end.
1011
1012
1013chk_dir(Path=[C|_], Config) when 0<C,C=<255 ->
1014    chk_dir([Path], Config);
1015
1016chk_dir(PathList, Config) ->
1017    Path = filename:join(PathList),
1018    AbsPath = id2abs(Path,Config),
1019    case file:read_file_info(AbsPath) of
1020	{ok, #file_info{type=directory}} ->
1021	    true;
1022	{ok, #file_info{type=Type}} ->
1023	    ct:fail("Expected dir ~p is a ~p",[Path,Type]);
1024	{error,Error} ->
1025	    ct:fail("Expected dir ~p: ~p",[Path,Error])
1026    end.
1027
1028chk_no_dir(PathList, Config) ->
1029    Path = filename:join(PathList),
1030    AbsPath = id2abs(Path,Config),
1031    case file:read_file_info(AbsPath) of
1032	{error,enoent} ->
1033	    true;
1034	{ok, #file_info{type=directory}} ->
1035	    ct:fail("Dir ~p erroneously exists",[Path]);
1036	{ok, #file_info{type=Type}} ->
1037	    ct:fail("~p ~p erroneously exists",[Type,Path]);
1038	{error,Error} ->
1039	    ct:fail("Unexpected error for ~p: ~p",[Path,Error])
1040    end.
1041
1042%%--------------------------------------------------------------------
1043find_executable(Config) ->
1044    search_executable(proplists:get_value(ftpservers, Config, ?default_ftp_servers)).
1045
1046
1047search_executable([{Name,Paths,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}|Srvrs]) ->
1048    case os_find(Name,Paths) of
1049	false ->
1050	    ct:log("~p not found",[Name]),
1051	    search_executable(Srvrs);
1052	AbsName ->
1053	    ct:comment("Found ~p",[AbsName]),
1054	    {ok, {AbsName,_StartCmd,_ChkUp,_StopCommand,_ConfigUpd,_Host,_Port}}
1055    end;
1056search_executable([]) ->
1057    false.
1058
1059
1060os_find(Name, Paths) ->
1061    case os:find_executable(Name, Paths) of
1062	false -> os:find_executable(Name);
1063	AbsName -> AbsName
1064    end.
1065
1066%%%----------------------------------------------------------------
1067start_ftpd(Config0) ->
1068    {AbsName,StartCmd,_ChkUp,_StopCommand,ConfigRewrite,Host,Port} =
1069	proplists:get_value(ftpd_data, Config0),
1070    case StartCmd(Config0, AbsName) of
1071	{ok,StartResult} ->
1072	    Config = [{ftpd_host,Host},
1073		      {ftpd_port,Port},
1074		      {ftpd_start_result,StartResult} | ConfigRewrite(Config0)],
1075	    try
1076		ftp__close(ftp__open(Config,[verbose]))
1077	    of
1078		Config1 when is_list(Config1) ->
1079		    ct:log("Usuable ftp server ~p started on ~p:~p",[AbsName,Host,Port]),
1080		    Config
1081	    catch
1082		Class:Exception ->
1083		    ct:log("Ftp server ~p started on ~p:~p but is unusable:~n~p:~p",
1084			   [AbsName,Host,Port,Class,Exception]),
1085		    {skip, [AbsName," started but unusuable"]}
1086	    end;
1087	{error,Msg} ->
1088	    {skip, [AbsName," not started: ",Msg]}
1089    end.
1090
1091stop_ftpd(Config) ->
1092    {_Name,_StartCmd,_ChkUp,StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config),
1093    StopCommand(proplists:get_value(ftpd_start_result,Config)).
1094
1095ps_ftpd(Config) ->
1096    {_Name,_StartCmd,ChkUp,_StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config),
1097    ct:log( ChkUp(proplists:get_value(ftpd_start_result,Config)) ).
1098
1099
1100ftpd_running(Config) ->
1101    {_Name,_StartCmd,ChkUp,_StopCommand,_ConfigUpd,_Host,_Port} = proplists:get_value(ftpd_data, Config),
1102    ChkUp(proplists:get_value(ftpd_start_result,Config)).
1103
1104ftp__open(Config, Options) ->
1105    Host = proplists:get_value(ftpd_host,Config),
1106    Port = proplists:get_value(ftpd_port,Config),
1107    ct:log("Host=~p, Port=~p",[Host,Port]),
1108    {ok,Pid} = ftp:open(Host, [{port,Port} | Options]),
1109    [{ftp,Pid}|Config].
1110
1111ftp__close(Config) ->
1112    ok = ftp:close(proplists:get_value(ftp,Config)),
1113    Config.
1114
1115ftp_start_service(Config, Options) ->
1116    Host = proplists:get_value(ftpd_host,Config),
1117    Port = proplists:get_value(ftpd_port,Config),
1118    ct:log("Host=~p, Port=~p",[Host,Port]),
1119    {ok,Pid} = ftp:start_service([{host, Host},{port,Port} | Options]),
1120    [{ftp,Pid}|Config].
1121
1122ftp_stop_service(Config) ->
1123    ok = ftp:stop_service(proplists:get_value(ftp,Config)),
1124    Config.
1125
1126split(Cs) -> string:tokens(Cs, "\r\n").
1127
1128find_diff(Bin1, Bin2) ->
1129    case find_diff(Bin1, Bin2, 1) of
1130	{error, {diff,Pos,RC,LC}} ->
1131	    ct:log("Contents differ at position ~p.~nOp1: ~p~nOp2: ~p",[Pos,RC,LC]),
1132	    ct:fail("Contents differ at pos ~p",[Pos]);
1133	Other ->
1134	    Other
1135    end.
1136
1137find_diff(A, A, _) -> true;
1138find_diff(<<H,T1/binary>>, <<H,T2/binary>>, Pos) -> find_diff(T1, T2, Pos+1);
1139find_diff(RC, LC, Pos) -> {error, {diff, Pos, RC, LC}}.
1140
1141set_state(Ops, Config) when is_list(Ops) -> lists:foldl(fun set_state/2, Config, Ops);
1142
1143set_state(reset, Config) ->
1144    rm('*', id2abs("",Config)),
1145    PrivDir = proplists:get_value(priv_dir,Config),
1146    file:set_cwd(PrivDir),
1147    ftp:lcd(proplists:get_value(ftp,Config),PrivDir),
1148    set_state({mkdir,""},Config);
1149set_state({mkdir,Id}, Config) ->
1150    Abs = id2abs(Id, Config),
1151    mk_path(Abs),
1152    file:make_dir(Abs),
1153    Config;
1154set_state({mkfile,Id,Contents}, Config) ->
1155    Abs = id2abs(Id, Config),
1156    mk_path(Abs),
1157    ok = file:write_file(Abs, Contents),
1158    Config.
1159
1160mk_path(Abs) -> lists:foldl(fun mk_path/2, [], filename:split(filename:dirname(Abs))).
1161
1162mk_path(F, Pfx) ->
1163    case file:read_file_info(AbsName=filename:join(Pfx,F)) of
1164	{ok,#file_info{type=directory}} ->
1165	    AbsName;
1166	{error,eexist} ->
1167	    AbsName;
1168	{error,enoent} ->
1169	    ok = file:make_dir(AbsName),
1170	    AbsName
1171    end.
1172
1173rm('*', Pfx) ->
1174    {ok,Fs} = file:list_dir(Pfx),
1175    lists:foreach(fun(F) -> rm(F, Pfx) end, Fs);
1176rm(F, Pfx) ->
1177    case file:read_file_info(AbsName=filename:join(Pfx,F)) of
1178	{ok,#file_info{type=directory}} ->
1179	    {ok,Fs} = file:list_dir(AbsName),
1180	    lists:foreach(fun(F1) -> rm(F1,AbsName) end, Fs),
1181	    ok = file:del_dir(AbsName);
1182
1183	{ok,#file_info{type=regular}} ->
1184	    ok = file:delete(AbsName);
1185
1186	{error,enoent} ->
1187	    ok
1188    end.
1189
1190id2abs(Id, Conf) -> filename:join(proplists:get_value(priv_dir,Conf),ids(Id)).
1191id2ftp(Id, Conf) -> (proplists:get_value(id2ftp,Conf))(ids(Id)).
1192id2ftp_result(Id, Conf) -> (proplists:get_value(id2ftp_result,Conf))(ids(Id)).
1193
1194ids([[_|_]|_]=Ids) -> filename:join(Ids);
1195ids(Id) -> Id.
1196
1197
1198is_expected_absName(Id, File, Conf) -> File = (proplists:get_value(id2abs,Conf))(Id).
1199is_expected_ftpInName(Id, File, Conf) -> File = (proplists:get_value(id2ftp,Conf))(Id).
1200is_expected_ftpOutName(Id, File, Conf) -> File = (proplists:get_value(id2ftp_result,Conf))(Id).
1201
1202
1203%%%----------------------------------------------------------------
1204%%% Help functions for the option '{progress,Progress}'
1205%%%
1206
1207%%%----------------
1208%%% Callback:
1209
1210progress(#progress{} = P, _File, {file_size, Total} = M) ->
1211    ct:pal("Progress: ~p",[M]),
1212    progress_report_receiver ! start,
1213    P#progress{total = Total};
1214
1215progress(#progress{current = Current} = P, _File, {transfer_size, 0} = M) ->
1216    ct:pal("Progress: ~p",[M]),
1217    progress_report_receiver ! finish,
1218    case P#progress.total of
1219	unknown -> P;
1220	Current -> P;
1221	Total   -> ct:fail({error, {progress, {total,Total}, {current,Current}}}),
1222		   P
1223    end;
1224
1225progress(#progress{current = Current} = P, _File, {transfer_size, Size} = M) ->
1226    ct:pal("Progress: ~p",[M]),
1227    progress_report_receiver ! update,
1228    P#progress{current = Current + Size};
1229
1230progress(P, _File, M) ->
1231    ct:pal("Progress **** Strange: ~p",[M]),
1232    P.
1233
1234
1235%%%----------------
1236%%% Help process that counts the files transferred:
1237
1238progress_report_receiver_init(Parent, N) ->
1239    register(progress_report_receiver, self()),
1240    progress_report_receiver_expect_N_files(Parent, N).
1241
1242progress_report_receiver_expect_N_files(_Parent, 0) ->
1243    ct:pal("progress_report got all files!", []);
1244progress_report_receiver_expect_N_files(Parent, N) ->
1245    ct:pal("progress_report expects ~p more files",[N]),
1246    receive
1247	start -> ok
1248    end,
1249    progress_report_receiver_loop(Parent, N-1).
1250
1251
1252progress_report_receiver_loop(Parent, N) ->
1253    ct:pal("progress_report expect update | finish. N = ~p",[N]),
1254    receive
1255	update ->
1256	    ct:pal("progress_report got update",[]),
1257	    progress_report_receiver_loop(Parent, N);
1258	finish  ->
1259	    ct:pal("progress_report got finish, send ~p to ~p",[{self(),ok}, Parent]),
1260	    Parent ! {self(), ok},
1261	    progress_report_receiver_expect_N_files(Parent, N)
1262    end.
1263
1264%%%----------------------------------------------------------------
1265%%% Help functions for bug OTP-6035
1266
1267is_error_report_6035(LogFile) ->
1268    case file:read_file(LogFile) of
1269	{ok, Bin} ->
1270	    nomatch =/= binary:match(Bin, <<"=ERROR REPORT====">>);
1271	_ ->
1272	    false
1273    end.
1274
1275