diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/snmp/src/agent/snmpa_mib.erl | |
download | otp-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.erl | 892 |
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). + |