aboutsummaryrefslogtreecommitdiffstats
path: root/lib/snmp/src/agent/snmpa_mib.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/snmp/src/agent/snmpa_mib.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/snmp/src/agent/snmpa_mib.erl')
-rw-r--r--lib/snmp/src/agent/snmpa_mib.erl892
1 files changed, 892 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/snmpa_mib.erl b/lib/snmp/src/agent/snmpa_mib.erl
new file mode 100644
index 0000000000..370989d0be
--- /dev/null
+++ b/lib/snmp/src/agent/snmpa_mib.erl
@@ -0,0 +1,892 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-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%
+%%
+-module(snmpa_mib).
+
+%% c(snmpa_mib).
+
+%%%-----------------------------------------------------------------
+%%% This module implements a MIB server.
+%%%-----------------------------------------------------------------
+
+%% External exports
+-export([start_link/3, stop/1,
+ lookup/2, next/3, which_mib/2, which_mibs/1, whereis_mib/2,
+ load_mibs/2, unload_mibs/2,
+ register_subagent/3, unregister_subagent/2, info/1, info/2,
+ verbosity/2, dump/1, dump/2,
+ backup/2,
+ invalidate_cache/1,
+ gc_cache/1, gc_cache/2, gc_cache/3,
+ enable_cache/1, disable_cache/1,
+ enable_cache_autogc/1, disable_cache_autogc/1,
+ update_cache_gclimit/2,
+ update_cache_age/2,
+ which_cache_size/1
+ ]).
+
+%% Internal exports
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-include_lib("kernel/include/file.hrl").
+-include("snmpa_internal.hrl").
+-include("snmp_types.hrl").
+-include("snmp_verbosity.hrl").
+-include("snmp_debug.hrl").
+
+
+-define(SERVER, ?MODULE).
+-define(NO_CACHE, no_mibs_cache).
+-define(DEFAULT_CACHE_USAGE, true).
+-define(CACHE_GC_TICKTIME, timer:minutes(1)).
+-define(DEFAULT_CACHE_AUTOGC, false).
+-define(DEFAULT_CACHE_GCLIMIT, 100).
+-define(DEFAULT_CACHE_AGE, timer:minutes(10)).
+-define(CACHE_GC_TRIGGER, cache_gc_trigger).
+
+
+
+-ifdef(snmp_debug).
+-define(GS_START_LINK(Prio, Mibs, Opts),
+ gen_server:start_link(?MODULE, [Prio, Mibs, Opts], [{debug,[trace]}])).
+-else.
+-define(GS_START_LINK(Prio, Mibs, Opts),
+ gen_server:start_link(?MODULE, [Prio, Mibs, Opts], [])).
+-endif.
+
+
+%%-----------------------------------------------------------------
+%% Internal Data structures
+%%
+%% State
+%% data - is the MIB data (defined in snmpa_mib_data)
+%% meo - mib entry override
+%% teo - trap (notification) entry override
+%%-----------------------------------------------------------------
+-record(state, {data, meo, teo, backup,
+ cache, cache_tmr, cache_autogc, cache_gclimit, cache_age}).
+
+
+
+%%-----------------------------------------------------------------
+%% Func: start_link/1
+%% Args: Mibs is a list of mibnames.
+%% Prio is priority of mib-server
+%% Opts is a list of options
+%% Purpose: starts the mib server synchronized
+%% Returns: {ok, Pid} | {error, Reason}
+%%-----------------------------------------------------------------
+start_link(Prio, Mibs, Opts) ->
+ ?d("start_link -> entry with"
+ "~n Prio: ~p"
+ "~n Mibs: ~p"
+ "~n Opts: ~p", [Prio, Mibs, Opts]),
+ ?GS_START_LINK(Prio, Mibs, Opts).
+
+verbosity(MibServer, Verbosity) ->
+ cast(MibServer, {verbosity,Verbosity}).
+
+stop(MibServer) ->
+ call(MibServer, stop).
+
+invalidate_cache(MibServer) ->
+ call(MibServer, invalidate_cache).
+
+gc_cache(MibServer) ->
+ call(MibServer, gc_cache).
+
+gc_cache(MibServer, Age) ->
+ call(MibServer, {gc_cache, Age}).
+
+gc_cache(MibServer, Age, GcLimit) ->
+ call(MibServer, {gc_cache, Age, GcLimit}).
+
+which_cache_size(MibServer) ->
+ call(MibServer, cache_size).
+
+enable_cache(MibServer) ->
+ update_cache_opts(MibServer, cache, true).
+disable_cache(MibServer) ->
+ update_cache_opts(MibServer, cache, false).
+
+enable_cache_autogc(MibServer) ->
+ update_cache_opts(MibServer, autogc, true).
+disable_cache_autogc(MibServer) ->
+ update_cache_opts(MibServer, autogc, false).
+
+update_cache_gclimit(MibServer, GcLimit)
+ when ((is_integer(GcLimit) andalso (GcLimit > 0)) orelse
+ (GcLimit =:= infinity)) ->
+ update_cache_opts(MibServer, gclimit, GcLimit);
+update_cache_gclimit(_, BadLimit) ->
+ {error, {bad_gclimit, BadLimit}}.
+
+update_cache_age(MibServer, Age)
+ when is_integer(Age) andalso (Age > 0) ->
+ update_cache_opts(MibServer, age, Age);
+update_cache_age(_, BadAge) ->
+ {error, {bad_age, BadAge}}.
+
+update_cache_opts(MibServer, Key, Value) ->
+ call(MibServer, {update_cache_opts, Key, Value}).
+
+
+%%-----------------------------------------------------------------
+%% Func: lookup/2
+%% Purpose: Finds the mib entry corresponding to the Oid. If it is a
+%% variable, the Oid must be <Oid for var>.0 and if it is
+%% a table, Oid must be <table>.<entry>.<col>.<any>
+%% Returns: {variable, MibEntry} |
+%% {table_column, MibEntry, TableEntryOid} |
+%% {subagent, SubAgentPid} |
+%% false
+%%-----------------------------------------------------------------
+lookup(MibServer, Oid) ->
+ call(MibServer, {lookup, Oid}).
+
+which_mib(MibServer, Oid) ->
+ call(MibServer, {which_mib, Oid}).
+
+
+%%-----------------------------------------------------------------
+%% Func: next/3
+%% Purpose: Finds the lexicographically next oid.
+%% Returns: {subagent, SubAgentPid, SANextOid} |
+%% endOfMibView |
+%% genErr |
+%% NextOid
+%% The SANextOid is used by the agent if the SubAgent returns
+%% endOfMib, in a new call to next/2.
+%%-----------------------------------------------------------------
+next(MibServer, Oid, MibView) ->
+ call(MibServer, {next, Oid, MibView}).
+
+
+%%----------------------------------------------------------------------
+%% Purpose: Loads mibs into the mib process.
+%% Args: Mibs is a list of Filenames (compiled mibs).
+%% Returns: ok | {error, Reason}
+%%----------------------------------------------------------------------
+load_mibs(MibServer, Mibs) ->
+ call(MibServer, {load_mibs, Mibs}).
+
+
+%%----------------------------------------------------------------------
+%% Purpose: Loads mibs into the mib process.
+%% Args: Mibs is a list of Filenames (compiled mibs).
+%% Returns: ok | {error, Reason}
+%%----------------------------------------------------------------------
+unload_mibs(MibServer, Mibs) ->
+ call(MibServer, {unload_mibs, Mibs}).
+
+
+%%----------------------------------------------------------------------
+%% Purpose: Simple management functions
+%% Args: Mib is the name of the mib (atom)
+%% Returns: ok | {error, Reason}
+%%----------------------------------------------------------------------
+which_mibs(MibServer) ->
+ call(MibServer, which_mibs).
+
+whereis_mib(MibServer, Mib) ->
+ call(MibServer, {whereis_mib, Mib}).
+
+
+%%----------------------------------------------------------------------
+%% Registers subagent with pid Pid under subtree Oid.
+%%----------------------------------------------------------------------
+register_subagent(MibServer, Oid, Pid) ->
+ call(MibServer, {register_subagent, Oid, Pid}).
+
+unregister_subagent(MibServer, OidOrPid) ->
+ call(MibServer, {unregister_subagent, OidOrPid}).
+
+info(MibServer) ->
+ call(MibServer, info).
+
+info(MibServer, Type) ->
+ call(MibServer, {info, Type}).
+
+dump(MibServer) ->
+ call(MibServer, dump).
+
+dump(MibServer, File) when is_list(File) ->
+ call(MibServer, {dump, File}).
+
+backup(MibServer, BackupDir) when is_list(BackupDir) ->
+ call(MibServer, {backup, BackupDir}).
+
+
+%%--------------------------------------------------
+%% The standard MIB 'stdmib' must be present in the
+%% current directory.
+%%--------------------------------------------------
+init([Prio, Mibs, Opts]) ->
+ ?d("init -> entry with"
+ "~n Prio: ~p"
+ "~n Mibs: ~p"
+ "~n Opts: ~p", [Prio, Mibs, Opts]),
+ case (catch do_init(Prio, Mibs, Opts)) of
+ {ok, State} ->
+ {ok, State};
+ {error, Reason} ->
+ config_err("failed starting mib-server: ~n~p", [Reason]),
+ {stop, {error, Reason}};
+ Error ->
+ config_err("failed starting mib-server: ~n~p", [Error]),
+ {stop, {error, Error}}
+ end.
+
+do_init(Prio, Mibs, Opts) ->
+ process_flag(priority, Prio),
+ process_flag(trap_exit, true),
+ put(sname,ms),
+ put(verbosity, ?vvalidate(get_verbosity(Opts))),
+ ?vlog("starting",[]),
+
+ %% Extract the cache options
+ {Cache, CacheOptions} =
+ case get_opt(cache, Opts, ?DEFAULT_CACHE_USAGE) of
+ true ->
+ {new_cache(), []};
+ false ->
+ {?NO_CACHE, []};
+ CacheOpts when is_list(CacheOpts) ->
+ {new_cache(), CacheOpts};
+ Bad ->
+ throw({error, {bad_option, {cache, Bad}}})
+ end,
+ CacheAutoGC = get_cacheopt_autogc(Cache, CacheOptions),
+ CacheGcLimit = get_cacheopt_gclimit(Cache, CacheOptions),
+ CacheAge = get_cacheopt_age(Cache, CacheOptions),
+
+ %% Maybe start the cache gc timer
+ CacheGcTimer =
+ if
+ ((Cache =/= ?NO_CACHE) andalso
+ (CacheAutoGC =:= true)) ->
+ start_cache_gc_timer();
+ true ->
+ undefined
+ end,
+
+ MeOverride = get_me_override(Opts),
+ TeOverride = get_te_override(Opts),
+ MibStorage = get_mib_storage(Opts),
+ Data = snmpa_mib_data:new(MibStorage),
+ ?vtrace("init -> mib data created",[]),
+ case (catch mib_operations(load_mib, Mibs, Data,
+ MeOverride, TeOverride, true)) of
+ {ok, Data2} ->
+ ?vdebug("started",[]),
+ snmpa_mib_data:sync(Data2),
+ ?vdebug("mib data synced",[]),
+ {ok, #state{data = Data2,
+ teo = TeOverride,
+ meo = MeOverride,
+ cache = Cache,
+ cache_tmr = CacheGcTimer,
+ cache_autogc = CacheAutoGC,
+ cache_gclimit = CacheGcLimit,
+ cache_age = CacheAge}};
+ {'aborted at', Mib, _NewData, Reason} ->
+ ?vinfo("failed loading mib ~p: ~p",[Mib,Reason]),
+ {error, {Mib, Reason}}
+ end.
+
+
+%%----------------------------------------------------------------------
+%% Returns: {ok, NewMibData} | {'aborted at', Mib, NewData, Reason}
+%% Args: Operation is load_mib | unload_mib.
+%%----------------------------------------------------------------------
+mib_operations(Operation, Mibs, Data, MeOverride, TeOverride) ->
+ mib_operations(Operation, Mibs, Data, MeOverride, TeOverride, false).
+
+
+mib_operations(_Operation, [], Data, _MeOverride, _TeOverride, _Force) ->
+ {ok, Data};
+mib_operations(Operation, [Mib|Mibs], Data0, MeOverride, TeOverride, Force) ->
+ ?vtrace("mib operations ~p on"
+ "~n Mibs: ~p"
+ "~n with "
+ "~n MeOverride: ~p"
+ "~n TeOverride: ~p"
+ "~n Force: ~p", [Operation,Mibs,MeOverride,TeOverride,Force]),
+ Data = mib_operation(Operation, Mib, Data0, MeOverride, TeOverride, Force),
+ mib_operations(Operation, Mibs, Data, MeOverride, TeOverride, Force).
+
+mib_operation(Operation, Mib, Data0, MeOverride, TeOverride, Force)
+ when is_list(Mib) ->
+ ?vtrace("mib operation on mib ~p", [Mib]),
+ case apply(snmpa_mib_data, Operation, [Data0,Mib,MeOverride,TeOverride]) of
+ {error, 'already loaded'} when (Operation =:= load_mib) andalso
+ (Force =:= true) ->
+ ?vlog("ignore mib ~p -> already loaded", [Mib]),
+ Data0;
+ {error, 'not loaded'} when (Operation =:= unload_mib) andalso
+ (Force =:= true) ->
+ ?vlog("ignore mib ~p -> not loaded", [Mib]),
+ Data0;
+ {error, Reason} ->
+ ?vlog("mib_operation -> failed ~p of mib ~p for ~p",
+ [Operation, Mib, Reason]),
+ throw({'aborted at', Mib, Data0, Reason});
+ {ok, Data} ->
+ Data
+ end;
+mib_operation(_Op, Mib, Data, _MeOverride, _TeOverride, _Force) ->
+ throw({'aborted at', Mib, Data, bad_mibname}).
+
+
+%%-----------------------------------------------------------------
+%% Handle messages
+%%-----------------------------------------------------------------
+
+handle_call(invalidate_cache, _From, #state{cache = Cache} = State) ->
+ ?vlog("invalidate_cache", []),
+ NewCache = maybe_invalidate_cache(Cache),
+ {reply, ignore, State#state{cache = NewCache}};
+
+handle_call(cache_size, _From, #state{cache = Cache} = State) ->
+ ?vlog("cache_size", []),
+ Reply = maybe_cache_size(Cache),
+ {reply, Reply, State};
+
+handle_call(gc_cache, _From,
+ #state{cache = Cache,
+ cache_age = Age,
+ cache_gclimit = GcLimit} = State) ->
+ ?vlog("gc_cache", []),
+ Result = maybe_gc_cache(Cache, Age, GcLimit),
+ {reply, Result, State};
+
+handle_call({gc_cache, Age}, _From,
+ #state{cache = Cache,
+ cache_gclimit = GcLimit} = State) ->
+ ?vlog("gc_cache with Age = ~p", [Age]),
+ Result = maybe_gc_cache(Cache, Age, GcLimit),
+ {reply, Result, State};
+
+handle_call({gc_cache, Age, GcLimit}, _From,
+ #state{cache = Cache} = State) ->
+ ?vlog("gc_cache with Age = ~p and GcLimut = ~p", [Age, GcLimit]),
+ Result = maybe_gc_cache(Cache, Age, GcLimit),
+ {reply, Result, State};
+
+handle_call({update_cache_opts, Key, Value}, _From, State) ->
+ ?vlog("update_cache_opts: ~p -> ~p", [Key, Value]),
+ {Result, NewState} = handle_update_cache_opts(Key, Value, State),
+ {reply, Result, NewState};
+
+handle_call({lookup, Oid}, _From,
+ #state{data = Data, cache = Cache} = State) ->
+ ?vlog("lookup ~p", [Oid]),
+ Key = {lookup, Oid},
+ {Reply, NewState} =
+ case maybe_cache_lookup(Cache, Key) of
+ ?NO_CACHE ->
+ {snmpa_mib_data:lookup(Data, Oid), State};
+ [] ->
+ Rep = snmpa_mib_data:lookup(Data, Oid),
+ ets:insert(Cache, {Key, Rep, timestamp()}),
+ {Rep, maybe_start_cache_gc_timer(State)};
+ [{Key, Rep, _}] ->
+ ?vdebug("lookup -> found in cache", []),
+ ets:update_element(Cache, Key, {3, timestamp()}),
+ {Rep, State}
+ end,
+ ?vdebug("lookup -> Reply: ~p", [Reply]),
+ {reply, Reply, NewState};
+
+handle_call({which_mib, Oid}, _From, #state{data = Data} = State) ->
+ ?vlog("which_mib ~p",[Oid]),
+ Reply = snmpa_mib_data:which_mib(Data, Oid),
+ ?vdebug("which_mib: ~p",[Reply]),
+ {reply, Reply, State};
+
+handle_call({next, Oid, MibView}, _From,
+ #state{data = Data, cache = Cache} = State) ->
+ ?vlog("next ~p [~p]", [Oid, MibView]),
+ Key = {next, Oid, MibView},
+ {Reply, NewState} =
+ case maybe_cache_lookup(Cache, Key) of
+ ?NO_CACHE ->
+ {snmpa_mib_data:next(Data, Oid, MibView), State};
+ [] ->
+ Rep = snmpa_mib_data:next(Data, Oid, MibView),
+ ets:insert(Cache, {Key, Rep, timestamp()}),
+ {Rep, maybe_start_cache_gc_timer(State)};
+ [{Key, Rep, _}] ->
+ ?vdebug("lookup -> found in cache", []),
+ ets:update_element(Cache, Key, {3, timestamp()}),
+ {Rep, State}
+ end,
+ ?vdebug("next -> Reply: ~p", [Reply]),
+ {reply, Reply, NewState};
+
+handle_call({load_mibs, Mibs}, _From,
+ #state{data = Data,
+ teo = TeOverride,
+ meo = MeOverride,
+ cache = Cache} = State) ->
+ ?vlog("load mibs ~p",[Mibs]),
+ %% Invalidate cache
+ NewCache = maybe_invalidate_cache(Cache),
+ {NData,Reply} =
+ case (catch mib_operations(load_mib, Mibs, Data,
+ MeOverride, TeOverride)) of
+ {'aborted at', Mib, NewData, Reason} ->
+ ?vlog("aborted at ~p for reason ~p",[Mib,Reason]),
+ {NewData,{error, {'load aborted at', Mib, Reason}}};
+ {ok, NewData} ->
+ {NewData,ok}
+ end,
+ snmpa_mib_data:sync(NData),
+ {reply, Reply, State#state{data = NData, cache = NewCache}};
+
+handle_call({unload_mibs, Mibs}, _From,
+ #state{data = Data,
+ teo = TeOverride,
+ meo = MeOverride,
+ cache = Cache} = State) ->
+ ?vlog("unload mibs ~p",[Mibs]),
+ %% Invalidate cache
+ NewCache = maybe_invalidate_cache(Cache),
+ %% Unload mib(s)
+ {NData,Reply} =
+ case (catch mib_operations(unload_mib, Mibs, Data,
+ MeOverride, TeOverride)) of
+ {'aborted at', Mib, NewData, Reason} ->
+ ?vlog("aborted at ~p for reason ~p",[Mib,Reason]),
+ {NewData, {error, {'unload aborted at', Mib, Reason}}};
+ {ok, NewData} ->
+ {NewData,ok}
+ end,
+ snmpa_mib_data:sync(NData),
+ {reply, Reply, State#state{data = NData, cache = NewCache}};
+
+handle_call(which_mibs, _From, #state{data = Data} = State) ->
+ ?vlog("which mibs",[]),
+ Reply = snmpa_mib_data:which_mibs(Data),
+ {reply, Reply, State};
+
+handle_call({whereis_mib, Mib}, _From, #state{data = Data} = State) ->
+ ?vlog("whereis mib: ~p",[Mib]),
+ Reply = snmpa_mib_data:whereis_mib(Data, Mib),
+ {reply, Reply, State};
+
+handle_call({register_subagent, Oid, Pid}, _From,
+ #state{data = Data, cache = Cache} = State) ->
+ ?vlog("register subagent ~p, ~p",[Oid,Pid]),
+ %% Invalidate cache
+ NewCache = maybe_invalidate_cache(Cache),
+ case snmpa_mib_data:register_subagent(Data, Oid, Pid) of
+ {error, Reason} ->
+ ?vlog("registration failed: ~p",[Reason]),
+ {reply, {error, Reason}, State#state{cache = NewCache}};
+ NewData ->
+ {reply, ok, State#state{data = NewData, cache = NewCache}}
+ end;
+
+handle_call({unregister_subagent, OidOrPid}, _From,
+ #state{data = Data, cache = Cache} = State) ->
+ ?vlog("unregister subagent ~p",[OidOrPid]),
+ %% Invalidate cache
+ NewCache = maybe_invalidate_cache(Cache),
+ case snmpa_mib_data:unregister_subagent(Data, OidOrPid) of
+ {ok, NewData, DeletedSubagentPid} ->
+ {reply, {ok, DeletedSubagentPid}, State#state{data = NewData,
+ cache = NewCache}};
+ {error, Reason} ->
+ ?vlog("unregistration failed: ~p",[Reason]),
+ {reply, {error, Reason}, State#state{cache = NewCache}};
+ NewData ->
+ {reply, ok, State#state{data = NewData, cache = NewCache}}
+ end;
+
+handle_call(info, _From, #state{data = Data, cache = Cache} = State) ->
+ ?vlog("info",[]),
+ Reply =
+ case (catch snmpa_mib_data:info(Data)) of
+ Info when is_list(Info) ->
+ [{cache, size_cache(Cache)} | Info];
+ E ->
+ [{error, E}]
+ end,
+ {reply, Reply, State};
+
+handle_call({info, Type}, _From, #state{data = Data} = State) ->
+ ?vlog("info ~p",[Type]),
+ Reply =
+ case (catch snmpa_mib_data:info(Data, Type)) of
+ Info when is_list(Info) ->
+ Info;
+ E ->
+ [{error, E}]
+ end,
+ {reply, Reply, State};
+
+handle_call(dump, _From, State) ->
+ ?vlog("dump",[]),
+ Reply = snmpa_mib_data:dump(State#state.data),
+ {reply, Reply, State};
+
+handle_call({dump, File}, _From, #state{data = Data} = State) ->
+ ?vlog("dump on ~s",[File]),
+ Reply = snmpa_mib_data:dump(Data, File),
+ {reply, Reply, State};
+
+handle_call({backup, BackupDir}, From, #state{data = Data} = State) ->
+ ?vlog("backup to ~s",[BackupDir]),
+ Pid = self(),
+ V = get(verbosity),
+ case file:read_file_info(BackupDir) of
+ {ok, #file_info{type = directory}} ->
+ BackupServer =
+ erlang:spawn_link(
+ fun() ->
+ put(sname, ambs),
+ put(verbosity, V),
+ Dir = filename:join([BackupDir]),
+ Reply = snmpa_mib_data:backup(Data, Dir),
+ Pid ! {backup_done, Reply},
+ unlink(Pid)
+ end),
+ ?vtrace("backup server: ~p", [BackupServer]),
+ {noreply, State#state{backup = {BackupServer, From}}};
+ {ok, _} ->
+ {reply, {error, not_a_directory}, State};
+ Error ->
+ {reply, Error, State}
+ end;
+
+handle_call(stop, _From, State) ->
+ ?vlog("stop",[]),
+ {stop, normal, ok, State};
+
+handle_call(Req, _From, State) ->
+ warning_msg("received unknown request: ~n~p", [Req]),
+ Reply = {error, {unknown, Req}},
+ {reply, Reply, State}.
+
+handle_cast({verbosity, Verbosity}, State) ->
+ ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]),
+ put(verbosity,snmp_verbosity:validate(Verbosity)),
+ {noreply, State};
+
+handle_cast(Msg, State) ->
+ warning_msg("received unknown message: ~n~p", [Msg]),
+ {noreply, State}.
+
+
+handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) ->
+ ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]),
+ gen_server:reply(From, {error, Reason}),
+ {noreply, S#state{backup = undefined}};
+
+handle_info({'EXIT', Pid, Reason}, S) ->
+ %% The only other processes we should be linked to are
+ %% either the master agent or our supervisor, so die...
+ {stop, {received_exit, Pid, Reason}, S};
+
+handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) ->
+ ?vlog("backup done:"
+ "~n Reply: ~p", [Reply]),
+ gen_server:reply(From, Reply),
+ {noreply, S#state{backup = undefined}};
+
+handle_info(?CACHE_GC_TRIGGER, #state{cache = Cache,
+ cache_age = Age,
+ cache_gclimit = GcLimit,
+ cache_autogc = true} = S)
+ when (Cache =/= ?NO_CACHE) ->
+ ?vlog("cache gc trigger event", []),
+ maybe_gc_cache(Cache, Age, GcLimit),
+ Tmr = start_cache_gc_timer(),
+ {noreply, S#state{cache_tmr = Tmr}};
+
+handle_info(?CACHE_GC_TRIGGER, S) ->
+ ?vlog("out-of-date cache gc trigger event - ignore", []),
+ {noreply, S#state{cache_tmr = undefined}};
+
+handle_info(Info, State) ->
+ warning_msg("received unknown info: ~n~p", [Info]),
+ {noreply, State}.
+
+terminate(_Reason, #state{data = Data}) ->
+ catch snmpa_mib_data:close(Data),
+ ok.
+
+
+
+%%----------------------------------------------------------
+%% Code change
+%%----------------------------------------------------------
+
+%% downgrade
+%%
+%% code_change({down, _Vsn}, S1, downgrade_to_pre_4_12) ->
+%% #state{data = Data, meo = MEO, teo = TEO, backup = B, cache = Cache} = S1,
+%% del_cache(Cache),
+%% S2 = {state, Data, MEO, TEO, B},
+%% {ok, S2};
+
+%% %% upgrade
+%% %%
+%% code_change(_Vsn, S1, upgrade_from_pre_4_12) ->
+%% {state, Data, MEO, TEO, B} = S1,
+%% Cache = new_cache(),
+%% S2 = #state{data = Data, meo = MEO, teo = TEO, backup = B, cache = Cache},
+%% {ok, S2};
+
+code_change(_Vsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%-----------------------------------------------------------------
+%% Option access functions
+%%-----------------------------------------------------------------
+
+get_verbosity(Options) ->
+ get_opt(verbosity, Options, ?default_verbosity).
+
+get_me_override(Options) ->
+ get_opt(mibentry_override, Options, false).
+
+get_te_override(Options) ->
+ get_opt(trapentry_override, Options, false).
+
+get_mib_storage(Options) ->
+ get_opt(mib_storage, Options, ets).
+
+get_cacheopt_autogc(Cache, CacheOpts) ->
+ IsValid = fun(AutoGC) when ((AutoGC =:= true) orelse
+ (AutoGC =:= false)) ->
+ true;
+ (_) ->
+ false
+ end,
+ get_cacheopt(Cache, autogc, CacheOpts,
+ false, ?DEFAULT_CACHE_AUTOGC,
+ IsValid).
+
+get_cacheopt_gclimit(Cache, CacheOpts) ->
+ IsValid = fun(Limit) when ((is_integer(Limit) andalso (Limit > 0)) orelse
+ (Limit =:= infinity)) ->
+ true;
+ (_) ->
+ false
+ end,
+ get_cacheopt(Cache, gclimit, CacheOpts,
+ infinity, ?DEFAULT_CACHE_GCLIMIT,
+ IsValid).
+
+get_cacheopt_age(Cache, CacheOpts) ->
+ IsValid = fun(Age) when is_integer(Age) andalso (Age > 0) ->
+ true;
+ (_) ->
+ false
+ end,
+ get_cacheopt(Cache, age, CacheOpts,
+ ?DEFAULT_CACHE_AGE, ?DEFAULT_CACHE_AGE,
+ IsValid).
+
+get_cacheopt(?NO_CACHE, _, _, NoCacheVal, _, _) ->
+ NoCacheVal;
+get_cacheopt(_, Key, Opts, _, Default, IsValid) ->
+ Val = get_opt(Key, Opts, Default),
+ case IsValid(Val) of
+ true ->
+ Val;
+ false ->
+ throw({error, {bad_option, {Key, Val}}})
+ end.
+
+
+%% ----------------------------------------------------------------
+
+handle_update_cache_opts(cache, true = _Value,
+ #state{cache = ?NO_CACHE} = State) ->
+ {ok, State#state{cache = new_cache()}};
+handle_update_cache_opts(cache, true = _Value, State) ->
+ {ok, State};
+
+handle_update_cache_opts(cache, false = _Value,
+ #state{cache = ?NO_CACHE} = State) ->
+ {ok, State};
+handle_update_cache_opts(cache, false = _Value,
+ #state{cache = Cache,
+ cache_tmr = Tmr} = State) ->
+ maybe_stop_cache_gc_timer(Tmr),
+ del_cache(Cache),
+ {ok, State#state{cache = ?NO_CACHE, cache_tmr = undefined}};
+
+handle_update_cache_opts(autogc, true = _Value,
+ #state{cache_autogc = true} = State) ->
+ {ok, State};
+handle_update_cache_opts(autogc, true = Value, State) ->
+ {ok, maybe_start_cache_gc_timer(State#state{cache_autogc = Value})};
+handle_update_cache_opts(autogc, false = _Value,
+ #state{cache_autogc = false} = State) ->
+ {ok, State};
+handle_update_cache_opts(autogc, false = Value,
+ #state{cache_tmr = Tmr} = State) ->
+ maybe_stop_cache_gc_timer(Tmr),
+ {ok, State#state{cache_autogc = Value, cache_tmr = undefined}};
+
+handle_update_cache_opts(age, Age, State) ->
+ {ok, State#state{cache_age = Age}};
+
+handle_update_cache_opts(gclimit, GcLimit, State) ->
+ {ok, State#state{cache_gclimit = GcLimit}};
+
+handle_update_cache_opts(BadKey, Value, State) ->
+ {{error, {bad_cache_opt, BadKey, Value}}, State}.
+
+
+maybe_stop_cache_gc_timer(undefined) ->
+ ok;
+maybe_stop_cache_gc_timer(Tmr) ->
+ erlang:cancel_timer(Tmr).
+
+
+maybe_start_cache_gc_timer(#state{cache = Cache,
+ cache_autogc = true,
+ cache_tmr = undefined} = State)
+ when (Cache =/= ?NO_CACHE) ->
+ Tmr = start_cache_gc_timer(),
+ State#state{cache_tmr = Tmr};
+maybe_start_cache_gc_timer(State) ->
+ State.
+
+start_cache_gc_timer() ->
+ erlang:send_after(?CACHE_GC_TICKTIME, self(), ?CACHE_GC_TRIGGER).
+
+
+%% ----------------------------------------------------------------
+
+maybe_gc_cache(?NO_CACHE, _Age) ->
+ ?vtrace("cache not enabled", []),
+ ok;
+maybe_gc_cache(Cache, Age) ->
+ MatchSpec = gc_cache_matchspec(Age),
+ Keys = ets:select(Cache, MatchSpec),
+ do_gc_cache(Cache, Keys),
+ {ok, length(Keys)}.
+
+maybe_gc_cache(?NO_CACHE, _Age, _GcLimit) ->
+ ok;
+maybe_gc_cache(Cache, Age, infinity = _GcLimit) ->
+ maybe_gc_cache(Cache, Age);
+maybe_gc_cache(Cache, Age, GcLimit) ->
+ MatchSpec = gc_cache_matchspec(Age),
+ Keys =
+ case ets:select(Cache, MatchSpec, GcLimit) of
+ {Match, _Cont} ->
+ Match;
+ '$end_of_table' ->
+ []
+ end,
+ do_gc_cache(Cache, Keys),
+ {ok, length(Keys)}.
+
+gc_cache_matchspec(Age) ->
+ Oldest = timestamp() - Age,
+ MatchHead = {'$1', '_', '$2'},
+ Guard = [{'<', '$2', Oldest}],
+ MatchFunc = {MatchHead, Guard, ['$1']},
+ MatchSpec = [MatchFunc],
+ MatchSpec.
+
+do_gc_cache(_, []) ->
+ ok;
+do_gc_cache(Cache, [Key|Keys]) ->
+ ets:delete(Cache, Key),
+ do_gc_cache(Cache, Keys).
+
+maybe_invalidate_cache(?NO_CACHE) ->
+ ?NO_CACHE;
+maybe_invalidate_cache(Cache) ->
+ del_cache(Cache),
+ new_cache().
+
+maybe_cache_size(?NO_CACHE) ->
+ {error, not_enabled};
+maybe_cache_size(Cache) ->
+ {ok, ets:info(Cache, size)}.
+
+new_cache() ->
+ ets:new(snmpa_mib_cache, [set, protected, {keypos, 1}]).
+
+del_cache(?NO_CACHE) ->
+ ok;
+del_cache(Cache) ->
+ ets:delete(Cache).
+
+maybe_cache_lookup(?NO_CACHE, _) ->
+ ?NO_CACHE;
+maybe_cache_lookup(Cache, Key) ->
+ ets:lookup(Cache, Key).
+
+size_cache(?NO_CACHE) ->
+ undefined;
+size_cache(Cache) ->
+ case (catch ets:info(Cache, memory)) of
+ Sz when is_integer(Sz) ->
+ Sz;
+ _ ->
+ undefined
+ end.
+
+timestamp() ->
+ snmp_misc:now(ms).
+
+
+%% ----------------------------------------------------------------
+
+get_opt(Key, Options, Default) ->
+ snmp_misc:get_option(Key, Options, Default).
+
+
+%% ----------------------------------------------------------------
+
+cast(MibServer, Msg) ->
+ gen_server:cast(MibServer, Msg).
+
+call(MibServer, Req) ->
+ call(MibServer, Req, infinity).
+
+call(MibServer, Req, To) ->
+ gen_server:call(MibServer, Req, To).
+
+
+%% ----------------------------------------------------------------
+
+%% info_msg(F, A) ->
+%% ?snmpa_info("Mib server: " ++ F, A).
+
+warning_msg(F, A) ->
+ ?snmpa_warning("Mib server: " ++ F, A).
+
+%% error_msg(F, A) ->
+%% ?snmpa_error("Mib server: " ++ F, A).
+
+config_err(F, A) ->
+ snmpa_error:config_err(F, A).
+