%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2003-2009. All Rights Reserved. %% %% 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 online 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. %% %% %CopyrightEnd% %% %%% @doc Generic connection owner process. %%% %%% @type handle() = pid(). A handle for using a connection implemented %%% with ct_gen_conn.erl. -module(ct_gen_conn). -compile(export_all). -export([start/4, stop/1]). -export([call/2, do_within_time/2]). -ifdef(debug). -define(dbg,true). -else. -define(dbg,false). -endif. -record(gen_opts,{callback, name, address, init_data, conn_pid, cb_state, ct_util_server}). %%%----------------------------------------------------------------- %%% @spec start(Name,Address,InitData,CallbackMod) -> %%% {ok,Handle} | {error,Reason} %%% Name = term() %%% CallbackMod = atom() %%% InitData = term() %%% Address = term() %%% %%% @doc Open a connection and start the generic connection owner process. %%% %%% <p>The <code>CallbackMod</code> is a specific callback module for %%% each type of connection (e.g. telnet, ftp,...). It must export the %%% function <code>init/3</code> which takes the arguments %%% <code>Name</code>, <code>Addresse</code>) and %%% <code>InitData</code> and returna %%% <code>{ok,ConnectionPid,State}</code> or %%% <code>{error,Reason}</code>.</p> start(Name,Address,InitData,CallbackMod) -> case ct_util:does_connection_exist(Name,Address,CallbackMod) of {ok,Pid} -> log("ct_gen_conn:start","Using existing connection!\n",[]), {ok,Pid}; false -> Self = self(), Pid = spawn(fun() -> init_gen(Self, #gen_opts{callback=CallbackMod, name=Name, address=Address, init_data=InitData}) end), MRef = erlang:monitor(process,Pid), receive {connected,Pid} -> erlang:demonitor(MRef), ct_util:register_connection(Name,Address,CallbackMod,Pid), {ok,Pid}; {Error,Pid} -> receive {'DOWN',MRef,process,_,_} -> ok end, Error; {'DOWN',MRef,process,_,Reason} -> log("ct_gen_conn:start", "Connection process died: ~p\n", [Reason]), {error,{connection_process_died,Reason}} end end. %%%----------------------------------------------------------------- %%% @spec stop(Handle) -> ok %%% Handle = handle() %%% %%% @doc Close the telnet connection and stop the process managing it. stop(Pid) -> call(Pid,stop). %%%----------------------------------------------------------------- %%% @spec log(Heading,Format,Args) -> ok %%% %%% @doc Log activities on the current connection (tool-internal use only). %%% @see ct_logs:log/3 log(Heading,Format,Args) -> log(log,[Heading,Format,Args]). %%%----------------------------------------------------------------- %%% @spec start_log(Heading) -> ok %%% %%% @doc Log activities on the current connection (tool-internal use only). %%% @see ct_logs:start_log/1 start_log(Heading) -> log(start_log,[Heading]). %%%----------------------------------------------------------------- %%% @spec cont_log(Format,Args) -> ok %%% %%% @doc Log activities on the current connection (tool-internal use only). %%% @see ct_logs:cont_log/2 cont_log(Format,Args) -> log(cont_log,[Format,Args]). %%%----------------------------------------------------------------- %%% @spec end_log() -> ok %%% %%% @doc Log activities on the current connection (tool-internal use only). %%% @see ct_logs:end_log/0 end_log() -> log(end_log,[]). %%%----------------------------------------------------------------- %%% @spec do_within_time(Fun,Timeout) -> FunResult | {error,Reason} %%% Fun = function() %%% Timeout = integer() %%% %%% @doc Execute a function within a limited time (tool-internal use only). %%% %%% <p>Execute the given <code>Fun</code>, but interrupt if it takes %%% more than <code>Timeout</code> milliseconds.</p> %%% %%% <p>The execution is also interrupted if the connection is %%% closed.</p> do_within_time(Fun,Timeout) -> Self = self(), Silent = get(silent), TmpPid = spawn_link(fun() -> put(silent,Silent), R = Fun(), Self ! {self(),R} end), ConnPid = get(conn_pid), receive {TmpPid,Result} -> Result; {'EXIT',ConnPid,_Reason}=M -> unlink(TmpPid), exit(TmpPid,kill), self() ! M, {error,connection_closed} after Timeout -> exit(TmpPid,kill), receive {TmpPid,Result} -> %% TmpPid just managed to send the result at the same time %% as the timeout expired. receive {'EXIT',TmpPid,_reason} -> ok end, Result; {'EXIT',TmpPid,killed} -> %% TmpPid did not send the result before the timeout expired. {error,timeout} end end. %%%================================================================= %%% Internal functions call(Pid,Msg) -> MRef = erlang:monitor(process,Pid), Ref = make_ref(), Pid ! {Msg,{self(),Ref}}, receive {Ref, Result} -> erlang:demonitor(MRef), case Result of {retry,_Data} -> call(Pid,Result); Other -> Other end; {'DOWN',MRef,process,_,Reason} -> {error,{process_down,Pid,Reason}} end. return({To,Ref},Result) -> To ! {Ref, Result}. init_gen(Parent,Opts) -> process_flag(trap_exit,true), CtUtilServer = whereis(ct_util_server), link(CtUtilServer), put(silent,false), case catch (Opts#gen_opts.callback):init(Opts#gen_opts.name, Opts#gen_opts.address, Opts#gen_opts.init_data) of {ok,ConnPid,State} when is_pid(ConnPid) -> link(ConnPid), put(conn_pid,ConnPid), Parent ! {connected,self()}, loop(Opts#gen_opts{conn_pid=ConnPid, cb_state=State, ct_util_server=CtUtilServer}); {error,Reason} -> Parent ! {{error,Reason},self()} end. loop(Opts) -> receive {'EXIT',Pid,Reason} when Pid==Opts#gen_opts.conn_pid -> log("Connection down!\nOpening new!","Reason: ~p\nAddress: ~p\n", [Reason,Opts#gen_opts.address]), case reconnect(Opts) of {ok, NewPid, NewState} -> link(NewPid), put(conn_pid,NewPid), loop(Opts#gen_opts{conn_pid=NewPid,cb_state=NewState}); Error -> ct_util:unregister_connection(self()), log("Reconnect failed. Giving up!","Reason: ~p\n",[Error]) end; {'EXIT',Pid,Reason} -> case Opts#gen_opts.ct_util_server of Pid -> exit(Reason); _ -> loop(Opts) end; {stop, From} -> ct_util:unregister_connection(self()), (Opts#gen_opts.callback):terminate(Opts#gen_opts.conn_pid, Opts#gen_opts.cb_state), return(From,ok), ok; {{retry,{Error,_Name,CPid,_Msg}}, From} when CPid == Opts#gen_opts.conn_pid -> %% only retry if failure is because of a reconnection Return = case Error of {error,_} -> Error; Reason -> {error,Reason} end, return(From, Return), loop(Opts); {{retry,{_Error,_Name,_CPid,Msg}}, From} -> log("Rerunning command","Connection reestablished. Rerunning command...",[]), {Return,NewState} = (Opts#gen_opts.callback):handle_msg(Msg,Opts#gen_opts.cb_state), return(From, Return), loop(Opts#gen_opts{cb_state=NewState}); {Msg,From={Pid,_Ref}} when is_pid(Pid) -> {Return,NewState} = (Opts#gen_opts.callback):handle_msg(Msg,Opts#gen_opts.cb_state), return(From, Return), loop(Opts#gen_opts{cb_state=NewState}) end. nozero({ok,S}) when is_list(S) -> {ok,[C || C <- S, C=/=0, C=/=13]}; nozero(M) -> M. reconnect(Opts) -> (Opts#gen_opts.callback):reconnect(Opts#gen_opts.address, Opts#gen_opts.cb_state). log(Func,Args) -> case get(silent) of true when not ?dbg-> ok; _ -> apply(ct_logs,Func,Args) end.