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