<commit_before>%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
-module(ssh_sftp_SUITE).

%% Note: This directive should only be used in test suites.
-compile(export_all).

-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
-include("ssh_test_lib.hrl").
						% Default timetrap timeout
-define(default_timeout, ?t:minutes(1)).

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

suite() ->
    [{ct_hooks,[ts_install_cth]},
     {timetrap,{seconds,40}}].

all() -> 
    [{group, not_unicode},
     {group, unicode}
    ].


init_per_suite(Config) ->
    ?CHECK_CRYPTO(
       begin
	   ct:log("file:native_name_encoding() = ~p,~nio:getopts() = ~p",
		  [file:native_name_encoding(),io:getopts()]),
	   ssh:start(),
	   Config
       end).

end_per_suite(_onfig) ->
    ssh:stop().

%%--------------------------------------------------------------------
groups() -> 
    [{not_unicode, [], [{group,erlang_server},
			{group,openssh_server},
			{group,big_recvpkt_size},
			sftp_nonexistent_subsystem]},

     {unicode, [], [{group,erlang_server},
		    {group,openssh_server},
		    sftp_nonexistent_subsystem]},

     {big_recvpkt_size, [], [{group,erlang_server},
			     {group,openssh_server}]},

     {erlang_server, [], [{group,write_read_tests},
			  version_option,
			  {group,remote_tar}]},

     {openssh_server, [], [{group,write_read_tests},
			   {group,remote_tar}]},

     {remote_tar, [], [create_empty_tar, 
		       ascii_filename_ascii_contents_to_tar,
		       ascii_filename_unicode_contents_to_tar,
		       unicode_filename_ascii_contents_to_tar,
		       files_to_tar,
		       big_file_to_tar, files_chunked_to_tar,
		       directory_to_tar, binaries_to_tar, null_crypto_tar, 
		       simple_crypto_tar_small, simple_crypto_tar_big,
		       read_tar, read_null_crypto_tar, read_crypto_tar, 
		       aes_cbc256_crypto_tar, aes_ctr_stream_crypto_tar
		      ]},

     {write_read_tests, [], [open_close_file, open_close_dir, read_file, read_dir,
			     write_file, write_file_iolist, write_big_file, sftp_read_big_file,
			     rename_file, mk_rm_dir, remove_file, links,
			     retrieve_attributes, set_attributes, file_owner_access, async_read,
			     async_write, position, pos_read, pos_write,
			     start_channel_sock
			    ]}
    ].

init_per_group(not_unicode, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    DataDir = proplists:get_value(data_dir, Config),
    [{user, "Alladin"},
     {passwd, "<PASSWORD>"},
     {data, <<"Hello world!">>},
     {filename,     "sftp.txt"},
     {testfile,     "test.txt"},
     {linktest,     "link_test.txt"},
     {tar_filename, "sftp_tar_test.tar"},
     {tar_F1_txt,   "f1.txt"},
     {datadir_tar, filename:join(DataDir,"sftp_tar_test_data")}
     | Config];

init_per_group(unicode, Config) ->
    case (file:native_name_encoding() == utf8) 
	andalso ("四" == [22235])
    of
	true ->
	    ct:comment("Begin ~p",[grps(Config)]),
	    DataDir = proplists:get_value(data_dir, Config),
	    NewConfig =
		[{user, "åke高兴"},
		 {passwd, "<PASSWORD>"},
		 {data, <<"foobar å 一二三四いちにさんち">>},
		 {filename,     "sftp瑞点.txt"},
		 {testfile,     "testハンス.txt"},
		 {linktest,     "link_test語.txt"},
		 {tar_filename, "sftp_tar_test一二三.tar"},
		 {tar_F1_txt,   "F一.txt"},
		 {tar_F3_txt,   "f3.txt"},
		 {tar_F4_txt,   "g四.txt"},
		 {datadir_tar, filename:join(DataDir,"sftp_tar_test_data_高兴")}
		 | lists:foldl(fun(K,Cf) -> lists:keydelete(K,1,Cf) end, 
			       Config,
			       [user, passwd, data,
				filename, testfile, linktest,
				tar_filename, tar_F1_txt, datadir_tar
			       ]
			      )
		],
	    FN = fn(proplists:get_value(tar_F1_txt,NewConfig), NewConfig),
	    case catch file:read_file(FN) of
		{ok,FN_contents} ->
		    ct:log("Readable file:read_file(~tp) ->~n~tp",[FN,FN_contents]),
		    NewConfig;
		Other ->
		    ct:log("Unreadable file:read_file(~tp) ->~n~p",[FN,Other]),
		    {skip, "Not unicode file reading"}
	    end;

	_ ->
	    {skip, "Not unicode file encoding"}
    end;

init_per_group(big_recvpkt_size, Config) ->
    [{pkt_sz,123456} | Config];

init_per_group(erlang_server, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    PrivDir = proplists:get_value(priv_dir, Config),
    SysDir =  proplists:get_value(data_dir, Config),
    User = proplists:get_value(user, Config),
    Passwd = proplists:get_value(passwd, Config),
    Sftpd = {_, HostX, PortX} =
	ssh_test_lib:daemon([{system_dir, SysDir},
			     {user_dir, PrivDir},
			     {user_passwords,
			      [{User, Passwd}]}]),
    [{peer, {fmt_host(HostX),PortX}}, {group, erlang_server}, {sftpd, Sftpd} | Config];

init_per_group(openssh_server, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    Host = ssh_test_lib:hostname(),
    case (catch ssh_sftp:start_channel(Host,
				       [{user_interaction, false},
					{silently_accept_hosts, true}])) of
	{ok, _ChannelPid, Connection} ->
	    [{peer, {_HostName,{IPx,Portx}}}] = ssh:connection_info(Connection,[peer]),
	    ssh:close(Connection),
	    [{peer, {fmt_host(IPx),Portx}}, {group, openssh_server} | Config];
	{error,"Key exchange failed"} ->
	    {skip, "openssh server doesn't support the tested kex algorithm"};
	Other ->
            ct:log("No openssh server. Cause:~n~p~n",[Other]),
	    {skip, "No openssh daemon (see log in testcase)"} 
    end;

init_per_group(remote_tar, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    {Host,Port} =  proplists:get_value(peer, Config),
    ct:log("Server (~p) at ~p:~p",[proplists:get_value(group,Config),Host,Port]),
    User = proplists:get_value(user, Config),
    Passwd = proplists:get_value(passwd, Config),
    {ok, Connection} =
	case proplists:get_value(group, Config) of
	    erlang_server ->
		ssh:connect(Host, Port,
			    [{user, User},
			     {password, <PASSWORD>},
			     {user_interaction, false},
			     {silently_accept_hosts, true}]);
	    openssh_server ->
		ssh:connect(Host, Port,
			    [{user_interaction, false},
			     {silently_accept_hosts, true}])
	end,
    [{remote_tar, true}, 
     {connection, Connection} | Config];

init_per_group(write_read_tests, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    Config.

grps(Config) ->
    proplists:get_all_values(
      name, 
      lists:flatten([proplists:get_value(tc_group_properties,Config,[]),
		     proplists:get_value(tc_group_path,Config,[])])).

end_per_group(erlang_server, Config) ->
    ct:comment("End ~p",[grps(Config)]),
    Config;
end_per_group(_, Config) ->
    ct:comment("End ~p",[grps(Config)]),
    Config.

%%--------------------------------------------------------------------

init_per_testcase(sftp_nonexistent_subsystem, Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    SysDir =  proplists:get_value(data_dir, Config),
    User = proplists:get_value(user, Config),
    Passwd = proplists:get_value(passwd, Config),
    Sftpd = ssh_test_lib:daemon([{system_dir, SysDir},
				 {user_dir, PrivDir},
				 {subsystems, []},
				 {user_passwords,
				  [{User, Passwd}]}
				]),
    [{sftpd, Sftpd} | Config];

init_per_testcase(version_option, Config0) ->
    Config = prepare(Config0),
    TmpConfig0 = lists:keydelete(watchdog, 1, Config),
    TmpConfig = lists:keydelete(sftp, 1, TmpConfig0),
    Dog = ct:timetrap(?default_timeout),
    {_,Host, Port} =  proplists:get_value(sftpd, Config),
    User = proplists:get_value(user, Config),
    Passwd = proplists:get_value(passwd, Config),
    {ok, ChannelPid, Connection}  = 
	ssh_sftp:start_channel(Host, Port,
			       [{sftp_vsn, 3},
				{user, User},
				{password, <PASSWORD>},
				{user_interaction, false},
				{silently_accept_hosts, true}]),
    Sftp = {ChannelPid, Connection},
    [{sftp,Sftp}, {watchdog, Dog} | TmpConfig];

init_per_testcase(Case, Config00) ->
    Config0 = prepare(Config00),
    Config1 = lists:keydelete(watchdog, 1, Config0),
    Config2 = lists:keydelete(sftp, 1, Config1),
    Dog = ct:timetrap(2 * ?default_timeout),
    User = proplists:get_value(user, Config0),
    Passwd = proplists:get_value(passwd, Config0),
    PktSzOpt = case proplists:get_value(pkt_sz, Config0) of
		   undefined -> [];
		   Sz -> [{packet_size,Sz}]
	       end,
    Config =
	case proplists:get_value(group,Config2) of
	    erlang_server ->
		{_,Host, Port} =  proplists:get_value(sftpd, Config2),
		{ok, ChannelPid, Connection}  = 
		    ssh_sftp:start_channel(Host, Port,
					   [{user, User},
					    {password, <PASSWORD>},
					    {user_interaction, false},
					    {silently_accept_hosts, true}
					    | PktSzOpt
					   ]
					  ),
		Sftp = {ChannelPid, Connection},
		[{sftp, Sftp}, {watchdog, Dog} | Config2];
	    openssh_server when Case == links ->
		{skip, "known bug in openssh"};
	    openssh_server ->
		Host = ssh_test_lib:hostname(),
		{ok, ChannelPid, Connection} = 
		    ssh_sftp:start_channel(Host, 
					   [{user_interaction, false},
					    {silently_accept_hosts, true}
					    | PktSzOpt
					   ]),
		Sftp = {ChannelPid, Connection},
		[{sftp, Sftp}, {watchdog, Dog} | Config2]
	end,

    case catch proplists:get_value(remote_tar,Config) of
	%% The 'catch' is for the case of Config={skip,...}
	true ->
	    %% Provide a ChannelPid independent of the sftp-channel already opened.
	    {ok,ChPid2} = ssh_sftp:start_channel(proplists:get_value(connection,Config)),
	    [{channel_pid2,ChPid2} | Config];
	_ ->
	    Config
    end.

end_per_testcase(sftp_nonexistent_subsystem, Config) ->
    Config;
end_per_testcase(rename_file, Config) ->
    NewFileName = proplists:get_value(testfile, Config),
    file:delete(NewFileName),  
    end_per_testcase(Config);
end_per_testcase(_, Config) ->
    end_per_testcase(Config).

end_per_testcase(Config) ->
    {Sftp, Connection} = proplists:get_value(sftp, Config),
    ok = ssh_sftp:stop_channel(Sftp),
    catch ssh_sftp:stop_channel(proplists:get_value(channel_pid2, Config)),
    ok = ssh:close(Connection).

%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
open_close_file() ->
    [{doc, "Test API functions open/3 and close/2"}].
open_close_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),

    {Sftp, _} = proplists:get_value(sftp, Config),

    ok = open_close_file(Sftp, FileName, [read]),
    ok = open_close_file(Sftp, FileName, [write]),
    ok = open_close_file(Sftp, FileName, [write, creat]),
    ok = open_close_file(Sftp, FileName, [write, trunc]),
    ok = open_close_file(Sftp, FileName, [append]),
    ok = open_close_file(Sftp, FileName, [read, binary]).

open_close_file(Server, File, Mode) ->
    {ok, Handle} = ssh_sftp:open(Server, File, Mode),
    ok = ssh_sftp:close(Server, Handle).

%%--------------------------------------------------------------------
open_close_dir() ->
    [{doc, "Test API functions opendir/2 and close/2"}].
open_close_dir(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(sftp_priv_dir, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    FileName = proplists:get_value(filename, Config),

    {ok, Handle} = ssh_sftp:opendir(Sftp, PrivDir),
    ok = ssh_sftp:close(Sftp, Handle),
    {error, _} =  ssh_sftp:opendir(Sftp, FileName).

%%--------------------------------------------------------------------
read_file() ->
    [{doc, "Test API funtion read_file/2"}].
read_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok, Data} = ssh_sftp:read_file(Sftp, FileName),
    {ok, Data} = ssh_sftp:read_file(Sftp, FileName),
    {ok, Data} = file:read_file(FileName).

%%--------------------------------------------------------------------
read_dir() ->
    [{doc,"Test API function list_dir/2"}].
read_dir(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(sftp_priv_dir, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
    ct:log("sftp list dir: ~p~n", [Files]).

%%--------------------------------------------------------------------
write_file() ->
    [{doc, "Test API function write_file/2"}].
write_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = list_to_binary("Hej hopp!"),
    ok = ssh_sftp:write_file(Sftp, FileName, [Data]),
    {ok, Data} = file:read_file(FileName).

%%--------------------------------------------------------------------
write_file_iolist() ->
    [{doc, "Test API function write_file/2 with iolists"}].
write_file_iolist(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = list_to_binary("Hej hopp!"),
    lists:foreach(
      fun(D) ->
	      ok = ssh_sftp:write_file(Sftp, FileName, [D]),
	      Expected = if is_binary(D) -> D;
			    is_list(D) -> list_to_binary(D)
			 end,
	      {ok, Expected} = file:read_file(FileName)
      end,
      [Data, [Data,Data], [[Data],[Data]], [[[Data]],[[[[Data]],Data]]],
       [[[[Data]],Data],binary_to_list(Data)],
       [[[[Data]],Data],[[binary_to_list(Data)],[[binary_to_list(Data)]]]]
      ]).

%%--------------------------------------------------------------------
write_big_file() ->
    [{doc, "Test API function write_file/2 with big data"}].
write_big_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = list_to_binary(lists:duplicate(750000,"a")),
    ok = ssh_sftp:write_file(Sftp, FileName, [Data]),
    {ok, Data} = file:read_file(FileName).

%%--------------------------------------------------------------------
sftp_read_big_file() ->
    [{doc, "Test API function read_file/2 with big data"}].
sftp_read_big_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = list_to_binary(lists:duplicate(750000,"a")),
    ct:log("Data size to write is ~p bytes",[size(Data)]),
    ok = ssh_sftp:write_file(Sftp, FileName, [Data]),
    {ok, Data} = ssh_sftp:read_file(Sftp, FileName).

%%--------------------------------------------------------------------
remove_file() ->
    [{doc,"Test API function delete/2"}].
remove_file(Config) when is_list(Config) ->
    PrivDir =  proplists:get_value(sftp_priv_dir, Config),
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
    true = lists:member(filename:basename(FileName), Files),
    ok = ssh_sftp:delete(Sftp, FileName),
    {ok, NewFiles} = ssh_sftp:list_dir(Sftp, PrivDir),
    false = lists:member(filename:basename(FileName), NewFiles),
    {error, no_such_file} = ssh_sftp:delete(Sftp, FileName).
%%--------------------------------------------------------------------
rename_file() ->
    [{doc, "Test API function rename_file/2"}].
rename_file(Config) when is_list(Config) ->
    PrivDir =  proplists:get_value(sftp_priv_dir, Config),
    FileName = proplists:get_value(filename, Config),
    NewFileName = proplists:get_value(testfile, Config),

    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
    ct:log("FileName: ~p, Files: ~p~n", [FileName, Files]),
    true = lists:member(filename:basename(FileName), Files),
    false = lists:member(filename:basename(NewFileName), Files),
    ok = ssh_sftp:rename(Sftp, FileName, NewFileName),
    {ok, NewFiles} = ssh_sftp:list_dir(Sftp, PrivDir),
    ct:log("FileName: ~p, Files: ~p~n", [FileName, NewFiles]),

    false = lists:member(filename:basename(FileName), NewFiles),
    true = lists:member(filename:basename(NewFileName), NewFiles).

%%--------------------------------------------------------------------
mk_rm_dir() ->
    [{doc,"Test API functions make_dir/2, del_dir/2"}].
mk_rm_dir(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(sftp_priv_dir, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    DirName = filename:join(PrivDir, "test"),
    ok = ssh_sftp:make_dir(Sftp, DirName),
    ok = ssh_sftp:del_dir(Sftp, DirName),
    NewDirName = filename:join(PrivDir, "foo/bar"),
    {error, _} = ssh_sftp:make_dir(Sftp, NewDirName),
    {error, _} = ssh_sftp:del_dir(Sftp, PrivDir).

%%--------------------------------------------------------------------
links() ->
    [{doc,"Tests API function make_symlink/3"}].
links(Config) when is_list(Config) ->
    case os:type() of
	{win32, _} ->
	    {skip, "Links are not fully supported by windows"};
	_ ->
	    {Sftp, _} = proplists:get_value(sftp, Config),
	    FileName = proplists:get_value(filename, Config),
	    LinkFileName = proplists:get_value(linktest, Config),

	    ok = ssh_sftp:make_symlink(Sftp, LinkFileName, FileName),
	    {ok, FileName} = ssh_sftp:read_link(Sftp, LinkFileName)
    end.

%%--------------------------------------------------------------------
retrieve_attributes() ->
    [{doc, "Test API function read_file_info/3"}].
retrieve_attributes(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),

    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok, FileInfo} = ssh_sftp:read_file_info(Sftp, FileName),
    {ok, NewFileInfo} = file:read_file_info(FileName),

    %% TODO comparison. There are some differences now is that ok?
    ct:log("SFTP: ~p   FILE: ~p~n", [FileInfo, NewFileInfo]).

%%--------------------------------------------------------------------
set_attributes() ->
    [{doc,"Test API function write_file_info/3"}].
set_attributes(Config) when is_list(Config) ->
    FileName = proplists:get_value(testfile, Config),

    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok,Fd} = file:open(FileName, write),
    io:put_chars(Fd,"foo"),
    ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#400}),
    {error, eacces} = file:write_file(FileName, "hello again"),
    ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#600}),
    ok = file:write_file(FileName, "hello again").

%%--------------------------------------------------------------------
file_owner_access() ->
    [{doc,"Test file user access validity"}].
file_owner_access(Config) when is_list(Config) ->
    case os:type() of
        {win32, _} ->
            {skip, "Not a relevant test on Windows"};
        _ ->
            FileName = proplists:get_value(filename, Config),
            {Sftp, _} = proplists:get_value(sftp, Config),

            {ok, #file_info{mode = InitialMode}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#000}),
            {ok, #file_info{access = none}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#400}),
            {ok, #file_info{access = read}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#200}),
            {ok, #file_info{access = write}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#600}),
            {ok, #file_info{access = read_write}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=InitialMode}),

            ok
    end.

%%--------------------------------------------------------------------
async_read() ->
    [{doc,"Test API aread/3"}].
async_read(Config) when is_list(Config) ->
    {Sftp, _} = proplists:get_value(sftp, Config),

    FileName = proplists:get_value(filename, Config),
    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),
    {async, Ref} = ssh_sftp:aread(Sftp, Handle, 20),

    receive
	{async_reply, Ref, {ok, Data}} ->
	    ct:log("Data: ~p~n", [Data]),
	    ok;
	Msg ->
	    ct:fail(Msg)
    after 
	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
    end.
%%--------------------------------------------------------------------
async_write() ->
    [{doc,"Test API awrite/3"}].
async_write(Config) when is_list(Config) ->
    {Sftp, _} = proplists:get_value(sftp, Config),
    FileName = proplists:get_value(testfile, Config),
    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [write]),
    Data = list_to_binary("foobar"),
    {async, Ref} = ssh_sftp:awrite(Sftp, Handle, Data),

    receive
	{async_reply, Ref, ok} ->
	    {ok, Data} = file:read_file(FileName);
	Msg ->
	    ct:fail(Msg)
    end.

%%--------------------------------------------------------------------

position() ->
    [{doc, "Test API functions position/3"}].
position(Config) when is_list(Config) ->
    FileName = proplists:get_value(testfile, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = list_to_binary("1234567890"),
    ok = ssh_sftp:write_file(Sftp, FileName, [Data]),
    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),

    {ok, 3} = ssh_sftp:position(Sftp, Handle, {bof, 3}),
    {ok, "4"} = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 10} = ssh_sftp:position(Sftp, Handle, eof),
    eof = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 6} = ssh_sftp:position(Sftp, Handle, {bof, 6}),
    {ok, "7"} = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 9} = ssh_sftp:position(Sftp, Handle, {cur, 2}),
    {ok, "0"} = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 0} = ssh_sftp:position(Sftp, Handle, bof),
    {ok, "1"} = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 1} = ssh_sftp:position(Sftp, Handle, cur),
    {ok, "2"} = ssh_sftp:read(Sftp, Handle, 1).

%%--------------------------------------------------------------------
pos_read() ->
    [{doc,"Test API functions pread/3 and apread/3"}].
pos_read(Config) when is_list(Config) ->
    FileName = proplists:get_value(testfile, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    Data = list_to_binary("Hej hopp!"),
    ok = ssh_sftp:write_file(Sftp, FileName, [Data]),

    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),
    {async, Ref} = ssh_sftp:apread(Sftp, Handle, {bof, 5}, 4),

    NewData  = "opp!",

    receive
	{async_reply, Ref, {ok, NewData}} ->
	    ok;
	Msg ->
	    ct:fail(Msg)
    after 
	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
    end,

    NewData1  = "hopp",

    {ok, NewData1} = ssh_sftp:pread(Sftp, Handle, {bof, 4}, 4).

%%--------------------------------------------------------------------
pos_write() ->
    [{doc,"Test API functions pwrite/4 and apwrite/4"}].
pos_write(Config) when is_list(Config) ->
    FileName = proplists:get_value(testfile, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [write]),

    Data = list_to_binary("Bye,"),
    ok = ssh_sftp:write_file(Sftp, FileName, [Data]),

    NewData = list_to_binary(" see you tomorrow"),
    {async, Ref} = ssh_sftp:apwrite(Sftp, Handle, {bof, 4}, NewData),
    receive
	{async_reply, Ref, ok} ->
	    ok;
	Msg ->
	    ct:fail(Msg)
    after 
	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
    end,

    ok = ssh_sftp:pwrite(Sftp, Handle, eof, list_to_binary("!")),

    NewData1 = list_to_binary("Bye, see you tomorrow!"),
    {ok, NewData1} = ssh_sftp:read_file(Sftp, FileName).

%%--------------------------------------------------------------------
start_channel_sock(Config) ->
    LoginOpts =
	case proplists:get_value(group,Config) of
	    erlang_server -> 
		[{user,     proplists:get_value(user, Config)},
		 {password, proplists:get_value(passwd, Config)}];
	    openssh_server ->
		[] % Use public key
	end,

    Opts = [{user_interaction, false},
	    {silently_accept_hosts, true}
	    | LoginOpts],
<commit_msg>ssh: Extend unicode tests for sftp<commit_after>%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2005-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

%%
-module(ssh_sftp_SUITE).

%% Note: This directive should only be used in test suites.
-compile(export_all).

-include_lib("common_test/include/ct.hrl").
-include_lib("kernel/include/file.hrl").
-include("ssh_test_lib.hrl").
						% Default timetrap timeout
-define(default_timeout, ?t:minutes(1)).

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------

suite() ->
    [{ct_hooks,[ts_install_cth]},
     {timetrap,{seconds,40}}].

all() -> 
    [{group, not_unicode},
     {group, unicode}
    ].


init_per_suite(Config) ->
    ?CHECK_CRYPTO(
       begin
	   ct:log("file:native_name_encoding() = ~p,~nio:getopts() = ~p",
		  [file:native_name_encoding(),io:getopts()]),
	   ssh:start(),
	   Config
       end).

end_per_suite(_onfig) ->
    ssh:stop().

%%--------------------------------------------------------------------
groups() -> 
    [{not_unicode, [], [{group,erlang_server},
			{group,openssh_server},
			{group,big_recvpkt_size},
			sftp_nonexistent_subsystem]},

     {unicode, [], [{group,erlang_server},
		    {group,openssh_server},
		    sftp_nonexistent_subsystem]},

     {big_recvpkt_size, [], [{group,erlang_server},
			     {group,openssh_server}]},

     {erlang_server, [], [{group,write_read_tests},
			  version_option,
			  {group,remote_tar}]},

     {openssh_server, [], [{group,write_read_tests},
			   {group,remote_tar}]},

     {remote_tar, [], [create_empty_tar, 
		       ascii_filename_ascii_contents_to_tar,
		       ascii_filename_unicode_contents_to_tar,
		       unicode_filename_ascii_contents_to_tar,
		       files_to_tar,
		       big_file_to_tar, files_chunked_to_tar,
		       directory_to_tar, binaries_to_tar, null_crypto_tar, 
		       simple_crypto_tar_small, simple_crypto_tar_big,
		       read_tar, read_null_crypto_tar, read_crypto_tar, 
		       aes_cbc256_crypto_tar, aes_ctr_stream_crypto_tar
		      ]},

     {write_read_tests, [], [open_close_file, open_close_dir, read_file, read_dir,
			     write_file, write_file_iolist, write_big_file, sftp_read_big_file,
			     rename_file, mk_rm_dir, remove_file, links,
			     retrieve_attributes, set_attributes, file_owner_access, async_read,
			     async_write, position, pos_read, pos_write,
			     start_channel_sock
			    ]}
    ].

init_per_group(not_unicode, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    DataDir = proplists:get_value(data_dir, Config),
    [{user, "Alladin"},
     {passwd, "<PASSWORD>"},
     {data, <<"Hello world!">>},
     {data_pos, ["Hej ","hopp"]},
     {filename,     "sftp.txt"},
     {testfile,     "test.txt"},
     {linktest,     "link_test.txt"},
     {tar_filename, "sftp_tar_test.tar"},
     {tar_F1_txt,   "f1.txt"},
     {datadir_tar, filename:join(DataDir,"sftp_tar_test_data")}
     | Config];

init_per_group(unicode, Config) ->
    case (file:native_name_encoding() == utf8) 
	andalso ("四" == [22235])
    of
	true ->
	    ct:comment("Begin ~p",[grps(Config)]),
	    DataDir = proplists:get_value(data_dir, Config),
	    NewConfig =
		[{user, "åke高兴"},
		 {passwd, "<PASSWORD>"},
                 {data, "foobar å 一二三四いちにさんち"},
                 {data_pos, ["中","国"]},
		 {filename,     "sftp瑞点.txt"},
		 {testfile,     "testハンス.txt"},
		 {linktest,     "link_test語.txt"},
		 {tar_filename, "sftp_tar_test一二三.tar"},
		 {tar_F1_txt,   "F一.txt"},
		 {tar_F3_txt,   "f3.txt"},
		 {tar_F4_txt,   "g四.txt"},
		 {datadir_tar, filename:join(DataDir,"sftp_tar_test_data_高兴")}
		 | lists:foldl(fun(K,Cf) -> lists:keydelete(K,1,Cf) end, 
			       Config,
			       [user, passwd, data,
				filename, testfile, linktest,
				tar_filename, tar_F1_txt, datadir_tar
			       ]
			      )
		],
	    FN = fn(proplists:get_value(tar_F1_txt,NewConfig), NewConfig),
	    case catch file:read_file(FN) of
		{ok,FN_contents} ->
		    ct:log("Readable file:read_file(~tp) ->~n~tp",[FN,FN_contents]),
		    NewConfig;
		Other ->
		    ct:log("Unreadable file:read_file(~tp) ->~n~p",[FN,Other]),
		    {skip, "Not unicode file reading"}
	    end;

	_ ->
	    {skip, "Not unicode file encoding"}
    end;

init_per_group(big_recvpkt_size, Config) ->
    [{pkt_sz,123456} | Config];

init_per_group(erlang_server, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    PrivDir = proplists:get_value(priv_dir, Config),
    SysDir =  proplists:get_value(data_dir, Config),
    User = proplists:get_value(user, Config),
    Passwd = proplists:get_value(passwd, Config),
    Sftpd = {_, HostX, PortX} =
	ssh_test_lib:daemon([{system_dir, SysDir},
			     {user_dir, PrivDir},
			     {user_passwords,
			      [{User, Passwd}]}]),
    [{peer, {fmt_host(HostX),PortX}}, {group, erlang_server}, {sftpd, Sftpd} | Config];

init_per_group(openssh_server, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    Host = ssh_test_lib:hostname(),
    case (catch ssh_sftp:start_channel(Host,
				       [{user_interaction, false},
					{silently_accept_hosts, true}])) of
	{ok, _ChannelPid, Connection} ->
	    [{peer, {_HostName,{IPx,Portx}}}] = ssh:connection_info(Connection,[peer]),
	    ssh:close(Connection),
	    [{peer, {fmt_host(IPx),Portx}}, {group, openssh_server} | Config];
	{error,"Key exchange failed"} ->
	    {skip, "openssh server doesn't support the tested kex algorithm"};
	Other ->
            ct:log("No openssh server. Cause:~n~p~n",[Other]),
	    {skip, "No openssh daemon (see log in testcase)"} 
    end;

init_per_group(remote_tar, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    {Host,Port} =  proplists:get_value(peer, Config),
    ct:log("Server (~p) at ~p:~p",[proplists:get_value(group,Config),Host,Port]),
    User = proplists:get_value(user, Config),
    Passwd = <PASSWORD>(passwd, Config),
    {ok, Connection} =
	case proplists:get_value(group, Config) of
	    erlang_server ->
		ssh:connect(Host, Port,
			    [{user, User},
			     {password, <PASSWORD>},
			     {user_interaction, false},
			     {silently_accept_hosts, true}]);
	    openssh_server ->
		ssh:connect(Host, Port,
			    [{user_interaction, false},
			     {silently_accept_hosts, true}])
	end,
    [{remote_tar, true}, 
     {connection, Connection} | Config];

init_per_group(write_read_tests, Config) ->
    ct:comment("Begin ~p",[grps(Config)]),
    Config.

grps(Config) ->
    proplists:get_all_values(
      name, 
      lists:flatten([proplists:get_value(tc_group_properties,Config,[]),
		     proplists:get_value(tc_group_path,Config,[])])).

end_per_group(erlang_server, Config) ->
    ct:comment("End ~p",[grps(Config)]),
    Config;
end_per_group(_, Config) ->
    ct:comment("End ~p",[grps(Config)]),
    Config.

%%--------------------------------------------------------------------

init_per_testcase(sftp_nonexistent_subsystem, Config) ->
    PrivDir = proplists:get_value(priv_dir, Config),
    SysDir =  proplists:get_value(data_dir, Config),
    User = proplists:get_value(user, Config),
    Passwd = proplists:get_value(passwd, Config),
    Sftpd = ssh_test_lib:daemon([{system_dir, SysDir},
				 {user_dir, PrivDir},
				 {subsystems, []},
				 {user_passwords,
				  [{User, Passwd}]}
				]),
    [{sftpd, Sftpd} | Config];

init_per_testcase(version_option, Config0) ->
    Config = prepare(Config0),
    TmpConfig0 = lists:keydelete(watchdog, 1, Config),
    TmpConfig = lists:keydelete(sftp, 1, TmpConfig0),
    Dog = ct:timetrap(?default_timeout),
    {_,Host, Port} =  proplists:get_value(sftpd, Config),
    User = proplists:get_value(user, Config),
    Passwd = proplists:get_value(passwd, Config),
    {ok, ChannelPid, Connection}  = 
	ssh_sftp:start_channel(Host, Port,
			       [{sftp_vsn, 3},
				{user, User},
				{password, <PASSWORD>},
				{user_interaction, false},
				{silently_accept_hosts, true}]),
    Sftp = {ChannelPid, Connection},
    [{sftp,Sftp}, {watchdog, Dog} | TmpConfig];

init_per_testcase(Case, Config00) ->
    Config0 = prepare(Config00),
    Config1 = lists:keydelete(watchdog, 1, Config0),
    Config2 = lists:keydelete(sftp, 1, Config1),
    Dog = ct:timetrap(2 * ?default_timeout),
    User = proplists:get_value(user, Config0),
    Passwd = proplists:get_value(passwd, Config0),
    PktSzOpt = case proplists:get_value(pkt_sz, Config0) of
		   undefined -> [];
		   Sz -> [{packet_size,Sz}]
	       end,
    Config =
	case proplists:get_value(group,Config2) of
	    erlang_server ->
		{_,Host, Port} =  proplists:get_value(sftpd, Config2),
		{ok, ChannelPid, Connection}  = 
		    ssh_sftp:start_channel(Host, Port,
					   [{user, User},
					    {password, <PASSWORD>},
					    {user_interaction, false},
					    {silently_accept_hosts, true}
					    | PktSzOpt
					   ]
					  ),
		Sftp = {ChannelPid, Connection},
		[{sftp, Sftp}, {watchdog, Dog} | Config2];
	    openssh_server when Case == links ->
		{skip, "known bug in openssh"};
	    openssh_server ->
		Host = ssh_test_lib:hostname(),
		{ok, ChannelPid, Connection} = 
		    ssh_sftp:start_channel(Host, 
					   [{user_interaction, false},
					    {silently_accept_hosts, true}
					    | PktSzOpt
					   ]),
		Sftp = {ChannelPid, Connection},
		[{sftp, Sftp}, {watchdog, Dog} | Config2]
	end,

    case catch proplists:get_value(remote_tar,Config) of
	%% The 'catch' is for the case of Config={skip,...}
	true ->
	    %% Provide a ChannelPid independent of the sftp-channel already opened.
	    {ok,ChPid2} = ssh_sftp:start_channel(proplists:get_value(connection,Config)),
	    [{channel_pid2,ChPid2} | Config];
	_ ->
	    Config
    end.

end_per_testcase(sftp_nonexistent_subsystem, Config) ->
    Config;
end_per_testcase(rename_file, Config) ->
    NewFileName = proplists:get_value(testfile, Config),
    file:delete(NewFileName),  
    end_per_testcase(Config);
end_per_testcase(_, Config) ->
    end_per_testcase(Config).

end_per_testcase(Config) ->
    {Sftp, Connection} = proplists:get_value(sftp, Config),
    ok = ssh_sftp:stop_channel(Sftp),
    catch ssh_sftp:stop_channel(proplists:get_value(channel_pid2, Config)),
    ok = ssh:close(Connection).

%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------
open_close_file() ->
    [{doc, "Test API functions open/3 and close/2"}].
open_close_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),

    {Sftp, _} = proplists:get_value(sftp, Config),

    ok = open_close_file(Sftp, FileName, [read]),
    ok = open_close_file(Sftp, FileName, [write]),
    ok = open_close_file(Sftp, FileName, [write, creat]),
    ok = open_close_file(Sftp, FileName, [write, trunc]),
    ok = open_close_file(Sftp, FileName, [append]),
    ok = open_close_file(Sftp, FileName, [read, binary]).

open_close_file(Server, File, Mode) ->
    {ok, Handle} = ssh_sftp:open(Server, File, Mode),
    ok = ssh_sftp:close(Server, Handle).

%%--------------------------------------------------------------------
open_close_dir() ->
    [{doc, "Test API functions opendir/2 and close/2"}].
open_close_dir(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(sftp_priv_dir, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    FileName = proplists:get_value(filename, Config),

    {ok, Handle} = ssh_sftp:opendir(Sftp, PrivDir),
    ok = ssh_sftp:close(Sftp, Handle),
    {error, _} =  ssh_sftp:opendir(Sftp, FileName).

%%--------------------------------------------------------------------
read_file() ->
    [{doc, "Test API funtion read_file/2"}].
read_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok, Data} = ssh_sftp:read_file(Sftp, FileName),
    {ok, Data} = ssh_sftp:read_file(Sftp, FileName),
    {ok, Data} = file:read_file(FileName).

%%--------------------------------------------------------------------
read_dir() ->
    [{doc,"Test API function list_dir/2"}].
read_dir(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(sftp_priv_dir, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
    ct:log("sftp list dir: ~p~n", [Files]).

%%--------------------------------------------------------------------
write_file() ->
    [{doc, "Test API function write_file/2"}].
write_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    Data = proplists:get_value(data, Config),
    Expected = unicode:characters_to_binary(Data),
    ok = ssh_sftp:write_file(Sftp, FileName, [Data]),
    {ok, Expected} = file:read_file(FileName).

%%--------------------------------------------------------------------
write_file_iolist() ->
    [{doc, "Test API function write_file/2 with iolists"}].
write_file_iolist(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = proplists:get_value(data, Config),
    DataB = unicode:characters_to_binary(Data),
    lists:foreach(
      fun(D) ->
	      ok = ssh_sftp:write_file(Sftp, FileName, [D]),
	      Expected = try iolist_to_binary(D)
                         catch _:_ -> unicode:characters_to_binary(D)
			 end,
	      {ok, Expected} = file:read_file(FileName)
      end,
      [Data, [Data,Data], [[Data],[Data]], [[[Data]],[[[[Data]],Data]]],
       [[[[Data]],Data],DataB],
       [[[[Data]],Data],[[DataB],[[DataB]]]]
      ]).

%%--------------------------------------------------------------------
write_big_file() ->
    [{doc, "Test API function write_file/2 with big data"}].
write_big_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = [lists:duplicate(750000,"a"),
            proplists:get_value(data, Config)],
    ok = ssh_sftp:write_file(Sftp, FileName, Data),
    Expected = unicode:characters_to_binary(Data),
    {ok, Expected} = file:read_file(FileName).

%%--------------------------------------------------------------------
sftp_read_big_file() ->
    [{doc, "Test API function read_file/2 with big data"}].
sftp_read_big_file(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = [lists:duplicate(750000,"a"),
            proplists:get_value(data, Config)],
    ok = ssh_sftp:write_file(Sftp, FileName, Data),
    Expected = unicode:characters_to_binary(Data),
    {ok, Expected} = ssh_sftp:read_file(Sftp, FileName).

%%--------------------------------------------------------------------
remove_file() ->
    [{doc,"Test API function delete/2"}].
remove_file(Config) when is_list(Config) ->
    PrivDir =  proplists:get_value(sftp_priv_dir, Config),
    FileName = proplists:get_value(filename, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
    true = lists:member(filename:basename(FileName), Files),
    ok = ssh_sftp:delete(Sftp, FileName),
    {ok, NewFiles} = ssh_sftp:list_dir(Sftp, PrivDir),
    false = lists:member(filename:basename(FileName), NewFiles),
    {error, no_such_file} = ssh_sftp:delete(Sftp, FileName).
%%--------------------------------------------------------------------
rename_file() ->
    [{doc, "Test API function rename_file/2"}].
rename_file(Config) when is_list(Config) ->
    PrivDir =  proplists:get_value(sftp_priv_dir, Config),
    FileName = proplists:get_value(filename, Config),
    NewFileName = proplists:get_value(testfile, Config),

    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
    ct:log("FileName: ~p, Files: ~p~n", [FileName, Files]),
    true = lists:member(filename:basename(FileName), Files),
    false = lists:member(filename:basename(NewFileName), Files),
    ok = ssh_sftp:rename(Sftp, FileName, NewFileName),
    {ok, NewFiles} = ssh_sftp:list_dir(Sftp, PrivDir),
    ct:log("FileName: ~p, Files: ~p~n", [FileName, NewFiles]),

    false = lists:member(filename:basename(FileName), NewFiles),
    true = lists:member(filename:basename(NewFileName), NewFiles).

%%--------------------------------------------------------------------
mk_rm_dir() ->
    [{doc,"Test API functions make_dir/2, del_dir/2"}].
mk_rm_dir(Config) when is_list(Config) ->
    PrivDir = proplists:get_value(sftp_priv_dir, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    DirName = filename:join(PrivDir, "test"),
    ok = ssh_sftp:make_dir(Sftp, DirName),
    ok = ssh_sftp:del_dir(Sftp, DirName),
    NewDirName = filename:join(PrivDir, "foo/bar"),
    {error, _} = ssh_sftp:make_dir(Sftp, NewDirName),
    {error, _} = ssh_sftp:del_dir(Sftp, PrivDir).

%%--------------------------------------------------------------------
links() ->
    [{doc,"Tests API function make_symlink/3"}].
links(Config) when is_list(Config) ->
    case os:type() of
	{win32, _} ->
	    {skip, "Links are not fully supported by windows"};
	_ ->
	    {Sftp, _} = proplists:get_value(sftp, Config),
	    FileName = proplists:get_value(filename, Config),
	    LinkFileName = proplists:get_value(linktest, Config),

	    ok = ssh_sftp:make_symlink(Sftp, LinkFileName, FileName),
	    {ok, FileName} = ssh_sftp:read_link(Sftp, LinkFileName)
    end.

%%--------------------------------------------------------------------
retrieve_attributes() ->
    [{doc, "Test API function read_file_info/3"}].
retrieve_attributes(Config) when is_list(Config) ->
    FileName = proplists:get_value(filename, Config),

    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok, FileInfo} = ssh_sftp:read_file_info(Sftp, FileName),
    {ok, NewFileInfo} = file:read_file_info(FileName),

    %% TODO comparison. There are some differences now is that ok?
    ct:log("SFTP: ~p   FILE: ~p~n", [FileInfo, NewFileInfo]).

%%--------------------------------------------------------------------
set_attributes() ->
    [{doc,"Test API function write_file_info/3"}].
set_attributes(Config) when is_list(Config) ->
    FileName = proplists:get_value(testfile, Config),

    {Sftp, _} = proplists:get_value(sftp, Config),
    {ok,Fd} = file:open(FileName, write),
    io:put_chars(Fd,"foo"),
    ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#400}),
    {error, eacces} = file:write_file(FileName, "hello again"),
    ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#600}),
    ok = file:write_file(FileName, "hello again").

%%--------------------------------------------------------------------
file_owner_access() ->
    [{doc,"Test file user access validity"}].
file_owner_access(Config) when is_list(Config) ->
    case os:type() of
        {win32, _} ->
            {skip, "Not a relevant test on Windows"};
        _ ->
            FileName = proplists:get_value(filename, Config),
            {Sftp, _} = proplists:get_value(sftp, Config),

            {ok, #file_info{mode = InitialMode}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#000}),
            {ok, #file_info{access = none}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#400}),
            {ok, #file_info{access = read}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#200}),
            {ok, #file_info{access = write}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#600}),
            {ok, #file_info{access = read_write}} = ssh_sftp:read_file_info(Sftp, FileName),

            ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=InitialMode}),

            ok
    end.

%%--------------------------------------------------------------------
async_read() ->
    [{doc,"Test API aread/3"}].
async_read(Config) when is_list(Config) ->
    {Sftp, _} = proplists:get_value(sftp, Config),

    FileName = proplists:get_value(filename, Config),
    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),
    {async, Ref} = ssh_sftp:aread(Sftp, Handle, 20),

    receive
	{async_reply, Ref, {ok, Data}} ->
	    ct:log("Data: ~p~n", [Data]),
	    ok;
	Msg ->
	    ct:fail(Msg)
    after 
	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
    end.
%%--------------------------------------------------------------------
async_write() ->
    [{doc,"Test API awrite/3"}].
async_write(Config) when is_list(Config) ->
    {Sftp, _} = proplists:get_value(sftp, Config),
    FileName = proplists:get_value(testfile, Config),
    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [write]),
    Data = proplists:get_value(data, Config),
    Expected = unicode:characters_to_binary(Data),
    {async, Ref} = ssh_sftp:awrite(Sftp, Handle, Data),

    receive
	{async_reply, Ref, ok} ->
	    {ok, Expected} = file:read_file(FileName);
	Msg ->
	    ct:fail(Msg)
    end.

%%--------------------------------------------------------------------

position() ->
    [{doc, "Test API functions position/3"}].
position(Config) when is_list(Config) ->
    FileName = proplists:get_value(testfile, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    Data = list_to_binary("1234567890"),
    ok = ssh_sftp:write_file(Sftp, FileName, Data),
    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),

    {ok, 3} = ssh_sftp:position(Sftp, Handle, {bof, 3}),
    {ok, "4"} = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 10} = ssh_sftp:position(Sftp, Handle, eof),
    eof = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 6} = ssh_sftp:position(Sftp, Handle, {bof, 6}),
    {ok, "7"} = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 9} = ssh_sftp:position(Sftp, Handle, {cur, 2}),
    {ok, "0"} = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 0} = ssh_sftp:position(Sftp, Handle, bof),
    {ok, "1"} = ssh_sftp:read(Sftp, Handle, 1),

    {ok, 1} = ssh_sftp:position(Sftp, Handle, cur),
    {ok, "2"} = ssh_sftp:read(Sftp, Handle, 1).

%%--------------------------------------------------------------------
pos_read() ->
    [{doc,"Test API functions pread/3 and apread/3"}].
pos_read(Config) when is_list(Config) ->
    FileName = proplists:get_value(testfile, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),
    Data = proplists:get_value(data_pos, Config),
    [Expect1,Expect2] = Es = [binary_to_list(unicode:characters_to_binary(D)) || D <- Data],
    [Len1,Len2] = [length(E) || E <- Es],
    ok = ssh_sftp:write_file(Sftp, FileName, Data),

    {ok,Read} = file:read_file(FileName),
    ct:log("File: <~ts>~n<~p>~nExpect1: <~p>~nExpect2: <~p>", [Read, Read, Expect1, Expect2]),

    {ok,Handle} = ssh_sftp:open(Sftp, FileName, [read]),
    {async,Ref} = ssh_sftp:apread(Sftp, Handle, {bof, Len1}, Len2),

    receive
	{async_reply, Ref, {ok,Expect2}} ->
	    ok;
	Msg ->
	    ct:fail(Msg)
    after 
	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
    end,

   {ok,Expect1} = ssh_sftp:pread(Sftp, Handle, {bof,0}, Len1).

%%--------------------------------------------------------------------
pos_write() ->
    [{doc,"Test API functions pwrite/4 and apwrite/4"}].
pos_write(Config) when is_list(Config) ->
    FileName = proplists:get_value(testfile, Config),
    {Sftp, _} = proplists:get_value(sftp, Config),

    {ok, Handle} = ssh_sftp:open(Sftp, FileName, [write]),

    Data = list_to_binary("Bye,"),
    ok = ssh_sftp:write_file(Sftp, FileName, [Data]),

    NewData = list_to_binary(" see you tomorrow"),
    {async, Ref} = ssh_sftp:apwrite(Sftp, Handle, {bof, 4}, NewData),
    receive
	{async_reply, Ref, ok} ->
	    ok;
	Msg ->
	    ct:fail(Msg)
    after 
	30000 -> ct:fail("timeout ~p:~p",[?MODULE,?LINE])
    end,

    LastData = proplists:get_value(data, Config),
    ok = ssh_sftp:pwrite(Sftp, Handle, eof, LastData),

    NewData1 = unicode:characters_to_binary("Bye, see you tomorrow" ++ LastData),
    {ok, NewData1} = ssh_sftp:read_file(Sftp, FileName).

%%--------------------------------------------------------------------
start_channel_sock(Config) ->
    LoginOpts =
	case proplists:get_value(group,Config) of
	    erlang_server -> 
		[{user,     proplists:get_value(user, Config)},
		 {password, proplists:get_value(passwd, Config)}];
	    openssh_server ->
		[] % Use public key
	end,

    Opts = [{user_interaction, false},
	    {silently_accept_hosts, true}
	    | LoginOpts],

{'fraction_non_alphanumeric': 0.221767021723056, 'fraction_numerical': 0.007753951917504346, 'mean_word_length': 2.830666768736125, 'pattern_counts': {'":': 0, '<': 26, '<?xml version=': 0, '>': 229, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7463559', 'n_tokens_mistral': 18890, 'n_tokens_neox': 17130, 'n_words': 3149}