%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1996-2024. 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(snmpa_supervisor).
-moduledoc """
A supervisor for the SNMP agent Processes

This is the top supervisor for the agent part of the SNMP application. There is
always one supervisor at each node with an SNMP agent (master agent or
sub-agent).
""".

-behaviour(supervisor).

%% External exports
-export([start_link/2, stop/0, stop/1]).
-export([start_sub_sup/1, start_master_sup/1]).
-export([start_sub_agent/3, stop_sub_agent/1]).

%% Internal exports
-export([init/1, config/2]).

-compile({no_auto_import,[erase/1]}).

-define(SERVER, ?MODULE).

-include("snmpa_internal.hrl").
-include("snmp_verbosity.hrl").
-include("snmp_debug.hrl").


%%-----------------------------------------------------------------
%% Process structure
%% =================
%%
%%                                "application"
%%                                     |
%%                                  app-sup
%%                                     |
%%             ___________________ supervisor __________________
%%            /              |              |          \         \ 
%%   ___misc_sup___    target_cache  symbolic_store   local_db   agent_sup
%%  /     |        \                                               |   | 
%% mib  net_if  note_store                                        MA - SA
%%
%%  The supervisor (one at each node) starts:
%%    snmpa_symbolic_store (one at each node)
%%    snmpa_local_db (one at each node)
%%    snmpa_target_cache (one at each node) 
%%    MA - which starts
%%      own mib (hangs it under misc_sup)
%%      net_if (hangs it under misc_sup) 
%%      note_store (hangs it under misc_sup)
%%    SAs - which starts
%%      own mib (hangs it under misc_sup)
%%
%% This structure is intended to make the agent fault tolerant. The
%% agent processes (by far most code is in these processes) can be
%% restarted in case of a failure, as all data needed by these procs
%% are stored in the other procs. Any other process crash leads to
%% that the entire snmpa_supervisor crashes. If it is restarted, all
%% dynamically loaded mibs must be reloaded.
%%
%% It is up to the supervisor to configure the internal and
%% external mibs. Note that the 
%% agent group (internal MIB) and sysObjectID *must* be configured
%% in some way.
%%-----------------------------------------------------------------

-doc false.
start_link(Type, Opts) ->
    ?d("start_link -> entry with"
      "~n   Type. ~p"
      "~n   Opts. ~p", [Type, Opts]),
    start_link(get_agent_type(Opts), Opts, Type).

start_link(sub, Opts, _Type) ->
    start_sub_sup(Opts);
start_link(master, Opts, normal) ->
    start_master_sup(Opts);
start_link(master, Opts, {takeover, Node}) ->
    case start_master_sup(Opts) of
	{ok, Pid} ->
            OwnMibNames = get_own_loaded_mibs(),
            try_load_other_loaded_mibs(Node, OwnMibNames),
            {ok, Pid};
        Else ->
            Else
    end.


-doc false.
stop() ->
    stop(0).

-doc false.
stop(Timeout) ->
    case whereis(?SERVER) of
	Pid when is_pid(Pid) ->
            stop(Pid, Timeout);
	_ ->
	    not_running
    end.

%% For some unfathomable reason there is no "nice" way to stop
%% a supervisor. The "normal" way to do it is:
%% 1) exit(Pid, kill) (kaboom)
%% 2) If the caller is the *parent*: exit(Pid, shutdown)
%% So, here we do it the really ugly way...but since this function is 
%% intended for testing (mostly)...
stop(Pid, Timeout) when (Timeout =:= 0) ->
    sys:terminate(Pid, shutdown),
    ok;
stop(Pid, Timeout) ->
    MRef = erlang:monitor(process, Pid),
    sys:terminate(Pid, shutdown),
    receive
        {'DOWN', MRef, process, Pid, _} ->
            ok
    after Timeout ->
            erlang:demonitor(MRef, [flush]),
            {error, timeout}
    end.
    

get_own_loaded_mibs() ->
    AgentInfo = snmpa:info(snmp_master_agent),
    [ Name || {Name, _, _} <- loaded_mibs(AgentInfo) ].

try_load_other_loaded_mibs(Node, OwnMibs) ->
    case rpc:call(Node, snmpa, info, [snmp_master_agent]) of
        {badrpc, R} ->
            error_msg("could not takeover loaded mibs: ~p", [R]);
	AgentInfo ->
            LoadedMibs = loaded_mibs(AgentInfo),
            MibsToLoad = mibs_to_load(LoadedMibs, OwnMibs),
            lists:foreach(fun(M) -> takeover_mib(M) end, MibsToLoad)
    end.

loaded_mibs(AgentInfo) ->
    {value, {_, MibInfo}}    = key1search(mib_server,  AgentInfo),
    {value, {_, LoadedMibs}} = key1search(loaded_mibs, MibInfo),
    LoadedMibs.

mibs_to_load(OtherMibs, OwnMibs) ->
    [{N, S, F} || {N, S, F} <- OtherMibs, not lists:member(N, OwnMibs)].

takeover_mib({'STANDARD-MIB', _Symbolic, _FileName}) ->
    ok;
takeover_mib({'SNMPv2-MIB', _Symbolic, _FileName}) ->
    ok;
takeover_mib({_MibName, _Symbolic, FileName}) ->
    case snmpa:load_mibs(snmp_master_agent, [FileName]) of
        ok -> 
            ok;
        {error, R} ->
            error_msg("could not reload mib ~p: ~p", [FileName, R])
    end.


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

-doc """
Starts a supervisor for the SNMP agent system without a master agent. The
supervisor starts all involved SNMP agent processes, but no agent processes.
Sub-agents should be started by calling
[`start_sub_agent/3`](`start_sub_agent/3`).

`db_dir` is mandatory.

See [configuration parameters](snmp_config.md#configuration_params) for a
description of the options.
""".
-spec start_sub_sup(Opts) -> {ok, Pid} | {error, Reason} when
      Opts   :: [Opt],
      Opt    :: {db_dir, snmp:dir()} | {atom(), term()},
      Pid    :: pid(),
      Reason :: {already_started, Pid} | term().

start_sub_sup(Opts) ->
    ?d("start_sub_sup -> entry with"
      "~n   Opts: ~p", [Opts]),
    (catch do_start_sub_sup(Opts)).

do_start_sub_sup(Opts) ->
    verify_mandatory([db_dir], Opts),
    ?d("do_start_sub_sup -> start (sub) supervisor",[]),
    supervisor:start_link({local, ?SERVER}, ?MODULE, [sub, Opts]).  

-doc """
Starts a supervisor for the SNMP agent system. The supervisor starts all
involved SNMP processes, including the master agent. Sub-agents should be
started by calling `start_subagent/3`.

`db_dir` is mandatory.

`dir` in config is mandatory.

See [snmp config](snmp_config.md) for a description of the options.
""".
-spec start_master_sup(Opts) -> {ok, Pid} | {error, Reason} when
      Opts     :: [Opt],
      Opt      :: {db_dir, string()} | {config, ConfOpts} | {atom(), term()},
      ConfOpts :: [ConfOpt],
      ConfOpt  :: {dir, string()} | {atom(), term()},
      Pid      :: pid(),
      Reason   :: {already_started, Pid} | term().

start_master_sup(Opts) ->
    (catch do_start_master_sup(Opts)).

do_start_master_sup(Opts) ->
    verify_mandatory([db_dir], Opts),
    case supervisor:start_link({local, ?SERVER}, ?MODULE, [master, Opts]) of
        {ok, Pid} = OK ->
            %% <HACKETI-HACK-HACK>
            Key = master_agent_child_spec,
            MasterAgentSpec = lookup(Key),
            case snmpa_agent_sup:start_master_agent(MasterAgentSpec) of
                {ok, MPid} when is_pid(MPid) ->
                    erase(Key),
                    OK;
                {error, {Reason, _ChildSpec}} ->
                    stop(Pid, 0),
                    {error, Reason}
            end;
            %% </HACKETI-HACK-HACK>
        Else ->
            Else
    end.

verify_mandatory([], _) ->
    ok;
verify_mandatory([Key|Keys], Opts) ->
    case lists:keymember(Key, 1, Opts) of
	true ->
	    verify_mandatory(Keys, Opts);
	false ->
	    throw({error, {missing_mandatory_option, Key}})
    end.


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

-doc """
Starts a sub-agent on the node where the function is called. The
`snmpa_supervisor` must be running.

If the supervisor is not running, the function fails with the reason `badarg`.
""".
-spec start_sub_agent(ParentAgent, Subtree, Mibs) ->
          {ok, Pid} | {error, Reason} when
      ParentAgent :: pid(),
      Subtree     :: snmp:oid(),
      Mibs        :: [MibName],
      MibName     :: string(),
      Pid         :: pid(),
      Reason      :: term().

start_sub_agent(ParentAgent, Subtree, Mibs) 
  when is_pid(ParentAgent) andalso is_list(Mibs) ->
    ?d("start_sub_agent -> entry with"
      "~n   ParentAgent: ~p"
      "~n   Subtree:     ~p"
      "~n   Mibs:        ~p", [ParentAgent, Subtree, Mibs]),
    snmpa_agent_sup:start_subagent(ParentAgent, Subtree, Mibs).

-doc """
Stops the sub-agent on the node where the function is called. The
`snmpa_supervisor` must be running.

If the supervisor is not running, the function fails with the reason `badarg`.
""".
-spec stop_sub_agent(SubAgentPid) -> ok | no_such_child when
      SubAgentPid :: pid().

stop_sub_agent(SubAgentPid) ->
    snmpa_agent_sup:stop_subagent(SubAgentPid).


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

-doc false.
init([AgentType, Opts]) ->
    ?d("init -> entry with"
      "~n   AgentType: ~p"
      "~n   Opts:      ~p", [AgentType, Opts]),

    put(sname,     asup),
    put(verbosity, get_verbosity(Opts)),

    ?vlog("starting",[]),

    ?vdebug("create agent table",[]),
    ets:new(snmp_agent_table, [set, public, named_table]),

    ?vdebug("create community cache",[]),
    ets:new(snmp_community_cache, [bag, public, named_table]),

    %% Get restart type for the agent
    Restart = get_opt(restart_type, Opts, permanent),
    ?vdebug("agent restart type: ~w", [Restart]),

    %% -- Agent type --
    store(agent_type, AgentType),

    %% -- Prio --
    Prio = get_opt(priority, Opts, normal),
    ?vdebug("[agent table] store priority: ~p",[Prio]),
    store(priority, Prio),

    %% -- Versions -- 
    Vsns = get_opt(versions, Opts, [v1,v2,v3]),
    ?vdebug("[agent table] store versions: ~p",[Vsns]),
    store(versions, Vsns),

    %% -- Max number of VBs in a Get-BULK response --
    GbMaxVBs = get_gb_max_vbs(Opts),
    ?vdebug("[agent table] Get-BULK max VBs: ~p", [GbMaxVBs]),
    store(gb_max_vbs, GbMaxVBs),

    %% -- DB-directory --
    DbDir = get_opt(db_dir, Opts),
    ?vdebug("[agent table] store db_dir: ~n   ~p",[DbDir]),
    store(db_dir, filename:join([DbDir])),

    DbInitError = get_opt(db_init_error, Opts, terminate),
    ?vdebug("[agent table] store db_init_error: ~n   ~p",[DbInitError]),
    store(db_init_error, DbInitError),

    %% -- Error report module --
    ErrorReportMod = get_opt(error_report_mod, Opts, snmpa_error_logger),
    ?vdebug("[agent table] store error report module: ~w",[ErrorReportMod]),
    store(error_report_mod, ErrorReportMod),

    %% -- mib storage --
    %% MibStorage has only one mandatory part: module
    %% Everything else is module dependent and therefore 
    %% put in a special option: options
    MibStorage = 
	case get_opt(mib_storage, Opts, [{module, snmpa_mib_storage_ets}]) of

	    %% --- ETS wrappers ---

	    ets ->
		[{module, snmpa_mib_storage_ets}];
	    {ets, default} ->
		[{module,  snmpa_mib_storage_ets}, 
		 {options, [{dir,    filename:join([DbDir])}, 
			    {action, keep}]}];
	    {ets, Dir} when is_list(Dir) ->
		[{module,  snmpa_mib_storage_ets}, 
		 {options, [{dir,    filename:join([Dir])}, 
			    {action, keep}]}];
	    {ets, default, Action} when ((Action =:= keep) orelse 
					 (Action =:= clear)) ->
		[{module,  snmpa_mib_storage_ets}, 
		 {options, [{dir,    filename:join([DbDir])}, 
			    {action, Action}]}];
	    {ets, Dir, Action} when is_list(Dir) andalso 
				    ((Action =:= keep) orelse 
				     (Action =:= clear)) ->
		[{module,  snmpa_mib_storage_ets}, 
		 {options, [{dir,    filename:join([Dir])}, 
			    {action, Action}]}];

	    %% --- DETS wrappers ---

	    dets ->
		[{module,  snmpa_mib_storage_dets}, 
		 {options, [{dir,    filename:join([DbDir])}, 
			    {action, keep}]}];
	    {dets, default} ->
		[{module,  snmpa_mib_storage_dets}, 
		 {options, [{dir,    filename:join([DbDir])}, 
			    {action, keep}]}];
	    {dets, default, Action} when ((Action =:= keep) orelse 
					  (Action =:= clear)) ->
		[{module,  snmpa_mib_storage_dets}, 
		 {options, [{dir,    filename:join([DbDir])}, 
			    {action, Action}]}];
	    {dets, Dir, Action} when is_list(Dir) andalso 
				     ((Action =:= keep) orelse 
				      (Action =:= clear)) ->
		[{module,  snmpa_mib_storage_dets}, 
		 {options, [{dir,    filename:join([Dir])}, 
			    {action, Action}]}];

	    %% --- Mnesia wrappers ---

	    mnesia ->
		[{module,  snmpa_mib_storage_mnesia}, 
		 {options, [{nodes,  erlang:nodes()}, 
			    {action, keep}]}];
	    {mnesia, Nodes0} ->
		Nodes = 
		    if
			Nodes0 =:= visible ->
			    erlang:nodes(visible);
			Nodes0 =:= connected ->
			    erlang:nodes(connected);
			Nodes0 =:= [] ->
			    [node()];
			true ->
			    Nodes0
		    end,
		[{module, snmpa_mib_storage_mnesia}, 
		 {options, [{nodes,  Nodes}, 
			    {action, keep}]}];
	    {mnesia, Nodes0, Action} when ((Action =:= keep) orelse 
					   (Action =:= clear)) ->
		Nodes = 
		    if
			Nodes0 =:= visible ->
			    erlang:nodes(visible);
			Nodes0 =:= connected ->
			    erlang:nodes(connected);
			Nodes0 =:= [] ->
			    [node()];
			true ->
			    Nodes0
		    end,
		[{module,  snmpa_mib_storage_mnesia}, 
		 {options, [{nodes,  Nodes}, 
			    {action, Action}]}];

	    Other when is_list(Other) ->
		Other
	end,

    ?vdebug("[agent table] store mib storage: ~w", [MibStorage]),
    store(mib_storage, MibStorage),

    %% -- Agent mib storage --
    AgentMibStorage = get_opt(agent_mib_storage, Opts, persistent),
    %% ?vdebug("[agent table] store agent mib storage: ~w",[AgentMibStorage]),
    store(agent_mib_storage, AgentMibStorage),

    %% -- System start time --
    ?vdebug("[agent table] store system start time",[]),
    store(system_start_time, snmp_misc:now(cs)),

    %% -- Symbolic store options --
    SsOpts = get_opt(symbolic_store, Opts, []),
    ?vdebug("[agent table] store symbolic store options: ~w",[SsOpts]),
    store(symbolic_store, SsOpts),

    %% -- Local DB options --
    LdbOpts = get_opt(local_db, Opts, []),
    ?vdebug("[agent table] store local db options: ~w",[LdbOpts]),
    store(local_db, LdbOpts),

    %% -- Target cache options --
    TargetCacheOpts = get_opt(target_cache, Opts, []),
    ?vdebug("[agent table] store target cache options: ~w",[TargetCacheOpts]),
    store(target_cache, TargetCacheOpts),

    %% -- Specs --
    SupFlags = {one_for_all, 0, 3600},

    MiscSupSpec = 
	sup_spec(snmpa_misc_sup, [], Restart, infinity),

    SymStoreOpts = [{mib_storage, MibStorage} | SsOpts], 
    SymStoreArgs = [Prio, SymStoreOpts],
    SymStoreSpec = 
	worker_spec(snmpa_symbolic_store, SymStoreArgs, Restart, 2000),

    LdbArgs = [Prio, DbDir, DbInitError, LdbOpts],
    LocalDbSpec = 
	worker_spec(snmpa_local_db, LdbArgs, Restart, 5000),

    ?vdebug("init VACM",[]),
    snmpa_vacm:init(DbDir, DbInitError),

    TargetCacheArgs = [Prio, TargetCacheOpts],
    TargetCacheSpec = 
	worker_spec(snmpa_target_cache, TargetCacheArgs, transient, 2000, []),

    Rest =
	case AgentType of
	    master ->
		% If we're starting the master, we must also
		% configure the tables.

		%% -- Config --
		ConfOpts = get_opt(config, Opts, []),
		?vdebug("[agent table] store config options: ~p", [ConfOpts]),
		store(config, ConfOpts),

		ConfigArgs = [Vsns, ConfOpts],
		ConfigSpec = 
		    worker_spec(config, ?MODULE, config, ConfigArgs, 
				transient, 2000, [?MODULE]),

		%% -- Agent verbosity --
		AgentVerb  = get_opt(agent_verbosity, Opts, silence),

		%% -- Discovery processing --
		DiscoOpts = get_opt(discovery, Opts, []),
		?vdebug("[agent table] store discovery options: ~p", [DiscoOpts]),
		store(discovery, DiscoOpts),

		%% -- Mibs --
		Mibs = get_mibs(get_opt(mibs, Opts, []), Vsns),
		?vdebug("[agent table] store mibs: ~n   ~p",[Mibs]),
		store(mibs, Mibs),

		Ref  = make_ref(),

		%% -- Get module --
		GetModule  = get_opt(get_mechanism, Opts, snmpa_get),
		?vdebug("[agent table] store get-module: ~p", [GetModule]),
		store(get_mechanism, GetModule),

		%% -- Set module --
		SetModule  = get_opt(set_mechanism, Opts, snmpa_set),
		?vdebug("[agent table] store set-module: ~p",[SetModule]),
		store(set_mechanism, SetModule),

		%% -- Authentication service --
		AuthModule = get_opt(authentication_service, Opts, snmpa_acm),
		?vdebug("[agent table] store authentication service: ~w",
			[AuthModule]),
		store(authentication_service, AuthModule),

		%% -- Multi-threaded --
		MultiT = get_opt(multi_threaded, Opts, false),
		?vdebug("[agent table] store multi-threaded: ~p", [MultiT]),
		store(multi_threaded, MultiT),

		%% -- Audit trail log --
		case get_opt(audit_trail_log, Opts, not_found) of
		    not_found ->
			?vdebug("[agent table] no audit trail log", []),
			ok;
		    AtlOpts ->
			?vdebug("[agent table] "
				"store audit trail log options: ~p",
				[AtlOpts]),
			store(audit_trail_log, AtlOpts),
			ok
		end,

		%% -- MIB server --
		MibsOpts = get_opt(mib_server, Opts, []),
		?vdebug("[agent table] store mib-server options: "
			"~n   ~p", [MibsOpts]),
		store(mib_server, MibsOpts),

		%% -- Network interface --
		NiOpts = get_opt(net_if, Opts, []),
		?vdebug("[agent table] store net-if options: "
			"~n   ~p", [NiOpts]),
		store(net_if, NiOpts),

		%% -- Note store --
		NsOpts = get_opt(note_store, Opts, []),
		?vdebug("[agent table] store note-store options: "
			"~n   ~p",[NsOpts]),
		store(note_store, NsOpts),

		AgentOpts = 
		    [{verbosity,              AgentVerb},
		     {mibs,                   Mibs},
		     {mib_storage,            MibStorage},
		     {get_mechanism,          GetModule},
		     {set_mechanism,          SetModule},
		     {authentication_service, AuthModule},
		     {multi_threaded,         MultiT},
		     {versions,               Vsns},
		     {net_if,                 NiOpts},
		     {mib_server,             MibsOpts},
		     {note_store,             NsOpts},
		     {gb_max_vbs,             GbMaxVBs} |
		     get_opt(master_agent_options, Opts, [])],
		     
		AgentSpec =
		    worker_spec(snmpa_agent, 
				[Prio, snmp_master_agent, none, Ref, AgentOpts],
				Restart, 15000),
                %% <HACKETI-HACK-HACK>
                %% The point is to make start failure more quiet
                %% Often the failure happens in the master agent,
                %% so we move the start of that out of this function
                %% and into the 'do_start_master_sup' function.
                %% At some point we should rewrite this. Maybe start all
                %% children the same way (explicitly).
                store(master_agent_child_spec, AgentSpec),
		AgentSupSpec = 
		    sup_spec(snmpa_agent_sup, [], 
			     Restart, infinity), 
                %% </HACKETI-HACK-HACK>
		[ConfigSpec, AgentSupSpec];
	    _ ->
		?vdebug("[sub agent] spec for the agent supervisor",[]),
		AgentSupSpec = 
		    sup_spec(snmpa_agent_sup, [], Restart, infinity), 
		[AgentSupSpec]
	end,
    ?vdebug("init done",[]),
    {ok, {SupFlags, [MiscSupSpec, SymStoreSpec, LocalDbSpec, TargetCacheSpec | 
		     Rest]}}.


store(Key, Value) ->
    ets:insert(snmp_agent_table, {Key, Value}).

lookup(Key) ->
    [{Key, Value}] = ets:lookup(snmp_agent_table, Key),
    Value.

erase(Key) ->
    ets:delete(snmp_agent_table, Key).

get_mibs(Mibs, Vsns) ->
    MibDir = filename:join(code:priv_dir(snmp), "mibs"),
    StdMib = 
	case (lists:member(v2, Vsns) or lists:member(v3, Vsns)) of
	    true  -> filename:join([MibDir, "SNMPv2-MIB"]);
	    false -> filename:join([MibDir, "STANDARD-MIB"])
	end,
    ?vdebug("add standard and v2 mibs",[]),
    NMibs = add_mib(StdMib, Mibs, ["SNMPv2-MIB", "STANDARD-MIB"]),
    case lists:member(v3, Vsns) of
	true -> 
	    ?vdebug("add v3 mibs",[]),
	    add_v3_mibs(MibDir, NMibs);
	false -> 
	    NMibs
    end.
    
add_v3_mibs(MibDir, Mibs) ->
    NMibs = add_mib(filename:join(MibDir, "SNMP-FRAMEWORK-MIB"),
		    Mibs, ["SNMP-FRAMEWORK-MIB"]),
    add_mib(filename:join(MibDir, "SNMP-MPD-MIB"),
	    NMibs, ["SNMP-MPD-MIB"]).

add_mib(DefaultMib, [], _BaseNames) -> [DefaultMib];
add_mib(DefaultMib, [Mib | T], BaseNames) ->
    case lists:member(filename:basename(Mib), BaseNames) of
	true  -> [Mib | T]; % The user defined his own version of the mib
	false -> [Mib | add_mib(DefaultMib, T, BaseNames)]
    end.


-doc false.
config(Vsns, Opts) ->
    ?d("config -> entry with"
      "~n   Vsns. ~p"
      "~n   Opts. ~p", [Vsns, Opts]),
    Verbosity = get_opt(verbosity, Opts, silence),
    put(sname, conf),
    put(verbosity, Verbosity),

    ?vlog("starting", []),

    ConfDir   = get_opt(dir, Opts),
    ForceLoad = get_opt(force_load, Opts, false),

    case (catch conf(ConfDir, Vsns, ForceLoad)) of
	ok -> 
	    ?d("config -> done", []),
	    ignore;
	{'EXIT', Reason} -> 
	    ?vlog("configure failed (exit) with reason: ~p",[Reason]),
	    {error, {config_error, Reason}}
    end.

conf(Dir, Vsns, true) ->
    conf1(Dir, Vsns, reconfigure);
conf(Dir, Vsns, false) ->
    conf1(Dir, Vsns, configure).

conf1(Dir, Vsns, Func) ->
    ?vlog("start ~w",[Func]),

    ?vdebug("~w snmp_standard_mib",[Func]),
    snmp_standard_mib:Func(Dir),
    ?vdebug("init snmp_framework_mib",[]),
    snmp_framework_mib:init(),
    ?vdebug("configure snmp_framework_mib",[]),
    snmp_framework_mib:configure(Dir),
    ?vdebug("~w snmp_target_mib",[Func]),
    snmp_target_mib:Func(Dir),
    ?vdebug("~w snmp_notification_mib",[Func]),
    snmp_notification_mib:Func(Dir),
    ?vdebug("~w snmp_view_based_acm_mib",[Func]),
    snmp_view_based_acm_mib:Func(Dir),
    case lists:member(v1, Vsns) or lists:member(v2, Vsns) of
	true ->
	    ?vdebug("we need to handle v1 and/or v2 =>~n"
		    "   ~w snmp_community_mib",[Func]),
	    snmp_community_mib:Func(Dir);
	false ->
	    ?vdebug("no need to handle v1 or v2",[]),
	    ok
    end,
    case lists:member(v3, Vsns) of
	true ->
	    ?vdebug("we need to handle v3 =>~n"
		    "   ~w snmp_user_based_sm_mib",[Func]),
	    snmp_user_based_sm_mib:Func(Dir);
	false ->
	    ?vdebug("no need to handle v3",[]),
	    ok
    end,
    ?vdebug("conf done",[]),
    ok.


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

sup_spec(Name, Args, Type, Time) ->
    ?d("sup_spec -> entry with"
       "~n   Name: ~p"
       "~n   Args: ~p"
       "~n   Type: ~p"
       "~n   Time: ~p", [Name, Args, Type, Time]),
    {Name, 
     {Name, start_link, Args}, 
     Type, Time, supervisor, [Name, supervisor]}.

worker_spec(Name, Args, Type, Time) ->
    worker_spec(Name, Name, Args, Type, Time, []).

worker_spec(Name, Args, Type, Time, Modules) ->
    worker_spec(Name, Name, Args, Type, Time, Modules).

worker_spec(Name, Mod, Args, Type, Time, Modules) ->
    worker_spec(Name, Mod, start_link, Args, Type, Time, Modules).

worker_spec(Name, Mod, Func, Args, Type, Time, Modules) 
  when is_atom(Name) andalso 
       is_atom(Mod) andalso 
       is_atom(Func) andalso 
       is_list(Args) andalso 
       is_atom(Type) andalso 
       is_list(Modules) ->
    ?d("worker_spec -> entry with"
       "~n   Name:    ~p"
       "~n   Mod:     ~p"
       "~n   Func:    ~p"
       "~n   Args:    ~p"
       "~n   Type:    ~p"
       "~n   Time:    ~p"
       "~n   Modules: ~p", [Name, Mod, Func, Args, Type, Time, Modules]),
    {Name, 
     {Mod, Func, Args}, 
     Type, Time, worker, [Name] ++ Modules}.


get_verbosity(Opts) -> 
    SupOpts = get_opt(supervisor, Opts, []),
    get_opt(verbosity, SupOpts, silence).


get_agent_type(Opts) ->
    get_opt(agent_type, Opts, master).


%% We validate this option! This should really be done for all
%% options, but it is beyond the scope of this ticket, OTP-9700.

get_gb_max_vbs(Opts) ->
    Validate = 
	fun(GbMaxVBs) 
	   when ((is_integer(GbMaxVBs) andalso (GbMaxVBs > 0)) orelse
		 (GbMaxVBs =:= infinity)) ->
		ok;
	   (_) ->
		error
	end,
    get_option(gb_max_vbs, ?DEFAULT_GB_MAX_VBS, Validate, Opts).

get_option(Key, Default, Validate, Opts) 
  when is_list(Opts) andalso is_function(Validate) ->
    Value = get_opt(Key, Opts, Default),
    case Validate(Value) of
	ok ->
	    Value;
	error ->
	    exit({bad_option, Key, Value})
    end.
    

get_opt(Key, Opts) ->
    snmp_misc:get_option(Key, Opts).

get_opt(Key, Opts, Def) ->
    snmp_misc:get_option(Key, Opts, Def).

key1search(Key, List) ->
    lists:keysearch(Key, 1, List).


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

error_msg(F, A) ->
    ?snmpa_error(F, A).

% i(F) ->
%     i(F, []).

% i(F, A) ->
%     io:format("~p:~p: " ++ F ++ "~n", [node(),?MODULE|A]).
