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