anchor.diff:  %% %CopyrightEnd%
   %%
   
  -%%% Generic connection owner process.
  -%%%
  -%%% -type handle() = pid(). A handle for using a connection implemented
  -%%% with ct_gen_conn.erl.
  +%%
  +%% Generic connection owner process.
  +%%
   
   -module(ct_gen_conn).
   
  --export([start/4, stop/1, get_conn_pid/1, check_opts/1]).
  --export([call/2, call/3, return/2, do_within_time/2]).
  --export([log/3, start_log/1, cont_log/2, cont_log_no_timestamp/2, end_log/0]).
  +-behaviour(gen_server).
  +
  +-export([start/4,
  +         stop/1,
  +         get_conn_pid/1]).
  +
  +-export([call/2,
  +         call/3,
  +         return/2,
  +         do_within_time/2]).
  +
  +-export([log/3,
  +         start_log/1,
  +         cont_log/2,
  +         cont_log_no_timestamp/2,
  +         end_log/0]).
  +
  +%% gen_server callbacks
  +-export([init/1,
  +         handle_info/2,
  +         handle_cast/2,
  +         handle_call/3,
  +         code_change/3,
  +         terminate/2]).
  +
  +%% test
  +-export([make_opts/1]).
   
   -ifdef(debug).
   -define(dbg,true).
  
positive.code: -define(dbg,false).
  -endif.
  
  -record(gen_opts,{callback,
  		  name,
  		  address,
  		  init_data,
  		  reconnect    = true,
  		  forward      = false,
  		  use_existing = true,
  		  old          = false,
  		  conn_pid,
  		  cb_state,
  		  ct_util_server}).
  
  %%%-----------------------------------------------------------------
  %%% -spec start(Address,InitData,CallbackMod,Opts) ->
  %%%                                     {ok,Handle} | {error,Reason}
  %%%      Name = term()
  %%%      CallbackMod = atom()
  %%%      InitData = term()
  %%%      Address = term()
  %%%      Opts = [Opt]
  %%%      Opt = {name,Name} | {use_existing_connection,boolean()} |
  %%%            {reconnect,boolean()} | {forward_messages,boolean()}
  %%%
  %%% Open a connection and start the generic connection owner process.
  %%%
  %%% The CallbackMod is a specific callback module for
  %%% each type of connection (e.g. telnet, ftp,...). It must export the
  %%% function init/3 which takes the arguments
  %%% Name, Addresse) and
  %%% InitData and returna
  %%% {ok,ConnectionPid,State} or
  %%% {error,Reason}.
  %%%
  %%% If no name is given, the Name argument in init/3 will
  %%% have the value undefined.
  %%%
  %%% The callback modules must also export
  %%%
  %%% handle_msg(Msg,From,State) -> {reply,Reply,State} |
  %%%                               {noreply,State} |
  %%%                               {stop,Reply,State}
  %%% terminate(ConnectionPid,State) -> term()
  %%% close(Handle) -> term()
  %%%
  %%% The close/1 callback function is actually a callback
  %%% for ct_util, for closing registered connections when
  %%% ct_util_server is terminated. Handle is the Pid of
  %%% the ct_gen_conn process.
  %%%
  %%% If option reconnect is true, then the
  %%% callback must also export
  %%%
  %%% reconnect(Address,State) -> {ok,ConnectionPid,State}
  %%%
  %%% If option forward_messages is <ocde>true, then
  %%% the callback must also export
  %%%
  %%% handle_msg(Msg,State) -> {noreply,State} | {stop,State}
  %%%
  %%% An old interface still exists. This is used by ct_telnet, ct_ftp
  %%% and ct_ssh. The start function then has an explicit
  %%% Name argument, and no Opts argument. The
  %%% callback must export:
  %%%
  %%% init(Name,Address,InitData) -> {ok,ConnectionPid,State}
  %%% handle_msg(Msg,State) -> {Reply,State}
  %%% reconnect(Address,State) -> {ok,ConnectionPid,State}
  %%% terminate(ConnectionPid,State) -> term()
  %%% close(Handle) -> term()
  %%%
  start(Address,InitData,CallbackMod,Opts) when is_list(Opts) ->
      do_start(Address,InitData,CallbackMod,Opts);
  start(Name,Address,InitData,CallbackMod) ->
      do_start(Address,InitData,CallbackMod,[{name,Name},{old,true}]).
  
  %%%-----------------------------------------------------------------
  %%% -spec stop(Handle) -> ok
  %%%      Handle = handle()
  %%%
  %%% Close the connection and stop the process managing it.
  stop(Handle) ->
      call(Handle,stop,5000).
  
  %%%-----------------------------------------------------------------
  %%% -spec get_conn_pid(Handle) -> ok
  %%%      Handle = handle()
  %%%
  %%% Return the connection pid associated with Handle
  get_conn_pid(Handle) ->
      call(Handle,get_conn_pid).
  
  %%%-----------------------------------------------------------------
  %%% -spec log(Heading,Format,Args) -> ok
  %%%
  %%% 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
  %%%
  %%% 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
  %%%
  %%% 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 cont_log_no_timestamp(Format,Args) -> ok
  %%%
  %%% Log activities on the current connection (tool-internal use only).
  %%% See ct_logs:cont_log/2
  cont_log_no_timestamp(Format,Args) ->
      log(cont_log_no_timestamp,[Format,Args]).
  
  %%%-----------------------------------------------------------------
  %%% -spec end_log() -> ok
  %%%
  %%% 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()
  %%%
  %%% Execute a function within a limited time (tool-internal use only).
  %%%
  %%% Execute the given Fun, but interrupt if it takes
  %%% more than Timeout milliseconds.
  %%%
  %%% The execution is also interrupted if the connection is
  %%% closed.
  do_within_time(Fun,Timeout) ->
      Self = self(),
      Silent = get(silent),
  
negative.code: 	    end
      end.
  
  %% ===========================================================================
  
  do_start(Address, InitData, CallbackMod, OptsList) ->
      #gen_opts{name = Name}
          = Opts
          = make_opts(OptsList, #gen_opts{callback  = CallbackMod,
                                          address   = Address,
                                          init_data = InitData}),
      %% Testing for an existing connection is slightly broken as long
      %% as calls to start aren't serialized: concurrent starts can both
      %% end up in the final clause.
      case ct_util:does_connection_exist(Name, Address, CallbackMod) of
          {ok, _Pid} = Ok when Opts#gen_opts.use_existing ->
              log("ct_gen_conn:start","Using existing connection!\n", []),
              Ok;
          {ok, Pid} when not Opts#gen_opts.use_existing ->
              {error, {connection_exists, Pid}};
          false ->
              do_start(Opts)
      end.
  
  do_start(Opts) ->
      try gen_server:start(?MODULE, Opts, []) of
          {ok, _} = Ok    -> Ok;
          {error, Reason} -> {error, rc(Reason)}
      catch
          exit: Reason ->
              log("ct_gen_conn:start",
                  "Connection process died: ~tp\n",
                  [Reason]),
              {error, {connection_process_died, Reason}}
      end.
  
  %% Unwrap a {shutdown, _} exit reason for backwards compatibility.
  rc({shutdown, Reason}) -> Reason;
  rc(T)                  -> T.
  
  make_opts(Opts) ->
      make_opts(Opts, #gen_opts{}).
  
  make_opts(Opts, #gen_opts{} = Rec) ->
      lists:foldl(fun opt/2, Rec, Opts).
  
  opt({name, Name}, Rec)                    -> Rec#gen_opts{name = Name};
  opt({reconnect, Bool}, Rec)               -> Rec#gen_opts{reconnect = Bool};
  opt({forward_messages, Bool}, Rec)        -> Rec#gen_opts{forward = Bool};
  opt({use_existing_connection, Bool}, Rec) -> Rec#gen_opts{use_existing = Bool};
  opt({old, Bool}, Rec)                     -> Rec#gen_opts{old = Bool}.
  
  %% ===========================================================================
  
  call(Pid, Msg) ->
      call(Pid, Msg, infinity).
  
  -spec call(Handle, Msg, Tmo)
     -> term()
   when Handle :: handle(),
        Msg :: term(),
        Tmo :: non_neg_integer()
             | infinity.
  
  call(Pid, Msg, infinity = Tmo) ->
      gen_call(Pid, Msg, Tmo);
  
  %% Spawn a middleman process if the call can timeout to avoid the
  %% possibilty of a reply being left in the caller's mailbox after a
  %% timeout.
  call(Pid, Msg, Tmo) ->
      {_, MRef} = spawn_monitor(fun() -> exit(gen_call(Pid, Msg, Tmo)) end),
      receive {'DOWN', MRef, process, _, RC} -> RC end.
  
  gen_call(Pid, Msg, Tmo) ->
      try gen_server:call(Pid, Msg, Tmo) of
          T -> retry(Pid, T, Tmo)
      catch
          exit: Reason -> {error, {process_down, Pid, rc(Pid, Reason)}}
      end.
  
  retry(Pid, {retry, _} = T, Tmo) -> gen_call(Pid, T, Tmo);
  retry(_, T, _)                  -> T.
  
  %% Unwrap the MFA gen_server puts into exit reasons.
  rc(Pid, {Reason, {gen_server, call, _}}) ->
      rc(Pid, Reason);
  
  rc(Pid, timeout) ->
      log("ct_gen_conn",
          "Connection process ~w not responding. Killing now!",
          [Pid]),
      exit(Pid, kill),
      forced_termination;
  
  rc(_, Reason) ->
      rc(Reason).
  
  return(From, Result) ->
      gen_server:reply(From, Result).
  
  %% ===========================================================================
  %% gen_server callbacks
  
  %% init/1
  
  init(#gen_opts{callback = Mod,
                 name = Name,
                 address = Addr,
                 init_data = InitData}
       = Opts) ->
      process_flag(trap_exit, true),
      ct_util:mark_process(),
      put(silent, false),
      try Mod:init(Name, Addr, InitData) of
          {ok, ConnPid, State} when is_pid(ConnPid) ->
              link(ConnPid),
              put(conn_pid, ConnPid),
              SrvPid = whereis(ct_util_server),
              link(SrvPid),
              ct_util:register_connection(Name, Addr, Mod, self()),
              {ok, Opts#gen_opts{conn_pid = ConnPid,
                                 cb_state = State,
                                 ct_util_server = SrvPid}};
          {error, Reason} ->
              {stop, {shutdown, Reason}}
      catch
          C: Reason when C /= error ->
              {stop, {shutdown, Reason}}
      end.
  
  %% handle_call/2
  
  handle_call(get_conn_pid, _From, #gen_opts{conn_pid = Pid} = Opts) ->
      {reply, Pid, Opts};
  
  handle_call(stop, _From, Opts) ->
      {stop, normal, ok, Opts};
  
  %% Only retry if failure is because of a reconnection.
  handle_call({retry, {Error, _Name, ConnPid, _Msg}},
              _From,
              #gen_opts{conn_pid = ConnPid}
              = Opts) ->
      {reply, error_rc(Error), Opts};
  
  handle_call({retry, {_Error, _Name, _CPid, Msg}},
              _From,
              #gen_opts{callback = Mod,
                        cb_state = State}
              = Opts) ->
      log("Rerunning command","Connection reestablished. "
          "Rerunning command...",
          []),
      {Reply, NewState} = Mod:handle_msg(Msg, State),
      {reply, Reply, Opts#gen_opts{cb_state = NewState}};
  
  handle_call(Msg, _From, #gen_opts{old = true,
                                    callback = Mod,
                                    cb_state = State}
                          = Opts) ->
      {Reply, NewState} = Mod:handle_msg(Msg, State),
      {reply, Reply, Opts#gen_opts{cb_state = NewState}};
  
  handle_call(Msg, From, #gen_opts{callback = Mod,
                                    cb_state = State}
                          = Opts) ->
      case Mod:handle_msg(Msg, From, State) of
          {reply, Reply, NewState} ->
              {reply, Reply, Opts#gen_opts{cb_state = NewState}};
          {noreply, NewState} ->
              {noreply, Opts#gen_opts{cb_state = NewState}};
          {stop, Reply, NewState} ->
              {stop, normal, Reply, Opts#gen_opts{cb_state = NewState}}
      end.
  
  %% handle_cast/2
  
  handle_cast(_, Opts) ->
      {noreply, Opts}.
  
  %% handle_info/2
  
  handle_info({'EXIT', Pid, Reason},
              #gen_opts{reconnect = true,
                        conn_pid = Pid,
                        address = Addr}
              = Opts) ->
      log("Connection down!\nOpening new!",
          "Reason: ~tp\nAddress: ~tp\n",
          [Reason, Addr]),
      case reconnect(Opts) of
          {ok, NewPid, NewState} ->
              link(NewPid),
              put(conn_pid, NewPid),
              {noreply, Opts#gen_opts{conn_pid = NewPid,
                                      cb_state = NewState}};
          Error ->
              log("Reconnect failed. Giving up!",
                  "Reason: ~tp\n",
                  [Error]),
              {stop, normal, Opts}
      end;
  
  handle_info({'EXIT', Pid, Reason},
              #gen_opts{reconnect = false,
                        conn_pid = Pid}
              = Opts) ->
      log("Connection closed!", "Reason: ~tp\n", [Reason]),
      {stop, normal, Opts};
  
  handle_info({'EXIT', Pid, Reason},
              #gen_opts{ct_util_server = Pid}
              = Opts) ->
      {stop, {shutdown, Reason}, Opts};
  
  handle_info(Msg, #gen_opts{forward = true,
                             callback = Mod,
                             cb_state = State}
                   = Opts) ->
      case Mod:handle_msg(Msg, State) of
          {noreply, NewState} ->
              {noreply, Opts#gen_opts{cb_state = NewState}};
          {stop, NewState} ->
              {stop, normal, Opts#gen_opts{cb_state = NewState}}
      end;
  
  handle_info(_, #gen_opts{} = Opts) ->
      {noreply, Opts}.
  
  %% code_change/2
  
  code_change(_Vsn, State, _Extra) ->
      {ok, State}.
  
  %% terminate/2
  
  %% Cleanup is only in this case since ct_util also cleans up and
  %% expects us not to, which is at least an odd expectation.
  terminate(normal, #gen_opts{callback = Mod,
                              conn_pid = Pid,
                              cb_state = State}) ->
      ct_util:unregister_connection(self()),
      unlink(Pid),
      Mod:terminate(Pid, State);
  
  terminate(_, #gen_opts{}) ->
      ok.
  
  %% ===========================================================================
  
  error_rc({error, _} = T) -> T;
  error_rc(Reason)         -> {error, Reason}.
  
  reconnect(#gen_opts{callback = Mod,
                      address = Addr,
                      cb_state = State}) ->
      Mod:reconnect(Addr, State).
  
  log(Func, Args) ->
      case get(silent) of
          true when not ?dbg ->
              ok;
          _ ->
              apply(ct_logs, Func, Args)
      end.
  