1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(snmpa_mib).
21
22
23%%%-----------------------------------------------------------------
24%%% This module implements a MIB server.
25%%%-----------------------------------------------------------------
26
27%% External exports
28-export([start_link/3, stop/1,
29	 lookup/2, next/3, which_mib/2, which_mibs/1, whereis_mib/2,
30	 load_mibs/3, unload_mibs/3,
31	 register_subagent/3, unregister_subagent/2, info/1, info/2,
32	 verbosity/2, dump/1, dump/2,
33	 backup/2,
34	 invalidate_cache/1,
35	 gc_cache/1, gc_cache/2, gc_cache/3,
36	 enable_cache/1, disable_cache/1,
37	 enable_cache_autogc/1, disable_cache_autogc/1,
38	 update_cache_gclimit/2,
39	 update_cache_age/2,
40	 which_cache_size/1
41	]).
42
43%% Utility exports
44-export([subscribe_gc_events/1, unsubscribe_gc_events/1]).
45
46%% Internal exports
47-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
48	 code_change/3]).
49
50%% <BACKWARD-COMPAT>
51-export([load_mibs/2, unload_mibs/2]).
52%% </BACKWARD-COMPAT>
53
54
55-include_lib("kernel/include/file.hrl").
56-include("snmpa_internal.hrl").
57-include("snmp_types.hrl").
58-include("snmp_verbosity.hrl").
59-include("snmp_debug.hrl").
60
61
62-define(SERVER,                  ?MODULE).
63-define(NO_CACHE,                no_mibs_cache).
64-define(DEFAULT_CACHE_USAGE,     true).
65-define(CACHE_GC_TICKTIME,       timer:minutes(1)).
66-define(DEFAULT_CACHE_AUTOGC,    true).
67-define(DEFAULT_CACHE_GCLIMIT,   infinity). % 100).
68-define(DEFAULT_CACHE_GCVERBOSE, false).
69-define(DEFAULT_CACHE_AGE,       timer:minutes(10)).
70-define(CACHE_GC_TRIGGER,        cache_gc_trigger).
71
72
73
74-ifdef(snmp_debug).
75-define(GS_START_LINK(Prio, Mibs, Opts),
76        gen_server:start_link(?MODULE, [Prio, Mibs, Opts], [{debug,[trace]}])).
77-else.
78-define(GS_START_LINK(Prio, Mibs, Opts),
79        gen_server:start_link(?MODULE, [Prio, Mibs, Opts], [])).
80-endif.
81
82
83%%-----------------------------------------------------------------
84%% Internal Data structures
85%%
86%%   State
87%%       data - is the MIB data (defined in mib_data module)
88%%       meo  - mib entry override
89%%       teo  - trap (notification) entry override
90%%-----------------------------------------------------------------
91-record(state,
92	{data, meo, teo, backup,
93	 cache, cache_tmr, cache_autogc, cache_gclimit, cache_age,
94         cache_sub, cache_gcverbose = false,
95	 data_mod}).
96
97
98
99%%-----------------------------------------------------------------
100%% Func: start_link/1
101%% Args: Mibs is a list of mibnames.
102%%       Prio is priority of mib-server
103%%       Opts is a list of options
104%% Purpose: starts the mib server synchronized
105%% Returns: {ok, Pid} | {error, Reason}
106%%-----------------------------------------------------------------
107start_link(Prio, Mibs, Opts) ->
108    ?d("start_link -> entry with"
109	"~n   Prio: ~p"
110	"~n   Mibs: ~p"
111	"~n   Opts: ~p", [Prio, Mibs, Opts]),
112    ?GS_START_LINK(Prio, Mibs, Opts).
113
114verbosity(MibServer, Verbosity) ->
115    cast(MibServer, {verbosity,Verbosity}).
116
117stop(MibServer) ->
118    call(MibServer, stop).
119
120invalidate_cache(MibServer) ->
121    call(MibServer, invalidate_cache).
122
123gc_cache(MibServer) ->
124    call(MibServer, gc_cache).
125
126gc_cache(MibServer, Age) ->
127    call(MibServer, {gc_cache, Age}).
128
129gc_cache(MibServer, Age, GcLimit) ->
130    call(MibServer, {gc_cache, Age, GcLimit}).
131
132which_cache_size(MibServer) ->
133    call(MibServer, cache_size).
134
135enable_cache(MibServer) ->
136    update_cache_opts(MibServer, cache, true).
137disable_cache(MibServer) ->
138    update_cache_opts(MibServer, cache, false).
139
140enable_cache_autogc(MibServer) ->
141    update_cache_opts(MibServer, autogc, true).
142disable_cache_autogc(MibServer) ->
143    update_cache_opts(MibServer, autogc, false).
144
145update_cache_gclimit(MibServer, GcLimit)
146  when ((is_integer(GcLimit) andalso (GcLimit > 0)) orelse
147	(GcLimit =:= infinity)) ->
148    update_cache_opts(MibServer, gclimit, GcLimit);
149update_cache_gclimit(_, BadLimit) ->
150    {error, {bad_gclimit, BadLimit}}.
151
152update_cache_age(MibServer, Age)
153  when is_integer(Age) andalso (Age > 0) ->
154    update_cache_opts(MibServer, age, Age);
155update_cache_age(_, BadAge) ->
156    {error, {bad_age, BadAge}}.
157
158update_cache_opts(MibServer, Key, Value) ->
159    call(MibServer, {update_cache_opts, Key, Value}).
160
161
162subscribe_gc_events(MibServer) ->
163    call(MibServer, {subscribe_gc_events, self()}).
164
165unsubscribe_gc_events(MibServer) ->
166    call(MibServer, {unsubscribe_gc_events, self()}).
167
168
169%%-----------------------------------------------------------------
170%% Func: lookup/2
171%% Purpose: Finds the mib entry corresponding to the Oid. If it is a
172%%          variable, the Oid must be <Oid for var>.0 and if it is
173%%          a table, Oid must be <table>.<entry>.<col>.<any>
174%% Returns: {variable, MibEntry} |
175%%          {table_column, MibEntry, TableEntryOid} |
176%%          {subagent, SubAgentPid} |
177%%          false
178%%-----------------------------------------------------------------
179lookup(MibServer, Oid) ->
180    call(MibServer, {lookup, Oid}).
181
182which_mib(MibServer, Oid) ->
183    call(MibServer, {which_mib, Oid}).
184
185
186%%-----------------------------------------------------------------
187%% Func: next/3
188%% Purpose: Finds the lexicographically next oid.
189%% Returns: {subagent, SubAgentPid, SANextOid} |
190%%          endOfMibView |
191%%          genErr |
192%%          NextOid
193%%   The SANextOid is used by the agent if the SubAgent returns
194%%   endOfMib, in a new call to next/2.
195%%-----------------------------------------------------------------
196next(MibServer, Oid, MibView) ->
197    call(MibServer, {next, Oid, MibView}).
198
199
200%%----------------------------------------------------------------------
201%% Purpose: Loads mibs into the mib process.
202%% Args: Mibs is a list of Filenames (compiled mibs).
203%%       Force is a boolean
204%% Returns: ok | {error, Reason}
205%%----------------------------------------------------------------------
206
207%% <BACKWARD-COMPAT>
208load_mibs(MibServer, Mibs) ->
209    load_mibs(MibServer, Mibs, false).
210%% </BACKWARD-COMPAT>
211
212load_mibs(MibServer, Mibs, Force) ->
213    call(MibServer, {load_mibs, Mibs, Force}).
214
215
216%%----------------------------------------------------------------------
217%% Purpose: Loads mibs into the mib process.
218%% Args: Mibs is a list of Filenames (compiled mibs).
219%%       Force is a boolean
220%% Returns: ok | {error, Reason}
221%%----------------------------------------------------------------------
222%% <BACKWARD-COMPAT>
223unload_mibs(MibServer, Mibs) ->
224    unload_mibs(MibServer, Mibs, false).
225%% </BACKWARD-COMPAT>
226
227unload_mibs(MibServer, Mibs, Force) ->
228    call(MibServer, {unload_mibs, Mibs, Force}).
229
230
231%%----------------------------------------------------------------------
232%% Purpose: Simple management functions
233%% Args: Mib is the name of the mib (atom)
234%% Returns: ok | {error, Reason}
235%%----------------------------------------------------------------------
236which_mibs(MibServer) ->
237    call(MibServer, which_mibs).
238
239whereis_mib(MibServer, Mib) ->
240    call(MibServer, {whereis_mib, Mib}).
241
242
243%%----------------------------------------------------------------------
244%% Registers subagent with pid Pid under subtree Oid.
245%%----------------------------------------------------------------------
246register_subagent(MibServer, Oid, Pid) ->
247    call(MibServer, {register_subagent, Oid, Pid}).
248
249unregister_subagent(MibServer, OidOrPid) ->
250    call(MibServer, {unregister_subagent, OidOrPid}).
251
252info(MibServer) ->
253    call(MibServer, info).
254
255info(MibServer, Type) ->
256    call(MibServer, {info, Type}).
257
258dump(MibServer) ->
259    dump(MibServer, io).
260
261dump(MibServer, File) when (File =:= io) orelse is_list(File) ->
262    call(MibServer, {dump, File}).
263
264backup(MibServer, BackupDir) when is_list(BackupDir) ->
265    call(MibServer, {backup, BackupDir}).
266
267
268%%--------------------------------------------------
269%% The standard MIB 'stdmib' must be present in the
270%% current directory.
271%%--------------------------------------------------
272init([Prio, Mibs, Opts]) ->
273    ?d("init -> entry with"
274	"~n   Prio: ~p"
275	"~n   Mibs: ~p"
276	"~n   Opts: ~p", [Prio, Mibs, Opts]),
277    case (catch do_init(Prio, Mibs, Opts)) of
278	{ok, State} ->
279	    {ok, State};
280	{error, Reason} ->
281	    config_err("failed starting mib-server: ~n~p", [Reason]),
282	    {stop, {error, Reason}};
283	Error ->
284	    config_err("failed starting mib-server: ~n~p", [Error]),
285	    {stop, {error, Error}}
286    end.
287
288do_init(Prio, Mibs, Opts) ->
289    process_flag(priority, Prio),
290    process_flag(trap_exit, true),
291    put(sname, ms),
292    put(verbosity, ?vvalidate(get_verbosity(Opts))),
293    ?vlog("starting", []),
294
295    %% Extract the cache options
296    {Cache, CacheOptions} =
297	case get_opt(cache, Opts, ?DEFAULT_CACHE_USAGE) of
298	    true ->
299		{new_cache(), []};
300	    false ->
301		{?NO_CACHE, []};
302	    CacheOpts when is_list(CacheOpts) ->
303		{new_cache(), CacheOpts};
304	    Bad ->
305		throw({error, {bad_option, {cache, Bad}}})
306	end,
307    CacheAutoGC  = get_cacheopt_autogc(Cache,    CacheOptions),
308    CacheGcLimit = get_cacheopt_gclimit(Cache,   CacheOptions),
309    CacheAge     = get_cacheopt_age(Cache,       CacheOptions),
310    CacheGcVerb  = get_cacheopt_gcverbose(Cache, CacheOptions),
311
312    %% Maybe start the cache gc timer
313    CacheGcTimer =
314	if
315	    ((Cache =/= ?NO_CACHE) andalso
316	     (CacheAutoGC =:= true)) ->
317		start_cache_gc_timer();
318	    true ->
319		undefined
320	end,
321
322    MeOverride = get_me_override(Opts),
323    TeOverride = get_te_override(Opts),
324    MibStorage = get_mib_storage(Opts),
325    MibDataMod = get_data_mod(Opts),
326    ?vtrace("init -> try create mib data with"
327	    "~n   MeOverride: ~p"
328	    "~n   TeOverride: ~p"
329	    "~n   MibStorage: ~p", [MeOverride, TeOverride, MibStorage]),
330    Data       = MibDataMod:new(MibStorage),
331    ?vdebug("init -> mib data created", []),
332    case (catch mib_operations(MibDataMod,
333			       load_mib, Mibs, Data,
334			       MeOverride, TeOverride, true)) of
335	{ok, Data2} ->
336	    ?vdebug("started",[]),
337	    MibDataMod:sync(Data2),
338	    ?vdebug("mib data synced",[]),
339	    {ok, #state{data            = Data2,
340			teo             = TeOverride,
341			meo             = MeOverride,
342			cache           = Cache,
343			cache_tmr       = CacheGcTimer,
344			cache_autogc    = CacheAutoGC,
345			cache_gclimit   = CacheGcLimit,
346			cache_age       = CacheAge,
347			cache_gcverbose = CacheGcVerb,
348			data_mod        = MibDataMod}};
349	{'aborted at', Mib, _NewData, Reason} ->
350	    ?vinfo("failed loading mib ~p: ~p",[Mib,Reason]),
351	    {error, {Mib, Reason}}
352    end.
353
354
355%%----------------------------------------------------------------------
356%% Returns: {ok, NewMibData} | {'aborted at', Mib, NewData, Reason}
357%% Args: Operation is load_mib | unload_mib.
358%%----------------------------------------------------------------------
359mib_operations(_Mod, _Operation, [], Data, _MeOverride, _TeOverride, _Force) ->
360    {ok, Data};
361mib_operations(Mod, Operation, [Mib|Mibs], Data0, MeOverride, TeOverride, Force) ->
362    ?vtrace("mib operations ~p on"
363	    "~n   Mibs: ~p"
364	    "~n   with "
365	    "~n   MeOverride: ~p"
366	    "~n   TeOverride: ~p"
367	    "~n   Force:      ~p",
368	    [Operation, Mibs, MeOverride, TeOverride, Force]),
369    Data = mib_operation(Mod,
370			 Operation, Mib, Data0, MeOverride, TeOverride, Force),
371    mib_operations(Mod, Operation, Mibs, Data, MeOverride, TeOverride, Force).
372
373mib_operation(Mod, Operation, Mib, Data0, MeOverride, TeOverride, Force)
374  when is_list(Mib) ->
375    ?vtrace("mib operation on mib ~p", [Mib]),
376    case apply(Mod, Operation, [Data0, Mib, MeOverride, TeOverride]) of
377	{error, already_loaded} when (Operation =:= load_mib) andalso
378				       (Force =:= true) ->
379	    ?vlog("ignore mib ~p -> already loaded", [Mib]),
380	    Data0;
381	{error, not_loaded} when (Operation =:= unload_mib) andalso
382				 (Force =:= true) ->
383	    ?vlog("ignore mib ~p -> not loaded", [Mib]),
384	    Data0;
385	{error, Reason} ->
386	    ?vlog("mib_operation -> failed ~p of mib ~p for ~p",
387		[Operation, Mib, Reason]),
388	    throw({'aborted at', Mib, Data0, Reason});
389	{ok, Data} ->
390	    Data
391    end;
392mib_operation(_Mod, _Op, Mib, Data, _MeOverride, _TeOverride, _Force) ->
393    throw({'aborted at', Mib, Data, bad_mibname}).
394
395
396%%-----------------------------------------------------------------
397%% Handle messages
398%%-----------------------------------------------------------------
399
400handle_call(invalidate_cache, _From, #state{cache = Cache} = State) ->
401    ?vlog("invalidate_cache", []),
402    NewCache = maybe_invalidate_cache(Cache),
403    {reply, ignore, State#state{cache = NewCache}};
404
405handle_call(cache_size, _From, #state{cache = Cache} = State) ->
406    ?vlog("cache_size", []),
407    Reply = maybe_cache_size(Cache),
408    {reply, Reply, State};
409
410handle_call(gc_cache, _From,
411	    #state{cache         = Cache,
412		   cache_age     = Age,
413		   cache_gclimit = GcLimit} = State) ->
414    ?vlog("gc_cache", []),
415    Result = maybe_gc_cache(Cache, Age, GcLimit),
416    {reply, Result, State};
417
418handle_call({gc_cache, Age}, _From,
419	    #state{cache         = Cache,
420		   cache_gclimit = GcLimit} = State) ->
421    ?vlog("gc_cache with Age = ~p", [Age]),
422    Result = maybe_gc_cache(Cache, Age, GcLimit),
423    {reply, Result, State};
424
425handle_call({gc_cache, Age, GcLimit}, _From,
426	    #state{cache = Cache} = State) ->
427    ?vlog("gc_cache with Age = ~p and GcLimut = ~p", [Age, GcLimit]),
428    Result = maybe_gc_cache(Cache, Age, GcLimit),
429    {reply, Result, State};
430
431handle_call({update_cache_opts, Key, Value}, _From, State) ->
432    ?vlog("update_cache_opts: ~p -> ~p", [Key, Value]),
433    {Result, NewState} = handle_update_cache_opts(Key, Value, State),
434    {reply, Result, NewState};
435
436
437handle_call({subscribe_gc_events, Pid}, _From,
438            #state{cache_sub = Sub} = State)
439  when (Sub =:= undefined) ->
440    ?vdebug("subscribe_gc_events: ~p => ok", [Pid]),
441    {reply, ok, State#state{cache_sub = Pid}};
442handle_call({subscribe_gc_events, Pid}, _From,
443            #state{cache_sub = Pid} = State) ->
444    ?vinfo("subscribe_gc_events: ~p => error:already-subscribed", [Pid]),
445    {reply, {error, already_subscribed}, State};
446handle_call({subscribe_gc_events, Pid}, _From,
447            #state{cache_sub = Sub} = State)
448  when is_pid(Sub) andalso (Pid =/= Sub) ->
449    ?vinfo("subscribe_gc_events: ~p => error:already-subscribed ~p",
450           [Pid, Sub]),
451    {reply, {error, {already_subscribed, Sub}}, State};
452
453handle_call({unsubscribe_gc_events, Pid}, _From,
454            #state{cache_sub = Pid} = State) ->
455    ?vdebug("unsubscribe_gc_events: ~p => ok", [Pid]),
456    {reply, ok, State#state{cache_sub = undefined}};
457handle_call({unsubscribe_gc_events, Pid}, _From,
458            #state{cache_sub = Sub} = State)
459  when (Sub =:= undefined) ->
460    ?vinfo("unsubscribe_gc_events: ~p => error:not-subscribed", [Pid]),
461    {reply, {error, not_subscribed}, State};
462handle_call({unsubscribe_gc_events, Pid}, _From,
463            #state{cache_sub = Sub} = State)
464  when is_pid(Sub) andalso (Pid =/= Sub) ->
465    ?vinfo("unsubscribe_gc_events: ~p => error:not-subscribed ~p",
466           [Pid, Sub]),
467    {reply, {error, {not_subscribed, Sub}}, State};
468
469
470handle_call({lookup, Oid}, _From,
471	    #state{data = Data, cache = Cache, data_mod = Mod} = State) ->
472    ?vlog("lookup ~p", [Oid]),
473    Key = {lookup, Oid},
474    {Reply, NewState} =
475	case maybe_cache_lookup(Cache, Key) of
476	    ?NO_CACHE ->
477		{Mod:lookup(Data, Oid), State};
478	    [] ->
479		Rep = Mod:lookup(Data, Oid),
480		ets:insert(Cache, {Key, Rep, timestamp()}),
481		{Rep, maybe_start_cache_gc_timer(State)};
482	    [{Key, Rep, _}] ->
483		?vtrace("lookup -> found in cache - update timestamp", []),
484		ets:update_element(Cache, Key, {3, timestamp()}),
485		{Rep, State}
486	end,
487    ?vdebug("lookup -> Reply: ~p", [Reply]),
488    {reply, Reply, NewState};
489
490handle_call({which_mib, Oid}, _From,
491	    #state{data = Data, data_mod = Mod} = State) ->
492    ?vlog("which_mib ~p",[Oid]),
493    Reply = Mod:which_mib(Data, Oid),
494    ?vdebug("which_mib: ~p",[Reply]),
495    {reply, Reply, State};
496
497handle_call({next, Oid, MibView}, _From,
498	    #state{data = Data, cache = Cache, data_mod = Mod} = State) ->
499    ?vlog("next ~p [~p]", [Oid, MibView]),
500    Key = {next, Oid, MibView},
501    {Reply, NewState} =
502	case maybe_cache_lookup(Cache, Key) of
503	    ?NO_CACHE ->
504		{Mod:next(Data, Oid, MibView), State};
505	    [] ->
506		Rep = Mod:next(Data, Oid, MibView),
507		ets:insert(Cache, {Key, Rep, timestamp()}),
508		{Rep, maybe_start_cache_gc_timer(State)};
509	    [{Key, Rep, _}] ->
510		?vdebug("lookup -> found in cache - update timestamp", []),
511		ets:update_element(Cache, Key, {3, timestamp()}),
512		{Rep, State}
513	end,
514    ?vdebug("next -> Reply: ~p", [Reply]),
515    {reply, Reply, NewState};
516
517%% <BACKWARD-COMPAT>
518handle_call({load_mibs, Mibs}, From, State) ->
519    handle_call({load_mibs, Mibs, false}, From, State);
520%% </BACKWARD-COMPAT>
521
522handle_call({load_mibs, Mibs, Force}, _From,
523	    #state{data         = Data,
524		   teo          = TeOverride,
525		   meo          = MeOverride,
526		   cache        = Cache,
527		   data_mod     = Mod} = State) ->
528    ?vlog("[~w] load mibs ~p", [Force, Mibs]),
529    %% Invalidate cache
530    NewCache = maybe_invalidate_cache(Cache),
531    {NData, Reply} =
532	case (catch mib_operations(Mod, load_mib, Mibs, Data,
533				   MeOverride, TeOverride, Force)) of
534	    {'aborted at', Mib, NewData, Reason} ->
535		?vlog("aborted at ~p for reason ~p",[Mib,Reason]),
536		{NewData, {error, {'load aborted at', Mib, Reason}}};
537	    {ok, NewData} ->
538		{NewData, ok}
539	end,
540    Mod:sync(NData),
541    {reply, Reply, State#state{data = NData, cache = NewCache}};
542
543%% <BACKWARD-COMPAT>
544handle_call({unload_mibs, Mibs}, From, State) ->
545    handle_call({unload_mibs, Mibs, false}, From, State);
546%% </BACKWARD-COMPAT>
547
548handle_call({unload_mibs, Mibs, Force}, _From,
549	    #state{data         = Data,
550		   teo          = TeOverride,
551		   meo          = MeOverride,
552		   cache        = Cache,
553		   data_mod     = Mod} = State) ->
554    ?vlog("[~w] unload mibs ~p", [Force, Mibs]),
555    %% Invalidate cache
556    NewCache = maybe_invalidate_cache(Cache),
557    %% Unload mib(s)
558    {NData, Reply} =
559	case (catch mib_operations(Mod, unload_mib, Mibs, Data,
560				   MeOverride, TeOverride, Force)) of
561	    {'aborted at', Mib, NewData, Reason} ->
562		?vlog("aborted at ~p for reason ~p", [Mib,Reason]),
563		{NewData, {error, {'unload aborted at', Mib, Reason}}};
564	    {ok, NewData} ->
565		{NewData, ok}
566	end,
567    Mod:sync(NData),
568    {reply, Reply, State#state{data = NData, cache = NewCache}};
569
570handle_call(which_mibs, _From, #state{data = Data, data_mod = Mod} = State) ->
571    ?vlog("which mibs",[]),
572    Reply = Mod:which_mibs(Data),
573    {reply, Reply, State};
574
575handle_call({whereis_mib, Mib}, _From,
576	    #state{data         = Data,
577		   data_mod     = Mod} = State) ->
578    ?vlog("whereis mib: ~p",[Mib]),
579    Reply = Mod:whereis_mib(Data, Mib),
580    {reply, Reply, State};
581
582handle_call({register_subagent, Oid, Pid}, _From,
583	    #state{data      = Data,
584		   cache     = Cache,
585		   data_mod  = Mod} = State) ->
586    ?vlog("register subagent ~p, ~p",[Oid,Pid]),
587    %% Invalidate cache
588    NewCache = maybe_invalidate_cache(Cache),
589    case Mod:register_subagent(Data, Oid, Pid) of
590	{error, Reason} ->
591	    ?vlog("registration failed: ~p",[Reason]),
592	    {reply, {error, Reason}, State#state{cache = NewCache}};
593	{ok, NewData} ->
594	    {reply, ok, State#state{data = NewData, cache = NewCache}}
595    end;
596
597handle_call({unregister_subagent, OidOrPid}, _From,
598	    #state{data     = Data,
599		   cache    = Cache,
600		   data_mod = Mod} = State) ->
601    ?vlog("unregister subagent ~p",[OidOrPid]),
602    %% Invalidate cache
603    NewCache = maybe_invalidate_cache(Cache),
604    case Mod:unregister_subagent(Data, OidOrPid) of
605	{ok, NewData} ->
606	    {reply, ok, State#state{data = NewData, cache = NewCache}};
607	{ok, NewData, DeletedSubagentPid} ->
608	    {reply, {ok, DeletedSubagentPid}, State#state{data  = NewData,
609							  cache = NewCache}};
610	{error, Reason} ->
611	    ?vlog("unregistration failed: ~p",[Reason]),
612	    {reply, {error, Reason}, State#state{cache = NewCache}}
613    end;
614
615handle_call(info, _From, #state{data     = Data,
616				cache    = Cache,
617				data_mod = Mod} = State) ->
618    ?vlog("info",[]),
619    Reply =
620	case (catch Mod:info(Data)) of
621	    Info when is_list(Info) ->
622		[{cache, cache_info(Cache)} | Info];
623	    E ->
624		    [{error, E}]
625	    end,
626    {reply, Reply, State};
627
628handle_call({info, Type}, _From,
629	    #state{data     = Data,
630		   data_mod = Mod} = State) ->
631    ?vlog("info ~p",[Type]),
632    Reply =
633	case (catch Mod:info(Data, Type)) of
634	    Info when is_list(Info) ->
635		Info;
636	    E ->
637		[{error, E}]
638	end,
639    {reply, Reply, State};
640
641handle_call({dump, File}, _From,
642	    #state{data = Data, data_mod = Mod} = State) ->
643    ?vlog("dump on ~s",[File]),
644    Reply = Mod:dump(Data, File),
645    {reply, Reply, State};
646
647%% This check (that there is no backup already in progress) is also
648%% done in the master agent process, but just in case a user issues
649%% a backup call to this process directly, we add a similar check here.
650handle_call({backup, BackupDir}, From,
651	    #state{backup   = undefined,
652		   data     = Data,
653		   data_mod = Mod} = State) ->
654    ?vlog("backup to ~s", [BackupDir]),
655    Pid = self(),
656    V   = get(verbosity),
657    case file:read_file_info(BackupDir) of
658	{ok, #file_info{type = directory}} ->
659	    BackupServer =
660		erlang:spawn_link(
661		  fun() ->
662			  put(sname, ambs),
663			  put(verbosity, V),
664			  Dir   = filename:join([BackupDir]),
665			  Reply = Mod:backup(Data, Dir),
666			  Pid ! {backup_done, Reply},
667			  unlink(Pid)
668		  end),
669	    ?vtrace("backup server: ~p", [BackupServer]),
670	    {noreply, State#state{backup = {BackupServer, From}}};
671	{ok, _} ->
672	    {reply, {error, not_a_directory}, State};
673	Error ->
674	    {reply, Error, State}
675    end;
676
677handle_call({backup, _BackupDir}, _From, #state{backup = Backup} = S) ->
678    ?vinfo("backup already in progress: ~p", [Backup]),
679    {reply, {error, backup_in_progress}, S};
680
681handle_call(stop, _From, State) ->
682    ?vlog("stop",[]),
683    {stop, normal, ok, State};
684
685handle_call(Req, _From, State) ->
686    warning_msg("received unknown request: ~n~p", [Req]),
687    Reply = {error, {unknown, Req}},
688    {reply, Reply, State}.
689
690handle_cast({verbosity, Verbosity}, State) ->
691    ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]),
692    put(verbosity,snmp_verbosity:validate(Verbosity)),
693    {noreply, State};
694
695handle_cast(Msg, State) ->
696    warning_msg("received unknown message: ~n~p", [Msg]),
697    {noreply, State}.
698
699
700handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) ->
701    ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]),
702    gen_server:reply(From, {error, Reason}),
703    {noreply, S#state{backup = undefined}};
704
705handle_info({'EXIT', Pid, Reason}, S) ->
706    %% The only other processes we should be linked to are
707    %% either the master agent or our supervisor, so die...
708    {stop, {received_exit, Pid, Reason}, S};
709
710handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) ->
711    ?vlog("backup done:"
712	  "~n   Reply: ~p", [Reply]),
713    gen_server:reply(From, Reply),
714    {noreply, S#state{backup = undefined}};
715
716handle_info(?CACHE_GC_TRIGGER, #state{cache           = Cache,
717				      cache_age       = Age,
718				      cache_gclimit   = GcLimit,
719				      cache_autogc    = true,
720                                      cache_gcverbose = GcVerbose} = S)
721  when (Cache =/= ?NO_CACHE) ->
722    gcvprint(GcVerbose, "GC: begin"),
723    case maybe_gc_cache(Cache, Age, GcLimit) of
724        {ok, NumDeleted} = Result when (NumDeleted > 0) ->
725            gcvprint(GcVerbose,
726                     "GC: ~w elements deleted from cache", [NumDeleted]),
727            maybe_send_gc_result(S, Result);
728        _ ->
729            ok
730    end,
731    Tmr = start_cache_gc_timer(),
732    {noreply, S#state{cache_tmr = Tmr}};
733
734handle_info(?CACHE_GC_TRIGGER, S) ->
735    ?vlog("out-of-date cache gc trigger event - ignore", []),
736    {noreply, S#state{cache_tmr = undefined}};
737
738handle_info(Info, State) ->
739    warning_msg("received unknown info: ~n~p", [Info]),
740    {noreply, State}.
741
742terminate(_Reason, #state{data = Data, data_mod = Mod}) ->
743    catch Mod:close(Data),
744    ok.
745
746
747
748%%----------------------------------------------------------
749%% Code change
750%%----------------------------------------------------------
751
752%% downgrade
753%%
754%% code_change({down, _Vsn}, S1, downgrade_to_pre_4_12) ->
755%%     #state{data = Data, meo = MEO, teo = TEO, backup = B, cache = Cache} = S1,
756%%     del_cache(Cache),
757%%     S2 = {state, Data, MEO, TEO, B},
758%%     {ok, S2};
759
760code_change({down, Vsn}, #state{data = Data0, data_mod = Mod} = State, Extra) ->
761    Data = Mod:code_change(down, Vsn, Extra, Data0),
762    {ok, State#state{data = Data}};
763
764
765%% %% upgrade
766%% %%
767%% code_change(_Vsn, S1, upgrade_from_pre_4_12) ->
768%%     {state, Data, MEO, TEO, B} = S1,
769%%     Cache = new_cache(),
770%%     S2 = #state{data = Data, meo = MEO, teo = TEO, backup = B, cache = Cache},
771%%     {ok, S2};
772
773code_change(Vsn, #state{data = Data0, data_mod = Mod} = State, Extra) ->
774    Data = Mod:code_change(up, Vsn, Extra, Data0),
775    {ok, State#state{data = Data}}.
776
777
778%%-----------------------------------------------------------------
779%% Option access functions
780%%-----------------------------------------------------------------
781
782get_verbosity(Options) ->
783    get_opt(verbosity, Options, ?default_verbosity).
784
785get_me_override(Options) ->
786    get_opt(mibentry_override, Options, false).
787
788get_te_override(Options) ->
789    get_opt(trapentry_override, Options, false).
790
791get_mib_storage(Options) ->
792    get_opt(mib_storage, Options).
793
794get_data_mod(Options) ->
795    get_opt(data_module, Options, snmpa_mib_data_tttn).
796
797get_cacheopt_autogc(Cache, CacheOpts) ->
798    IsValid = fun(AutoGC) when ((AutoGC =:= true) orelse
799				(AutoGC =:= false)) ->
800		      true;
801		 (_) ->
802		      false
803	      end,
804    get_cacheopt(Cache, autogc, CacheOpts,
805		 false, ?DEFAULT_CACHE_AUTOGC,
806		 IsValid).
807
808get_cacheopt_gclimit(Cache, CacheOpts) ->
809    IsValid = fun(Limit) when ((is_integer(Limit) andalso
810                               (Limit > 0)) orelse
811                              (Limit =:= infinity)) ->
812		      true;
813		 (_) ->
814		      false
815	      end,
816    get_cacheopt(Cache, gclimit, CacheOpts,
817		 infinity, ?DEFAULT_CACHE_GCLIMIT,
818		 IsValid).
819
820get_cacheopt_age(Cache, CacheOpts) ->
821    IsValid = fun(Age) when is_integer(Age) andalso (Age > 0) ->
822		      true;
823		 (_) ->
824		      false
825	      end,
826    get_cacheopt(Cache, age, CacheOpts,
827		 ?DEFAULT_CACHE_AGE, ?DEFAULT_CACHE_AGE,
828		 IsValid).
829
830get_cacheopt_gcverbose(Cache, CacheOpts) ->
831    IsValid = fun(Verbosity) when is_boolean(Verbosity) ->
832		      true;
833		 (_) ->
834		      false
835	      end,
836    get_cacheopt(Cache, gcverbose, CacheOpts,
837		 ?DEFAULT_CACHE_GCVERBOSE, ?DEFAULT_CACHE_GCVERBOSE,
838		 IsValid).
839
840get_cacheopt(?NO_CACHE, _, _, NoCacheVal, _, _) ->
841    NoCacheVal;
842get_cacheopt(_, Key, Opts, _, Default, IsValid) ->
843    Val = get_opt(Key, Opts, Default),
844    case IsValid(Val) of
845	true ->
846	    Val;
847	false ->
848	    throw({error, {bad_option, {Key, Val}}})
849    end.
850
851
852%% ----------------------------------------------------------------
853
854handle_update_cache_opts(cache, true = _Value,
855			 #state{cache = ?NO_CACHE} = State) ->
856    {ok, State#state{cache = new_cache()}};
857handle_update_cache_opts(cache, true = _Value, State) ->
858    {ok, State};
859
860handle_update_cache_opts(cache, false = _Value,
861			 #state{cache = ?NO_CACHE} = State) ->
862    {ok, State};
863handle_update_cache_opts(cache, false = _Value,
864			 #state{cache     = Cache,
865				cache_tmr = Tmr} = State) ->
866    maybe_stop_cache_gc_timer(Tmr),
867    del_cache(Cache),
868    {ok, State#state{cache = ?NO_CACHE, cache_tmr = undefined}};
869
870handle_update_cache_opts(autogc, true = _Value,
871			 #state{cache_autogc = true} = State) ->
872    {ok, State};
873handle_update_cache_opts(autogc, true = Value, State) ->
874    {ok, maybe_start_cache_gc_timer(State#state{cache_autogc = Value})};
875handle_update_cache_opts(autogc, false = _Value,
876			 #state{cache_autogc = false} = State) ->
877    {ok, State};
878handle_update_cache_opts(autogc, false = Value,
879			 #state{cache_tmr = Tmr} = State) ->
880    maybe_stop_cache_gc_timer(Tmr),
881    {ok, State#state{cache_autogc = Value, cache_tmr = undefined}};
882
883handle_update_cache_opts(age, Age, State) ->
884    {ok, State#state{cache_age = Age}};
885
886handle_update_cache_opts(gclimit, GcLimit, State) ->
887    {ok, State#state{cache_gclimit = GcLimit}};
888
889handle_update_cache_opts(BadKey, Value, State) ->
890    {{error, {bad_cache_opt, BadKey, Value}}, State}.
891
892
893maybe_stop_cache_gc_timer(undefined) ->
894    ok;
895maybe_stop_cache_gc_timer(Tmr) ->
896    erlang:cancel_timer(Tmr).
897
898
899maybe_start_cache_gc_timer(#state{cache        = Cache,
900				  cache_autogc = true,
901				  cache_tmr    = undefined} = State)
902  when (Cache =/= ?NO_CACHE) ->
903    Tmr = start_cache_gc_timer(),
904    State#state{cache_tmr = Tmr};
905maybe_start_cache_gc_timer(State) ->
906    State.
907
908start_cache_gc_timer() ->
909    erlang:send_after(?CACHE_GC_TICKTIME, self(), ?CACHE_GC_TRIGGER).
910
911
912%% ----------------------------------------------------------------
913
914gcvprint(GcVerbose, F) ->
915    gcvprint(GcVerbose, F, []).
916
917gcvprint(true, F, A) ->
918    ?vinfo(F, A);
919gcvprint(_, _, _) ->
920    ok.
921
922maybe_gc_cache(?NO_CACHE, _Age) ->
923    ?vtrace("cache not enabled", []),
924    ok;
925maybe_gc_cache(Cache, Age) ->
926    MatchSpec  = gc_cache_matchspec_del(Age),
927    NumDeleted = ets:select_delete(Cache, MatchSpec),
928    {ok, NumDeleted}.
929
930maybe_gc_cache(?NO_CACHE, _Age, _GcLimit) ->
931    ok;
932maybe_gc_cache(Cache, Age, infinity = _GcLimit) ->
933    maybe_gc_cache(Cache, Age);
934maybe_gc_cache(Cache, Age, GcLimit) ->
935    MatchSpec = gc_cache_matchspec_key(Age),
936    Keys =
937	case ets:select(Cache, MatchSpec, GcLimit) of
938	    {Match, _Cont} ->
939		Match;
940	    '$end_of_table' ->
941		[]
942	end,
943    do_gc_cache(Cache, Keys),
944    {ok, length(Keys)}.
945
946gc_cache_matchspec_del(Age) ->
947    %% The entry is a 3-tuple: {Key, Value, Timestamp}
948    MatchHead = {'_', '_', '$2'},
949    Return    = true,
950    gc_cache_matchspec(Age, MatchHead, Return).
951
952gc_cache_matchspec_key(Age) ->
953    %% The entry is a 3-tuple: {Key, Value, Timestamp}
954    MatchHead = {'$1', '_', '$2'},
955    Return    = '$1',
956    gc_cache_matchspec(Age, MatchHead, Return).
957
958gc_cache_matchspec(Age, MatchHead, Return) ->
959    Oldest    = timestamp() - Age,
960    Guard     = [{'<', '$2', Oldest}],
961    MatchFunc = {MatchHead, Guard, [Return]},
962    MatchSpec = [MatchFunc],
963    MatchSpec.
964
965
966do_gc_cache(_, []) ->
967    ok;
968do_gc_cache(Cache, [Key|Keys]) ->
969    ets:delete(Cache, Key),
970    do_gc_cache(Cache, Keys).
971
972maybe_invalidate_cache(?NO_CACHE) ->
973    ?NO_CACHE;
974maybe_invalidate_cache(Cache) ->
975    del_cache(Cache),
976    new_cache().
977
978maybe_cache_size(?NO_CACHE) ->
979    {error, not_enabled};
980maybe_cache_size(Cache) ->
981    {ok, ets:info(Cache, size)}.
982
983new_cache() ->
984    ets:new(snmpa_mib_cache, [set, protected, {keypos, 1}]).
985
986del_cache(?NO_CACHE) ->
987    ok;
988del_cache(Cache) ->
989    ets:delete(Cache).
990
991maybe_cache_lookup(?NO_CACHE, _) ->
992    ?NO_CACHE;
993maybe_cache_lookup(Cache, Key) ->
994    ets:lookup(Cache, Key).
995
996
997cache_info(?NO_CACHE) ->
998    undefined;
999cache_info(Cache) ->
1000    try
1001        begin
1002            [
1003             {memory, ets:info(Cache, memory)},
1004             {size,   ets:info(Cache, size)},
1005             {stats,  ets:info(Cache, stats)}
1006            ]
1007        end
1008    catch
1009        _:_:_ ->
1010            undefined
1011    end.
1012
1013
1014timestamp() ->
1015    snmp_misc:now(ms).
1016
1017
1018maybe_send_gc_result(S, Result) ->
1019    maybe_send_gc_event(S, gc_result, Result).
1020
1021maybe_send_gc_event(#state{cache_sub = Sub}, Ev, Info) when is_pid(Sub) ->
1022    Sub ! {self(), Ev, Info};
1023maybe_send_gc_event(_, _, _) ->
1024    ok.
1025
1026
1027%% ----------------------------------------------------------------
1028
1029get_opt(Key, Options) ->
1030    snmp_misc:get_option(Key, Options).
1031
1032get_opt(Key, Options, Default) ->
1033    snmp_misc:get_option(Key, Options, Default).
1034
1035
1036%% ----------------------------------------------------------------
1037
1038cast(MibServer, Msg) ->
1039    gen_server:cast(MibServer, Msg).
1040
1041call(MibServer, Req) ->
1042    call(MibServer, Req, infinity).
1043
1044call(MibServer, Req, To) ->
1045    gen_server:call(MibServer, Req, To).
1046
1047
1048%% ----------------------------------------------------------------
1049
1050%% info_msg(F, A) ->
1051%%     ?snmpa_info("Mib server: " ++ F, A).
1052
1053warning_msg(F, A) ->
1054    ?snmpa_warning("Mib server: " ++ F, A).
1055
1056%% error_msg(F, A) ->
1057%%     ?snmpa_error("Mib server: " ++ F, A).
1058
1059config_err(F, A) ->
1060    snmpa_error:config_err(F, A).
1061
1062