ssh_sftp.erl

来自「OTP是开放电信平台的简称」· ERL 代码 · 共 983 行 · 第 1/2 页

ERL
983
字号
%% ``The contents of this file are subject to the Erlang Public License,%% Version 1.1, (the "License"); you may not use this file except in%% compliance with the License. You should have received a copy of the%% Erlang Public License along with this software. If not, it can be%% retrieved via the world wide web at http://www.erlang.org/.%% %% Software distributed under the License is distributed on an "AS IS"%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See%% the License for the specific language governing rights and limitations%% under the License.%% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings%% AB. All Rights Reserved.''%% %%     $Id$%%%%% Description: SFTP protocol front-end-module(ssh_sftp).-behaviour(gen_server).%%--------------------------------------------------------------------%% Include files%%---------------------------------------------------------------------include_lib("kernel/include/file.hrl").-include("ssh.hrl").-include("ssh_xfer.hrl").-import(lists, [member/2, map/2, foldl/3, reverse/1, foreach/2]).%%--------------------------------------------------------------------%% External exports%% -export([start/3, start_link/3]).%% -export([start/2, start_link/2]).%% -export([start/1, start_link/1]).-export([connect/1, connect/2, connect/3]).-export([open_mode/2]).%% API-export([open/3, opendir/2, close/2, readdir/2, pread/4, read/3,	 apread/4, aread/3, pwrite/4, write/3, apwrite/4, awrite/3,	 position/3, real_path/2, read_file_info/2, get_file_info/2,	 write_file_info/3, read_link_info/2, read_link/2, make_symlink/3,	 rename/3, delete/2, make_dir/2, del_dir/2, stop/1, send_window/1,	 recv_window/1, list_dir/2, read_file/2, write_file/3]).%% gen_server callbacks-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,	 code_change/3]).%% Other exports-export([info_to_attr/1, attr_to_info/1]).-record(state, 	{	  xf,	  rep_buf = <<>>,	  req_id,	  req_list = [],  %% {ReqId, Fun}	  inf   %% list of fileinf	 }).-record(fileinf,	{	  handle,	  offset,	  size,	  mode	 }).-define(FILEOP_TIMEOUT, 60000).-define(NEXT_REQID(S),	S#state { req_id = (S#state.req_id + 1) band 16#ffffffff}).-define(XF(S), S#state.xf).-define(REQID(S), S#state.req_id).%%====================================================================%% External functions%%====================================================================open(Pid, File, Mode) ->    gen_server:call(Pid, {open, false, File, Mode}, ?FILEOP_TIMEOUT).opendir(Pid, Path) ->    gen_server:call(Pid, {opendir, false, Path}, ?FILEOP_TIMEOUT).close(Pid, Handle) ->    gen_server:call(Pid, {close,false,Handle}, ?FILEOP_TIMEOUT).readdir(Pid,Handle) ->    gen_server:call(Pid, {readdir,false,Handle}, ?FILEOP_TIMEOUT).pread(Pid, Handle, Offset, Len) ->    gen_server:call(Pid, {pread,false,Handle, Offset, Len}, ?FILEOP_TIMEOUT).read(Pid, Handle, Len) ->    gen_server:call(Pid, {read,false,Handle, Len}, ?FILEOP_TIMEOUT).    apread(Pid, Handle, Offset, Len) ->    gen_server:call(Pid, {pread,true,Handle, Offset, Len}, ?FILEOP_TIMEOUT).aread(Pid, Handle, Len) ->    gen_server:call(Pid, {read,true,Handle, Len}, ?FILEOP_TIMEOUT).    pwrite(Pid, Handle, Offset, Data) ->    gen_server:call(Pid, {pwrite,false,Handle,Offset,Data}, ?FILEOP_TIMEOUT).write(Pid, Handle, Data) ->    gen_server:call(Pid, {write,false,Handle,Data}, ?FILEOP_TIMEOUT).apwrite(Pid, Handle, Offset, Data) ->    gen_server:call(Pid, {pwrite,true,Handle,Offset,Data}, ?FILEOP_TIMEOUT).awrite(Pid, Handle, Data) ->    gen_server:call(Pid, {write,true,Handle,Data}, ?FILEOP_TIMEOUT).position(Pid, Handle, Pos) ->    gen_server:call(Pid, {position, Handle, Pos}, ?FILEOP_TIMEOUT).real_path(Pid, Path) ->    gen_server:call(Pid, {real_path, false, Path}, ?FILEOP_TIMEOUT).read_file_info(Pid, Name) ->    gen_server:call(Pid, {read_file_info,false,Name}, ?FILEOP_TIMEOUT).get_file_info(Pid, Handle) ->    gen_server:call(Pid, {get_file_info,false,Handle}, ?FILEOP_TIMEOUT).write_file_info(Pid, Name, Info) ->    gen_server:call(Pid, {write_file_info,false,Name, Info}, ?FILEOP_TIMEOUT).read_link_info(Pid, Name) ->    gen_server:call(Pid, {read_link_info,false,Name}, ?FILEOP_TIMEOUT).read_link(Pid, LinkName) ->    case gen_server:call(Pid, {read_link,false,LinkName}, ?FILEOP_TIMEOUT) of	 {ok, [{Name, _Attrs}]} ->	    {ok, Name};	ErrMsg ->	    ErrMsg    end.make_symlink(Pid, Name, Target) ->    gen_server:call(Pid, {make_symlink,false, Name, Target}, ?FILEOP_TIMEOUT). rename(Pid, FromFile, ToFile) ->    gen_server:call(Pid, {rename,false,FromFile, ToFile}, ?FILEOP_TIMEOUT).delete(Pid, Name) ->    gen_server:call(Pid, {delete,false,Name}, ?FILEOP_TIMEOUT).make_dir(Pid, Name) ->    gen_server:call(Pid, {make_dir,false,Name}, ?FILEOP_TIMEOUT).del_dir(Pid, Name) ->    gen_server:call(Pid, {del_dir,false,Name}, ?FILEOP_TIMEOUT).stop(Pid) ->    gen_server:call(Pid, stop).send_window(Pid) ->    gen_server:call(Pid, send_window, ?FILEOP_TIMEOUT).recv_window(Pid) ->    gen_server:call(Pid, recv_window, ?FILEOP_TIMEOUT).list_dir(Pid, Name) ->    case opendir(Pid, Name) of	{ok,Handle} ->	    Res = do_list_dir(Pid, Handle, []),	    close(Pid, Handle),	    case Res of		{ok, List} ->		    NList = foldl(fun({Nm, _Info},Acc) -> 					  [Nm|Acc] end, 				  [], List),		    {ok,NList};		Error -> Error	    end;	Error ->	    Error    end.do_list_dir(Pid, Handle, Acc) ->    case readdir(Pid, Handle) of	{name, Names} ->	    do_list_dir(Pid, Handle, Acc ++ Names);	eof ->	    {ok, Acc};	Error ->	    Error    end.read_file(Pid, Name) ->    case open(Pid, Name, [read, binary]) of	{ok, Handle} ->	    {ok,{_WindowSz,PacketSz}} = recv_window(Pid),	    Res = read_file_loop(Pid, Handle, PacketSz, []),	    close(Pid, Handle),	    Res;	Error ->	    Error    end.read_file_loop(Pid, Handle, PacketSz, Acc) ->    case read(Pid, Handle, PacketSz) of	{ok, Data}  ->	    read_file_loop(Pid, Handle, PacketSz, [Data|Acc]);	eof ->	    {ok, list_to_binary(reverse(Acc))};	Error ->	    Error    end.write_file(Pid, Name, List) when list(List) ->    write_file(Pid, Name, list_to_binary(List));write_file(Pid, Name, Bin) ->    case open(Pid, Name, [write, binary]) of	{ok, Handle} ->	    {ok,{_Window,Packet}} = send_window(Pid),	    Res = write_file_loop(Pid, Handle, 0, Bin, size(Bin), Packet),	    close(Pid, Handle),	    Res;	Error ->	    Error    end.write_file_loop(_Pid, _Handle, _Pos, _Bin, 0, _PacketSz) ->    ok;write_file_loop(Pid, Handle, Pos, Bin, Remain, PacketSz) ->    if Remain >= PacketSz ->	    <<_:Pos/binary, Data:PacketSz/binary, _/binary>> = Bin,	    case write(Pid, Handle, Data) of		ok ->		    write_file_loop(Pid, Handle, 				    Pos+PacketSz, Bin, Remain-PacketSz,				    PacketSz);		Error ->		    Error	    end;       true ->	    <<_:Pos/binary, Data/binary>> = Bin,	    write(Pid, Handle, Data)    end.%%--------------------------------------------------------------------%% Function: start_link/0%% Description: Starts the server%%--------------------------------------------------------------------%% start_link(CM) when is_pid(CM) ->%%     gen_server:start_link(?MODULE, [CM], []);%% start_link(Host) when is_list(Host) ->%%     gen_server:start_link(?MODULE, [Host, 22, []], []).%% start_link(Host, Opts) ->%%     gen_server:start_link(?MODULE, [Host, 22, Opts], []).    %% start_link(Host, Port, Opts) ->%%     gen_server:start_link(?MODULE, [Host, Port, Opts], []).connect(CM) when is_pid(CM) ->    gen_server:start(?MODULE, [CM], []);connect(Host) when is_list(Host) ->    gen_server:start(?MODULE, [Host, 22, []], []).connect(Host, Opts) ->    gen_server:start(?MODULE, [Host, 22, Opts], []).    connect(Host, Port, Opts) ->    gen_server:start(?MODULE, [Host, Port, Opts], []).%%====================================================================%% Server functions%%====================================================================%%--------------------------------------------------------------------%% Function: init/1%% Description: Initiates the server%% Returns: {ok, State}          |%%          {ok, State, Timeout} |%%          ignore               |%%          {stop, Reason}%%--------------------------------------------------------------------init([CM]) ->    case ssh_xfer:attach(CM, ?FILEOP_TIMEOUT) of	{ok,Xf,RBuf} ->	    {ok, #state { req_id = 0, xf = Xf, rep_buf=RBuf,			  inf = new_inf()}};	Error ->	    {stop, Error }    end;init([Host,Port,Opts]) ->    SaveFlag = process_flag(trap_exit, true),    case ssh_xfer:connect(Host, Port, Opts) of	{ok, Xf, RBuf} ->	    process_flag(trap_exit, SaveFlag),	    {ok, #state { req_id = 0, xf = Xf, rep_buf=RBuf,			  inf = new_inf()}};	Error ->	    {stop, Error}    end.%%--------------------------------------------------------------------%% Function: handle_call/3%% Description: Handling call messages%% Returns: {reply, Reply, State}          |%%          {reply, Reply, State, Timeout} |%%          {noreply, State}               |%%          {noreply, State, Timeout}      |%%          {stop, Reason, Reply, State}   | (terminate/2 is called)%%          {stop, Reason, State}            (terminate/2 is called)%%--------------------------------------------------------------------handle_call({open,Async,FileName,Mode}, From, State) ->    XF = State#state.xf,    {Access,Flags,Attrs} = open_mode(XF#ssh_xfer.vsn, Mode),    ReqID = State#state.req_id,    ssh_xfer:open(XF, ReqID, FileName, Access, Flags, Attrs),    case Async of	true ->	    {reply, {async,ReqID},	     wait_req(ReqID, State,		      fun({ok,Handle},State1) ->			      open2(ReqID,FileName,Handle,Mode,Async,				    From,State1);			 (Rep,State1) ->			      async_reply(ReqID, Rep, From, State1)		      end)};	false ->	    {noreply,	     wait_req(ReqID, State,		      fun({ok,Handle},State1) ->			      open2(ReqID,FileName,Handle,Mode,Async,				    From,State1);			 (Rep,State1) ->			      sync_reply(Rep, From, State1)		      end)}    end;handle_call({opendir,Async,Path}, From, State) ->    ReqID = State#state.req_id,    ssh_xfer:opendir(?XF(State), ReqID, Path),    make_reply(ReqID, Async, From, State);handle_call({readdir,Async,Handle}, From, State) ->    ReqID = State#state.req_id,    ssh_xfer:readdir(?XF(State), ReqID, Handle),    make_reply(ReqID, Async, From, State);handle_call({close,_Async,Handle}, From, State) ->    %% wait until all operations on handle are done    case get_size(Handle, State) of	undefined ->	    ReqID = State#state.req_id,	    ssh_xfer:close(?XF(State), ReqID, Handle),	    make_reply_post(ReqID, false, From, State,			    fun(Rep, State1) ->				    {Rep, erase_handle(Handle, State1)}			    end);	_ ->	    case lseek_position(Handle, cur, State) of		{ok,_} ->		    ReqID = State#state.req_id,		    ssh_xfer:close(?XF(State), ReqID, Handle),		    make_reply_post(ReqID, false, From, State,				    fun(Rep, State1) ->					    {Rep, erase_handle(Handle, State1)}				    end);		Error ->		    {reply, Error, State}	    end    end;handle_call({pread,Async,Handle,At,Length}, From, State) ->    case lseek_position(Handle, At, State) of	{ok,Offset} ->	    ReqID = State#state.req_id,	    ssh_xfer:read(?XF(State),ReqID,Handle,Offset,Length),	    %% To get multiple async read to work we must update the offset	    %% before the operation begins	    State1 = update_offset(Handle, Offset+Length, State),	    make_reply_post(ReqID,Async,From,State1,			    fun({ok,Data}, State2) ->				    case get_mode(Handle, State2) of					binary -> {{ok,Data}, State2};					text ->					    {{ok,binary_to_list(Data)}, State2}				    end;			       (Rep, State2) -> 				    {Rep, State2}			    end);	Error ->	    {reply, Error, State}    end;handle_call({read,Async,Handle,Length}, From, State) ->    case lseek_position(Handle, cur, State) of	{ok,Offset} ->	    ReqID = State#state.req_id,	    ssh_xfer:read(?XF(State),ReqID,Handle,Offset,Length),	    %% To get multiple async read to work we must update the offset	    %% before the operation begins	    State1 = update_offset(Handle, Offset+Length, State),	    make_reply_post(ReqID,Async,From,State1,			    fun({ok,Data}, State2) ->				    case get_mode(Handle, State2) of					binary -> {{ok,Data}, State2};					text ->					    {{ok,binary_to_list(Data)}, State2}				    end;			       (Rep, State2) -> {Rep, State2}			    end);	Error ->	    {reply, Error, State}    end;handle_call({pwrite,Async,Handle,At,Data0}, From, State) ->    case lseek_position(Handle, At, State) of	{ok,Offset} ->	    Data = if binary(Data0) -> Data0;		      list(Data0) -> list_to_binary(Data0)		   end,	    ReqID = State#state.req_id,	    Size = size(Data),	    ssh_xfer:write(?XF(State),ReqID,Handle,Offset,Data),	    State1 = update_size(Handle, Offset+Size, State),	    make_reply(ReqID, Async, From, State1);	Error ->	    {reply, Error, State}    end;handle_call({write,Async,Handle,Data0}, From, State) ->    case lseek_position(Handle, cur, State) of	{ok,Offset} ->	    Data = if binary(Data0) -> Data0;		      list(Data0) -> list_to_binary(Data0)		   end,	    ReqID = State#state.req_id,	    Size = size(Data),	    ssh_xfer:write(?XF(State),ReqID,Handle,Offset,Data),	    State1 = update_offset(Handle, Offset+Size, State),	    make_reply(ReqID, Async, From, State1);	Error ->	    {reply, Error, State}    end;handle_call({position,Handle,At}, _From, State) ->    %% We could make this auto sync when all request to Handle is done?    case lseek_position(Handle, At, State) of	{ok,Offset} ->	    {reply, {ok, Offset}, update_offset(Handle, Offset, State)};	Error ->	    {reply, Error, State}    end;handle_call({rename,Async,FromFile,ToFile}, From, State) ->    ReqID = State#state.req_id,    ssh_xfer:rename(?XF(State),ReqID,FromFile,ToFile,[overwrite]),    make_reply(ReqID, Async, From, State);handle_call({delete,Async,Name}, From, State) ->    ReqID = State#state.req_id,    ssh_xfer:remove(?XF(State), ReqID, Name),    make_reply(ReqID, Async, From, State);handle_call({make_dir,Async,Name}, From, State) ->    ReqID = State#state.req_id,    ssh_xfer:mkdir(?XF(State), ReqID, Name,		   #ssh_xfer_attr{ type = directory }),    make_reply(ReqID, Async, From, State);handle_call({del_dir,Async,Name}, From, State) ->    ReqID = State#state.req_id,    ssh_xfer:rmdir(?XF(State), ReqID, Name),    make_reply(ReqID, Async, From, State);handle_call({real_path,Async,Name}, From, State) ->    ReqID = State#state.req_id,    ssh_xfer:realpath(?XF(State), ReqID, Name),    make_reply(ReqID, Async, From, State);handle_call({read_file_info,Async,Name}, From, State) ->    ReqID = State#state.req_id,    ssh_xfer:stat(?XF(State), ReqID, Name, all),    make_reply(ReqID, Async, From, State);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?