%%----------------------------------------------------------------------
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2000-2016. 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%
%%
%%
%%----------------------------------------------------------------------
%% File : CosFileTransfer_FileTransferSession_impl.erl
%% Description :
%%
%% Created : 12 Sept 2000
%%----------------------------------------------------------------------
-module('CosFileTransfer_FileTransferSession_impl').
%%----------------------------------------------------------------------
%% Include files
%%----------------------------------------------------------------------
-include_lib("orber/include/corba.hrl").
-include_lib("orber/src/orber_iiop.hrl").
-include("cosFileTransferApp.hrl").
%%----------------------------------------------------------------------
%% External exports
%%----------------------------------------------------------------------
-export([init/1,
terminate/2,
code_change/3,
handle_info/2]).
%% Interface functions
-export(['_get_protocols_supported'/2,
set_directory/3,
create_file/3,
create_directory/3,
get_file/3,
delete/3,
transfer/4,
append/4,
insert/5,
logout/2]).
%%----------------------------------------------------------------------
%% Internal exports
%%----------------------------------------------------------------------
-export([oe_orber_create_directory_current/2, oe_orber_get_content/4,
oe_orber_count_children/3]).
-export([invoke_call/3]).
%%----------------------------------------------------------------------
%% Records
%%----------------------------------------------------------------------
-record(state, {protocols, server, type, current, module, connection, mytype,
connection_timeout}).
%%----------------------------------------------------------------------
%% Macros
%%----------------------------------------------------------------------
-define(create_InitState(P, S, T, C, M, Co, Ty, CT),
#state{protocols=P, server=S, type=T, current=C, module=M, connection=Co,
mytype=Ty, connection_timeout=CT}).
-define(get_Protocols(S), S#state.protocols).
-define(get_Server(S), S#state.server).
-define(get_CurrentDir(S), S#state.current).
-define(get_Module(S), S#state.module).
-define(get_Connection(S), S#state.connection).
-define(get_MyType(S), S#state.mytype).
-define(get_ConnectionTimeout(S), S#state.connection_timeout).
-define(set_CurrentDir(S, C), S#state{current=C}).
-define(is_FTP(S), S#state.type=='FTP').
-define(is_FTAM(S), S#state.type=='FTAM').
-define(is_NATIVE(S), S#state.type=='NATIVE').
-define(is_ORBER_NATIVE(S), S#state.module==cosFileTransferNATIVE_file).
%%======================================================================
%% External functions
%%======================================================================
%%----------------------------------------------------------------------
%% Function : init/1
%% Returns : {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%% Description: Initiates the server
%%----------------------------------------------------------------------
init(['FTP', Host, Port, User, Password, _Account, Protocol, Timeout]) ->
{ok, Pid} = inets:start(ftpc, [{host, Host}, {port, Port}], stand_alone),
ok = ftp:user(Pid, User, Password),
{ok, PWD} = ftp:pwd(Pid),
{Connection, ProtocolSupport} = setup_local(Protocol),
{ok, ?create_InitState(ProtocolSupport, Pid, 'FTP',
PWD, ftp, Connection, Protocol, Timeout)};
init([{'NATIVE', Mod}, Host, Port, User, Password, _Account, Protocol, Timeout]) ->
{ok, Pid} = Mod:open(Host, Port),
ok = Mod:user(Pid, User, Password),
{ok, PWD} = Mod:pwd(Pid),
{Connection, ProtocolSupport} = setup_local(Protocol),
{ok, ?create_InitState(ProtocolSupport, Pid, 'NATIVE',
PWD, Mod, Connection, Protocol, Timeout)}.
%%----------------------------------------------------------------------
%% Function : terminate/2
%% Returns : any (ignored by gen_server)
%% Description: Shutdown the server
%%----------------------------------------------------------------------
terminate(_Reason, #state{type = Type, server = Server, module = Mod} = State) ->
case ?get_MyType(State) of
ssl ->
catch ssl:close(?get_Connection(State));
_ ->
catch gen_tcp:close(?get_Connection(State))
end,
case Type of
'FTP' ->
inets:stop(ftpc, Server);
'NATIVE' ->
Mod:close(Server);
_ ->
ok
end,
ok.
%%----------------------------------------------------------------------
%% Function : code_change/3
%% Returns : {ok, NewState}
%% Description: Convert process state when code is changed
%%----------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%---------------------------------------------------------------------%
%% function : handle_info/2
%% Arguments:
%% Returns :
%% Effect :
%%----------------------------------------------------------------------
handle_info(Info, State) ->
case Info of
{'EXIT', _Pid, Reason} ->
{stop, Reason, State};
_Other ->
{noreply, State}
end.
%%======================================================================
%% CosFileTransfer::FileTransferSession
%%======================================================================
%%---------------------------------------------------------------------%
%% Function : _get_protocols_supported
%% Arguments :
%% Returns : A list of CosFileTransfer::ProtocolSupport, i.e.,
%% struct ProtocolSupport {
%% Istring protocol_name;
%% ProtocolAddressList addresses; %% eq a list of strings.
%% };
%% Description:
%%----------------------------------------------------------------------
'_get_protocols_supported'(_OE_This, State) ->
{reply, ?get_Protocols(State), State}.
%%----------------------------------------------------------------------
%% Function : set_directory
%% Arguments : Directory - CosFileTransfer::Directory
%% Returns :
%% Description:
%%----------------------------------------------------------------------
set_directory(_OE_This, State, Directory) when ?is_FTP(State); ?is_NATIVE(State) ->
Mod = ?get_Module(State),
Path = filename:join('CosFileTransfer_Directory':
'_get_complete_file_name'(Directory)),
case catch Mod:cd(?get_Server(State), Path) of
ok ->
{reply, ok, ?set_CurrentDir(State, Path)};
{error, epath} ->
corba:raise(#'CosFileTransfer_FileNotFoundException'
{reason="Directory not found."});
{error, elogin} ->
corba:raise(#'CosFileTransfer_SessionException'
{reason="User not loggen in."});
{error, econn} ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Premature connection ending."});
_ ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason = "Unexpected error."})
end.
%%----------------------------------------------------------------------
%% Function : create_file
%% Arguments : FileNameList
%% Returns : File
%% Description: This operation creates a File Object representing a
%% file which may or may not exist. Typically used as
%% argument when invoking transfer/3. See also get_file/2.
%%----------------------------------------------------------------------
create_file(OE_This, State, FileNameList) ->
{reply, cosFileTransferApp:create_file(OE_This, FileNameList), State}.
%%----------------------------------------------------------------------
%% Function : create_directory
%% Arguments : FileNameList - full path name.
%% Returns : Directory
%% Description:
%%----------------------------------------------------------------------
create_directory(OE_This, State, FileNameList) when ?is_FTP(State);
?is_NATIVE(State) ->
Mod = ?get_Module(State),
case Mod:mkdir(?get_Server(State), filename:join(FileNameList)) of
ok ->
{reply, cosFileTransferApp:create_dir(OE_This, FileNameList), State};
{error, epath} ->
corba:raise(#'CosFileTransfer_FileNotFoundException'
{reason="Directory not found."});
{error, elogin} ->
corba:raise(#'CosFileTransfer_SessionException'
{reason="User not loggen in."});
{error, econn} ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Premature connection ending."});
_ ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Unknown error."})
end.
%%----------------------------------------------------------------------
%% Function : get_file
%% Arguments : FileNameList
%% Returns : FileWrapper
%% Description: This operation should be independent of the working Directory,
%% i.e., a full path name must be supplied. The file or
%% directory the returned object is supposed to represent
%% MUST(!!!!) exist.
%%----------------------------------------------------------------------
get_file(OE_This, State, FileNameList) when ?is_FTP(State);
?is_NATIVE(State) ->
case check_type(OE_This, State, filename:join(FileNameList)) of
{ndirectory, _Listing} ->
{reply,
#'CosFileTransfer_FileWrapper'{the_file =
cosFileTransferApp:
create_dir(OE_This,
FileNameList),
file_type = ndirectory},
State};
nfile ->
{reply,
#'CosFileTransfer_FileWrapper'{the_file =
cosFileTransferApp:
create_file(OE_This,
FileNameList),
file_type = nfile},
State};
Other ->
%% If we want to return {stop, ....}
Other
end.
%%----------------------------------------------------------------------
%% Function : delete
%% Arguments : File
%% Returns : -
%% Description:
%%----------------------------------------------------------------------
delete(_OE_This, State, File) when ?is_FTP(State); ?is_NATIVE(State) ->
Mod = ?get_Module(State),
Result =
case 'CosPropertyService_PropertySet':
get_property_value(File, "is_directory") of
#any{value=false} ->
Mod:delete(?get_Server(State),
filename:join('CosFileTransfer_File':
'_get_complete_file_name'(File)));
#any{value=true} ->
Mod:rmdir(?get_Server(State),
filename:join('CosFileTransfer_File':
'_get_complete_file_name'(File)));
Other ->
Other
end,
case Result of
ok ->
{reply, ok, State};
{error, epath} ->
corba:raise(#'CosFileTransfer_FileNotFoundException'
{reason="File or Directory not found."});
{error, elogin} ->
corba:raise(#'CosFileTransfer_SessionException'
{reason="User not loggen in."});
{error, econn} ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Premature connection ending."});
_ ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Unknown error."})
end.
%%----------------------------------------------------------------------
%% Function : transfer
%% Arguments : SrcFile eq DestFile eq CosFileTransfer::File
%% Returns : -
%% Description: DestFile must be a newly created File object, using create_file()
%% on the Target FileTransferSession, prior to calling transfer().
%%----------------------------------------------------------------------
transfer(OE_This, State, SrcFile, DestFile) when ?is_ORBER_NATIVE(State) ->
case which_FTS_type(OE_This, SrcFile, DestFile) of
{source, TargetFTS} ->
%% The source FTS is supposed to be the active one, set up a connection.
Protocols = 'CosFileTransfer_FileTransferSession':
'_get_protocols_supported'(TargetFTS),
SrcName = 'CosFileTransfer_File':'_get_complete_file_name'(SrcFile),
Pid = spawn(?MODULE, invoke_call, [self(), transfer,
[TargetFTS, SrcFile, DestFile]]),
send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
filename:join(SrcName)),
check_reply(Pid),
{reply, ok, State};
{target, _SourceFTS} ->
DestName = 'CosFileTransfer_File':'_get_complete_file_name'(DestFile),
receive_file(?get_MyType(State), ?get_Connection(State),
?get_ConnectionTimeout(State),
filename:join(DestName), write),
{reply, ok, State}
end;
transfer(OE_This, State, SrcFile, DestFile) when ?is_FTP(State); ?is_NATIVE(State) ->
case which_FTS_type(OE_This, SrcFile, DestFile) of
{source, TargetFTS} ->
source_FTS_operation(State, SrcFile, DestFile, transfer, 0, TargetFTS);
{target, _SourceFTS} ->
target_FTS_operation(State, SrcFile, DestFile, send, 0)
end.
%%----------------------------------------------------------------------
%% Function : append
%% Arguments : SrcFile eq DestFile eq CosFileTransfer::File
%% Returns : -
%% Description:
%%----------------------------------------------------------------------
append(OE_This, State, SrcFile, DestFile) when ?is_ORBER_NATIVE(State) ->
case which_FTS_type(OE_This, SrcFile, DestFile) of
{source, TargetFTS} ->
SrcName = filename:join('CosFileTransfer_File':
'_get_complete_file_name'(SrcFile)),
check_type(OE_This, State, SrcName),
%% The source FTS is supposed to be the active one, set up a connection.
Protocols = 'CosFileTransfer_FileTransferSession':
'_get_protocols_supported'(TargetFTS),
Pid = spawn(?MODULE, invoke_call, [self(), append,
[TargetFTS, SrcFile, DestFile]]),
send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
SrcName),
check_reply(Pid),
{reply, ok, State};
{target, _SourceFTS} ->
DestName = filename:join('CosFileTransfer_File':
'_get_complete_file_name'(DestFile)),
check_type(OE_This, State, DestName),
receive_file(?get_MyType(State), ?get_Connection(State),
?get_ConnectionTimeout(State), DestName, append),
{reply, ok, State}
end;
append(OE_This, State, SrcFile, DestFile) when ?is_NATIVE(State) ->
case which_FTS_type(OE_This, SrcFile, DestFile) of
{source, TargetFTS} ->
source_FTS_operation(State, SrcFile, DestFile, append, 0, TargetFTS);
{target, _SourceFTS} ->
target_FTS_operation(State, SrcFile, DestFile, append, 0)
end;
append(_OE_This, _State, _SrcFile, _DestFile) ->
corba:raise(#'NO_IMPLEMENT'{completion_status=?COMPLETED_NO}).
%%----------------------------------------------------------------------
%% Function : insert
%% Arguments : SrcFile eq DestFile eq CosFileTransfer::File
%% Offset - long
%% Returns : -
%% Description:
%%----------------------------------------------------------------------
insert(OE_This, State, SrcFile, DestFile, Offset) when ?is_NATIVE(State) ->
case which_FTS_type(OE_This, SrcFile, DestFile) of
{source, TargetFTS} when ?is_ORBER_NATIVE(State) ->
SrcName = 'CosFileTransfer_File':'_get_complete_file_name'(SrcFile),
check_type(OE_This, State, filename:join(SrcName)),
%% The source FTS is supposed to be the active one, set up a connection.
Protocols = 'CosFileTransfer_FileTransferSession':
'_get_protocols_supported'(TargetFTS),
Pid = spawn(?MODULE, invoke_call, [self(), insert,
[TargetFTS, SrcFile,
DestFile, Offset]]),
send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
filename:join(SrcName)),
check_reply(Pid),
{reply, ok, State};
{source, TargetFTS} ->
source_FTS_operation(State, SrcFile, DestFile, insert, Offset, TargetFTS);
{target, _SourceFTS} ->
target_FTS_operation(State, SrcFile, DestFile, insert, Offset)
end;
insert(_OE_This, _State, _SrcFile, _DestFile, _Offset) ->
corba:raise(#'NO_IMPLEMENT'{completion_status=?COMPLETED_NO}).
%%----------------------------------------------------------------------
%% Function : logout
%% Arguments : -
%% Returns : -
%% Description:
%%----------------------------------------------------------------------
logout(_OE_This, State) when ?is_FTP(State); ?is_NATIVE(State) ->
Mod = ?get_Module(State),
catch Mod:close(?get_Server(State)),
{stop, normal, ok, State}.
%%======================================================================
%% Internal functions
%%======================================================================
%%----------------------------------------------------------------------
%% Function : oe_orber_create_directory_current
%% Arguments : -
%% Returns : Directory
%% Description: Creates a Directory describing the working directory
%% of the remote server, e.g., an FTP-server.
%%----------------------------------------------------------------------
oe_orber_create_directory_current(OE_This, State) when ?is_FTP(State);
?is_NATIVE(State) ->
Mod = ?get_Module(State),
FileNameList = filename:split(?get_CurrentDir(State)),
case Mod:nlist(?get_Server(State), ?get_CurrentDir(State)) of
{ok, _Listing} ->
{reply, cosFileTransferApp:create_dir(OE_This, FileNameList),
State};
{error, epath} ->
corba:raise(#'CosFileTransfer_FileNotFoundException'
{reason="Directory not found."});
{error, elogin} ->
corba:raise(#'CosFileTransfer_SessionException'
{reason="User not loggen in."});
{error, econn} ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Premature connection ending."});
_ ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Unknown error."})
end.
%%----------------------------------------------------------------------
%% Function : oe_orber_get_content
%% Arguments : -
%% Returns : string
%% Description:
%%----------------------------------------------------------------------
oe_orber_get_content(OE_This, State, FileNameList, Parent) when ?is_FTP(State);
?is_NATIVE(State) ->
Mod = ?get_Module(State),
case Mod:nlist(?get_Server(State), filename:join(FileNameList)) of
{ok, Listing} ->
create_content(Listing, OE_This, State, Parent, FileNameList);
{error, epath} ->
{reply, [], State};
_ ->
corba:raise(#'CosFileTransfer_FileNotFoundException'
{reason="Directory not found."})
end.
%%----------------------------------------------------------------------
%% Function : oe_orber_count_children
%% Arguments : -
%% Returns : string
%% Description:
%%----------------------------------------------------------------------
oe_orber_count_children(OE_This, State, FileNameList) when ?is_FTP(State);
?is_NATIVE(State) ->
case catch check_type(OE_This, State, filename:join(FileNameList)) of
{ndirectory, Members} ->
{reply, length(Members), State};
{stop, normal, _, _} ->
{stop, normal,
{'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}},
State};
_->
corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO})
end.
%%----------------------------------------------------------------------
%% Function : delete_tmp_file
%% Arguments : -
%% Returns : ok | {'EXCEPTION', E}
%% Description:
%%----------------------------------------------------------------------
delete_tmp_file(TmpFileName, ErrorMsg) ->
case file:delete(TmpFileName) of
ok ->
ok;
_ ->
corba:raise(#'CosFileTransfer_RequestFailureException'{reason=ErrorMsg})
end.
%%----------------------------------------------------------------------
%% Function : invoke_call
%% Arguments : -
%% Returns : ok | {'EXCEPTION', E}
%% Description:
%%----------------------------------------------------------------------
invoke_call(Pid, Op, Args) ->
Result = (catch apply('CosFileTransfer_FileTransferSession', Op, Args)),
Pid ! {transfer_result, self(), Result},
ok.
%%----------------------------------------------------------------------
%% Function : check_reply
%% Arguments : Pid - the pid of the spawned process.
%% Returns : ok | {'EXCEPTION', E}
%% Description:
%%----------------------------------------------------------------------
check_reply(Pid) ->
receive
{transfer_result, Pid, ok} ->
ok;
{transfer_result, Pid, {'EXCEPTION', E}} ->
orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:check_reply();
Raised exception: ", [?LINE, E], ?DEBUG_LEVEL),
corba:raise(E);
{transfer_result, Pid, {'EXIT', Reason}} ->
orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:check_reply();
Got EXIT-signal with reason: ", [?LINE, Reason], ?DEBUG_LEVEL),
corba:raise(#'INTERNAL'{minor=199,
completion_status=?COMPLETED_NO})
after infinity ->
%% Should we add an exception here or do we reuse the iiop_timeout?
%% For now keep as is.
corba:raise(#'INTERNAL'{minor=199,
completion_status=?COMPLETED_NO})
end.
%%----------------------------------------------------------------------
%% Function : which_FTS_type
%% Arguments : -
%% Returns : {source, FTS} | {target, FTS} | {'EXCEPTION', #'BAD_PARAM'{}}
%% Description: Used to determine if the target FTS is supposed to act
%% as sender or receiver and also return the counter part FTS.
%% An exception is raised if the user supplied incorrect parameters.
%%----------------------------------------------------------------------
which_FTS_type(OE_This, SrcFile, DestFile) ->
TargetFTS = 'CosFileTransfer_File':'_get_associated_session'(DestFile),
SourceFTS = 'CosFileTransfer_File':'_get_associated_session'(SrcFile),
case corba_object:is_equivalent(OE_This, TargetFTS) of
true ->
{target, SourceFTS};
false ->
case corba_object:is_equivalent(OE_This, SourceFTS) of
true ->
{source, TargetFTS};
false ->
corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO})
end
end.
%%----------------------------------------------------------------------
%% Function : setup_connection
%% Arguments : A list of #'CosFileTransfer_ProtocolSupport'{}
%% Returns :
%% Description:
%%----------------------------------------------------------------------
setup_connection([], Protocol, _) ->
orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:setup_connection(~p);
The Protocols listed are not supported.", [?LINE, Protocol], ?DEBUG_LEVEL),
corba:raise(#'CosFileTransfer_TransferException'{reason="Unsupported protocol"});
setup_connection([#'CosFileTransfer_ProtocolSupport'{protocol_name="TCP/IP",
addresses=Addr}|_],
tcp, Timeout) ->
setup_connection_helper(Addr, gen_tcp, [], Timeout);
setup_connection([#'CosFileTransfer_ProtocolSupport'{protocol_name="SSL",
addresses=Addr}|_],
ssl, Timeout) ->
Options = [{certfile, cosFileTransferApp:ssl_client_certfile()},
{verify, cosFileTransferApp:ssl_client_verify()},
{depth, cosFileTransferApp:ssl_client_depth()}] ++
ssl_client_cacertfile_option(),
setup_connection_helper(Addr, ssl, Options, Timeout);
setup_connection([_|T], Type, Timeout) ->
setup_connection(T, Type, Timeout).
setup_connection_helper([], _, _, _) ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Unable to contact remote server."});
setup_connection_helper([H|T], Driver, Options, Timeout) ->
case string:tokens(H, ":") of
[Host, Port] when Driver == gen_tcp ->
case gen_tcp:connect(Host, list_to_integer(Port),
[binary,
{packet, raw},
{reuseaddr, true},
{nodelay, true}|Options], Timeout) of
{ok, Sock} ->
{gen_tcp, Sock};
_->
%% No response.
setup_connection_helper(T, Driver, Options, Timeout)
end;
[Host, Port] when Driver == ssl ->
case ssl:connect(Host, list_to_integer(Port),
[binary,
{packet, 0},
{active, false}|Options], Timeout) of
{ok, Sock} ->
{ssl, Sock};
_->
%% No response.
setup_connection_helper(T, Driver, Options, Timeout)
end;
_ ->
%% Badly configured address.
setup_connection_helper(T, Driver, Options, Timeout)
end.
ssl_client_cacertfile_option() ->
case cosFileTransferApp:ssl_client_cacertfile() of
[] ->
[];
X when is_list(X) ->
{cacertfile, X};
_ ->
[]
end.
%%----------------------------------------------------------------------
%% Function : create_content
%% Arguments :
%% Returns :
%% Description:
%%----------------------------------------------------------------------
create_content(Listing, OE_This, State, Parent, PathList) ->
create_content(string:tokens(Listing, ?SEPARATOR), OE_This,
State, Parent, PathList, []).
create_content([], _OE_This, State, _Parent, _PathList, Acc) ->
{reply, Acc, State};
create_content([H|T], OE_This, State, Parent, PathList, Acc) ->
FullPathList = PathList ++[filename:basename(H)],
case check_type(OE_This, State, filename:join(FullPathList)) of
nfile ->
create_content(T, OE_This, State, Parent, PathList,
[#'CosFileTransfer_FileWrapper'
{the_file = cosFileTransferApp:create_file(OE_This,
FullPathList,
Parent),
file_type = nfile}|Acc]);
{ndirectory, _Members} ->
create_content(T, OE_This, State, Parent, PathList,
[#'CosFileTransfer_FileWrapper'
{the_file = cosFileTransferApp:create_dir(OE_This,
FullPathList,
Parent),
file_type = ndirectory}|Acc]);
Other ->
Other
end.
%%----------------------------------------------------------------------
%% Function : MISC functions
%% Arguments :
%% Returns :
%% Description:
%%----------------------------------------------------------------------
setup_local(tcp) ->
{ok,Socket}=gen_tcp:listen(0, [binary,
{packet, 0},
{backlog,1},
{active, false}]),
{ok, Port} = inet:port(Socket),
{Socket, [#'CosFileTransfer_ProtocolSupport'{protocol_name="TCP/IP",
addresses = [local_address(Port)]}]};
setup_local(ssl) ->
Options = [{certfile, cosFileTransferApp:ssl_server_certfile()},
{verify, cosFileTransferApp:ssl_server_verify()},
{depth, cosFileTransferApp:ssl_server_depth()}] ++
ssl_server_cacertfile_option(),
{ok,Socket}=ssl:listen(0, [binary,
{packet, 0},
{backlog,1},
{active, false}|Options]),
{ok, {_Address, Port}} = ssl:sockname(Socket),
{Socket, [#'CosFileTransfer_ProtocolSupport'{protocol_name="SSL",
addresses = [local_address(Port)]}]}.
local_address(Port) ->
{ok, Hostname} = inet:gethostname(),
{ok, {A1, A2, A3, A4}} = inet:getaddr(Hostname, inet),
integer_to_list(A1) ++ "." ++ integer_to_list(A2) ++ "." ++ integer_to_list(A3)
++ "." ++ integer_to_list(A4)++":"++integer_to_list(Port).
ssl_server_cacertfile_option() ->
case cosFileTransferApp:ssl_server_cacertfile() of
[] ->
[];
X when is_list(X) ->
[{cacertfile, X}];
_ ->
[]
end.
%%----------------------------------------------------------------------
%% Function : source_file_operation
%% Arguments :
%% Returns :
%% Description:
%%----------------------------------------------------------------------
source_FTS_operation(State, SrcFile, DestFile, Op, Offset, FTS) ->
Mod = ?get_Module(State),
%% The source FTS is supposed to be the active one, set up a connection.
Protocols = 'CosFileTransfer_FileTransferSession':'_get_protocols_supported'(FTS),
SrcName = 'CosFileTransfer_File':'_get_complete_file_name'(SrcFile),
TempName = cosFileTransferApp:create_name("TemporarySrcFile"),
case Mod:recv(?get_Server(State), filename:join(SrcName), TempName) of
ok when Op == insert ->
%% Downloaded the File, we are now ready to transmit.
Pid = spawn(?MODULE, invoke_call, [self(), insert,
[FTS, SrcFile, DestFile, Offset]]),
send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
TempName),
%% Delete the temporary local copy.
delete_tmp_file(TempName,
"Transfer completed but failed to remove temporary local copy."),
check_reply(Pid),
{reply, ok, State};
ok ->
%% Downloaded the File, we are now ready to transmit.
Pid = spawn(?MODULE, invoke_call, [self(), Op, [FTS, SrcFile, DestFile]]),
send_file(Protocols, ?get_MyType(State), ?get_ConnectionTimeout(State),
TempName),
%% Delete the temporary local copy.
delete_tmp_file(TempName,
"Transfer completed but failed to remove temporary local copy."),
check_reply(Pid),
{reply, ok, State};
{error, epath} ->
corba:raise(#'CosFileTransfer_FileNotFoundException'
{reason="File not found."});
{error, elogin} ->
corba:raise(#'CosFileTransfer_SessionException'
{reason="User not loggen in."});
{error, econn} ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Premature connection ending."})
end.
%%----------------------------------------------------------------------
%% Function : target_file_operation
%% Arguments :
%% Returns :
%% Description:
%%----------------------------------------------------------------------
target_FTS_operation(State, _SrcFile, DestFile, Op, Offset) ->
Mod = ?get_Module(State),
DestName = 'CosFileTransfer_File':'_get_complete_file_name'(DestFile),
TempName = cosFileTransferApp:create_name("TemporaryDestFile"),
receive_file(?get_MyType(State), ?get_Connection(State),
?get_ConnectionTimeout(State), TempName, write),
Result =
if
Op == insert ->
Mod:insert(?get_Server(State), TempName, filename:join(DestName), Offset);
true ->
Mod:Op(?get_Server(State), TempName, filename:join(DestName))
end,
case Result of
ok ->
%% Delete the temporary local copy.
delete_tmp_file(TempName,
"Transfer completed but failed to remove temporary local copy."),
%% Completed the transfer successfully.
{reply, ok, State};
{error, epath} ->
delete_tmp_file(TempName,
"IllegalOperationException and not able to remove temporary local copy."),
corba:raise(#'CosFileTransfer_IllegalOperationException'
{reason="Not allowed by destination."});
{error, elogin} ->
delete_tmp_file(TempName,
"SessionException and not able to remove temporary local copy."),
corba:raise(#'CosFileTransfer_SessionException'
{reason="User not logged in."});
{error, econn} ->
delete_tmp_file(TempName,
"TransferException and not able to remove temporary local copy."),
corba:raise(#'CosFileTransfer_TransferException'
{reason="Premature connection ending."});
{error, etnospc} ->
delete_tmp_file(TempName,
"RequestFailureException and not able to remove temporary local copy."),
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Premature connection ending."});
{error, efnamena} ->
delete_tmp_file(TempName,
"IllegalOperationException and not able to remove temporary local copy."),
corba:raise(#'CosFileTransfer_IllegalOperationException'
{reason="Not allowed by destination."})
end.
%%----------------------------------------------------------------------
%% Function : receive_file
%% Arguments : Driver - currently only gen_tcp supported.
%% LSocket - which socket to use.
%% FileName - an absolute file name representing the
%% file we want to create or append to.
%% Type - 'read', 'write', 'append'.
%% Returns :
%% Description:
%%----------------------------------------------------------------------
receive_file(tcp, LSock, Timeout, FileName, Type) ->
%% The Type can be the ones allowed by the file-module, i.e.,
%% 'read', 'write' or 'append'
FD = file_open(FileName, Type),
case gen_tcp:accept(LSock, Timeout) of
{ok, Sock} ->
receive_file_helper(gen_tcp, Sock, FD);
{error, timeout} ->
orber:dbg("[~p] CosFileTransfer_FileTransferSession:receive_file();~n"
"gen_tcp:accept(~p) timed out", [?LINE, Timeout], ?DEBUG_LEVEL),
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="TCP accept timed out.."});
{error, Why} ->
orber:dbg("[~p] CosFileTransfer_FileTransferSession:receive_file();~n"
"gen_tcp:accept(~p) failed: ~p", [?LINE, Timeout, Why], ?DEBUG_LEVEL),
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="TCP accept failed."})
end;
receive_file(ssl, LSock, Timeout, FileName, Type) ->
%% The Type can be the ones allowed by the file-module, i.e.,
%% 'read', 'write' or 'append'
FD = file_open(FileName, Type),
case ssl:transport_accept(LSock, Timeout) of
{ok, Sock} ->
case ssl:ssl_accept(Sock, Timeout) of
ok ->
receive_file_helper(ssl, Sock, FD);
{error, Error} ->
orber:dbg("[~p] CosFileTransfer_FileTransferSession:receive_file();~n"
"ssl:ssl_accept(~p) failed: ~p",
[?LINE, Timeout, Error], ?DEBUG_LEVEL),
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="TCP accept failed."})
end;
{error, timeout} ->
orber:dbg("[~p] CosFileTransfer_FileTransferSession:receive_file();~n"
"ssl:transport_accept(~p) timed out",
[?LINE, Timeout], ?DEBUG_LEVEL),
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="TCP accept timed out.."});
{error, Why} ->
orber:dbg("[~p] CosFileTransfer_FileTransferSession:receive_file();~n"
"ssl:transport_accept(~p) failed: ~p",
[?LINE, Timeout, Why], ?DEBUG_LEVEL),
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="TCP accept failed."})
end.
receive_file_helper(Driver, Sock, FD) ->
case Driver:recv(Sock, 0) of
{ok, Bin} ->
file:write(FD, Bin),
receive_file_helper(Driver, Sock, FD);
{error, closed} ->
file:close(FD);
What ->
orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:receive_file(~p);
Error occured when receiving data: ~p", [?LINE, Driver, What], ?DEBUG_LEVEL),
corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO})
end.
%%----------------------------------------------------------------------
%% Function : send_file
%% Arguments : Driver - currently only gen_tcp supported.
%% Sock - which socket to use.
%% FileName - an absolute file name representing the
%% file we want to send.
%% Returns :
%% Description:
%%----------------------------------------------------------------------
send_file(Protocols, Type, Timeout, FileName) ->
{Driver, Sock} = setup_connection(Protocols, Type, Timeout),
FD = file_open(FileName, read),
BuffSize = cosFileTransferApp:get_buffert_size(),
send_file_helper(Driver, Sock, FD, BuffSize).
send_file_helper(Driver, Sock, FD, BuffSize) ->
case file:read(FD, BuffSize) of
eof ->
file:close(FD),
Driver:close(Sock);
{ok, Bin} ->
case Driver:send(Sock, Bin) of
ok ->
send_file_helper(Driver, Sock, FD, BuffSize);
What ->
orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:send_file_helper(~p);
Error occured when sending data: ~p", [?LINE, Driver, What], ?DEBUG_LEVEL),
corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO})
end
end.
file_open(File, Type) ->
case file:open(File, [raw, binary, Type]) of
{ok, FD} ->
FD;
{error, What} ->
orber:debug_level_print("[~p] CosFileTransfer_FileTransferSession:file_open(~p);
Failed to open the file due to: ~p", [?LINE, File, What], ?DEBUG_LEVEL),
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Unable to open given file."})
end.
%%----------------------------------------------------------------------
%% Function : check_type
%% Arguments : FullName - an absolute file name representing the
%% file or directory we want to evaluate.
%% Returns :
%% Description:
%% When communcating with FTP-servers on different platforms a variety of
%% answers can be returned. A few examples:
%%
%% ### ftp:nlist on an empty directory ###
%% {ok, ""}, {error, epath}
%%
%% ### ftp:nlist on a non-existing directory or file ###
%% {ok, "XXX: No such file or directory}, {error, epath}
%%
%% ### ftp:nlist on an existing directory with one contained item ###
%% {ok, "Item"}
%%
%% Comparing the above we see that it's virtually impossible to tell apart
%% {ok, "XXX: No such file or directory} and {ok, "Item"}.
%% Hence, it's easier to test if it's possible to do ftp:cd instead.
%% Ugly, but rather effective. If we look at the bright side, it's only
%% necessary when we try to lookup:
%% * non-existing item
%% * A directory with one member only.
%% * An empty directory.
%%
%% Furthermore, no need for traversing Listings etc.
%%----------------------------------------------------------------------
check_type(_OE_This, State, FullName) when ?is_FTP(State); ?is_NATIVE(State) ->
Mod = ?get_Module(State),
Result =
case Mod:nlist(?get_Server(State), FullName) of
{ok, Listing} when length(Listing) > 0->
case string:tokens(Listing, ?SEPARATOR) of
[FullName] ->
nfile;
Members when length(Members) > 1 ->
%% Must test if more than one member since sometimes
%% this operation returns for example:
%% {ok, "XXX No such file or drectory"}
{ndirectory, Members};
Member ->
case Mod:cd(?get_Server(State), FullName) of
ok ->
case Mod:cd(?get_Server(State),
?get_CurrentDir(State)) of
ok ->
{ndirectory, Member};
_ ->
%% Failed, we cannot continue since the
%% FTS now pointso an incorrect Directory.
%% Hence, we must terminate.
{stop, normal,
{'EXCEPTION',
#'CosFileTransfer_RequestFailureException'
{reason="Unknown error."}}, State}
end;
{error, E} ->
{error, E};
_ ->
nfile
end
end;
{error, epath} ->
%% Might be a file.
DirName = filename:dirname(FullName),
case Mod:nlist(?get_Server(State), DirName) of
{ok, Listing} when length(Listing) > 0->
Members = string:tokens(Listing, ?SEPARATOR),
case lists:member(FullName, Members) of
true ->
nfile;
_ ->
BName = filename:basename(FullName),
case lists:member(BName, Members) of
true ->
nfile;
_ ->
{error, epath}
end
end;
_ ->
{error, epath}
end;
_ ->
case Mod:cd(?get_Server(State), FullName) of
ok ->
case Mod:cd(?get_Server(State), ?get_CurrentDir(State)) of
ok ->
{ndirectory, []};
_ ->
%% Failed, we cannot continue since the
%% FTS now pointso an incorrect Directory.
%% Hence, we must terminate.
{stop, normal,
{'EXCEPTION',
#'CosFileTransfer_RequestFailureException'
{reason="Unknown error."}}, State}
end;
_ ->
{error, epath}
end
end,
case Result of
{error, epath} ->
corba:raise(#'CosFileTransfer_FileNotFoundException'
{reason="File or Directory not found."});
{error, elogin} ->
corba:raise(#'CosFileTransfer_SessionException'
{reason="User not logged in."});
{error, econn} ->
corba:raise(#'CosFileTransfer_RequestFailureException'
{reason="Premature connection ending."});
Other ->
Other
end.
%%======================================================================
%% END OF MODULE
%%======================================================================