diff options
Diffstat (limited to 'lib/snmp/src/agent')
-rw-r--r-- | lib/snmp/src/agent/depend.mk | 20 | ||||
-rw-r--r-- | lib/snmp/src/agent/modules.mk | 16 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmp_user_based_sm_mib.erl | 35 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa.erl | 33 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_agent.erl | 185 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_agent_sup.erl | 10 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_mib.erl | 207 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_mib_data.erl | 1374 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_mib_data_ttln.erl | 1402 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_mib_data_tttn.erl | 1443 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_mib_storage.erl | 181 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_mib_storage_dets.erl | 309 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_mib_storage_ets.erl | 341 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_mib_storage_mnesia.erl | 302 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_supervisor.erl | 107 | ||||
-rw-r--r-- | lib/snmp/src/agent/snmpa_symbolic_store.erl | 283 |
16 files changed, 4590 insertions, 1658 deletions
diff --git a/lib/snmp/src/agent/depend.mk b/lib/snmp/src/agent/depend.mk index ea9261e266..4b12b66e3b 100644 --- a/lib/snmp/src/agent/depend.mk +++ b/lib/snmp/src/agent/depend.mk @@ -77,10 +77,6 @@ $(EBIN)/snmpa_error_logger.$(EMULATOR): \ snmpa_error_report.erl \ snmpa_error_logger.erl -$(EBIN)/snmpa_general_db.$(EMULATOR): \ - snmpa_general_db.erl \ - ../misc/snmp_verbosity.hrl - $(EBIN)/snmpa_local_db.$(EMULATOR): \ snmpa_local_db.erl \ ../misc/snmp_debug.hrl \ @@ -88,6 +84,18 @@ $(EBIN)/snmpa_local_db.$(EMULATOR): \ ../../include/snmp_types.hrl \ ../../include/STANDARD-MIB.hrl +$(EBIN)/snmpa_mib_storage.$(EMULATOR): \ + snmpa_mib_storage.erl + +$(EBIN)/snmpa_mib_storage_ets.$(EMULATOR): \ + snmpa_mib_storage_ets.erl + +$(EBIN)/snmpa_mib_storage_dets.$(EMULATOR): \ + snmpa_mib_storage_dets.erl + +$(EBIN)/snmpa_mib_storage_mnesia.$(EMULATOR): \ + snmpa_mib_storage_mnesia.erl + $(EBIN)/snmpa_mib.$(EMULATOR): \ snmpa_mib.erl \ ../misc/snmp_debug.hrl \ @@ -96,6 +104,10 @@ $(EBIN)/snmpa_mib.$(EMULATOR): \ $(EBIN)/snmpa_mib_data.$(EMULATOR): \ snmpa_mib_data.erl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_mib_data_tttn.$(EMULATOR): \ + snmpa_mib_data_tttn.erl \ ../misc/snmp_debug.hrl \ ../misc/snmp_verbosity.hrl \ ../../include/snmp_types.hrl diff --git a/lib/snmp/src/agent/modules.mk b/lib/snmp/src/agent/modules.mk index 33ab41b434..34765475b9 100644 --- a/lib/snmp/src/agent/modules.mk +++ b/lib/snmp/src/agent/modules.mk @@ -2,7 +2,7 @@ # %CopyrightBegin% # -# Copyright Ericsson AB 2004-2009. All Rights Reserved. +# Copyright Ericsson AB 2004-2013. 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 @@ -21,15 +21,21 @@ BEHAVIOUR_MODULES = \ snmpa_authentication_service \ snmpa_discovery_handler \ snmpa_error_report \ + snmpa_mib_storage \ + snmpa_mib_data \ snmpa_network_interface \ snmpa_network_interface_filter \ snmpa_notification_delivery_info_receiver \ snmpa_notification_filter \ snmpa_set_mechanism +# snmpa is "plain" interface module but also defines some agent specific types +# and therefor must be compiled before the modules that use them, including +# the behaviour modules... +# snmpa_mib_data_ttln MODULES = \ - $(BEHAVIOUR_MODULES) \ snmpa \ + $(BEHAVIOUR_MODULES) \ snmpa_acm \ snmpa_agent \ snmpa_agent_sup \ @@ -39,10 +45,12 @@ MODULES = \ snmpa_error \ snmpa_error_io \ snmpa_error_logger \ - snmpa_general_db \ snmpa_local_db \ + snmpa_mib_storage_ets \ + snmpa_mib_storage_dets \ + snmpa_mib_storage_mnesia \ snmpa_mib \ - snmpa_mib_data \ + snmpa_mib_data_tttn \ snmpa_mib_lib \ snmpa_misc_sup \ snmpa_mpd \ diff --git a/lib/snmp/src/agent/snmp_user_based_sm_mib.erl b/lib/snmp/src/agent/snmp_user_based_sm_mib.erl index 3c4ba1af66..223d3f7218 100644 --- a/lib/snmp/src/agent/snmp_user_based_sm_mib.erl +++ b/lib/snmp/src/agent/snmp_user_based_sm_mib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2012. All Rights Reserved. +%% Copyright Ericsson AB 1999-2013. 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 @@ -214,12 +214,12 @@ check_user(User) -> case element(?usmUserAuthProtocol, User) of ?usmNoAuthProtocol -> ok; ?usmHMACMD5AuthProtocol -> - case is_crypto_supported(md5_mac_96) of + case is_crypto_supported(md5) of true -> ok; false -> exit({unsupported_crypto, md5_mac_96}) end; ?usmHMACSHAAuthProtocol -> - case is_crypto_supported(sha_mac_96) of + case is_crypto_supported(sha) of true -> ok; false -> exit({unsupported_crypto, sha_mac_96}) end @@ -227,14 +227,14 @@ check_user(User) -> case element(?usmUserPrivProtocol, User) of ?usmNoPrivProtocol -> ok; ?usmDESPrivProtocol -> - case is_crypto_supported(des_cbc_decrypt) of + case is_crypto_supported(des_cbc) of true -> ok; - false -> exit({unsupported_crypto, des_cbc_decrypt}) + false -> exit({unsupported_crypto, des_cbc}) end; ?usmAesCfb128Protocol -> - case is_crypto_supported(aes_cfb_128_decrypt) of + case is_crypto_supported(aes_cfb128) of true -> ok; - false -> exit({unsupported_crypto, aes_cfb_128_decrypt}) + false -> exit({unsupported_crypto, aes_cfb128}) end end. @@ -874,13 +874,13 @@ validate_auth_protocol(RowIndex, Cols) -> _ -> inconsistentValue(?usmUserAuthProtocol) end; ?usmHMACMD5AuthProtocol -> - case is_crypto_supported(md5_mac_96) of + case is_crypto_supported(md5) of true -> ok; false -> wrongValue(?usmUserAuthProtocol) end; ?usmHMACSHAAuthProtocol -> - case is_crypto_supported(sha_mac_96) of + case is_crypto_supported(sha) of true -> ok; false -> wrongValue(?usmUserAuthProtocol) @@ -1008,7 +1008,7 @@ validate_priv_protocol(RowIndex, Cols) -> ?usmDESPrivProtocol -> %% The 'catch' handles the case when 'crypto' is %% not present in the system. - case is_crypto_supported(des_cbc_decrypt) of + case is_crypto_supported(des_cbc) of true -> case get_auth_proto(RowIndex, Cols) of ?usmNoAuthProtocol -> @@ -1022,7 +1022,7 @@ validate_priv_protocol(RowIndex, Cols) -> ?usmAesCfb128Protocol -> %% The 'catch' handles the case when 'crypto' is %% not present in the system. - case is_crypto_supported(aes_cfb_128_decrypt) of + case is_crypto_supported(aes_cfb128) of true -> case get_auth_proto(RowIndex, Cols) of ?usmNoAuthProtocol -> @@ -1164,7 +1164,7 @@ mk_key_change(Hash, OldKey, NewKey) -> %% case in the standard where Random is pre-defined. mk_key_change(Alg, OldKey, NewKey, KeyLen, Random) -> %% OldKey and Random is of length KeyLen... - Digest = lists:sublist(binary_to_list(crypto:Alg(OldKey++Random)), KeyLen), + Digest = lists:sublist(binary_to_list(crypto:hash(Alg, OldKey++Random)), KeyLen), %% ... and so is Digest Delta = snmp_misc:str_xor(Digest, NewKey), Random ++ Delta. @@ -1181,7 +1181,7 @@ extract_new_key(Hash, OldKey, KeyChange) -> sha -> sha end, {Random, Delta} = split(KeyLen, KeyChange, []), - Digest = lists:sublist(binary_to_list(crypto:Alg(OldKey++Random)), KeyLen), + Digest = lists:sublist(binary_to_list(crypto:hash(Alg, OldKey++Random)), KeyLen), NewKey = snmp_misc:str_xor(Digest, Delta), NewKey. @@ -1219,13 +1219,10 @@ split(N, [H | T], FirstRev) when N > 0 -> split(N-1, T, [H | FirstRev]). +-compile({inline, [{is_crypto_supported,1}]}). is_crypto_supported(Func) -> - %% The 'catch' handles the case when 'crypto' is - %% not present in the system (or not started). - case catch lists:member(Func, crypto:info()) of - true -> true; - _ -> false - end. + snmp_misc:is_crypto_supported(Func). + inconsistentValue(V) -> throw({inconsistentValue, V}). inconsistentName(N) -> throw({inconsistentName, N}). diff --git a/lib/snmp/src/agent/snmpa.erl b/lib/snmp/src/agent/snmpa.erl index b45a47ec6b..14b93439df 100644 --- a/lib/snmp/src/agent/snmpa.erl +++ b/lib/snmp/src/agent/snmpa.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2012. All Rights Reserved. +%% Copyright Ericsson AB 2004-2013. 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 @@ -111,13 +111,44 @@ -export([print_mib_info/0, print_mib_tables/0, print_mib_variables/0]). +-export_type([ + me/0, + + %% Agent config types + mib_storage/0, + mib_storage_opt/0, + mib_storage_module/0, + mib_storage_options/0 + ]). + +-deprecated([{old_info_format, 1, next_major_release}]). + + -include("snmpa_atl.hrl"). -include("snmpa_internal.hrl"). +-include_lib("snmp/include/snmp_types.hrl"). % type of me needed. -define(DISCO_EXTRA_INFO, undefined). %%----------------------------------------------------------------- +%% Types +%%----------------------------------------------------------------- + +-type me() :: #me{}. + +%% Agent config types +-type mib_storage() :: [mib_storage_opt()]. +-type mib_storage_opt() :: {module, mib_storage_module()} | + {options, mib_storage_options()}. + +%% Module implementing the snmpa_mib_storage behaviour +-type mib_storage_module() :: atom(). +%% Options specific to the above module +-type mib_storage_options() :: list(). + + +%%----------------------------------------------------------------- %% This utility function is used to convert an old SNMP application %% config (prior to snmp-4.0) to a SNMP agent config (as of %% snmp-4.0). diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl index 57846db13b..c267ce5a70 100644 --- a/lib/snmp/src/agent/snmpa_agent.erl +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -159,12 +159,15 @@ %% it is sent to the worker, and the worker is marked as busy. %% If a request is received when the worker is busy, a new temporary %% worker is spawned. +%% %% Code change %% =========== %% Note that the worker(s) execute the same module as the master %% agent. For code change we have two options - ignore the workers, %% or send them a code change message. +%% %%----------------------------------------------------------------- + -record(state, {type, parent, worker, @@ -219,22 +222,19 @@ %%----------------------------------------------------------------- start_link(Prio, Parent, Ref, Options) -> ?d("start_link -> entry with" - "~n Prio: ~p" - "~n Parent: ~p" - "~n Ref: ~p" - "~n Options: ~p", [Prio, Parent, Ref, Options]), - %% gen_server:start_link(?MODULE, [Prio, Parent, Ref, Options], []). + "~n Prio: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Parent, Ref, Options]), ?GS_START_LINK3(Prio, Parent, Ref, Options). start_link(Prio, Name, Parent, Ref, Options) -> ?d("start_link -> entry with" - "~n Prio: ~p" - "~n Name: ~p" - "~n Parent: ~p" - "~n Ref: ~p" - "~n Options: ~p", [Prio, Name, Parent, Ref, Options]), -% gen_server:start_link({local, Name}, ?MODULE, -% [Prio, Parent, Ref, Options], []). + "~n Prio: ~p" + "~n Name: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Name, Parent, Ref, Options]), ?GS_START_LINK4(Prio, Name, Parent, Ref, Options). stop(Agent) -> call(Agent, stop). @@ -335,10 +335,10 @@ increment_counter(Counter, Initial, Max) -> init([Prio, Parent, Ref, Options]) -> ?d("init -> entry with" - "~n Prio: ~p" - "~n Parent: ~p" - "~n Ref: ~p" - "~n Options: ~p", [Prio, Parent, Ref, Options]), + "~n Prio: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Parent, Ref, Options]), case (catch do_init(Prio, Parent, Ref, Options)) of {ok, State} -> ?vdebug("started",[]), @@ -1457,80 +1457,80 @@ handle_mibs_cache_request(MibServer, Req) -> %% Downgrade %% -code_change({down, _Vsn}, S1, downgrade_to_pre_4_17_3) -> - #state{type = Type, - parent = Parent, - worker = Worker, - worker_state = WorkerState, - set_worker = SetWorker, - multi_threaded = MT, - ref = Ref, - vsns = Vsns, - nfilters = NF, - note_store = NoteStore, - mib_server = MS, - net_if = NetIf, - net_if_mod = NetIfMod, - backup = Backup, - disco = Disco, - mibs_cache_request = MCR} = S1, - S2 = {state, - type = Type, - parent = Parent, - worker = Worker, - worker_state = WorkerState, - set_worker = SetWorker, - multi_threaded = MT, - ref = Ref, - vsns = Vsns, - nfilters = NF, - note_store = NoteStore, - mib_server = MS, - net_if = NetIf, - net_if_mod = NetIfMod, - backup = Backup, - disco = Disco, - mibs_cache_request = MCR}, - {ok, S2}; - -%% Upgrade -%% -code_change(_Vsn, S1, upgrade_from_pre_4_17_3) -> - {state, - type = Type, - parent = Parent, - worker = Worker, - worker_state = WorkerState, - set_worker = SetWorker, - multi_threaded = MT, - ref = Ref, - vsns = Vsns, - nfilters = NF, - note_store = NoteStore, - mib_server = MS, - net_if = NetIf, - net_if_mod = NetIfMod, - backup = Backup, - disco = Disco, - mibs_cache_request = MCR} = S1, - S2 = #state{type = Type, - parent = Parent, - worker = Worker, - worker_state = WorkerState, - set_worker = SetWorker, - multi_threaded = MT, - ref = Ref, - vsns = Vsns, - nfilters = NF, - note_store = NoteStore, - mib_server = MS, - net_if = NetIf, - net_if_mod = NetIfMod, - backup = Backup, - disco = Disco, - mibs_cache_request = MCR, - gb_max_vbs = ?DEFAULT_GB_MAX_VBS}, - {ok, S2}; +%% code_change({down, _Vsn}, S1, downgrade_to_pre_4_17_3) -> +%% #state{type = Type, +%% parent = Parent, +%% worker = Worker, +%% worker_state = WorkerState, +%% set_worker = SetWorker, +%% multi_threaded = MT, +%% ref = Ref, +%% vsns = Vsns, +%% nfilters = NF, +%% note_store = NoteStore, +%% mib_server = MS, +%% net_if = NetIf, +%% net_if_mod = NetIfMod, +%% backup = Backup, +%% disco = Disco, +%% mibs_cache_request = MCR} = S1, +%% S2 = {state, +%% type = Type, +%% parent = Parent, +%% worker = Worker, +%% worker_state = WorkerState, +%% set_worker = SetWorker, +%% multi_threaded = MT, +%% ref = Ref, +%% vsns = Vsns, +%% nfilters = NF, +%% note_store = NoteStore, +%% mib_server = MS, +%% net_if = NetIf, +%% net_if_mod = NetIfMod, +%% backup = Backup, +%% disco = Disco, +%% mibs_cache_request = MCR}, +%% {ok, S2}; + +%% %% Upgrade +%% %% +%% code_change(_Vsn, S1, upgrade_from_pre_4_17_3) -> +%% {state, +%% type = Type, +%% parent = Parent, +%% worker = Worker, +%% worker_state = WorkerState, +%% set_worker = SetWorker, +%% multi_threaded = MT, +%% ref = Ref, +%% vsns = Vsns, +%% nfilters = NF, +%% note_store = NoteStore, +%% mib_server = MS, +%% net_if = NetIf, +%% net_if_mod = NetIfMod, +%% backup = Backup, +%% disco = Disco, +%% mibs_cache_request = MCR} = S1, +%% S2 = #state{type = Type, +%% parent = Parent, +%% worker = Worker, +%% worker_state = WorkerState, +%% set_worker = SetWorker, +%% multi_threaded = MT, +%% ref = Ref, +%% vsns = Vsns, +%% nfilters = NF, +%% note_store = NoteStore, +%% mib_server = MS, +%% net_if = NetIf, +%% net_if_mod = NetIfMod, +%% backup = Backup, +%% disco = Disco, +%% mibs_cache_request = MCR, +%% gb_max_vbs = ?DEFAULT_GB_MAX_VBS}, +%% {ok, S2}; code_change(_Vsn, S, _Extra) -> {ok, S}. @@ -4411,7 +4411,7 @@ get_mibs(Opts) -> get_option(mibs, Opts, []). get_mib_storage(Opts) -> - get_option(mib_storage, Opts, ets). + get_option(mib_storage, Opts). get_set_mechanism(Opts) -> get_option(set_mechanism, Opts, snmpa_set). @@ -4450,6 +4450,9 @@ net_if_verbosity(_Pid,_Verbosity) -> ok. +get_option(Key, Opts) -> + snmp_misc:get_option(Key, Opts). + get_option(Key, Opts, Default) -> snmp_misc:get_option(Key, Opts, Default). diff --git a/lib/snmp/src/agent/snmpa_agent_sup.erl b/lib/snmp/src/agent/snmpa_agent_sup.erl index 9b8c4d12a6..2805e2dc0d 100644 --- a/lib/snmp/src/agent/snmpa_agent_sup.erl +++ b/lib/snmp/src/agent/snmpa_agent_sup.erl @@ -29,10 +29,12 @@ -export([init/1]). -define(SERVER, ?MODULE). +%% Always use plain ets for sub-agents -ifdef(snmp_debug). --define(DEFAULT_OPTS, [{verbosity, trace}]). +-define(DEFAULT_SA_OPTS, [{mib_storage, [{module, snmpa_mib_storage_ets}]}, + {verbosity, trace}]). -else. --define(DEFAULT_OPTS, []). +-define(DEFAULT_SA_OPTS, [{mib_storage, [{module, snmpa_mib_storage_ets}]}]). -endif. @@ -63,8 +65,8 @@ start_subagent(ParentAgent, Subtree, Mibs) -> Ref = make_ref(), ?d("start_subagent -> Ref: ~p", [Ref]), Options = [{priority, Prio}, - {mibs, Mibs}, - {misc_sup, snmpa_misc_sup} | ?DEFAULT_OPTS], + {mibs, Mibs}, + {misc_sup, snmpa_misc_sup} | ?DEFAULT_SA_OPTS], Agent = {{sub_agent, Max}, {snmpa_agent, start_link, [Prio, ParentAgent, Ref, Options]}, diff --git a/lib/snmp/src/agent/snmpa_mib.erl b/lib/snmp/src/agent/snmpa_mib.erl index 575a018c0c..031309b990 100644 --- a/lib/snmp/src/agent/snmpa_mib.erl +++ b/lib/snmp/src/agent/snmpa_mib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2012. All Rights Reserved. +%% Copyright Ericsson AB 1996-2013. 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 @@ -18,7 +18,6 @@ %% -module(snmpa_mib). -%% c(snmpa_mib). %%%----------------------------------------------------------------- %%% This module implements a MIB server. @@ -75,12 +74,14 @@ %% Internal Data structures %% %% State -%% data - is the MIB data (defined in snmpa_mib_data) +%% data - is the MIB data (defined in mib_data module) %% meo - mib entry override %% teo - trap (notification) entry override %%----------------------------------------------------------------- --record(state, {data, meo, teo, backup, - cache, cache_tmr, cache_autogc, cache_gclimit, cache_age}). +-record(state, + {data, meo, teo, backup, + cache, cache_tmr, cache_autogc, cache_gclimit, cache_age, + data_mod}). @@ -224,9 +225,9 @@ info(MibServer, Type) -> call(MibServer, {info, Type}). dump(MibServer) -> - call(MibServer, dump). + dump(MibServer, io). -dump(MibServer, File) when is_list(File) -> +dump(MibServer, File) when (File =:= io) orelse is_list(File) -> call(MibServer, {dump, File}). backup(MibServer, BackupDir) when is_list(BackupDir) -> @@ -256,7 +257,7 @@ init([Prio, Mibs, Opts]) -> do_init(Prio, Mibs, Opts) -> process_flag(priority, Prio), process_flag(trap_exit, true), - put(sname,ms), + put(sname, ms), put(verbosity, ?vvalidate(get_verbosity(Opts))), ?vlog("starting",[]), @@ -289,13 +290,19 @@ do_init(Prio, Mibs, Opts) -> 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, + MibDataMod = get_data_mod(Opts), + ?vtrace("init -> try create mib data with" + "~n MeOverride: ~p" + "~n TeOverride: ~p" + "~n MibStorage: ~p", [MeOverride, TeOverride, MibStorage]), + Data = MibDataMod:new(MibStorage), + ?vdebug("init -> mib data created", []), + case (catch mib_operations(MibDataMod, + load_mib, Mibs, Data, MeOverride, TeOverride, true)) of {ok, Data2} -> ?vdebug("started",[]), - snmpa_mib_data:sync(Data2), + MibDataMod:sync(Data2), ?vdebug("mib data synced",[]), {ok, #state{data = Data2, teo = TeOverride, @@ -304,7 +311,8 @@ do_init(Prio, Mibs, Opts) -> cache_tmr = CacheGcTimer, cache_autogc = CacheAutoGC, cache_gclimit = CacheGcLimit, - cache_age = CacheAge}}; + cache_age = CacheAge, + data_mod = MibDataMod}}; {'aborted at', Mib, _NewData, Reason} -> ?vinfo("failed loading mib ~p: ~p",[Mib,Reason]), {error, {Mib, Reason}} @@ -315,32 +323,34 @@ do_init(Prio, Mibs, Opts) -> %% 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(Mod, Operation, Mibs, Data, MeOverride, TeOverride) -> + mib_operations(Mod, Operation, Mibs, Data, MeOverride, TeOverride, false). -mib_operations(_Operation, [], Data, _MeOverride, _TeOverride, _Force) -> +mib_operations(_Mod, _Operation, [], Data, _MeOverride, _TeOverride, _Force) -> {ok, Data}; -mib_operations(Operation, [Mib|Mibs], Data0, MeOverride, TeOverride, Force) -> +mib_operations(Mod, 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) + "~n Mibs: ~p" + "~n with " + "~n MeOverride: ~p" + "~n TeOverride: ~p" + "~n Force: ~p", + [Operation, Mibs, MeOverride, TeOverride, Force]), + Data = mib_operation(Mod, + Operation, Mib, Data0, MeOverride, TeOverride, Force), + mib_operations(Mod, Operation, Mibs, Data, MeOverride, TeOverride, Force). + +mib_operation(Mod, 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 + case apply(Mod, 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) -> + {error, not_loaded} when (Operation =:= unload_mib) andalso + (Force =:= true) -> ?vlog("ignore mib ~p -> not loaded", [Mib]), Data0; {error, Reason} -> @@ -350,7 +360,7 @@ mib_operation(Operation, Mib, Data0, MeOverride, TeOverride, Force) {ok, Data} -> Data end; -mib_operation(_Op, Mib, Data, _MeOverride, _TeOverride, _Force) -> +mib_operation(_Mod, _Op, Mib, Data, _MeOverride, _TeOverride, _Force) -> throw({'aborted at', Mib, Data, bad_mibname}). @@ -395,15 +405,15 @@ handle_call({update_cache_opts, Key, Value}, _From, State) -> {reply, Result, NewState}; handle_call({lookup, Oid}, _From, - #state{data = Data, cache = Cache} = State) -> + #state{data = Data, cache = Cache, data_mod = Mod} = 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}; + {Mod:lookup(Data, Oid), State}; [] -> - Rep = snmpa_mib_data:lookup(Data, Oid), + Rep = Mod:lookup(Data, Oid), ets:insert(Cache, {Key, Rep, timestamp()}), {Rep, maybe_start_cache_gc_timer(State)}; [{Key, Rep, _}] -> @@ -414,22 +424,23 @@ handle_call({lookup, Oid}, _From, ?vdebug("lookup -> Reply: ~p", [Reply]), {reply, Reply, NewState}; -handle_call({which_mib, Oid}, _From, #state{data = Data} = State) -> +handle_call({which_mib, Oid}, _From, + #state{data = Data, data_mod = Mod} = State) -> ?vlog("which_mib ~p",[Oid]), - Reply = snmpa_mib_data:which_mib(Data, Oid), + Reply = Mod:which_mib(Data, Oid), ?vdebug("which_mib: ~p",[Reply]), {reply, Reply, State}; handle_call({next, Oid, MibView}, _From, - #state{data = Data, cache = Cache} = State) -> + #state{data = Data, cache = Cache, data_mod = Mod} = 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}; + {Mod:next(Data, Oid, MibView), State}; [] -> - Rep = snmpa_mib_data:next(Data, Oid, MibView), + Rep = Mod:next(Data, Oid, MibView), ets:insert(Cache, {Key, Rep, timestamp()}), {Rep, maybe_start_cache_gc_timer(State)}; [{Key, Rep, _}] -> @@ -441,89 +452,99 @@ handle_call({next, Oid, MibView}, _From, {reply, Reply, NewState}; handle_call({load_mibs, Mibs}, _From, - #state{data = Data, - teo = TeOverride, - meo = MeOverride, - cache = Cache} = State) -> + #state{data = Data, + teo = TeOverride, + meo = MeOverride, + cache = Cache, + data_mod = Mod} = State) -> ?vlog("load mibs ~p",[Mibs]), %% Invalidate cache NewCache = maybe_invalidate_cache(Cache), - {NData,Reply} = - case (catch mib_operations(load_mib, Mibs, Data, + {NData, Reply} = + case (catch mib_operations(Mod, 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}}}; + {NewData, {error, {'load aborted at', Mib, Reason}}}; {ok, NewData} -> - {NewData,ok} + {NewData, ok} end, - snmpa_mib_data:sync(NData), + Mod: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) -> + #state{data = Data, + teo = TeOverride, + meo = MeOverride, + cache = Cache, + data_mod = Mod} = 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, + {NData, Reply} = + case (catch mib_operations(Mod, unload_mib, Mibs, Data, MeOverride, TeOverride)) of {'aborted at', Mib, NewData, Reason} -> - ?vlog("aborted at ~p for reason ~p",[Mib,Reason]), + ?vlog("aborted at ~p for reason ~p", [Mib,Reason]), {NewData, {error, {'unload aborted at', Mib, Reason}}}; {ok, NewData} -> - {NewData,ok} + {NewData, ok} end, - snmpa_mib_data:sync(NData), + Mod:sync(NData), {reply, Reply, State#state{data = NData, cache = NewCache}}; -handle_call(which_mibs, _From, #state{data = Data} = State) -> +handle_call(which_mibs, _From, #state{data = Data, data_mod = Mod} = State) -> ?vlog("which mibs",[]), - Reply = snmpa_mib_data:which_mibs(Data), + Reply = Mod:which_mibs(Data), {reply, Reply, State}; -handle_call({whereis_mib, Mib}, _From, #state{data = Data} = State) -> +handle_call({whereis_mib, Mib}, _From, + #state{data = Data, + data_mod = Mod} = State) -> ?vlog("whereis mib: ~p",[Mib]), - Reply = snmpa_mib_data:whereis_mib(Data, Mib), + Reply = Mod:whereis_mib(Data, Mib), {reply, Reply, State}; handle_call({register_subagent, Oid, Pid}, _From, - #state{data = Data, cache = Cache} = State) -> + #state{data = Data, + cache = Cache, + data_mod = Mod} = 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 + case Mod:register_subagent(Data, Oid, Pid) of {error, Reason} -> ?vlog("registration failed: ~p",[Reason]), {reply, {error, Reason}, State#state{cache = NewCache}}; - NewData -> + {ok, NewData} -> {reply, ok, State#state{data = NewData, cache = NewCache}} end; handle_call({unregister_subagent, OidOrPid}, _From, - #state{data = Data, cache = Cache} = State) -> + #state{data = Data, + cache = Cache, + data_mod = Mod} = State) -> ?vlog("unregister subagent ~p",[OidOrPid]), %% Invalidate cache NewCache = maybe_invalidate_cache(Cache), - case snmpa_mib_data:unregister_subagent(Data, OidOrPid) of + case Mod:unregister_subagent(Data, OidOrPid) of + {ok, NewData} -> + {reply, ok, State#state{data = NewData, cache = NewCache}}; {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}} + {reply, {error, Reason}, State#state{cache = NewCache}} end; -handle_call(info, _From, #state{data = Data, cache = Cache} = State) -> +handle_call(info, _From, #state{data = Data, + cache = Cache, + data_mod = Mod} = State) -> ?vlog("info",[]), Reply = - case (catch snmpa_mib_data:info(Data)) of + case (catch Mod:info(Data)) of Info when is_list(Info) -> [{cache, size_cache(Cache)} | Info]; E -> @@ -531,10 +552,12 @@ handle_call(info, _From, #state{data = Data, cache = Cache} = State) -> end, {reply, Reply, State}; -handle_call({info, Type}, _From, #state{data = Data} = State) -> +handle_call({info, Type}, _From, + #state{data = Data, + data_mod = Mod} = State) -> ?vlog("info ~p",[Type]), Reply = - case (catch snmpa_mib_data:info(Data, Type)) of + case (catch Mod:info(Data, Type)) of Info when is_list(Info) -> Info; E -> @@ -542,21 +565,19 @@ handle_call({info, Type}, _From, #state{data = Data} = State) -> 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) -> +handle_call({dump, File}, _From, + #state{data = Data, data_mod = Mod} = State) -> ?vlog("dump on ~s",[File]), - Reply = snmpa_mib_data:dump(Data, File), + Reply = Mod:dump(Data, File), {reply, Reply, State}; %% This check (that there is no backup already in progress) is also %% done in the master agent process, but just in case a user issues %% a backup call to this process directly, we add a similar check here. handle_call({backup, BackupDir}, From, - #state{backup = undefined, data = Data} = State) -> + #state{backup = undefined, + data = Data, + data_mod = Mod} = State) -> ?vlog("backup to ~s", [BackupDir]), Pid = self(), V = get(verbosity), @@ -568,7 +589,7 @@ handle_call({backup, BackupDir}, From, put(sname, ambs), put(verbosity, V), Dir = filename:join([BackupDir]), - Reply = snmpa_mib_data:backup(Data, Dir), + Reply = Mod:backup(Data, Dir), Pid ! {backup_done, Reply}, unlink(Pid) end), @@ -637,8 +658,8 @@ 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), +terminate(_Reason, #state{data = Data, data_mod = Mod}) -> + catch Mod:close(Data), ok. @@ -655,6 +676,11 @@ terminate(_Reason, #state{data = Data}) -> %% S2 = {state, Data, MEO, TEO, B}, %% {ok, S2}; +code_change({down, Vsn}, #state{data = Data0, data_mod = Mod} = State, Extra) -> + Data = Mod:code_change(down, Vsn, Extra, Data0), + {ok, State#state{data = Data}}; + + %% %% upgrade %% %% %% code_change(_Vsn, S1, upgrade_from_pre_4_12) -> @@ -663,8 +689,9 @@ terminate(_Reason, #state{data = Data}) -> %% S2 = #state{data = Data, meo = MEO, teo = TEO, backup = B, cache = Cache}, %% {ok, S2}; -code_change(_Vsn, State, _Extra) -> - {ok, State}. +code_change(Vsn, #state{data = Data0, data_mod = Mod} = State, Extra) -> + Data = Mod:code_change(up, Vsn, Extra, Data0), + {ok, State#state{data = Data}}. %%----------------------------------------------------------------- @@ -681,7 +708,10 @@ get_te_override(Options) -> get_opt(trapentry_override, Options, false). get_mib_storage(Options) -> - get_opt(mib_storage, Options, ets). + get_opt(mib_storage, Options). + +get_data_mod(Options) -> + get_opt(data_module, Options, snmpa_mib_data_tttn). get_cacheopt_autogc(Cache, CacheOpts) -> IsValid = fun(AutoGC) when ((AutoGC =:= true) orelse @@ -868,6 +898,9 @@ timestamp() -> %% ---------------------------------------------------------------- +get_opt(Key, Options) -> + snmp_misc:get_option(Key, Options). + get_opt(Key, Options, Default) -> snmp_misc:get_option(Key, Options, Default). diff --git a/lib/snmp/src/agent/snmpa_mib_data.erl b/lib/snmp/src/agent/snmpa_mib_data.erl index b80d85d2ee..4d8a12b6c6 100644 --- a/lib/snmp/src/agent/snmpa_mib_data.erl +++ b/lib/snmp/src/agent/snmpa_mib_data.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% Copyright Ericsson AB 1996-2013. 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 @@ -18,1338 +18,92 @@ %% -module(snmpa_mib_data). -%%%----------------------------------------------------------------- -%%% This module implements the MIB internal data structures. -%%% An MIB Data Structure consists of three items; an ets-table, -%%% a tree and a list of registered subagents. -%%% The subagent information is consequently duplicated. It resides -%%% both in the tree and in the list. -%%% The ets-table contains all data associated with each variable, -%%% table, tableentry and tablecolumn in the MIB. -%%% The tree contains information of the Oids in the MIB. -%%% -%%% When a mib is loaded, the tree is built from the plain list -%%% in the binary file. -%%%----------------------------------------------------------------- --include("snmp_types.hrl"). --include("snmp_debug.hrl"). - --define(VMODULE,"MDATA"). --include("snmp_verbosity.hrl"). - --define(MIB_DATA,snmpa_mib_data). --define(MIB_NODE,snmpa_mib_node). --define(MIB_TREE,snmpa_mib_tree). --define(DUMMY_TREE_GENERATION,1). --define(DEFAULT_TREE,{tree,{undefined_node},internal}). -%%-define(DUMMY_TREE_DB,dummy_tree_db). -%%-define(DUMMY_TREE_DB_INIT,{?DUMMY_TREE_DB,?DEFAULT_TREE}). - +-include_lib("snmp/include/snmp_types.hrl"). %%%----------------------------------------------------------------- -%%% Table of contents -%%% ================= -%%% 1. Interface -%%% 2. Implementation of tree access -%%% 3. Tree building functions -%%% 4. Tree merging -%%% 5. Tree deletion routines -%%% 6. Functions for subagent handling -%%% 7. Misc functions +%%% This is the behaviour for the MIB server backend internal +%%% data storage. %%%----------------------------------------------------------------- +%% These types should really be defined elsewhere... +-export_type([ + mib_view/0, + mib_view_elem/0, + mib_view_mask/0, + mib_view_inclusion/0 + ]). -%%---------------------------------------------------------------------- -%% data_db is an database containing loaded mibs as: -%% {MibName = atom(), Symbolic = ?, FullFileName = string()} -%% it is either ets or mnesia -%% tree_db is a database containing _one_ record with the tree! -%% (the reason for this is part to get replication and part out of convenience) -%% ref_tree is the root node, without any subagent. -%% tree is the root node (same as ref_tree but with the subagents added). -%% subagents is a list of {SAPid, Oid} -%%---------------------------------------------------------------------- --record(mib_data, {mib_db, % table of #mib_info - node_db, % table of #node_info - tree_db, % table of #tree - tree, % The actual tree - subagents = []}). - --record(mib_info, {name, symbolic, file_name}). --record(node_info, {oid, mib_name, me}). - - -%% API --export([new/0, new/1, sync/1, close/1, - load_mib/4, unload_mib/4, which_mibs/1, whereis_mib/2, - info/1, info/2, - dump/1, dump/2, - backup/2, - lookup/2, next/3, which_mib/2, - register_subagent/3, unregister_subagent/2]). - -%% Internal exports --export([code_change/2]). - - -%%----------------------------------------------------------------- -%% A tree is represented as a N-tuple, where each element is a -%% node. A node is: -%% 1) {tree, Tree, Info} where Info can be {table, Id}, {table_entry, Id} -%% or perhaps 'internal' -%% 2) undefined_node (memory optimization (instead of {node, undefined})) -%% 3) {node, Info} where Info can be {subagent, Pid}, {variable, Id}, -%% {table_column, Id} -%% Id is {MibName, MibEntry} -%% The over all root is represented as {tree, Tree, internal}. -%% -%% tree() = {tree, nodes(), tree_info()} -%% nodes() = {tree() | node() | undefined_node, ...} -%% node() = {node, node_info()} -%% tree_info() = {table, Id} | {table_entry, Id} | internal -%% node_info() = {subagent, Pid} | {variable, Id} | {table_colum, Id} -%%----------------------------------------------------------------- - -%% This record is what is stored in the database. The 'tree' part -%% is described above... --record(tree,{generation = ?DUMMY_TREE_GENERATION, root = ?DEFAULT_TREE}). - - -%%%====================================================================== -%%% 1. Interface -%%%====================================================================== - -%%----------------------------------------------------------------- -%% Func: new/0, new/1 -%% Returns: A representation of mib data. -%%----------------------------------------------------------------- -new() -> - new(ets). - -%% Where -> A list of nodes where the tables will be created -new(Storage) -> - %% First we must check if there is already something to read - %% If a database already exists, then the tree structure has to be read - ?vtrace("open (mib) database",[]), - MibDb = snmpa_general_db:open(Storage, ?MIB_DATA, - mib_info, - record_info(fields,mib_info), set), - ?vtrace("open (mib) node database",[]), - NodeDb = snmpa_general_db:open(Storage, ?MIB_NODE, - node_info, - record_info(fields,node_info), set), - ?vtrace("open (mib) tree database",[]), - TreeDb = snmpa_general_db:open(Storage, ?MIB_TREE, - tree, - record_info(fields,tree), set), - Tree = - case snmpa_general_db:read(TreeDb, ?DUMMY_TREE_GENERATION) of - false -> - T = #tree{}, - snmpa_general_db:write(TreeDb, T), - T; - {value, T} -> - T - end, - install_mibs(MibDb, NodeDb), - #mib_data{mib_db = MibDb, - node_db = NodeDb, - tree_db = TreeDb, - tree = Tree}. - - -%%---------------------------------------------------------------------- -%% Returns: new mib data | {error, Reason} -%%---------------------------------------------------------------------- -load_mib(MibData,FileName,MeOverride,TeOverride) - when is_record(MibData,mib_data) andalso is_list(FileName) -> - ?vlog("load mib file: ~p",[FileName]), - ActualFileName = filename:rootname(FileName, ".bin") ++ ".bin", - MibName = list_to_atom(filename:basename(FileName, ".bin")), - (catch do_load_mib(MibData, ActualFileName, MibName, - MeOverride, TeOverride)). - -do_load_mib(MibData, ActualFileName, MibName, MeOverride, TeOverride) -> - ?vtrace("do_load_mib -> entry with" - "~n ActualFileName: ~s" - "~n MibName: ~p",[ActualFileName, MibName]), - #mib_data{mib_db = MibDb, - node_db = NodeDb, - %% tree_db = TreeDb, - tree = Tree} = MibData, - verify_not_loaded(MibDb, MibName), - ?vtrace("do_load_mib -> already loaded mibs:" - "~n ~p",[loaded(MibDb)]), - Mib = do_read_mib(ActualFileName), - ?vtrace("do_load_mib -> read mib ~s",[Mib#mib.name]), - NonInternalMes = - lists:filter(fun(ME) -> maybe_drop_me(ME) end, Mib#mib.mes), - OldRoot = Tree#tree.root, - T = build_tree(NonInternalMes, MibName), - ?d("load_mib -> " - "~n OldRoot: ~p" - "~n T: ~p", [OldRoot, T]), - case (catch merge_nodes(T, OldRoot)) of - {error_merge_nodes, Node1, Node2} -> - ?vlog("error merging nodes:" - "~n~p~nand~n~p", [Node1,Node2]), - {error, oid_conflict}; - NewRoot when is_tuple(NewRoot) andalso (element(1,NewRoot) =:= tree) -> - ?d("load_mib -> " - "~n NewRoot: ~p", [NewRoot]), - Symbolic = not lists:member(no_symbolic_info, Mib#mib.misc), - case (catch check_notif_and_mes(TeOverride, MeOverride, Symbolic, - Mib#mib.traps, NonInternalMes)) of - true -> - install_mes(NodeDb, MibName, NonInternalMes), - install_mib(MibDb, Symbolic, Mib, - MibName, ActualFileName, NonInternalMes), - ?vtrace("installed mib ~s", [Mib#mib.name]), - Tree2 = Tree#tree{root = NewRoot}, - %% snmpa_general_db:write(TreeDb, Tree2), %% Store later? - {ok, MibData#mib_data{tree = Tree2}}; - Else -> - Else - end - end. - - -verify_not_loaded(Db, Name) -> - case snmpa_general_db:read(Db, Name) of - {value, #mib_info{name = Name}} -> - throw({error, 'already loaded'}); - false -> - ok - end. - -do_read_mib(ActualFileName) -> - case snmp_misc:read_mib(ActualFileName) of - {error, Reason} -> - ?vlog("Failed reading mib file ~p with reason: ~p", - [ActualFileName,Reason]), - throw({error, Reason}); - {ok, Mib} -> - Mib - end. - -%% The Tree DB is handled in a special way since it can be very large. -sync(#mib_data{mib_db = M, - node_db = N, - tree_db = T, tree = Tree, subagents = []}) -> - snmpa_general_db:sync(M), - snmpa_general_db:sync(N), - snmpa_general_db:write(T, Tree), - snmpa_general_db:sync(T); -sync(#mib_data{mib_db = M, - node_db = N, - tree_db = T, tree = Tree, subagents = SAs}) -> - - snmpa_general_db:sync(M), - snmpa_general_db:sync(N), - - %% Ouch. Since the subagent info is dynamic we do not - %% want to store the tree containing subagent info. So, we - %% have to create a tmp tree without those and store it. - - case delete_subagents(Tree, SAs) of - {ok, TreeWithoutSAs} -> - snmpa_general_db:write(T, TreeWithoutSAs), - snmpa_general_db:sync(T); - Error -> - Error - end. - -delete_subagents(Tree, []) -> - {ok, Tree}; -delete_subagents(Tree0, [{_, Oid}|SAs]) -> - case (catch delete_subagent(Tree0, Oid)) of - {tree, _Tree, _Info} = Tree1 -> - delete_subagents(Tree1, SAs); - _Error -> - {error, {'invalid oid', Oid}} - end. - -%%---------------------------------------------------------------------- -%% (OTP-3601) -%%---------------------------------------------------------------------- -check_notif_and_mes(TeOverride,MeOverride,Symbolic,Traps,MEs) -> - ?vtrace("check notifications and mib entries",[]), - check_notifications(TeOverride,Symbolic,Traps), - check_mes(MeOverride,MEs). - -check_notifications(true, _Symbolic, _Traps) -> - ?vtrace("trapentry override = true => skip check",[]), - true; -check_notifications(_, Symbolic, Traps) -> - check_notifications(Symbolic, Traps). - -check_notifications(true, Traps) -> - check_notifications(Traps); -check_notifications(_, _) -> true. - -check_notifications([]) -> true; -check_notifications([#trap{trapname = Key} = Trap | Traps]) -> - ?vtrace("check notification [trap] with Key: ~p",[Key]), - case snmpa_symbolic_store:get_notification(Key) of - {value, Trap} -> check_notifications(Traps); - {value, _} -> throw({error, {'trap already defined', Key}}); - undefined -> check_notifications(Traps) - end; -check_notifications([#notification{trapname = Key} = Notif | Traps]) -> - ?vtrace("check notification [notification] with Key: ~p",[Key]), - case snmpa_symbolic_store:get_notification(Key) of - {value, Notif} -> - check_notifications(Traps); - {value, _} -> - throw({error, {'notification already defined', Key}}); - undefined -> - check_notifications(Traps) - end; -check_notifications([Crap | Traps]) -> - ?vlog("skipped check of: ~n~p",[Crap]), - check_notifications(Traps). - -check_mes(true,_) -> - ?vtrace("mibentry override = true => skip check",[]), - true; -check_mes(_,MEs) -> - check_mes(MEs). - -check_mes([]) -> true; -check_mes([#me{aliasname = Name, oid = Oid1} | MEs]) -> - ?vtrace("check mib entries with aliasname: ~p",[Name]), - case snmpa_symbolic_store:aliasname_to_oid(Name) of - {value, Oid1} -> - check_mes(MEs); - {value, Oid2} -> - ?vinfo("~n expecting '~p'~n but found '~p'",[Oid1, Oid2]), - throw({error, {'mibentry already defined', Name}}); - false -> - check_mes(MEs) - end; -check_mes([Crap | MEs]) -> - ?vlog("skipped check of: ~n~p",[Crap]), - check_mes(MEs). - - - -%%---------------------------------------------------------------------- -%% Returns: new mib data | {error, Reason} -%%---------------------------------------------------------------------- -unload_mib(MibData, FileName, _, _) when is_list(FileName) -> - MibName = list_to_atom(filename:basename(FileName, ".bin")), - (catch do_unload_mib(MibData, MibName)). - -do_unload_mib(MibData, MibName) -> - ?vtrace("do_unload_mib -> entry with" - "~n MibName: ~p", [MibName]), - #mib_data{mib_db = MibDb, - node_db = NodeDb, - %% tree_db = TreeDb, - tree = Tree} = MibData, - #mib_info{symbolic = Symbolic} = verify_loaded(MibDb, MibName), - NewRoot = delete_mib_from_tree(MibName, Tree#tree.root), - MEs = uninstall_mes(NodeDb, MibName), - uninstall_mib(MibDb, Symbolic, MibName, MEs), - NewMibData = MibData#mib_data{tree = Tree#tree{root = NewRoot}}, - {ok, NewMibData}. - -verify_loaded(Db, Name) -> - case snmpa_general_db:read(Db, Name) of - {value, MibInfo} -> - MibInfo; - false -> - throw({error, 'not loaded'}) - end. - - -close(#mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb}) -> - snmpa_general_db:close(MibDb), - snmpa_general_db:close(NodeDb), - snmpa_general_db:close(TreeDb), - ok. - -register_subagent(#mib_data{tree = T} = MibData, Oid, Pid) -> - case insert_subagent(Oid, T#tree.root) of - {error, Reason} -> - {error, Reason}; - NewRootTree -> - SAs = [{Pid, Oid} | MibData#mib_data.subagents], - T2 = T#tree{root = NewRootTree}, - MibData#mib_data{tree = T2, subagents = SAs} - end. - - -%%---------------------------------------------------------------------- -%% Purpose: Get a list of all loaded mibs -%% Returns: [{Name, File}] -%%---------------------------------------------------------------------- - -which_mibs(#mib_data{mib_db = Db}) -> - Mibs = snmpa_general_db:tab2list(Db), - [{Name, File} || #mib_info{name = Name, file_name = File} <- Mibs]. - - -%%---------------------------------------------------------------------- -%% Purpose: Get a list of all loaded mibs -%% Returns: [{Name, File}] -%%---------------------------------------------------------------------- - -whereis_mib(#mib_data{mib_db = Db}, Name) -> - case snmpa_general_db:read(Db, Name) of - {value, #mib_info{file_name = File}} -> - {ok, File}; - false -> - {error, not_found} - end. - - -%%---------------------------------------------------------------------- -%% Purpose: Deletes SA with Pid from all subtrees it handles. -%% Returns: NewMibData. -%%---------------------------------------------------------------------- -unregister_subagent(MibData, Pid) when is_pid(Pid) -> - SAs = MibData#mib_data.subagents, - case lists:keysearch(Pid, 1, SAs) of - false -> MibData; - {value, {Pid, Oid}} -> - % we should never get an error since Oid is found in MibData. - {ok, NewMibData, _DeletedSA} = unregister_subagent(MibData, Oid), - % continue if the same Pid handles other mib subtrees. - unregister_subagent(NewMibData, Pid) - end; - -%%---------------------------------------------------------------------- -%% Purpose: Deletes one unique subagent. -%% Returns: {error, Reason} | {ok, NewMibData, DeletedSubagentPid} -%%---------------------------------------------------------------------- -unregister_subagent(#mib_data{tree = T} = MibData, Oid) when is_list(Oid) -> - case catch delete_subagent(T#tree.root, Oid) of - {tree, Tree, Info} -> - OldSAs = MibData#mib_data.subagents, - {value, {Pid, _Oid}} = lists:keysearch(Oid, 2, OldSAs), - SAs = lists:keydelete(Oid, 2, OldSAs), - T2 = T#tree{root = {tree, Tree, Info}}, - {ok, - MibData#mib_data{tree = T2, subagents = SAs}, - Pid}; - _ -> - {error, {'invalid oid', Oid}} - end. - -%%---------------------------------------------------------------------- -%% Purpose: To inpect memory usage, loaded mibs, registered subagents -%%---------------------------------------------------------------------- -info(MibData) -> - ?vtrace("retrieve info",[]), - #mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb, - tree = Tree, subagents = SAs} = MibData, - LoadedMibs = old_format(snmpa_general_db:tab2list(MibDb)), - TreeSize = snmp_misc:mem_size(Tree), - {memory, ProcSize} = erlang:process_info(self(),memory), - MibDbSize = snmpa_general_db:info(MibDb, memory), - NodeDbSize = snmpa_general_db:info(NodeDb, memory), - TreeDbSize = snmpa_general_db:info(TreeDb, memory), - [{loaded_mibs, LoadedMibs}, {subagents, SAs}, {tree_size_bytes, TreeSize}, - {process_memory, ProcSize}, - {db_memory, [{mib,MibDbSize},{node,NodeDbSize},{tree,TreeDbSize}]}]. - -info(#mib_data{mib_db = MibDb}, loaded_mibs) -> - Mibs = snmpa_general_db:tab2list(MibDb), - [filename:rootname(FN, ".bin") || #mib_info{file_name = FN} <- Mibs]; -info(#mib_data{tree = Tree}, tree_size_bytes) -> - snmp_misc:mem_size(Tree); -info(_, process_memory) -> - {memory, ProcSize} = erlang:process_info(self(),memory), - ProcSize; -info(#mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb}, - db_memory) -> - MibDbSize = snmpa_general_db:info(MibDb, memory), - NodeDbSize = snmpa_general_db:info(NodeDb, memory), - TreeDbSize = snmpa_general_db:info(TreeDb, memory), - [{mib,MibDbSize},{node,NodeDbSize},{tree,TreeDbSize}]; -info(#mib_data{subagents = SAs}, subagents) -> - SAs. - -old_format(LoadedMibs) -> - ?vtrace("convert mib info to old format",[]), - [{N,S,F} || #mib_info{name=N,symbolic=S,file_name=F} <- LoadedMibs]. - - -%%---------------------------------------------------------------------- -%% A total dump for debugging. -%%---------------------------------------------------------------------- -dump(#mib_data{mib_db = MibDb, node_db = NodeDb, tree = Tree}) -> - (catch io:format("MIB-tables:~n~p~n~n", - [snmpa_general_db:tab2list(MibDb)])), - (catch io:format("MIB-entries:~n~p~n~n", - [snmpa_general_db:tab2list(NodeDb)])), - (catch io:format("Tree:~n~p~n", [Tree])), % good luck reading it! - ok. - -dump(#mib_data{mib_db = MibDb, node_db = NodeDb, tree = Tree}, File) -> - case file:open(File,[write]) of - {ok, Fd} -> - io:format(Fd,"~s~n", - [snmp:date_and_time_to_string(snmp:date_and_time())]), - (catch io:format(Fd,"MIB-tables:~n~p~n~n", - [snmpa_general_db:tab2list(MibDb)])), - (catch io:format(Fd, "MIB-entries:~n~p~n~n", - [snmpa_general_db:tab2list(NodeDb)])), - io:format(Fd,"Tree:~n~p~n", [Tree]), % good luck reading it! - file:close(Fd), - ok; - {error,Reason} -> - ?vinfo("~n Failed opening file '~s' for reason ~p", - [File,Reason]), - {error,Reason} - end. - - -backup(#mib_data{mib_db = M, node_db = N, tree_db = T}, BackupDir) -> - MRes = snmpa_general_db:backup(M, BackupDir), - NRes = snmpa_general_db:backup(N, BackupDir), - TRes = snmpa_general_db:backup(T, BackupDir), - handle_backup_res([{mib_db, MRes}, {node_db, NRes}, {tree_db, TRes}]). - -handle_backup_res(Res) -> - handle_backup_res(Res, []). - -handle_backup_res([], []) -> - ok; -handle_backup_res([], Err) -> - {error, lists:reverse(Err)}; -handle_backup_res([{_, ok}|Res], Err) -> - handle_backup_res(Res, Err); -handle_backup_res([{Tag, {error, Reason}}|Res], Err) -> - handle_backup_res(Res, [{Tag, Reason}|Err]); -handle_backup_res([{Tag, Error}|Res], Err) -> - handle_backup_res(Res, [{Tag, Error}|Err]). - - -%%%====================================================================== -%%% 2. Implementation of tree access -%%% lookup and next. -%%%====================================================================== - - -which_mib(#mib_data{tree = T} = D, Oid) -> - ?vtrace("which_mib -> entry with" - "~n Oid: ~p",[Oid]), - case (catch find_node(D, T#tree.root, Oid, [])) of - {variable, _ME, Mib} -> - ?vtrace("which_mib -> variable:" - "~n Mib: ~p", [Mib]), - {ok, Mib}; - {table, _EntryME, _, Mib} -> - ?vtrace("which_mib -> table:" - "~n Mib: ~p", [Mib]), - {ok, Mib}; - {subagent, SubAgentPid, _SANextOid} -> - ?vtrace("which_mib -> subagent:" - "~n SubAgentPid: ~p", [SubAgentPid]), - {error, {subagent, SubAgentPid}}; - {false, ErrorCode} -> - ?vtrace("which_mib -> false:" - "~n ErrorCode: ~p",[ErrorCode]), - {error, ErrorCode}; - false -> - ?vtrace("which_mib -> false",[]), - {error, noSuchObject}; - {'EXIT', R} -> - ?vtrace("which_mib -> exit:" - "~n R: ~p",[R]), - {error, noSuchObject} - end. - - -%%----------------------------------------------------------------- -%% 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, SAOid} | -%% {false, Reason} -%%----------------------------------------------------------------- -lookup(#mib_data{tree = T} = D, Oid) -> - ?vtrace("lookup -> entry with" - "~n Oid: ~p",[Oid]), - case (catch find_node(D, T#tree.root, Oid, [])) of - {variable, ME, _Mib} when is_record(ME, me) -> - ?vtrace("lookup -> variable:" - "~n ME: ~p",[ME]), - {variable, ME}; - {table, EntryME, {ColME, TableEntryOid}, _Mib} -> - ?vtrace("lookup -> table:" - "~n EntryME: ~p" - "~n ColME: ~p" - "~n RevTableEntryOid: ~p", - [EntryME, ColME, TableEntryOid]), - MFA = EntryME#me.mfa, - RetME = ColME#me{mfa = MFA}, - {table_column, RetME, TableEntryOid}; - {subagent, SubAgentPid, SANextOid} -> - ?vtrace("lookup -> subagent:" - "~n SubAgentPid: ~p" - "~n SANextOid: ~p", [SubAgentPid, SANextOid]), - {subagent, SubAgentPid, SANextOid}; - {false, ErrorCode} -> - ?vtrace("lookup -> false:" - "~n ErrorCode: ~p",[ErrorCode]), - {false, ErrorCode}; - false -> - ?vtrace("lookup -> false",[]), - {false, noSuchObject}; - {'EXIT', R} -> - ?vtrace("lookup -> exit:" - "~n R: ~p",[R]), - {false, noSuchObject} - end. - - -find_node(D, {tree, Tree, {table, _}}, RestOfOid, RevOid) -> - ?vtrace("find_node(tree,table) -> entry with" - "~n RestOfOid: ~p" - "~n RevOid: ~p",[RestOfOid, RevOid]), - find_node(D, {tree, Tree, internal}, RestOfOid, RevOid); -find_node(D, {tree, Tree, {table_entry, _}}, RestOfOid, RevOid) -> - ?vtrace("find_node(tree,table_entry) -> entry with" - "~n RestOfOid: ~p" - "~n RevOid: ~p",[RestOfOid, RevOid]), - #mib_data{node_db = Db} = D, - Oid = lists:reverse(RevOid), - case snmpa_general_db:read(Db, Oid) of - {value, #node_info{me = ME, mib_name = Mib}} -> - case find_node(D, {tree, Tree, internal}, RestOfOid, RevOid) of - {false, ErrorCode} -> {false, ErrorCode}; - Val -> {table, ME, Val, Mib} - end; - false -> - ?vinfo("find_node -> could not find table_entry ME with" - "~n RevOid: ~p" - "~n when" - "~n RestOfOid: ~p", - [RevOid, RestOfOid]), - false - end; -find_node(D, {tree, Tree, _Internal}, [Int | RestOfOid], RevOid) -> - ?vtrace("find_node(tree) -> entry with" - "~n Int: ~p" - "~n RestOfOid: ~p" - "~n RevOid: ~p",[Int, RestOfOid, RevOid]), - find_node(D, element(Int+1, Tree), RestOfOid, [Int | RevOid]); -find_node(D, {node, {table_column, _}}, RestOfOid, [ColInt | RevOid]) -> - ?vtrace("find_node(tree,table_column) -> entry with" - "~n RestOfOid: ~p" - "~n ColInt: ~p" - "~n RevOid: ~p",[RestOfOid, ColInt, RevOid]), - #mib_data{node_db = Db} = D, - Oid = lists:reverse([ColInt | RevOid]), - case snmpa_general_db:read(Db, Oid) of - {value, #node_info{me = ME}} -> - {ME, lists:reverse(RevOid)}; - false -> - X = snmpa_general_db:read(Db, lists:reverse([ColInt | RevOid])), - ?vinfo("find_node -> could not find table_column ME with" - "~n RevOid: ~p" - "~n trying [~p|~p]" - "~n X: ~p", - [RevOid, [ColInt | RevOid], X]), - false - end; -find_node(D, {node, {variable, _MibName}}, [0], RevOid) -> - ?vtrace("find_node(tree,variable,[0]) -> entry with" - "~n RevOid: ~p",[RevOid]), - #mib_data{node_db = Db} = D, - Oid = lists:reverse(RevOid), - %% {value, #node_info{me = ME}} = snmpa_general_db:read(Db, Oid), - case snmpa_general_db:read(Db, Oid) of - {value, #node_info{me = ME, mib_name = Mib}} -> - {variable, ME, Mib}; - false -> - ?vinfo("find_node -> could not find variable ME with" - "~n RevOid: ~p", [RevOid]), - false - end; -find_node(_D, {node, {variable, _MibName}}, [], _RevOid) -> - ?vtrace("find_node(tree,variable,[]) -> entry",[]), - {false, noSuchObject}; -find_node(_D, {node, {variable, _MibName}}, _, _RevOid) -> - ?vtrace("find_node(tree,variable) -> entry",[]), - {false, noSuchInstance}; -find_node(D, {node, subagent}, _RestOfOid, SARevOid) -> - ?vtrace("find_node(tree,subagent) -> entry with" - "~n SARevOid: ~p",[SARevOid]), - #mib_data{subagents = SAs} = D, - SAOid = lists:reverse(SARevOid), - case lists:keysearch(SAOid, 2, SAs) of - {value, {SubAgentPid, SAOid}} -> - {subagent, SubAgentPid, SAOid}; - false -> - ?vinfo("find_node -> could not find subagent with" - "~n SAOid: ~p" - "~n SAs: ~p", [SAOid, SAs]), - false - end; -find_node(_D, Node, _RestOfOid, _RevOid) -> - ?vtrace("find_node -> failed:~n~p",[Node]), - {false, noSuchObject}. - - -%%----------------------------------------------------------------- -%% Func: next/3 -%% Purpose: Finds the lexicographically next oid. -%% Returns: endOfMibView | -%% {subagent, SubAgentPid, SAOid} | -%% {variable, MibEntry, VarOid} | -%% {table, TableOid, TableRestOid, MibEntry} -%% If a variable is returnes, it is in the MibView. -%% If a table or subagent is returned, it *may* be in the MibView. -%%----------------------------------------------------------------- -next(#mib_data{tree = T} = D, Oid, MibView) -> - case catch next_node(D, T#tree.root, Oid, [], MibView) of - false -> endOfMibView; - Else -> Else - end. - -%%----------------------------------------------------------------- -%% This function is used as long as we have any Oid left. Take -%% one integer at a time from the Oid, and traverse the tree -%% accordingly. When the Oid is empty, call find_next. -%% Returns: {subagent, SubAgentPid, SAOid} | -%% false | -%% {variable, MibEntry, VarOid} | -%% {table, TableOid, TableRestOid, MibEntry} -%%----------------------------------------------------------------- -next_node(_D, undefined_node, _Oid, _RevOidSoFar, _MibView) -> - ?vtrace("next_node(undefined_node) -> entry", []), - false; - -next_node(_D, {tree, Tree, {table_entry, _Id}}, [Int | _Oid], - _RevOidSoFar, _MibView) - when Int+1 > size(Tree) -> - ?vtrace("next_node(tree,table_entry) -> entry when not found whith" - "~n Int: ~p" - "~n size(Tree): ~p", [Int, size(Tree)]), - false; -next_node(D, {tree, Tree, {table_entry, _MibName}}, - Oid, RevOidSoFar, MibView) -> - ?vtrace("next_node(tree,table_entry) -> entry when" - "~n size(Tree): ~p" - "~n Oid: ~p" - "~n RevOidSoFar: ~p" - "~n MibView: ~p", [size(Tree), Oid, RevOidSoFar, MibView]), - OidSoFar = lists:reverse(RevOidSoFar), - case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of - true -> - ?vdebug("next_node(tree,table_entry) -> not in mib view",[]), - false; - _ -> - #mib_data{node_db = Db} = D, - case snmpa_general_db:read(Db, OidSoFar) of - false -> - ?vinfo("next_node -> could not find table_entry with" - "~n OidSoFar: ~p", [OidSoFar]), - false; - {value, #node_info{me = ME}} -> - ?vtrace("next_node(tree,table_entry) -> found: ~n ~p", - [ME]), - {table, OidSoFar, Oid, ME} - end - end; - -next_node(D, {tree, Tree, _Info}, [Int | RestOfOid], RevOidSoFar, MibView) - when (Int < size(Tree)) andalso (Int >= 0) -> - ?vtrace("next_node(tree) -> entry when" - "~n size(Tree): ~p" - "~n Int: ~p" - "~n RestOfOid: ~p" - "~n RevOidSoFar: ~p" - "~n MibView: ~p", - [size(Tree), Int, RestOfOid, RevOidSoFar, MibView]), - case next_node(D, element(Int+1,Tree), - RestOfOid, [Int|RevOidSoFar], MibView) of - false -> - find_next(D, {tree, Tree, _Info}, Int+1, RevOidSoFar, MibView); - Else -> - Else - end; -%% no solution -next_node(D, {tree, Tree, _Info}, [], RevOidSoFar, MibView) -> - ?vtrace("next_node(tree,[]) -> entry when" - "~n size(Tree): ~p" - "~n RevOidSoFar: ~p" - "~n MibView: ~p", - [size(Tree), RevOidSoFar, MibView]), - find_next(D, {tree, Tree, _Info}, 0, RevOidSoFar, MibView); -next_node(_D, {tree, Tree, _Info}, _RestOfOid, _RevOidSoFar, _MibView) -> - ?vtrace("next_node(tree) -> entry when" - "~n size(Tree): ~p", [size(Tree)]), - false; - -next_node(D, {node, subagent}, Oid, RevOidSoFar, MibView) -> - ?vtrace("next_node(node,subagent) -> entry when" - "~n Oid: ~p" - "~n RevOidSoFar: ~p" - "~n MibView: ~p", - [Oid, RevOidSoFar, MibView]), - OidSoFar = lists:reverse(RevOidSoFar), - case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of - true -> - false; - _ -> - #mib_data{subagents = SAs} = D, - case lists:keysearch(OidSoFar, 2, SAs) of - {value, {SubAgentPid, OidSoFar}} -> - {subagent, SubAgentPid, OidSoFar}; - _ -> - ?vinfo("next_node -> could not find subagent with" - "~n OidSoFar: ~p" - "~n SAs: ~p", [OidSoFar, SAs]), - false - end - end; - -next_node(D, {node, {variable, _MibName}}, [], RevOidSoFar, MibView) -> - ?vtrace("next_node(node,variable,[]) -> entry when" - "~n RevOidSoFar: ~p" - "~n MibView: ~p", - [RevOidSoFar, MibView]), - OidSoFar = lists:reverse([0 | RevOidSoFar]), - case snmpa_acm:validate_mib_view(OidSoFar, MibView) of - true -> - #mib_data{node_db = Db} = D, - case snmpa_general_db:read(Db, lists:reverse(RevOidSoFar)) of - false -> - ?vinfo("next_node -> could not find variable with" - "~n RevOidSoFar: ~p", [RevOidSoFar]), - false; - {value, #node_info{me = ME}} -> - {variable, ME, OidSoFar} - end; - _ -> - false - end; - -next_node(_D, {node, {variable, _MibName}}, _Oid, _RevOidSoFar, _MibView) -> - ?vtrace("next_node(node,variable) -> entry", []), - false. - -%%----------------------------------------------------------------- -%% This function is used to find the first leaf from where we -%% are. -%% Returns: {subagent, SubAgentPid, SAOid} | -%% false | -%% {variable, MibEntry, VarOid} | -%% {table, TableOid, TableRestOid, MibEntry} -%% PRE: This function must always be called with a {internal, Tree} -%% node. -%%----------------------------------------------------------------- -find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView) - when Idx < size(Tree) -> - case find_next(D, element(Idx+1, Tree), 0, [Idx| RevOidSoFar], MibView) of - false -> - find_next(D, {tree, Tree, internal}, Idx+1, RevOidSoFar, MibView); - Other -> - Other - end; -find_next(_D, {tree, _Tree, internal}, _Idx, _RevOidSoFar, _MibView) -> - false; -find_next(_D, undefined_node, _Idx, _RevOidSoFar, _MibView) -> - false; -find_next(D, {tree, Tree, {table, _MibName}}, Idx, RevOidSoFar, MibView) -> - find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView); -find_next(D, {tree, _Tree, {table_entry, _MibName}}, _Index, - RevOidSoFar, MibView) -> - OidSoFar = lists:reverse(RevOidSoFar), - case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of - true -> - false; - _ -> - #mib_data{node_db = Db} = D, - case snmpa_general_db:read(Db, OidSoFar) of - false -> - ?vinfo("find_next -> could not find table_entry ME with" - "~n OidSoFar: ~p", [OidSoFar]), - false; - {value, #node_info{me = ME}} -> - {table, OidSoFar, [], ME} - end - end; -find_next(D, {node, {variable, _MibName}}, _Idx, RevOidSoFar, MibView) -> - OidSoFar = lists:reverse([0 | RevOidSoFar]), - case snmpa_acm:validate_mib_view(OidSoFar, MibView) of - true -> - #mib_data{node_db = Db} = D, - case snmpa_general_db:read(Db, lists:reverse(RevOidSoFar)) of - false -> - ?vinfo("find_next -> could not find variable with" - "~n RevOidSoFar: ~p", [RevOidSoFar]), - false; - {value, #node_info{me = ME}} -> - {variable, ME, OidSoFar} - end; - _ -> - false - end; -find_next(D, {node, subagent}, _Idx, RevOidSoFar, MibView) -> - OidSoFar = lists:reverse(RevOidSoFar), - case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of - true -> - false; - _ -> - #mib_data{subagents = SAs} = D, - case lists:keysearch(OidSoFar, 2, SAs) of - {value, {SubAgentPid, OidSoFar}} -> - {subagent, SubAgentPid, OidSoFar}; - false -> - ?vinfo("find_node -> could not find subagent with" - "~n OidSoFar: ~p" - "~n SAs: ~p", [OidSoFar, SAs]), - false - end - end. - -%%%====================================================================== -%%% 3. Tree building functions -%%% Used when loading mibs. -%%%====================================================================== - -build_tree(Mes, MibName) -> - ?d("build_tree -> " - "~n Mes: ~p", [Mes]), - {ListTree, []} = build_subtree([], Mes, MibName), - {tree, convert_tree(ListTree), internal}. - -%%---------------------------------------------------------------------- -%% Purpose: Builds the tree where all oids have prefix equal to LevelPrefix. -%% Returns: {Tree, RestMes} -%% RestMes are Mes that should not be in this subtree. -%% The Tree is a temporary and simplified data structure that is easy to -%% convert to the final tuple tree used by the MIB process. -%% A Node is represented as in the final tree. -%% The tree is not represented as a N-tuple, but as an Index-list. -%% Example: Temporary: [{1, Node1}, {3, Node3}] -%% Final: {Node1, undefined_node, Node3} -%% Pre: Mes are sorted on oid. -%%---------------------------------------------------------------------- -build_subtree(LevelPrefix, [Me | Mes], MibName) -> - ?vtrace("build subtree -> ~n" - " oid: ~p~n" - " LevelPrefix: ~p~n" - " MibName: ~p", [Me#me.oid, LevelPrefix, MibName]), - EType = Me#me.entrytype, - ?vtrace("build subtree -> EType = ~p",[EType]), - case in_subtree(LevelPrefix, Me) of - above -> - ?vtrace("build subtree -> above",[]), - {[], [Me|Mes]}; - {node, Index} -> - ?vtrace("build subtree -> node at ~p",[Index]), - {Tree, RestMes} = build_subtree(LevelPrefix, Mes, MibName), - {[{Index, {node, {EType, MibName}}} | Tree], RestMes}; - {subtree, Index, NewLevelPrefix} -> - ?vtrace("build subtree -> subtree at" - "~n ~w with ~w", - [Index, NewLevelPrefix]), - {BelowTree, RestMes} = - build_subtree(NewLevelPrefix, Mes, MibName), - {CurTree, RestMes2} = - build_subtree(LevelPrefix, RestMes, MibName), - {[{Index, {tree, BelowTree, {EType,MibName}}}| CurTree], RestMes2}; - {internal_subtree, Index, NewLevelPrefix} -> - ?vtrace("build subtree -> internal_subtree at" - "~n ~w with ~w", - [Index,NewLevelPrefix]), - {BelowTree, RestMes} = - build_subtree(NewLevelPrefix, [Me | Mes], MibName), - {CurTree, RestMes2} = - build_subtree(LevelPrefix, RestMes, MibName), - {[{Index, {tree, BelowTree, internal}} | CurTree], RestMes2} - end; - -build_subtree(_LevelPrefix, [], _MibName) -> - ?vtrace("build subtree -> done", []), - {[], []}. - -%%-------------------------------------------------- -%% Purpose: Determine how/if/where Me should be inserted in subtree -%% with LevelPrefix. This function does not build any tree, only -%% determinses what should be done (by build subtree). -%% Returns: -%% above - Indicating that this ME should _not_ be in this subtree. -%% {node, Index} - yes, construct a node with index Index on this level -%% {internal_subtree, Index, NewLevelPrefix} - yes, there should be an -%% internal subtree at this index. -%% {subtree, Index, NewLevelPrefix} - yes, construct a subtree with -%% NewLevelPrefix and insert this on current level in position Index. -%%-------------------------------------------------- -in_subtree(LevelPrefix, Me) -> - case lists:prefix(LevelPrefix, Me#me.oid) of - true when length(Me#me.oid) > length(LevelPrefix) -> - classify_how_in_subtree(LevelPrefix, Me); - _ -> - above - end. - -%%-------------------------------------------------- -%% See comment about in_subtree/2. This function takes care of all cases -%% where the ME really should be in _this_ subtree (not above). -%%-------------------------------------------------- -classify_how_in_subtree(LevelPrefix, Me) - when (length(Me#me.oid) =:= (length(LevelPrefix) + 1)) -> - Oid = Me#me.oid, - case node_or_subtree(Me#me.entrytype) of - subtree -> - {subtree, lists:last(Oid), Oid}; - node -> - {node, lists:last(Oid)} - end; - -classify_how_in_subtree(LevelPrefix, Me) - when (length(Me#me.oid) > (length(LevelPrefix) + 1)) -> - L1 = length(LevelPrefix) + 1, - Oid = Me#me.oid, - {internal_subtree, lists:nth(L1, Oid), lists:sublist(Oid, 1, L1)}. - -%%-------------------------------------------------- -%% Determines how to treat different kinds om MEs in the tree building process. -%% Pre: all internal nodes have been removed. -%%-------------------------------------------------- -node_or_subtree(table) -> subtree; -node_or_subtree(table_entry) -> subtree; -node_or_subtree(variable) -> node; -node_or_subtree(table_column) -> node. - -%%-------------------------------------------------- -%% Purpose: (Recursively) Converts a temporary tree (see above) to a final tree. -%% If input is a ListTree, output is a TupleTree. -%% If input is a Node, output is the same Node. -%% Pre: All Indexes are >= 0. -%%-------------------------------------------------- -convert_tree({Index, {tree, Tree, Info}}) when Index >= 0 -> - L = lists:map(fun convert_tree/1, Tree), - {Index, {tree, dict_list_to_tuple(L), Info}}; -convert_tree({Index, {node, Info}}) when Index >= 0 -> - {Index, {node, Info}}; -convert_tree(Tree) when is_list(Tree) -> - L = lists:map(fun convert_tree/1, Tree), - dict_list_to_tuple(L). - -%%---------------------------------------------------------------------- -%% Purpose: Converts a single level (that is non-recursively) from -%% the temporary indexlist to the N-tuple. -%% Input: A list of {Index, Data}. -%% Output: A tuple where element Index is Data. -%%---------------------------------------------------------------------- -dict_list_to_tuple(L) -> - L2 = lists:keysort(1, L), - list_to_tuple(integrate_indexes(0, L2)). - -%%---------------------------------------------------------------------- -%% Purpose: Helper function for dict_list_to_tuple/1. -%% Converts an indexlist to a N-list. -%% Input: A list of {Index, Data}. -%% Output: A (usually longer, never shorter) list where element Index is Data. -%% Example: [{1,hej}, {3, sven}] will give output -%% [undefined_node, hej, undefined_node, sven]. -%% Initially CurIndex should be 0. -%%---------------------------------------------------------------------- -integrate_indexes(CurIndex, [{CurIndex, Data} | T]) -> - [Data | integrate_indexes(CurIndex + 1, T)]; -integrate_indexes(_Index, []) -> - []; -integrate_indexes(CurIndex, L) -> - [undefined_node | integrate_indexes(CurIndex + 1, L)]. - -%%%====================================================================== -%%% 4. Tree merging -%%% Used by: load mib, insert subagent. -%%%====================================================================== - -%%---------------------------------------------------------------------- -%% Arg: Two root nodes (that is to be merged). -%% Returns: A new root node where the nodes have been merger to one. -%%---------------------------------------------------------------------- -merge_nodes(Same, Same) -> - Same; -merge_nodes(Node, undefined_node) -> - Node; -merge_nodes(undefined_node, Node) -> - Node; -merge_nodes({tree, Tree1, internal}, {tree, Tree2, internal}) -> - {tree, merge_levels(tuple_to_list(Tree1),tuple_to_list(Tree2)), internal}; -merge_nodes(Node1, Node2) -> - throw({error_merge_nodes, Node1, Node2}). - -%%---------------------------------------------------------------------- -%% Arg: Two levels to be merged. -%% Here, a level is represented as a list of nodes. A list is easier -%% to extend than a tuple. -%% Returns: The resulting, merged level tuple. -%%---------------------------------------------------------------------- -merge_levels(Level1, Level2) when length(Level1) =:= length(Level2) -> - MergeNodes = fun(N1, N2) -> merge_nodes(N1, N2) end, - list_to_tuple(snmp_misc:multi_map(MergeNodes, [Level1, Level2])); -merge_levels(Level1, Level2) when length(Level1) > length(Level2) -> - merge_levels(Level1, Level2 ++ - undefined_nodes_list(length(Level1) - length(Level2))); -merge_levels(Level1, Level2) when length(Level1) < length(Level2) -> - merge_levels(Level2, Level1). - -undefined_nodes_list(N) -> lists:duplicate(N, undefined_node). - - -%%%====================================================================== -%%% 5. Tree deletion routines -%%% (for unload mib) -%%%====================================================================== - -%%---------------------------------------------------------------------- -%% Purpose: Actually kicks of the tree reconstruction. -%% Returns: {list of removed MEs, NewTree} -%%---------------------------------------------------------------------- -delete_mib_from_tree(MibName, {tree, Tree, internal}) -> - case delete_tree(Tree, MibName) of - [] -> - {tree, {undefined_node}, internal}; % reduce - LevelList -> - {tree, list_to_tuple(LevelList), internal} - end. - -%%---------------------------------------------------------------------- -%% Purpose: Deletes all nodes associated to MibName from this level and -%% all levels below. -%% If the new level does not contain information (that is, no -%% other mibs use it) anymore the empty list is returned. -%% Returns: {MEs, The new level represented as a list} -%%---------------------------------------------------------------------- -delete_tree(Tree, MibName) when is_tuple(Tree) -> - NewLevel = delete_nodes(tuple_to_list(Tree), MibName, []), - case lists:filter(fun drop_undefined_nodes/1,NewLevel) of - [] -> []; - _A_perhaps_shorted_list -> - NewLevel % some other mib needs this level - end. - -%%---------------------------------------------------------------------- -%% Purpose: Nodes belonging to MibName are removed from the tree. -%% Recursively deletes sub trees to this node. -%% Returns: {MEs, NewNodesList} -%%---------------------------------------------------------------------- -delete_nodes([], _MibName, AccNodes) -> - lists:reverse(AccNodes); - -delete_nodes([{node, {variable, MibName}}|T], MibName, AccNodes) -> - delete_nodes(T, MibName, [undefined_node | AccNodes]); - -delete_nodes([{node, {table_column, MibName}}|T], MibName, AccNodes) -> - delete_nodes(T, MibName, [undefined_node | AccNodes]); - -delete_nodes([{tree, _Tree, {table, MibName}}|T], MibName, AccNodes) -> - delete_nodes(T, MibName, [undefined_node | AccNodes]); - -delete_nodes([{tree, _Tree, {table_entry, MibName}}|T], MibName, AccNodes) -> - delete_nodes(T, MibName, [undefined_node | AccNodes]); - -delete_nodes([{tree, Tree, Info}|T], MibName, AccNodes) -> - case delete_tree(Tree, MibName) of - [] -> % tree completely deleted - delete_nodes(T, MibName, [undefined_node | AccNodes]); - LevelList -> - delete_nodes(T, MibName, - [{tree, list_to_tuple(LevelList), Info} | AccNodes]) - end; - -delete_nodes([NodeToKeep|T], MibName, AccNodes) -> - delete_nodes(T, MibName, [NodeToKeep | AccNodes]). - -drop_undefined_nodes(undefined_node) -> false; -drop_undefined_nodes(_) -> true. - - -%%%====================================================================== -%%% 6. Functions for subagent handling -%%%====================================================================== - -%%---------------------------------------------------------------------- -%% Returns: A new Root|{error, reason} -%%---------------------------------------------------------------------- -insert_subagent(Oid, OldRoot) -> - ListTree = build_tree_for_subagent(Oid), - case catch convert_tree(ListTree) of - {'EXIT', _Reason} -> - {error, 'cannot construct tree from oid'}; - Level when is_tuple(Level) -> - T = {tree, Level, internal}, - case catch merge_nodes(T, OldRoot) of - {error_merge_nodes, _Node1, _Node2} -> - {error, oid_conflict}; - NewRoot when is_tuple(NewRoot) andalso - (element(1, NewRoot) =:= tree) -> - NewRoot - end - end. - -build_tree_for_subagent([Index]) -> - [{Index, {node, subagent}}]; - -build_tree_for_subagent([Index | T]) -> - [{Index, {tree, build_tree_for_subagent(T), internal}}]. +-type mib_view() :: [mib_view_elem()]. +-type mib_view_elem() :: {SubTree :: snmp:oid(), + Mask :: [non_neg_integer()], + Inclusion :: mib_view_inclusion()}. +-type mib_view_mask() :: [non_neg_integer()]. +-type mib_view_inclusion() :: 1 | 2. % 1 = included, 2 = excluded -%%---------------------------------------------------------------------- -%% Returns: A new tree where the subagent at Oid (2nd arg) has been deleted. -%%---------------------------------------------------------------------- -delete_subagent({tree, Tree, Info}, [Index]) -> - {node, subagent} = element(Index+1, Tree), - {tree, setelement(Index+1, Tree, undefined_node), Info}; -delete_subagent({tree, Tree, Info}, [Index | TI]) -> - {tree, setelement(Index+1, Tree, - delete_subagent(element(Index+1, Tree), TI)), Info}. +-type filename() :: file:filename(). -%%%====================================================================== -%%% 7. Misc functions -%%%====================================================================== -%%---------------------------------------------------------------------- -%% Installs the mibs found in the database when starting the agent. -%% Basically calls the instrumentation functions for all non-internal -%% mib-entries -%%---------------------------------------------------------------------- -install_mibs(MibDb, NodeDb) -> - MibNames = loaded(MibDb), - ?vtrace("install_mibs -> found following mibs in database: ~n" - "~p", [MibNames]), - install_mibs2(NodeDb, MibNames). +-callback new(MibStorage :: snmpa:mib_storage()) -> State :: term(). -install_mibs2(_, []) -> - ok; -install_mibs2(NodeDb, [MibName|MibNames]) -> - Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'}, - Nodes = snmpa_general_db:match_object(NodeDb, Pattern), - MEs = [ME || #node_info{me = ME} <- Nodes], - ?vtrace("install_mibs2 -> installing ~p MEs for mib ~p", - [length(MEs),MibName]), - NewF = fun(ME) -> call_instrumentation(ME, new) end, - lists:foreach(NewF, MEs), - install_mibs2(NodeDb, MibNames). - - -%%---------------------------------------------------------------------- -%% Does all side effect stuff during load_mib. -%%---------------------------------------------------------------------- -install_mib(Db, Symbolic, Mib, MibName, FileName, NonInternalMes) -> - ?vdebug("install_mib -> entry with" - "~n Symbolic: ~p" - "~n MibName: ~p" - "~n FileName: ~p", [Symbolic, MibName, FileName]), - Rec = #mib_info{name = MibName, symbolic = Symbolic, file_name = FileName}, - snmpa_general_db:write(Db, Rec), - install_mib2(Symbolic, MibName, Mib), - NewF = fun(ME) -> call_instrumentation(ME, new) end, - lists:foreach(NewF, NonInternalMes). +-callback close(State :: term()) -> ok. -install_mib2(true, MibName, Mib) -> - #mib{table_infos = TabInfos, - variable_infos = VarInfos, - mes = MEs, - asn1_types = ASN1Types, - traps = Traps} = Mib, - snmpa_symbolic_store:add_table_infos(MibName, TabInfos), - snmpa_symbolic_store:add_variable_infos(MibName, VarInfos), - snmpa_symbolic_store:add_aliasnames(MibName, MEs), - snmpa_symbolic_store:add_types(MibName, ASN1Types), - SetF = fun(Trap) -> - snmpa_symbolic_store:set_notification(Trap, MibName) - end, - lists:foreach(SetF, Traps); -install_mib2(_, _, _) -> - ok. +-callback sync(State :: term()) -> ok. -install_mes(_Db, _MibName, []) -> - ok; -install_mes(Db, MibName, [ME|MEs]) -> - Node = #node_info{oid = ME#me.oid, mib_name = MibName, me = ME}, - snmpa_general_db:write(Db, Node), - install_mes(Db, MibName, MEs). +-callback load_mib(State :: term(), FileName :: string(), + MeOverride :: boolean(), + TeOverride :: boolean()) -> + {ok, NewState :: term()} | {error, Reason :: already_loaded | term()}. +-callback unload_mib(State :: term(), FileName :: string(), + MeOverride :: boolean(), + TeOverride :: boolean()) -> + {ok, NewState :: term()} | {error, Reason :: not_loaded | term()}. -%%---------------------------------------------------------------------- -%% Does all side effect stuff during unload_mib. -%%---------------------------------------------------------------------- -uninstall_mib(Db, Symbolic, MibName, MEs) -> - ?vtrace("uninstall_mib -> entry with" - "~n Db: ~p" - "~n Symbolic: ~p" - "~n MibName: ~p", [Db, Symbolic, MibName]), - Res = snmpa_general_db:delete(Db, MibName), - ?vtrace("uninstall_mib -> (mib) db delete result: ~p", [Res]), - uninstall_mib2(Symbolic, MibName), - DelF = fun(ME) -> call_instrumentation(ME, delete) end, - lists:foreach(DelF, MEs). +-callback lookup(State :: term(), Oid :: snmp:oid()) -> + {false, Reason :: term()} | + {variable, MibEntry :: snmpa:me()} | + {table_column, MibEntry :: snmpa:me(), TableEntryOid :: snmp:oid()} | + {subagent, SubAgentPid :: pid(), SAOid :: snmp:oid()}. -uninstall_mib2(true, MibName) -> - snmpa_symbolic_store:delete_table_infos(MibName), - snmpa_symbolic_store:delete_variable_infos(MibName), - snmpa_symbolic_store:delete_aliasnames(MibName), - snmpa_symbolic_store:delete_types(MibName), - snmpa_symbolic_store:delete_notifications(MibName); -uninstall_mib2(_, _) -> - ok. +-callback next(State :: term(), Oid :: snmp:oid(), MibView :: mib_view()) -> + endOfView | false | + {subagent, SubAgentPid :: pid(), SAOid :: snmp:oid()} | + {variable, MibEntry :: snmpa:me(), VarOid :: snmp:oid()} | + {table, TableOid :: snmp:oid(), TableRestOid :: snmp:oid(), MibEntry :: snmpa:me()}. -uninstall_mes(Db, MibName) -> - Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'}, - snmpa_general_db:match_delete(Db, Pattern). +-callback register_subagent(State :: term(), + Oid :: snmp:oid(), + Pid :: pid()) -> + {ok, NewState :: term()} | {error, Reason :: term()}. +-callback unregister_subagent(State :: term(), + PidOrOid :: pid() | snmp:oid()) -> + {ok, NewState :: term()} | % When second arg was a pid() + {ok, NewState :: term(), Pid :: pid()} | % When second arg was a oid() + {error, Reason :: term()}. -%%---------------------------------------------------------------------- -%% Create a list of the names of all the loaded mibs -%%---------------------------------------------------------------------- -loaded(Db) -> - [N || #mib_info{name = N} <- snmpa_general_db:tab2list(Db)]. - +-callback dump(State :: term(), Destination :: io | filename()) -> + ok | {error, Reason :: term()}. -%%---------------------------------------------------------------------- -%% Calls MFA-instrumentation with 'new' or 'delete' operation. -%%---------------------------------------------------------------------- -call_instrumentation(#me{entrytype = variable, mfa={M,F,A}}, Operation) -> - ?vtrace("call instrumentation with" - "~n entrytype: variable" - "~n MFA: {~p,~p,~p}" - "~n Operation: ~p", - [M,F,A,Operation]), - catch apply(M, F, [Operation | A]); -call_instrumentation(#me{entrytype = table_entry, mfa={M,F,A}}, Operation) -> - ?vtrace("call instrumentation with" - "~n entrytype: table_entry" - "~n MFA: {~p,~p,~p}" - "~n Operation: ~p", - [M,F,A,Operation]), - catch apply(M, F, [Operation | A]); -call_instrumentation(_ShitME, _Operation) -> - done. +-callback which_mib(State :: term(), Oid :: snmp:oid()) -> + {ok, Mib :: string()} | {error, Reason :: term()}. +-callback which_mibs(State :: term()) -> + [{MibName :: atom(), Filename :: string()}]. -maybe_drop_me(#me{entrytype = internal}) -> false; -maybe_drop_me(#me{entrytype = group}) -> false; -maybe_drop_me(#me{imported = true}) -> false; -maybe_drop_me(_) -> true. +-callback whereis_mib(State :: term(), MibName :: atom()) -> + {ok, Filename :: string()} | {error, Reason :: term()}. +-callback info(State :: term()) -> list(). -%%---------------------------------------------------------------------- -%% Code change functions -%%---------------------------------------------------------------------- +-callback backup(State :: term(), BackupDir :: string()) -> + ok | {error, Reason :: term()}. -code_change(down, State) -> - ?d("code_change(down) -> entry",[]), - State; +-callback code_change(Direction :: up | down, + Vsn :: term(), + Extra :: term(), + State :: term()) -> + NewState :: term(). -code_change(up, State) -> - ?d("code_change(up)",[]), - State; -code_change(_Vsn, State) -> - State. diff --git a/lib/snmp/src/agent/snmpa_mib_data_ttln.erl b/lib/snmp/src/agent/snmpa_mib_data_ttln.erl new file mode 100644 index 0000000000..d367e8f13f --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib_data_ttln.erl @@ -0,0 +1,1402 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2013. 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_data_ttln). + +%%%----------------------------------------------------------------- +%%% +%%% THIS FILE IS JUST A PLACE HOLDER - IGNORE +%%% +%%%----------------------------------------------------------------- + + +%%%----------------------------------------------------------------- +%%% +%%% TTLN - TupleTreeListNodes +%%% +%%% This module implements the MIB internal data structures. +%%% An MIB Data Structure consists of three items; an ets-table, +%%% a tree and a list of registered subagents. +%%% The subagent information is consequently duplicated. It resides +%%% both in the tree and in the list. +%%% The ets-table contains all data associated with each variable, +%%% table, tableentry and tablecolumn in the MIB. +%%% The tree contains information of the Oids in the MIB. +%%% +%%% When a mib is loaded, the tree is built from the plain list +%%% in the binary file. +%%% +%%%----------------------------------------------------------------- + +-include("snmp_types.hrl"). +-include("snmp_debug.hrl"). + +-define(VMODULE,"MDATA_TTLN"). +-include("snmp_verbosity.hrl"). + +-behaviour(snmpa_mib_data). + +-define(MIB_DATA, snmpa_mib_data). +-define(MIB_NODE, snmpa_mib_node). +-define(MIB_TREE, snmpa_mib_tree). +-define(DUMMY_TREE_GENERATION, 1). +-define(DEFAULT_TREE, {tree,{undefined_node},internal}). + + +%%%----------------------------------------------------------------- +%%% Table of contents +%%% ================= +%%% 1. Interface +%%% 2. Implementation of tree access +%%% 3. Tree building functions +%%% 4. Tree merging +%%% 5. Tree deletion routines +%%% 6. Functions for subagent handling +%%% 7. Misc functions +%%%----------------------------------------------------------------- + + +%%---------------------------------------------------------------------- +%% data_db is an database containing loaded mibs as: +%% {MibName = atom(), Symbolic = ?, FullFileName = string()} +%% it is either ets or mnesia +%% tree_db is a database containing _one_ record with the tree! +%% (the reason for this is part to get replication and part out of convenience) +%% ref_tree is the root node, without any subagent. +%% tree is the root node (same as ref_tree but with the subagents added). +%% subagents is a list of {SAPid, Oid} +%%---------------------------------------------------------------------- +-record(mib_data, {mib_db, % table of #mib_info + node_db, % table of #node_info + tree_db, % table of #tree + tree, % The actual tree + subagents = []}). + +-record(mib_info, {name, symbolic, file_name}). +-record(node_info, {oid, mib_name, me}). + + +%% API +-export([new/0, new/1, sync/1, close/1, + load_mib/4, unload_mib/4, which_mibs/1, whereis_mib/2, + info/1, info/2, + dump/1, dump/2, + backup/2, + lookup/2, next/3, which_mib/2, + register_subagent/3, unregister_subagent/2]). + +%% Internal exports +-export([code_change/2]). + + +%%----------------------------------------------------------------- +%% A tree is represented as a N-tuple, where each element is a +%% node. A node is: +%% 1) {tree, Tree, Info} where Info can be {table, Id}, {table_entry, Id} +%% or perhaps 'internal' +%% 2) undefined_node (memory optimization (instead of {node, undefined})) +%% 3) {node, Info} where Info can be {subagent, Pid}, {variable, Id}, +%% {table_column, Id} +%% Id is {MibName, MibEntry} +%% The over all root is represented as {tree, Tree, internal}. +%% +%% tree() = {tree, nodes(), tree_info()} +%% nodes() = [tree() | node() | undefined_node] +%% node() = {node, node_info()} +%% tree_info() = {table, Id} | {table_entry, Id} | internal +%% node_info() = {subagent, Pid} | {variable, Id} | {table_colum, Id} +%%----------------------------------------------------------------- + +-type tree_generation() :: non_neg_integer(). +-type tree() :: #tree{}. +-type tree_nodes() :: [tree_node()]. +-type tree_node() :: tree() | + tree_node_elem() | + tree_node_empty(). +-type tree_node_elem() :: {node, tree_node_info()}. +-type tree_node_info() :: {subagent, Pid :: pid()} | + {variable, Id :: non_neg_integer()} | + {table_column, Id :: non_neg_integer()}. +-type tree_node_empty() :: {undefined_node, N :: pos_integer()}. +-type tree_info() :: {table, Id :: non_neg_integer()} | + {table_entry, Id :: non_neg_integer()} | + internal. + + +%% This record is what is stored in the database. The 'tree' part +%% is described above... +-record(mtree, + { + generation = ?DUMMY_TREE_GENERATION :: tree_generation(), + root = ?DEFAULT_TREE :: tree() + }). + +-record(tree, + { + %% The number of nodes is *not* actually the length of the + %% nodes list. Since the undefined-node(s) can be collapsed + %% into {undefined_node, N} we need to keep track of the + %% actual size some other way (so that we dont have the + %% traverse the nodes every time we want to check an index). + num_nodes :: non_neg_integer(), + nodes :: tree_nodes(), + tree_info :: tree_info() + }). + + + + +%%%====================================================================== +%%% 1. Interface +%%%====================================================================== + +%%----------------------------------------------------------------- +%% Func: new/0, new/1 +%% Returns: A representation of mib data. +%%----------------------------------------------------------------- +new() -> + new(ets). + +%% Where -> A list of nodes where the tables will be created +new(Storage) -> + %% First we must check if there is already something to read + %% If a database already exists, then the tree structure has to be read + ?vtrace("open (mib) database",[]), + MibDb = snmpa_general_db:open(Storage, ?MIB_DATA, + mib_info, + record_info(fields, mib_info), set), + ?vtrace("open (mib) node database",[]), + NodeDb = snmpa_general_db:open(Storage, ?MIB_NODE, + node_info, + record_info(fields, node_info), set), + ?vtrace("open (mib) tree database",[]), + TreeDb = snmpa_general_db:open(Storage, ?MIB_TREE, + tree, + record_info(fields, mtree), set), + MTree = + case snmpa_general_db:read(TreeDb, ?DUMMY_TREE_GENERATION) of + false -> + T = #mtree{}, + snmpa_general_db:write(TreeDb, T), + T; + {value, T} -> + T + end, + install_mibs(MibDb, NodeDb), + #mib_data{mib_db = MibDb, + node_db = NodeDb, + tree_db = TreeDb, + mtree = MTree}. + + +%%---------------------------------------------------------------------- +%% Returns: new mib data | {error, Reason} +%%---------------------------------------------------------------------- +load_mib(MibData,FileName,MeOverride,TeOverride) + when is_record(MibData,mib_data) andalso is_list(FileName) -> + ?vlog("load mib file: ~p",[FileName]), + ActualFileName = filename:rootname(FileName, ".bin") ++ ".bin", + MibName = list_to_atom(filename:basename(FileName, ".bin")), + (catch do_load_mib(MibData, ActualFileName, MibName, + MeOverride, TeOverride)). + +do_load_mib(MibData, ActualFileName, MibName, MeOverride, TeOverride) -> + ?vtrace("do_load_mib -> entry with" + "~n ActualFileName: ~s" + "~n MibName: ~p",[ActualFileName, MibName]), + #mib_data{mib_db = MibDb, + node_db = NodeDb, + %% tree_db = TreeDb, + tree = Tree} = MibData, + verify_not_loaded(MibDb, MibName), + ?vtrace("do_load_mib -> already loaded mibs:" + "~n ~p",[loaded(MibDb)]), + Mib = do_read_mib(ActualFileName), + ?vtrace("do_load_mib -> read mib ~s",[Mib#mib.name]), + NonInternalMes = + lists:filter(fun(ME) -> maybe_drop_me(ME) end, Mib#mib.mes), + OldRoot = Tree#tree.root, + T = build_tree(NonInternalMes, MibName), + ?d("load_mib -> " + "~n OldRoot: ~p" + "~n T: ~p", [OldRoot, T]), + case (catch merge_nodes(T, OldRoot)) of + {error_merge_nodes, Node1, Node2} -> + ?vlog("error merging nodes:" + "~n~p~nand~n~p", [Node1,Node2]), + {error, oid_conflict}; + NewRoot when is_tuple(NewRoot) andalso (element(1,NewRoot) =:= tree) -> + ?d("load_mib -> " + "~n NewRoot: ~p", [NewRoot]), + Symbolic = not lists:member(no_symbolic_info, Mib#mib.misc), + case (catch check_notif_and_mes(TeOverride, MeOverride, Symbolic, + Mib#mib.traps, NonInternalMes)) of + true -> + install_mes(NodeDb, MibName, NonInternalMes), + install_mib(MibDb, Symbolic, Mib, + MibName, ActualFileName, NonInternalMes), + ?vtrace("installed mib ~s", [Mib#mib.name]), + Tree2 = Tree#tree{root = NewRoot}, + %% snmpa_general_db:write(TreeDb, Tree2), %% Store later? + {ok, MibData#mib_data{tree = Tree2}}; + Else -> + Else + end + end. + + +verify_not_loaded(Db, Name) -> + case snmpa_general_db:read(Db, Name) of + {value, #mib_info{name = Name}} -> + throw({error, 'already loaded'}); + false -> + ok + end. + +do_read_mib(ActualFileName) -> + case snmp_misc:read_mib(ActualFileName) of + {error, Reason} -> + ?vlog("Failed reading mib file ~p with reason: ~p", + [ActualFileName,Reason]), + throw({error, Reason}); + {ok, Mib} -> + Mib + end. + +%% The Tree DB is handled in a special way since it can be very large. +sync(#mib_data{mib_db = M, + node_db = N, + tree_db = T, tree = Tree, subagents = []}) -> + snmpa_general_db:sync(M), + snmpa_general_db:sync(N), + snmpa_general_db:write(T, Tree), + snmpa_general_db:sync(T); +sync(#mib_data{mib_db = M, + node_db = N, + tree_db = T, tree = Tree, subagents = SAs}) -> + + snmpa_general_db:sync(M), + snmpa_general_db:sync(N), + + %% Ouch. Since the subagent info is dynamic we do not + %% want to store the tree containing subagent info. So, we + %% have to create a tmp tree without those and store it. + + case delete_subagents(Tree, SAs) of + {ok, TreeWithoutSAs} -> + snmpa_general_db:write(T, TreeWithoutSAs), + snmpa_general_db:sync(T); + Error -> + Error + end. + +delete_subagents(Tree, []) -> + {ok, Tree}; +delete_subagents(Tree0, [{_, Oid}|SAs]) -> + case (catch delete_subagent(Tree0, Oid)) of + {tree, _Tree, _Info} = Tree1 -> + delete_subagents(Tree1, SAs); + _Error -> + {error, {'invalid oid', Oid}} + end. + +%%---------------------------------------------------------------------- +%% (OTP-3601) +%%---------------------------------------------------------------------- +check_notif_and_mes(TeOverride,MeOverride,Symbolic,Traps,MEs) -> + ?vtrace("check notifications and mib entries",[]), + check_notifications(TeOverride,Symbolic,Traps), + check_mes(MeOverride,MEs). + +check_notifications(true, _Symbolic, _Traps) -> + ?vtrace("trapentry override = true => skip check",[]), + true; +check_notifications(_, Symbolic, Traps) -> + check_notifications(Symbolic, Traps). + +check_notifications(true, Traps) -> + check_notifications(Traps); +check_notifications(_, _) -> true. + +check_notifications([]) -> true; +check_notifications([#trap{trapname = Key} = Trap | Traps]) -> + ?vtrace("check notification [trap] with Key: ~p",[Key]), + case snmpa_symbolic_store:get_notification(Key) of + {value, Trap} -> check_notifications(Traps); + {value, _} -> throw({error, {'trap already defined', Key}}); + undefined -> check_notifications(Traps) + end; +check_notifications([#notification{trapname = Key} = Notif | Traps]) -> + ?vtrace("check notification [notification] with Key: ~p",[Key]), + case snmpa_symbolic_store:get_notification(Key) of + {value, Notif} -> + check_notifications(Traps); + {value, _} -> + throw({error, {'notification already defined', Key}}); + undefined -> + check_notifications(Traps) + end; +check_notifications([Crap | Traps]) -> + ?vlog("skipped check of: ~n~p",[Crap]), + check_notifications(Traps). + +check_mes(true,_) -> + ?vtrace("mibentry override = true => skip check",[]), + true; +check_mes(_,MEs) -> + check_mes(MEs). + +check_mes([]) -> true; +check_mes([#me{aliasname = Name, oid = Oid1} | MEs]) -> + ?vtrace("check mib entries with aliasname: ~p",[Name]), + case snmpa_symbolic_store:aliasname_to_oid(Name) of + {value, Oid1} -> + check_mes(MEs); + {value, Oid2} -> + ?vinfo("~n expecting '~p'~n but found '~p'",[Oid1, Oid2]), + throw({error, {'mibentry already defined', Name}}); + false -> + check_mes(MEs) + end; +check_mes([Crap | MEs]) -> + ?vlog("skipped check of: ~n~p",[Crap]), + check_mes(MEs). + + + +%%---------------------------------------------------------------------- +%% Returns: new mib data | {error, Reason} +%%---------------------------------------------------------------------- +unload_mib(MibData, FileName, _, _) when is_list(FileName) -> + MibName = list_to_atom(filename:basename(FileName, ".bin")), + (catch do_unload_mib(MibData, MibName)). + +do_unload_mib(MibData, MibName) -> + ?vtrace("do_unload_mib -> entry with" + "~n MibName: ~p", [MibName]), + #mib_data{mib_db = MibDb, + node_db = NodeDb, + %% tree_db = TreeDb, + tree = Tree} = MibData, + #mib_info{symbolic = Symbolic} = verify_loaded(MibDb, MibName), + NewRoot = delete_mib_from_tree(MibName, Tree#tree.root), + MEs = uninstall_mes(NodeDb, MibName), + uninstall_mib(MibDb, Symbolic, MibName, MEs), + NewMibData = MibData#mib_data{tree = Tree#tree{root = NewRoot}}, + {ok, NewMibData}. + +verify_loaded(Db, Name) -> + case snmpa_general_db:read(Db, Name) of + {value, MibInfo} -> + MibInfo; + false -> + throw({error, 'not loaded'}) + end. + + +close(#mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb}) -> + snmpa_general_db:close(MibDb), + snmpa_general_db:close(NodeDb), + snmpa_general_db:close(TreeDb), + ok. + +register_subagent(#mib_data{tree = T} = MibData, Oid, Pid) -> + case insert_subagent(Oid, T#tree.root) of + {error, Reason} -> + {error, Reason}; + NewRootTree -> + SAs = [{Pid, Oid} | MibData#mib_data.subagents], + T2 = T#tree{root = NewRootTree}, + MibData#mib_data{tree = T2, subagents = SAs} + end. + + +%%---------------------------------------------------------------------- +%% Purpose: Get a list of all loaded mibs +%% Returns: [{Name, File}] +%%---------------------------------------------------------------------- + +which_mibs(#mib_data{mib_db = Db}) -> + Mibs = snmpa_general_db:tab2list(Db), + [{Name, File} || #mib_info{name = Name, file_name = File} <- Mibs]. + + +%%---------------------------------------------------------------------- +%% Purpose: Get a list of all loaded mibs +%% Returns: [{Name, File}] +%%---------------------------------------------------------------------- + +whereis_mib(#mib_data{mib_db = Db}, Name) -> + case snmpa_general_db:read(Db, Name) of + {value, #mib_info{file_name = File}} -> + {ok, File}; + false -> + {error, not_found} + end. + + +%%---------------------------------------------------------------------- +%% Purpose: Deletes SA with Pid from all subtrees it handles. +%% Returns: NewMibData. +%%---------------------------------------------------------------------- +unregister_subagent(MibData, Pid) when is_pid(Pid) -> + SAs = MibData#mib_data.subagents, + case lists:keysearch(Pid, 1, SAs) of + false -> MibData; + {value, {Pid, Oid}} -> + % we should never get an error since Oid is found in MibData. + {ok, NewMibData, _DeletedSA} = unregister_subagent(MibData, Oid), + % continue if the same Pid handles other mib subtrees. + unregister_subagent(NewMibData, Pid) + end; + +%%---------------------------------------------------------------------- +%% Purpose: Deletes one unique subagent. +%% Returns: {error, Reason} | {ok, NewMibData, DeletedSubagentPid} +%%---------------------------------------------------------------------- +unregister_subagent(#mib_data{tree = T} = MibData, Oid) when is_list(Oid) -> + case catch delete_subagent(T#tree.root, Oid) of + {tree, Tree, Info} -> + OldSAs = MibData#mib_data.subagents, + {value, {Pid, _Oid}} = lists:keysearch(Oid, 2, OldSAs), + SAs = lists:keydelete(Oid, 2, OldSAs), + T2 = T#tree{root = {tree, Tree, Info}}, + {ok, + MibData#mib_data{tree = T2, subagents = SAs}, + Pid}; + _ -> + {error, {'invalid oid', Oid}} + end. + +%%---------------------------------------------------------------------- +%% Purpose: To inpect memory usage, loaded mibs, registered subagents +%%---------------------------------------------------------------------- +info(MibData) -> + ?vtrace("retrieve info",[]), + #mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb, + tree = Tree, subagents = SAs} = MibData, + LoadedMibs = old_format(snmpa_general_db:tab2list(MibDb)), + TreeSize = snmp_misc:mem_size(Tree), + {memory, ProcSize} = erlang:process_info(self(),memory), + MibDbSize = snmpa_general_db:info(MibDb, memory), + NodeDbSize = snmpa_general_db:info(NodeDb, memory), + TreeDbSize = snmpa_general_db:info(TreeDb, memory), + [{loaded_mibs, LoadedMibs}, {subagents, SAs}, {tree_size_bytes, TreeSize}, + {process_memory, ProcSize}, + {db_memory, [{mib,MibDbSize},{node,NodeDbSize},{tree,TreeDbSize}]}]. + +info(#mib_data{mib_db = MibDb}, loaded_mibs) -> + Mibs = snmpa_general_db:tab2list(MibDb), + [filename:rootname(FN, ".bin") || #mib_info{file_name = FN} <- Mibs]; +info(#mib_data{tree = Tree}, tree_size_bytes) -> + snmp_misc:mem_size(Tree); +info(_, process_memory) -> + {memory, ProcSize} = erlang:process_info(self(),memory), + ProcSize; +info(#mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb}, + db_memory) -> + MibDbSize = snmpa_general_db:info(MibDb, memory), + NodeDbSize = snmpa_general_db:info(NodeDb, memory), + TreeDbSize = snmpa_general_db:info(TreeDb, memory), + [{mib,MibDbSize},{node,NodeDbSize},{tree,TreeDbSize}]; +info(#mib_data{subagents = SAs}, subagents) -> + SAs. + +old_format(LoadedMibs) -> + ?vtrace("convert mib info to old format",[]), + [{N,S,F} || #mib_info{name=N,symbolic=S,file_name=F} <- LoadedMibs]. + + +%%---------------------------------------------------------------------- +%% A total dump for debugging. +%%---------------------------------------------------------------------- +dump(#mib_data{mib_db = MibDb, node_db = NodeDb, tree = Tree}) -> + (catch io:format("MIB-tables:~n~p~n~n", + [snmpa_general_db:tab2list(MibDb)])), + (catch io:format("MIB-entries:~n~p~n~n", + [snmpa_general_db:tab2list(NodeDb)])), + (catch io:format("Tree:~n~p~n", [Tree])), % good luck reading it! + ok. + +dump(#mib_data{mib_db = MibDb, node_db = NodeDb, tree = Tree}, File) -> + case file:open(File,[write]) of + {ok, Fd} -> + io:format(Fd,"~s~n", + [snmp:date_and_time_to_string(snmp:date_and_time())]), + (catch io:format(Fd,"MIB-tables:~n~p~n~n", + [snmpa_general_db:tab2list(MibDb)])), + (catch io:format(Fd, "MIB-entries:~n~p~n~n", + [snmpa_general_db:tab2list(NodeDb)])), + io:format(Fd,"Tree:~n~p~n", [Tree]), % good luck reading it! + file:close(Fd), + ok; + {error,Reason} -> + ?vinfo("~n Failed opening file '~s' for reason ~p", + [File,Reason]), + {error,Reason} + end. + + +backup(#mib_data{mib_db = M, node_db = N, tree_db = T}, BackupDir) -> + MRes = snmpa_general_db:backup(M, BackupDir), + NRes = snmpa_general_db:backup(N, BackupDir), + TRes = snmpa_general_db:backup(T, BackupDir), + handle_backup_res([{mib_db, MRes}, {node_db, NRes}, {tree_db, TRes}]). + +handle_backup_res(Res) -> + handle_backup_res(Res, []). + +handle_backup_res([], []) -> + ok; +handle_backup_res([], Err) -> + {error, lists:reverse(Err)}; +handle_backup_res([{_, ok}|Res], Err) -> + handle_backup_res(Res, Err); +handle_backup_res([{Tag, {error, Reason}}|Res], Err) -> + handle_backup_res(Res, [{Tag, Reason}|Err]); +handle_backup_res([{Tag, Error}|Res], Err) -> + handle_backup_res(Res, [{Tag, Error}|Err]). + + +%%%====================================================================== +%%% 2. Implementation of tree access +%%% lookup and next. +%%%====================================================================== + + +which_mib(#mib_data{tree = T} = D, Oid) -> + ?vtrace("which_mib -> entry with" + "~n Oid: ~p",[Oid]), + case (catch find_node(D, T#tree.root, Oid, [])) of + {variable, _ME, Mib} -> + ?vtrace("which_mib -> variable:" + "~n Mib: ~p", [Mib]), + {ok, Mib}; + {table, _EntryME, _, Mib} -> + ?vtrace("which_mib -> table:" + "~n Mib: ~p", [Mib]), + {ok, Mib}; + {subagent, SubAgentPid, _SANextOid} -> + ?vtrace("which_mib -> subagent:" + "~n SubAgentPid: ~p", [SubAgentPid]), + {error, {subagent, SubAgentPid}}; + {false, ErrorCode} -> + ?vtrace("which_mib -> false:" + "~n ErrorCode: ~p",[ErrorCode]), + {error, ErrorCode}; + false -> + ?vtrace("which_mib -> false",[]), + {error, noSuchObject}; + {'EXIT', R} -> + ?vtrace("which_mib -> exit:" + "~n R: ~p",[R]), + {error, noSuchObject} + end. + + +%%----------------------------------------------------------------- +%% 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, SAOid} | +%% {false, Reason} +%%----------------------------------------------------------------- +lookup(#mib_data{tree = T} = D, Oid) -> + ?vtrace("lookup -> entry with" + "~n Oid: ~p",[Oid]), + case (catch find_node(D, T#tree.root, Oid, [])) of + {variable, ME, _Mib} when is_record(ME, me) -> + ?vtrace("lookup -> variable:" + "~n ME: ~p",[ME]), + {variable, ME}; + {table, EntryME, {ColME, TableEntryOid}, _Mib} -> + ?vtrace("lookup -> table:" + "~n EntryME: ~p" + "~n ColME: ~p" + "~n RevTableEntryOid: ~p", + [EntryME, ColME, TableEntryOid]), + MFA = EntryME#me.mfa, + RetME = ColME#me{mfa = MFA}, + {table_column, RetME, TableEntryOid}; + {subagent, SubAgentPid, SANextOid} -> + ?vtrace("lookup -> subagent:" + "~n SubAgentPid: ~p" + "~n SANextOid: ~p", [SubAgentPid, SANextOid]), + {subagent, SubAgentPid, SANextOid}; + {false, ErrorCode} -> + ?vtrace("lookup -> false:" + "~n ErrorCode: ~p",[ErrorCode]), + {false, ErrorCode}; + false -> + ?vtrace("lookup -> false",[]), + {false, noSuchObject}; + {'EXIT', R} -> + ?vtrace("lookup -> exit:" + "~n R: ~p",[R]), + {false, noSuchObject} + end. + + +find_node(D, {tree, Tree, {table, _}}, RestOfOid, RevOid) -> + ?vtrace("find_node(tree,table) -> entry with" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[RestOfOid, RevOid]), + find_node(D, {tree, Tree, internal}, RestOfOid, RevOid); +find_node(D, {tree, Tree, {table_entry, _}}, RestOfOid, RevOid) -> + ?vtrace("find_node(tree,table_entry) -> entry with" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[RestOfOid, RevOid]), + #mib_data{node_db = Db} = D, + Oid = lists:reverse(RevOid), + case snmpa_general_db:read(Db, Oid) of + {value, #node_info{me = ME, mib_name = Mib}} -> + case find_node(D, {tree, Tree, internal}, RestOfOid, RevOid) of + {false, ErrorCode} -> {false, ErrorCode}; + Val -> {table, ME, Val, Mib} + end; + false -> + ?vinfo("find_node -> could not find table_entry ME with" + "~n RevOid: ~p" + "~n when" + "~n RestOfOid: ~p", + [RevOid, RestOfOid]), + false + end; +find_node(D, {tree, Tree, _Internal}, [Int | RestOfOid], RevOid) -> + ?vtrace("find_node(tree) -> entry with" + "~n Int: ~p" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[Int, RestOfOid, RevOid]), + find_node(D, element(Int+1, Tree), RestOfOid, [Int | RevOid]); +find_node(D, {node, {table_column, _}}, RestOfOid, [ColInt | RevOid]) -> + ?vtrace("find_node(tree,table_column) -> entry with" + "~n RestOfOid: ~p" + "~n ColInt: ~p" + "~n RevOid: ~p",[RestOfOid, ColInt, RevOid]), + #mib_data{node_db = Db} = D, + Oid = lists:reverse([ColInt | RevOid]), + case snmpa_general_db:read(Db, Oid) of + {value, #node_info{me = ME}} -> + {ME, lists:reverse(RevOid)}; + false -> + X = snmpa_general_db:read(Db, lists:reverse([ColInt | RevOid])), + ?vinfo("find_node -> could not find table_column ME with" + "~n RevOid: ~p" + "~n trying [~p|~p]" + "~n X: ~p", + [RevOid, [ColInt | RevOid], X]), + false + end; +find_node(D, {node, {variable, _MibName}}, [0], RevOid) -> + ?vtrace("find_node(tree,variable,[0]) -> entry with" + "~n RevOid: ~p",[RevOid]), + #mib_data{node_db = Db} = D, + Oid = lists:reverse(RevOid), + %% {value, #node_info{me = ME}} = snmpa_general_db:read(Db, Oid), + case snmpa_general_db:read(Db, Oid) of + {value, #node_info{me = ME, mib_name = Mib}} -> + {variable, ME, Mib}; + false -> + ?vinfo("find_node -> could not find variable ME with" + "~n RevOid: ~p", [RevOid]), + false + end; +find_node(_D, {node, {variable, _MibName}}, [], _RevOid) -> + ?vtrace("find_node(tree,variable,[]) -> entry",[]), + {false, noSuchObject}; +find_node(_D, {node, {variable, _MibName}}, _, _RevOid) -> + ?vtrace("find_node(tree,variable) -> entry",[]), + {false, noSuchInstance}; +find_node(D, {node, subagent}, _RestOfOid, SARevOid) -> + ?vtrace("find_node(tree,subagent) -> entry with" + "~n SARevOid: ~p",[SARevOid]), + #mib_data{subagents = SAs} = D, + SAOid = lists:reverse(SARevOid), + case lists:keysearch(SAOid, 2, SAs) of + {value, {SubAgentPid, SAOid}} -> + {subagent, SubAgentPid, SAOid}; + false -> + ?vinfo("find_node -> could not find subagent with" + "~n SAOid: ~p" + "~n SAs: ~p", [SAOid, SAs]), + false + end; +find_node(_D, Node, _RestOfOid, _RevOid) -> + ?vtrace("find_node -> failed:~n~p",[Node]), + {false, noSuchObject}. + + +%%----------------------------------------------------------------- +%% Func: next/3 +%% Purpose: Finds the lexicographically next oid. +%% Returns: endOfMibView | +%% {subagent, SubAgentPid, SAOid} | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%% If a variable is returnes, it is in the MibView. +%% If a table or subagent is returned, it *may* be in the MibView. +%%----------------------------------------------------------------- +next(#mib_data{tree = T} = D, Oid, MibView) -> + case catch next_node(D, T#tree.root, Oid, [], MibView) of + false -> endOfMibView; + Else -> Else + end. + +%%----------------------------------------------------------------- +%% This function is used as long as we have any Oid left. Take +%% one integer at a time from the Oid, and traverse the tree +%% accordingly. When the Oid is empty, call find_next. +%% Returns: {subagent, SubAgentPid, SAOid} | +%% false | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%%----------------------------------------------------------------- +next_node(_D, undefined_node, _Oid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(undefined_node) -> entry", []), + false; + +next_node(_D, {tree, Tree, {table_entry, _Id}}, [Int | _Oid], + _RevOidSoFar, _MibView) + when Int+1 > size(Tree) -> + ?vtrace("next_node(tree,table_entry) -> entry when not found whith" + "~n Int: ~p" + "~n size(Tree): ~p", [Int, size(Tree)]), + false; +next_node(D, {tree, Tree, {table_entry, _MibName}}, + Oid, RevOidSoFar, MibView) -> + ?vtrace("next_node(tree,table_entry) -> entry when" + "~n size(Tree): ~p" + "~n Oid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", [size(Tree), Oid, RevOidSoFar, MibView]), + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + ?vdebug("next_node(tree,table_entry) -> not in mib view",[]), + false; + _ -> + #mib_data{node_db = Db} = D, + case snmpa_general_db:read(Db, OidSoFar) of + false -> + ?vinfo("next_node -> could not find table_entry with" + "~n OidSoFar: ~p", [OidSoFar]), + false; + {value, #node_info{me = ME}} -> + ?vtrace("next_node(tree,table_entry) -> found: ~n ~p", + [ME]), + {table, OidSoFar, Oid, ME} + end + end; + +next_node(D, {tree, Tree, _Info}, [Int | RestOfOid], RevOidSoFar, MibView) + when (Int < size(Tree)) andalso (Int >= 0) -> + ?vtrace("next_node(tree) -> entry when" + "~n size(Tree): ~p" + "~n Int: ~p" + "~n RestOfOid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [size(Tree), Int, RestOfOid, RevOidSoFar, MibView]), + case next_node(D, element(Int+1,Tree), + RestOfOid, [Int|RevOidSoFar], MibView) of + false -> + find_next(D, {tree, Tree, _Info}, Int+1, RevOidSoFar, MibView); + Else -> + Else + end; +%% no solution +next_node(D, {tree, Tree, _Info}, [], RevOidSoFar, MibView) -> + ?vtrace("next_node(tree,[]) -> entry when" + "~n size(Tree): ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [size(Tree), RevOidSoFar, MibView]), + find_next(D, {tree, Tree, _Info}, 0, RevOidSoFar, MibView); +next_node(_D, {tree, Tree, _Info}, _RestOfOid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(tree) -> entry when" + "~n size(Tree): ~p", [size(Tree)]), + false; + +next_node(D, {node, subagent}, Oid, RevOidSoFar, MibView) -> + ?vtrace("next_node(node,subagent) -> entry when" + "~n Oid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [Oid, RevOidSoFar, MibView]), + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{subagents = SAs} = D, + case lists:keysearch(OidSoFar, 2, SAs) of + {value, {SubAgentPid, OidSoFar}} -> + {subagent, SubAgentPid, OidSoFar}; + _ -> + ?vinfo("next_node -> could not find subagent with" + "~n OidSoFar: ~p" + "~n SAs: ~p", [OidSoFar, SAs]), + false + end + end; + +next_node(D, {node, {variable, _MibName}}, [], RevOidSoFar, MibView) -> + ?vtrace("next_node(node,variable,[]) -> entry when" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [RevOidSoFar, MibView]), + OidSoFar = lists:reverse([0 | RevOidSoFar]), + case snmpa_acm:validate_mib_view(OidSoFar, MibView) of + true -> + #mib_data{node_db = Db} = D, + case snmpa_general_db:read(Db, lists:reverse(RevOidSoFar)) of + false -> + ?vinfo("next_node -> could not find variable with" + "~n RevOidSoFar: ~p", [RevOidSoFar]), + false; + {value, #node_info{me = ME}} -> + {variable, ME, OidSoFar} + end; + _ -> + false + end; + +next_node(_D, {node, {variable, _MibName}}, _Oid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(node,variable) -> entry", []), + false. + +%%----------------------------------------------------------------- +%% This function is used to find the first leaf from where we +%% are. +%% Returns: {subagent, SubAgentPid, SAOid} | +%% false | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%% PRE: This function must always be called with a {internal, Tree} +%% node. +%%----------------------------------------------------------------- +find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView) + when Idx < size(Tree) -> + case find_next(D, element(Idx+1, Tree), 0, [Idx| RevOidSoFar], MibView) of + false -> + find_next(D, {tree, Tree, internal}, Idx+1, RevOidSoFar, MibView); + Other -> + Other + end; +find_next(_D, {tree, _Tree, internal}, _Idx, _RevOidSoFar, _MibView) -> + false; +find_next(_D, undefined_node, _Idx, _RevOidSoFar, _MibView) -> + false; +find_next(D, {tree, Tree, {table, _MibName}}, Idx, RevOidSoFar, MibView) -> + find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView); +find_next(D, {tree, _Tree, {table_entry, _MibName}}, _Index, + RevOidSoFar, MibView) -> + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{node_db = Db} = D, + case snmpa_general_db:read(Db, OidSoFar) of + false -> + ?vinfo("find_next -> could not find table_entry ME with" + "~n OidSoFar: ~p", [OidSoFar]), + false; + {value, #node_info{me = ME}} -> + {table, OidSoFar, [], ME} + end + end; +find_next(D, {node, {variable, _MibName}}, _Idx, RevOidSoFar, MibView) -> + OidSoFar = lists:reverse([0 | RevOidSoFar]), + case snmpa_acm:validate_mib_view(OidSoFar, MibView) of + true -> + #mib_data{node_db = Db} = D, + case snmpa_general_db:read(Db, lists:reverse(RevOidSoFar)) of + false -> + ?vinfo("find_next -> could not find variable with" + "~n RevOidSoFar: ~p", [RevOidSoFar]), + false; + {value, #node_info{me = ME}} -> + {variable, ME, OidSoFar} + end; + _ -> + false + end; +find_next(D, {node, subagent}, _Idx, RevOidSoFar, MibView) -> + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{subagents = SAs} = D, + case lists:keysearch(OidSoFar, 2, SAs) of + {value, {SubAgentPid, OidSoFar}} -> + {subagent, SubAgentPid, OidSoFar}; + false -> + ?vinfo("find_node -> could not find subagent with" + "~n OidSoFar: ~p" + "~n SAs: ~p", [OidSoFar, SAs]), + false + end + end. + +%%%====================================================================== +%%% 3. Tree building functions +%%% Used when loading mibs. +%%%====================================================================== + +build_tree(Mes, MibName) -> + ?d("build_tree -> " + "~n Mes: ~p", [Mes]), + {ListTree, []} = build_subtree([], Mes, MibName), + {tree, convert_tree(ListTree), internal}. + +%%---------------------------------------------------------------------- +%% Purpose: Builds the tree where all oids have prefix equal to LevelPrefix. +%% Returns: {Tree, RestMes} +%% RestMes are Mes that should not be in this subtree. +%% The Tree is a temporary and simplified data structure that is easy to +%% convert to the final tuple tree used by the MIB process. +%% A Node is represented as in the final tree. +%% The tree is not represented as a N-tuple, but as an Index-list. +%% Example: Temporary: [{1, Node1}, {3, Node3}] +%% Final: {Node1, undefined_node, Node3} +%% Pre: Mes are sorted on oid. +%%---------------------------------------------------------------------- +build_subtree(LevelPrefix, [Me | Mes], MibName) -> + ?vtrace("build subtree -> ~n" + " oid: ~p~n" + " LevelPrefix: ~p~n" + " MibName: ~p", [Me#me.oid, LevelPrefix, MibName]), + EType = Me#me.entrytype, + ?vtrace("build subtree -> EType = ~p",[EType]), + case in_subtree(LevelPrefix, Me) of + above -> + ?vtrace("build subtree -> above",[]), + {[], [Me|Mes]}; + {node, Index} -> + ?vtrace("build subtree -> node at ~p",[Index]), + {Tree, RestMes} = build_subtree(LevelPrefix, Mes, MibName), + {[{Index, {node, {EType, MibName}}} | Tree], RestMes}; + {subtree, Index, NewLevelPrefix} -> + ?vtrace("build subtree -> subtree at" + "~n ~w with ~w", + [Index, NewLevelPrefix]), + {BelowTree, RestMes} = + build_subtree(NewLevelPrefix, Mes, MibName), + {CurTree, RestMes2} = + build_subtree(LevelPrefix, RestMes, MibName), + {[{Index, {tree, BelowTree, {EType,MibName}}}| CurTree], RestMes2}; + {internal_subtree, Index, NewLevelPrefix} -> + ?vtrace("build subtree -> internal_subtree at" + "~n ~w with ~w", + [Index,NewLevelPrefix]), + {BelowTree, RestMes} = + build_subtree(NewLevelPrefix, [Me | Mes], MibName), + {CurTree, RestMes2} = + build_subtree(LevelPrefix, RestMes, MibName), + {[{Index, {tree, BelowTree, internal}} | CurTree], RestMes2} + end; + +build_subtree(_LevelPrefix, [], _MibName) -> + ?vtrace("build subtree -> done", []), + {[], []}. + +%%-------------------------------------------------- +%% Purpose: Determine how/if/where Me should be inserted in subtree +%% with LevelPrefix. This function does not build any tree, only +%% determinses what should be done (by build subtree). +%% Returns: +%% above - Indicating that this ME should _not_ be in this subtree. +%% {node, Index} - yes, construct a node with index Index on this level +%% {internal_subtree, Index, NewLevelPrefix} - yes, there should be an +%% internal subtree at this index. +%% {subtree, Index, NewLevelPrefix} - yes, construct a subtree with +%% NewLevelPrefix and insert this on current level in position Index. +%%-------------------------------------------------- +in_subtree(LevelPrefix, Me) -> + case lists:prefix(LevelPrefix, Me#me.oid) of + true when length(Me#me.oid) > length(LevelPrefix) -> + classify_how_in_subtree(LevelPrefix, Me); + _ -> + above + end. + +%%-------------------------------------------------- +%% See comment about in_subtree/2. This function takes care of all cases +%% where the ME really should be in _this_ subtree (not above). +%%-------------------------------------------------- +classify_how_in_subtree(LevelPrefix, Me) + when (length(Me#me.oid) =:= (length(LevelPrefix) + 1)) -> + Oid = Me#me.oid, + case node_or_subtree(Me#me.entrytype) of + subtree -> + {subtree, lists:last(Oid), Oid}; + node -> + {node, lists:last(Oid)} + end; + +classify_how_in_subtree(LevelPrefix, Me) + when (length(Me#me.oid) > (length(LevelPrefix) + 1)) -> + L1 = length(LevelPrefix) + 1, + Oid = Me#me.oid, + {internal_subtree, lists:nth(L1, Oid), lists:sublist(Oid, 1, L1)}. + +%%-------------------------------------------------- +%% Determines how to treat different kinds om MEs in the tree building process. +%% Pre: all internal nodes have been removed. +%%-------------------------------------------------- +node_or_subtree(table) -> subtree; +node_or_subtree(table_entry) -> subtree; +node_or_subtree(variable) -> node; +node_or_subtree(table_column) -> node. + +%%-------------------------------------------------- +%% Purpose: (Recursively) Converts a temporary tree (see above) to a final tree. +%% If input is a ListTree, output is a TupleTree. +%% If input is a Node, output is the same Node. +%% Pre: All Indexes are >= 0. +%%-------------------------------------------------- +convert_tree({Index, {tree, Tree, Info}}) when Index >= 0 -> + L = lists:map(fun convert_tree/1, Tree), + {Index, {tree, dict_list_to_tuple(L), Info}}; +convert_tree({Index, {node, Info}}) when Index >= 0 -> + {Index, {node, Info}}; +convert_tree(Tree) when is_list(Tree) -> + L = lists:map(fun convert_tree/1, Tree), + dict_list_to_tuple(L). + +%%---------------------------------------------------------------------- +%% Purpose: Converts a single level (that is non-recursively) from +%% the temporary indexlist to the N-tuple. +%% Input: A list of {Index, Data}. +%% Output: A tuple where element Index is Data. +%%---------------------------------------------------------------------- +dict_list_to_tuple(L) -> + L2 = lists:keysort(1, L), + list_to_tuple(integrate_indexes(0, L2)). + +%%---------------------------------------------------------------------- +%% Purpose: Helper function for dict_list_to_tuple/1. +%% Converts an indexlist to a N-list. +%% Input: A list of {Index, Data}. +%% Output: A (usually longer, never shorter) list where element Index is Data. +%% Example: [{1,hej}, {3, sven}] will give output +%% [undefined_node, hej, undefined_node, sven]. +%% Initially CurIndex should be 0. +%%---------------------------------------------------------------------- +integrate_indexes(CurIndex, [{CurIndex, Data} | T]) -> + [Data | integrate_indexes(CurIndex + 1, T)]; +integrate_indexes(_Index, []) -> + []; +integrate_indexes(CurIndex, L) -> + [undefined_node | integrate_indexes(CurIndex + 1, L)]. + +%%%====================================================================== +%%% 4. Tree merging +%%% Used by: load mib, insert subagent. +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Arg: Two root nodes (that is to be merged). +%% Returns: A new root node where the nodes have been merger to one. +%%---------------------------------------------------------------------- +merge_nodes(Same, Same) -> + Same; +merge_nodes(Node, undefined_node) -> + Node; +merge_nodes(undefined_node, Node) -> + Node; +merge_nodes({tree, Tree1, internal}, {tree, Tree2, internal}) -> + {tree, merge_levels(tuple_to_list(Tree1),tuple_to_list(Tree2)), internal}; +merge_nodes(Node1, Node2) -> + throw({error_merge_nodes, Node1, Node2}). + +%%---------------------------------------------------------------------- +%% Arg: Two levels to be merged. +%% Here, a level is represented as a list of nodes. A list is easier +%% to extend than a tuple. +%% Returns: The resulting, merged level tuple. +%%---------------------------------------------------------------------- +merge_levels(Level1, Level2) when length(Level1) =:= length(Level2) -> + MergeNodes = fun(N1, N2) -> merge_nodes(N1, N2) end, + list_to_tuple(snmp_misc:multi_map(MergeNodes, [Level1, Level2])); +merge_levels(Level1, Level2) when length(Level1) > length(Level2) -> + merge_levels(Level1, Level2 ++ + undefined_nodes_list(length(Level1) - length(Level2))); +merge_levels(Level1, Level2) when length(Level1) < length(Level2) -> + merge_levels(Level2, Level1). + +undefined_nodes_list(N) -> lists:duplicate(N, undefined_node). + + +%%%====================================================================== +%%% 5. Tree deletion routines +%%% (for unload mib) +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Purpose: Actually kicks of the tree reconstruction. +%% Returns: {list of removed MEs, NewTree} +%%---------------------------------------------------------------------- +delete_mib_from_tree(MibName, {tree, Tree, internal}) -> + case delete_tree(Tree, MibName) of + [] -> + {tree, {undefined_node}, internal}; % reduce + LevelList -> + {tree, list_to_tuple(LevelList), internal} + end. + +%%---------------------------------------------------------------------- +%% Purpose: Deletes all nodes associated to MibName from this level and +%% all levels below. +%% If the new level does not contain information (that is, no +%% other mibs use it) anymore the empty list is returned. +%% Returns: {MEs, The new level represented as a list} +%%---------------------------------------------------------------------- +delete_tree(Tree, MibName) when is_tuple(Tree) -> + NewLevel = delete_nodes(tuple_to_list(Tree), MibName, []), + case lists:filter(fun drop_undefined_nodes/1,NewLevel) of + [] -> []; + _A_perhaps_shorted_list -> + NewLevel % some other mib needs this level + end. + +%%---------------------------------------------------------------------- +%% Purpose: Nodes belonging to MibName are removed from the tree. +%% Recursively deletes sub trees to this node. +%% Returns: {MEs, NewNodesList} +%%---------------------------------------------------------------------- +delete_nodes([], _MibName, AccNodes) -> + lists:reverse(AccNodes); + +delete_nodes([{node, {variable, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{node, {table_column, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, _Tree, {table, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, _Tree, {table_entry, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, Tree, Info}|T], MibName, AccNodes) -> + case delete_tree(Tree, MibName) of + [] -> % tree completely deleted + delete_nodes(T, MibName, [undefined_node | AccNodes]); + LevelList -> + delete_nodes(T, MibName, + [{tree, list_to_tuple(LevelList), Info} | AccNodes]) + end; + +delete_nodes([NodeToKeep|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [NodeToKeep | AccNodes]). + +drop_undefined_nodes(undefined_node) -> false; +drop_undefined_nodes(_) -> true. + + +%%%====================================================================== +%%% 6. Functions for subagent handling +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Returns: A new Root|{error, reason} +%%---------------------------------------------------------------------- +insert_subagent(Oid, OldRoot) -> + ListTree = build_tree_for_subagent(Oid), + case catch convert_tree(ListTree) of + {'EXIT', _Reason} -> + {error, 'cannot construct tree from oid'}; + Level when is_tuple(Level) -> + T = {tree, Level, internal}, + case catch merge_nodes(T, OldRoot) of + {error_merge_nodes, _Node1, _Node2} -> + {error, oid_conflict}; + NewRoot when is_tuple(NewRoot) andalso + (element(1, NewRoot) =:= tree) -> + NewRoot + end + end. + +build_tree_for_subagent([Index]) -> + [{Index, {node, subagent}}]; + +build_tree_for_subagent([Index | T]) -> + [{Index, {tree, build_tree_for_subagent(T), internal}}]. + +%%---------------------------------------------------------------------- +%% Returns: A new tree where the subagent at Oid (2nd arg) has been deleted. +%%---------------------------------------------------------------------- +delete_subagent({tree, Tree, Info}, [Index]) -> + {node, subagent} = element(Index+1, Tree), + {tree, setelement(Index+1, Tree, undefined_node), Info}; +delete_subagent({tree, Tree, Info}, [Index | TI]) -> + {tree, setelement(Index+1, Tree, + delete_subagent(element(Index+1, Tree), TI)), Info}. + +%%%====================================================================== +%%% 7. Misc functions +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Installs the mibs found in the database when starting the agent. +%% Basically calls the instrumentation functions for all non-internal +%% mib-entries +%%---------------------------------------------------------------------- +install_mibs(MibDb, NodeDb) -> + MibNames = loaded(MibDb), + ?vtrace("install_mibs -> found following mibs in database: ~n" + "~p", [MibNames]), + install_mibs2(NodeDb, MibNames). + +install_mibs2(_, []) -> + ok; +install_mibs2(NodeDb, [MibName|MibNames]) -> + Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'}, + Nodes = snmpa_general_db:match_object(NodeDb, Pattern), + MEs = [ME || #node_info{me = ME} <- Nodes], + ?vtrace("install_mibs2 -> installing ~p MEs for mib ~p", + [length(MEs),MibName]), + NewF = fun(ME) -> call_instrumentation(ME, new) end, + lists:foreach(NewF, MEs), + install_mibs2(NodeDb, MibNames). + + +%%---------------------------------------------------------------------- +%% Does all side effect stuff during load_mib. +%%---------------------------------------------------------------------- +install_mib(Db, Symbolic, Mib, MibName, FileName, NonInternalMes) -> + ?vdebug("install_mib -> entry with" + "~n Symbolic: ~p" + "~n MibName: ~p" + "~n FileName: ~p", [Symbolic, MibName, FileName]), + Rec = #mib_info{name = MibName, symbolic = Symbolic, file_name = FileName}, + snmpa_general_db:write(Db, Rec), + install_mib2(Symbolic, MibName, Mib), + NewF = fun(ME) -> call_instrumentation(ME, new) end, + lists:foreach(NewF, NonInternalMes). + +install_mib2(true, MibName, Mib) -> + #mib{table_infos = TabInfos, + variable_infos = VarInfos, + mes = MEs, + asn1_types = ASN1Types, + traps = Traps} = Mib, + snmpa_symbolic_store:add_table_infos(MibName, TabInfos), + snmpa_symbolic_store:add_variable_infos(MibName, VarInfos), + snmpa_symbolic_store:add_aliasnames(MibName, MEs), + snmpa_symbolic_store:add_types(MibName, ASN1Types), + SetF = fun(Trap) -> + snmpa_symbolic_store:set_notification(Trap, MibName) + end, + lists:foreach(SetF, Traps); +install_mib2(_, _, _) -> + ok. + +install_mes(_Db, _MibName, []) -> + ok; +install_mes(Db, MibName, [ME|MEs]) -> + Node = #node_info{oid = ME#me.oid, mib_name = MibName, me = ME}, + snmpa_general_db:write(Db, Node), + install_mes(Db, MibName, MEs). + + +%%---------------------------------------------------------------------- +%% Does all side effect stuff during unload_mib. +%%---------------------------------------------------------------------- +uninstall_mib(Db, Symbolic, MibName, MEs) -> + ?vtrace("uninstall_mib -> entry with" + "~n Db: ~p" + "~n Symbolic: ~p" + "~n MibName: ~p", [Db, Symbolic, MibName]), + Res = snmpa_general_db:delete(Db, MibName), + ?vtrace("uninstall_mib -> (mib) db delete result: ~p", [Res]), + uninstall_mib2(Symbolic, MibName), + DelF = fun(ME) -> call_instrumentation(ME, delete) end, + lists:foreach(DelF, MEs). + +uninstall_mib2(true, MibName) -> + snmpa_symbolic_store:delete_table_infos(MibName), + snmpa_symbolic_store:delete_variable_infos(MibName), + snmpa_symbolic_store:delete_aliasnames(MibName), + snmpa_symbolic_store:delete_types(MibName), + snmpa_symbolic_store:delete_notifications(MibName); +uninstall_mib2(_, _) -> + ok. + +uninstall_mes(Db, MibName) -> + Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'}, + snmpa_general_db:match_delete(Db, Pattern). + + +%%---------------------------------------------------------------------- +%% Create a list of the names of all the loaded mibs +%%---------------------------------------------------------------------- +loaded(Db) -> + [N || #mib_info{name = N} <- snmpa_general_db:tab2list(Db)]. + + +%%---------------------------------------------------------------------- +%% Calls MFA-instrumentation with 'new' or 'delete' operation. +%%---------------------------------------------------------------------- +call_instrumentation(#me{entrytype = variable, mfa={M,F,A}}, Operation) -> + ?vtrace("call instrumentation with" + "~n entrytype: variable" + "~n MFA: {~p,~p,~p}" + "~n Operation: ~p", + [M,F,A,Operation]), + catch apply(M, F, [Operation | A]); +call_instrumentation(#me{entrytype = table_entry, mfa={M,F,A}}, Operation) -> + ?vtrace("call instrumentation with" + "~n entrytype: table_entry" + "~n MFA: {~p,~p,~p}" + "~n Operation: ~p", + [M,F,A,Operation]), + catch apply(M, F, [Operation | A]); +call_instrumentation(_ShitME, _Operation) -> + done. + + +maybe_drop_me(#me{entrytype = internal}) -> false; +maybe_drop_me(#me{entrytype = group}) -> false; +maybe_drop_me(#me{imported = true}) -> false; +maybe_drop_me(_) -> true. + + +%%---------------------------------------------------------------------- +%% Code change functions +%%---------------------------------------------------------------------- + +code_change(down, State) -> + ?d("code_change(down) -> entry",[]), + State; + +code_change(up, State) -> + ?d("code_change(up)",[]), + State; + +code_change(_Vsn, State) -> + State. + diff --git a/lib/snmp/src/agent/snmpa_mib_data_tttn.erl b/lib/snmp/src/agent/snmpa_mib_data_tttn.erl new file mode 100644 index 0000000000..90ddf4869f --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib_data_tttn.erl @@ -0,0 +1,1443 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2013. 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_data_tttn). + + +%%%----------------------------------------------------------------- +%%% +%%% TTTN - TupleTreeTupleNodes +%%% +%%% This module implements the MIB (internal) data structures. +%%% The TTTN MIB Data structure consists of three items: +%%% +%%% 1) A mib-storage (as specified when new is called) for +%%% the data associated with each variable, table, +%%% table-entry and table-column in the MIB. +%%% 2) A tree contains information of the Oids in the MIB. +%%% 3) A list of registered subagents. +%%% +%%% The subagent information is consequently duplicated. It resides +%%% both in the tree and in the list. +%%% +%%% When a mib is loaded, the tree is built from the plain list +%%% in the binary file. +%%% +%%%----------------------------------------------------------------- + +-include_lib("snmp/include/snmp_types.hrl"). +-include_lib("snmp/src/misc/snmp_debug.hrl"). + +-define(VMODULE,"MDATA_TTTN"). +-include_lib("snmp/src/misc/snmp_verbosity.hrl"). + +-behaviour(snmpa_mib_data). + +-define(MIB_DATA, snmpa_mib_data). +-define(MIB_NODE, snmpa_mib_node). +-define(MIB_TREE, snmpa_mib_tree). +-define(DUMMY_TREE_GENERATION, 1). +-define(DEFAULT_TREE, {tree,{undefined_node},internal}). + + +%%%----------------------------------------------------------------- +%%% Table of contents +%%% ================= +%%% 1. Interface +%%% 2. Implementation of tree access +%%% 3. Tree building functions +%%% 4. Tree merging +%%% 5. Tree deletion routines +%%% 6. Functions for subagent handling +%%% 7. Misc functions +%%%----------------------------------------------------------------- + +-record(mib_data, + { + %% Mib storage module + module :: snmpa:mib_storage_module(), + + %% A database of loaded mibs + %% #mib_info{} + mib_db, + + %% A database with information about each node in the tree + %% #node_info{} + node_db, + + %% A database containing _one_ record with the tree + %% without the subagent(s). + %% (the reason for this is part to get replication + %% and part out of convenience) + %% #tree{} + tree_db, + + %% The root node (same as the tree part of the tree_db + %% but with the subagents added). + tree, + + %% A list of {SAPid, Oid} + subagents = [] + }). + +-record(mib_info, {name, symbolic, file_name}). +-record(node_info, {oid, mib_name, me}). + + +%% (behaviour) API +-export([new/1, + close/1, + sync/1, + load_mib/4, + unload_mib/4, + lookup/2, + next/3, + register_subagent/3, + unregister_subagent/2, + dump/2, + which_mib/2, which_mibs/1, + whereis_mib/2, + info/1, info/2, + backup/2, + code_change/4]). + + +%%----------------------------------------------------------------- +%% A tree is represented as a N-tuple, where each element is a +%% node. A node is: +%% 1) {tree, Tree, Info} where Info can be {table, Id}, {table_entry, Id} +%% or perhaps 'internal' +%% 2) undefined_node (memory optimization (instead of {node, undefined})) +%% 3) {node, Info} where Info can be {subagent, Pid}, {variable, Id}, +%% {table_column, Id} +%% Id is {MibName, MibEntry} +%% The over all root is represented as {tree, Tree, internal}. +%% +%% tree() = {tree, nodes(), tree_info()} +%% nodes() = {tree() | node() | undefined_node, ...} +%% node() = {node, node_info()} +%% tree_info() = {table, Id} | {table_entry, Id} | internal +%% node_info() = {subagent, Pid} | {variable, Id} | {table_colum, Id} +%%----------------------------------------------------------------- + +%% This record is what is stored in the database. The 'tree' part +%% is described above... +-record(tree, {generation = ?DUMMY_TREE_GENERATION, root = ?DEFAULT_TREE}). + + +%%%====================================================================== +%%% 1. Interface +%%%====================================================================== + +%%----------------------------------------------------------------- +%% Func: new/1 +%% Returns: A representation of mib data. +%%----------------------------------------------------------------- + +%% Where -> A list of nodes where the tables will be created +new(MibStorage) -> + Mod = snmp_misc:get_option(module, MibStorage), + Opts = snmp_misc:get_option(options, MibStorage, []), + + %% First we must check if there is already something to read + %% If a database already exists, then the tree structure has to be read + ?vdebug("open (mib) database",[]), + MibDb = + case Mod:open(?MIB_DATA, mib_info, record_info(fields, mib_info), + set, Opts) of + {ok, T1} -> + T1; + {error, Reason1} -> + throw({error, {open, mib_data, Reason1}}) + end, + + ?vdebug("open (mib) node database",[]), + NodeDb = + case Mod:open(?MIB_NODE, node_info, record_info(fields, node_info), + set, Opts) of + {ok, T2} -> + T2; + {error, Reason2} -> + throw({error, {open, mib_node, Reason2}}) + end, + + ?vdebug("open (mib) tree database",[]), + TreeDb = + case Mod:open(?MIB_TREE, tree, record_info(fields, tree), + set, Opts) of + {ok, T3} -> + T3; + {error, Reason3} -> + throw({error, {open, mib_tree, Reason3}}) + end, + + ?vdebug("write the default (mib) tree",[]), + Tree = + case Mod:read(TreeDb, ?DUMMY_TREE_GENERATION) of + false -> + T = #tree{}, + Mod:write(TreeDb, T), + T; + {value, T} -> + T + end, + ?vdebug("install (existing) mibs",[]), + install_mibs(Mod, MibDb, NodeDb), + ?vdebug("done",[]), + #mib_data{module = Mod, + mib_db = MibDb, + node_db = NodeDb, + tree_db = TreeDb, + tree = Tree}. + + +%%---------------------------------------------------------------------- +%% Returns: new mib data | {error, Reason} +%%---------------------------------------------------------------------- +load_mib(MibData, FileName, MeOverride, TeOverride) + when is_record(MibData,mib_data) andalso is_list(FileName) -> + ?vlog("load mib file: ~p",[FileName]), + ActualFileName = filename:rootname(FileName, ".bin") ++ ".bin", + MibName = list_to_atom(filename:basename(FileName, ".bin")), + (catch do_load_mib(MibData, ActualFileName, MibName, + MeOverride, TeOverride)). + +do_load_mib(MibData, ActualFileName, MibName, MeOverride, TeOverride) -> + ?vtrace("do_load_mib -> entry with" + "~n ActualFileName: ~s" + "~n MibName: ~p",[ActualFileName, MibName]), + #mib_data{module = Mod, + mib_db = MibDb, + node_db = NodeDb, + %% tree_db = TreeDb, + tree = Tree} = MibData, + verify_not_loaded(Mod, MibDb, MibName), + ?vtrace("do_load_mib -> already loaded mibs:" + "~n ~p", [loaded(Mod, MibDb)]), + Mib = do_read_mib(ActualFileName), + ?vtrace("do_load_mib -> read mib ~s",[Mib#mib.name]), + NonInternalMes = + lists:filter(fun(ME) -> maybe_drop_me(ME) end, Mib#mib.mes), + OldRoot = Tree#tree.root, + T = build_tree(NonInternalMes, MibName), + ?d("load_mib -> " + "~n OldRoot: ~p" + "~n T: ~p", [OldRoot, T]), + case (catch merge_nodes(T, OldRoot)) of + {error_merge_nodes, Node1, Node2} -> + ?vlog("error merging nodes:" + "~n~p~nand~n~p", [Node1,Node2]), + {error, oid_conflict}; + NewRoot when is_tuple(NewRoot) andalso (element(1,NewRoot) =:= tree) -> + ?d("load_mib -> " + "~n NewRoot: ~p", [NewRoot]), + Symbolic = not lists:member(no_symbolic_info, Mib#mib.misc), + case (catch check_notif_and_mes(TeOverride, MeOverride, Symbolic, + Mib#mib.traps, NonInternalMes)) of + true -> + install_mes(Mod, NodeDb, MibName, NonInternalMes), + install_mib(Mod, + MibDb, Symbolic, Mib, + MibName, ActualFileName, NonInternalMes), + ?vtrace("installed mib ~s", [Mib#mib.name]), + Tree2 = Tree#tree{root = NewRoot}, + {ok, MibData#mib_data{tree = Tree2}}; + Else -> + Else + end + end. + + +verify_not_loaded(Mod, Tab, Name) -> + case Mod:read(Tab, Name) of + {value, #mib_info{name = Name}} -> + throw({error, already_loaded}); + false -> + ok + end. + +do_read_mib(ActualFileName) -> + case snmp_misc:read_mib(ActualFileName) of + {error, Reason} -> + ?vlog("Failed reading mib file ~p with reason: ~p", + [ActualFileName, Reason]), + throw({error, Reason}); + {ok, Mib} -> + Mib + end. + +%% The Tree DB is handled in a special way since it can be very large. +sync(#mib_data{module = Mod, + mib_db = M, + node_db = N, + tree_db = T, tree = Tree, subagents = []}) -> + Mod:sync(M), + Mod:sync(N), + Mod:write(T, Tree), + Mod:sync(T); +sync(#mib_data{module = Mod, + mib_db = M, + node_db = N, + tree_db = T, tree = Tree, subagents = SAs}) -> + + Mod:sync(M), + Mod:sync(N), + + %% Ouch. Since the subagent info is dynamic we do not + %% want to store the tree containing subagent info. So, we + %% have to create a tmp tree without those and store it. + + case delete_subagents(Tree, SAs) of + {ok, TreeWithoutSAs} -> + Mod:write(T, TreeWithoutSAs), + Mod:sync(T); + Error -> + Error + end. + +delete_subagents(Tree, []) -> + {ok, Tree}; +delete_subagents(Tree0, [{_, Oid}|SAs]) -> + case (catch delete_subagent(Tree0, Oid)) of + {tree, _Tree, _Info} = Tree1 -> + delete_subagents(Tree1, SAs); + _Error -> + {error, {'invalid oid', Oid}} + end. + +%%---------------------------------------------------------------------- +%% (OTP-3601) +%%---------------------------------------------------------------------- +check_notif_and_mes(TeOverride,MeOverride,Symbolic,Traps,MEs) -> + ?vtrace("check notifications and mib entries",[]), + check_notifications(TeOverride,Symbolic,Traps), + check_mes(MeOverride,MEs). + +check_notifications(true, _Symbolic, _Traps) -> + ?vtrace("trapentry override = true => skip check",[]), + true; +check_notifications(_, Symbolic, Traps) -> + check_notifications(Symbolic, Traps). + +check_notifications(true, Traps) -> + check_notifications(Traps); +check_notifications(_, _) -> true. + +check_notifications([]) -> true; +check_notifications([#trap{trapname = Key} = Trap | Traps]) -> + ?vtrace("check notification [trap] with Key: ~p",[Key]), + case snmpa_symbolic_store:get_notification(Key) of + {value, Trap} -> check_notifications(Traps); + {value, _} -> throw({error, {'trap already defined', Key}}); + undefined -> check_notifications(Traps) + end; +check_notifications([#notification{trapname = Key} = Notif | Traps]) -> + ?vtrace("check notification [notification] with Key: ~p",[Key]), + case snmpa_symbolic_store:get_notification(Key) of + {value, Notif} -> + check_notifications(Traps); + {value, _} -> + throw({error, {'notification already defined', Key}}); + undefined -> + check_notifications(Traps) + end; +check_notifications([Crap | Traps]) -> + ?vlog("skipped check of: ~n~p",[Crap]), + check_notifications(Traps). + +check_mes(true,_) -> + ?vtrace("mibentry override = true => skip check",[]), + true; +check_mes(_,MEs) -> + check_mes(MEs). + +check_mes([]) -> true; +check_mes([#me{aliasname = Name, oid = Oid1} | MEs]) -> + ?vtrace("check mib entries with aliasname: ~p",[Name]), + case snmpa_symbolic_store:aliasname_to_oid(Name) of + {value, Oid1} -> + check_mes(MEs); + {value, Oid2} -> + ?vinfo("~n expecting '~p'~n but found '~p'",[Oid1, Oid2]), + throw({error, {'mibentry already defined', Name}}); + false -> + check_mes(MEs) + end; +check_mes([Crap | MEs]) -> + ?vlog("skipped check of: ~n~p",[Crap]), + check_mes(MEs). + + + +%%---------------------------------------------------------------------- +%% Returns: new mib data | {error, Reason} +%%---------------------------------------------------------------------- +unload_mib(MibData, FileName, _, _) when is_list(FileName) -> + MibName = list_to_atom(filename:basename(FileName, ".bin")), + (catch do_unload_mib(MibData, MibName)). + +do_unload_mib(MibData, MibName) -> + ?vtrace("do_unload_mib -> entry with" + "~n MibName: ~p", [MibName]), + #mib_data{module = Mod, + mib_db = MibDb, + node_db = NodeDb, + %% tree_db = TreeDb, + tree = Tree} = MibData, + #mib_info{symbolic = Symbolic} = verify_loaded(Mod, MibDb, MibName), + NewRoot = delete_mib_from_tree(MibName, Tree#tree.root), + MEs = uninstall_mes(Mod, NodeDb, MibName), + uninstall_mib(Mod, MibDb, Symbolic, MibName, MEs), + NewMibData = MibData#mib_data{tree = Tree#tree{root = NewRoot}}, + {ok, NewMibData}. + +verify_loaded(Mod, Tab, Name) -> + case Mod:read(Tab, Name) of + {value, MibInfo} -> + MibInfo; + false -> + throw({error, not_loaded}) + end. + + +close(#mib_data{module = Mod, + mib_db = MibDb, + node_db = NodeDb, + tree_db = TreeDb}) -> + Mod:close(MibDb), + Mod:close(NodeDb), + Mod:close(TreeDb), + ok. + +register_subagent(#mib_data{tree = T} = MibData, Oid, Pid) -> + case insert_subagent(Oid, T#tree.root) of + {error, Reason} -> + {error, Reason}; + NewRootTree -> + SAs = [{Pid, Oid} | MibData#mib_data.subagents], + T2 = T#tree{root = NewRootTree}, + {ok, MibData#mib_data{tree = T2, subagents = SAs}} + end. + + +%%---------------------------------------------------------------------- +%% Purpose: Get a list of all loaded mibs +%% Returns: [{Name, File}] +%%---------------------------------------------------------------------- + +which_mibs(#mib_data{module = Mod, mib_db = Db}) -> + Mibs = Mod:tab2list(Db), + [{Name, File} || #mib_info{name = Name, file_name = File} <- Mibs]. + + +%%---------------------------------------------------------------------- +%% Purpose: Get a list of all loaded mibs +%% Returns: [{Name, File}] +%%---------------------------------------------------------------------- + +whereis_mib(#mib_data{module = Mod, mib_db = Db}, Name) -> + case Mod:read(Db, Name) of + {value, #mib_info{file_name = File}} -> + {ok, File}; + false -> + {error, not_found} + end. + + +%%---------------------------------------------------------------------- +%% Purpose: Deletes SA with Pid from all subtrees it handles. +%% Returns: NewMibData. +%%---------------------------------------------------------------------- +unregister_subagent(#mib_data{subagents = SAs} = MibData, Pid) + when is_pid(Pid) -> + SAs = MibData#mib_data.subagents, + case lists:keysearch(Pid, 1, SAs) of + false -> + {ok, MibData}; + {value, {Pid, Oid}} -> + % we should never get an error since Oid is found in MibData. + {ok, NewMibData, _DeletedSA} = unregister_subagent(MibData, Oid), + % continue if the same Pid handles other mib subtrees. + unregister_subagent(NewMibData, Pid) + end; + +%%---------------------------------------------------------------------- +%% Purpose: Deletes one unique subagent. +%% Returns: {error, Reason} | {ok, NewMibData, DeletedSubagentPid} +%%---------------------------------------------------------------------- +unregister_subagent(#mib_data{tree = T} = MibData, Oid) when is_list(Oid) -> + case (catch delete_subagent(T#tree.root, Oid)) of + {tree, Tree, Info} -> + OldSAs = MibData#mib_data.subagents, + {value, {Pid, _Oid}} = lists:keysearch(Oid, 2, OldSAs), + SAs = lists:keydelete(Oid, 2, OldSAs), + T2 = T#tree{root = {tree, Tree, Info}}, + {ok, + MibData#mib_data{tree = T2, subagents = SAs}, + Pid}; + _ -> + {error, {invalid_oid, Oid}} + end. + +%%---------------------------------------------------------------------- +%% Purpose: To inpect memory usage, loaded mibs, registered subagents +%%---------------------------------------------------------------------- +info(MibData) -> + ?vtrace("retrieve info",[]), + #mib_data{module = Mod, + mib_db = MibDb, + node_db = NodeDb, + tree_db = TreeDb, + tree = Tree, + subagents = SAs} = MibData, + LoadedMibs = old_format(Mod:tab2list(MibDb)), + TreeSize = snmp_misc:mem_size(Tree), + {memory, ProcSize} = erlang:process_info(self(), memory), + MibDbSize = Mod:info(MibDb, memory), + NodeDbSize = Mod:info(NodeDb, memory), + TreeDbSize = Mod:info(TreeDb, memory), + [{loaded_mibs, LoadedMibs}, {subagents, SAs}, {tree_size_bytes, TreeSize}, + {process_memory, ProcSize}, + {db_memory, [{mib,MibDbSize},{node,NodeDbSize},{tree,TreeDbSize}]}]. + +info(#mib_data{module = Mod, mib_db = MibDb}, loaded_mibs) -> + Mibs = Mod:tab2list(MibDb), + [filename:rootname(FN, ".bin") || #mib_info{file_name = FN} <- Mibs]; +info(#mib_data{tree = Tree}, tree_size_bytes) -> + snmp_misc:mem_size(Tree); +info(_, process_memory) -> + {memory, ProcSize} = erlang:process_info(self(),memory), + ProcSize; +info(#mib_data{module = Mod, + mib_db = MibDb, + node_db = NodeDb, + tree_db = TreeDb}, + db_memory) -> + MibDbSize = Mod:info(MibDb, memory), + NodeDbSize = Mod:info(NodeDb, memory), + TreeDbSize = Mod:info(TreeDb, memory), + [{mib, MibDbSize}, {node, NodeDbSize}, {tree, TreeDbSize}]; +info(#mib_data{subagents = SAs}, subagents) -> + SAs. + +old_format(LoadedMibs) -> + ?vtrace("convert mib info to old format",[]), + [{N,S,F} || #mib_info{name=N,symbolic=S,file_name=F} <- LoadedMibs]. + + +%%---------------------------------------------------------------------- +%% A total dump for debugging. +%%---------------------------------------------------------------------- + +dump(#mib_data{module = Mod, + mib_db = MibDb, + node_db = NodeDb, + tree = Tree}, io) -> + (catch io:format("MIB-tables:~n~p~n~n", + [Mod:tab2list(MibDb)])), + (catch io:format("MIB-entries:~n~p~n~n", + [Mod:tab2list(NodeDb)])), + (catch io:format("Tree:~n~p~n", [Tree])), % good luck reading it! + ok; + +dump(#mib_data{module = Mod, + mib_db = MibDb, + node_db = NodeDb, + tree = Tree}, File) -> + case file:open(File, [write]) of + {ok, Fd} -> + io:format(Fd,"~s~n", + [snmp:date_and_time_to_string(snmp:date_and_time())]), + (catch io:format(Fd,"MIB-tables:~n~p~n~n", + [Mod:tab2list(MibDb)])), + (catch io:format(Fd, "MIB-entries:~n~p~n~n", + [Mod:tab2list(NodeDb)])), + io:format(Fd,"Tree:~n~p~n", [Tree]), % good luck reading it! + file:close(Fd), + ok; + {error, Reason} -> + ?vinfo("~n Failed opening file '~s' for reason ~p", + [File, Reason]), + {error, Reason} + end. + + +backup(#mib_data{module = Mod, + mib_db = M, + node_db = N, + tree_db = T}, BackupDir) -> + MRes = Mod:backup(M, BackupDir), + NRes = Mod:backup(N, BackupDir), + TRes = Mod:backup(T, BackupDir), + handle_backup_res([{mib_db, MRes}, {node_db, NRes}, {tree_db, TRes}]). + +handle_backup_res(Res) -> + handle_backup_res(Res, []). + +handle_backup_res([], []) -> + ok; +handle_backup_res([], Err) -> + {error, lists:reverse(Err)}; +handle_backup_res([{_, ok}|Res], Err) -> + handle_backup_res(Res, Err); +handle_backup_res([{Tag, {error, Reason}}|Res], Err) -> + handle_backup_res(Res, [{Tag, Reason}|Err]); +handle_backup_res([{Tag, Error}|Res], Err) -> + handle_backup_res(Res, [{Tag, Error}|Err]). + + +%%%====================================================================== +%%% 2. Implementation of tree access +%%% lookup and next. +%%%====================================================================== + + +which_mib(#mib_data{tree = T} = D, Oid) -> + ?vtrace("which_mib -> entry with" + "~n Oid: ~p",[Oid]), + case (catch find_node(D, T#tree.root, Oid, [])) of + {variable, _ME, Mib} -> + ?vtrace("which_mib -> variable:" + "~n Mib: ~p", [Mib]), + {ok, Mib}; + {table, _EntryME, _, Mib} -> + ?vtrace("which_mib -> table:" + "~n Mib: ~p", [Mib]), + {ok, Mib}; + {subagent, SubAgentPid, _SANextOid} -> + ?vtrace("which_mib -> subagent:" + "~n SubAgentPid: ~p", [SubAgentPid]), + {error, {subagent, SubAgentPid}}; + {false, ErrorCode} -> + ?vtrace("which_mib -> false:" + "~n ErrorCode: ~p",[ErrorCode]), + {error, ErrorCode}; + false -> + ?vtrace("which_mib -> false",[]), + {error, noSuchObject}; + {'EXIT', R} -> + ?vtrace("which_mib -> exit:" + "~n R: ~p",[R]), + {error, noSuchObject} + end. + + +%%----------------------------------------------------------------- +%% 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, SAOid} | +%% {false, Reason} +%%----------------------------------------------------------------- +lookup(#mib_data{tree = T} = D, Oid) -> + ?vtrace("lookup -> entry with" + "~n Oid: ~p",[Oid]), + case (catch find_node(D, T#tree.root, Oid, [])) of + {variable, ME, _Mib} when is_record(ME, me) -> + ?vtrace("lookup -> variable:" + "~n ME: ~p",[ME]), + {variable, ME}; + {table, EntryME, {ColME, TableEntryOid}, _Mib} -> + ?vtrace("lookup -> table:" + "~n EntryME: ~p" + "~n ColME: ~p" + "~n RevTableEntryOid: ~p", + [EntryME, ColME, TableEntryOid]), + MFA = EntryME#me.mfa, + RetME = ColME#me{mfa = MFA}, + {table_column, RetME, TableEntryOid}; + {subagent, SubAgentPid, SANextOid} -> + ?vtrace("lookup -> subagent:" + "~n SubAgentPid: ~p" + "~n SANextOid: ~p", [SubAgentPid, SANextOid]), + {subagent, SubAgentPid, SANextOid}; + {false, ErrorCode} -> + ?vtrace("lookup -> false:" + "~n ErrorCode: ~p",[ErrorCode]), + {false, ErrorCode}; + false -> + ?vtrace("lookup -> false",[]), + {false, noSuchObject}; + {'EXIT', R} -> + ?vtrace("lookup -> exit:" + "~n R: ~p",[R]), + {false, noSuchObject} + end. + + +find_node(D, {tree, Tree, {table, _}}, RestOfOid, RevOid) -> + ?vtrace("find_node(tree,table) -> entry with" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[RestOfOid, RevOid]), + find_node(D, {tree, Tree, internal}, RestOfOid, RevOid); +find_node(D, {tree, Tree, {table_entry, _}}, RestOfOid, RevOid) -> + ?vtrace("find_node(tree,table_entry) -> entry with" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[RestOfOid, RevOid]), + #mib_data{module = Mod, node_db = Db} = D, + Oid = lists:reverse(RevOid), + case Mod:read(Db, Oid) of + {value, #node_info{me = ME, mib_name = Mib}} -> + case find_node(D, {tree, Tree, internal}, RestOfOid, RevOid) of + {false, ErrorCode} -> {false, ErrorCode}; + Val -> {table, ME, Val, Mib} + end; + false -> + ?vinfo("find_node -> could not find table_entry ME with" + "~n RevOid: ~p" + "~n when" + "~n RestOfOid: ~p", + [RevOid, RestOfOid]), + false + end; +find_node(D, {tree, Tree, _Internal}, [Int | RestOfOid], RevOid) -> + ?vtrace("find_node(tree) -> entry with" + "~n Int: ~p" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[Int, RestOfOid, RevOid]), + find_node(D, element(Int+1, Tree), RestOfOid, [Int | RevOid]); +find_node(D, {node, {table_column, _}}, RestOfOid, [ColInt | RevOid]) -> + ?vtrace("find_node(tree,table_column) -> entry with" + "~n RestOfOid: ~p" + "~n ColInt: ~p" + "~n RevOid: ~p",[RestOfOid, ColInt, RevOid]), + #mib_data{module = Mod, node_db = Db} = D, + Oid = lists:reverse([ColInt | RevOid]), + case Mod:read(Db, Oid) of + {value, #node_info{me = ME}} -> + {ME, lists:reverse(RevOid)}; + false -> + X = Mod:read(Db, lists:reverse([ColInt | RevOid])), + ?vinfo("find_node -> could not find table_column ME with" + "~n RevOid: ~p" + "~n trying [~p|~p]" + "~n X: ~p", + [RevOid, [ColInt | RevOid], X]), + false + end; +find_node(D, {node, {variable, _MibName}}, [0], RevOid) -> + ?vtrace("find_node(tree,variable,[0]) -> entry with" + "~n RevOid: ~p",[RevOid]), + #mib_data{module = Mod, node_db = Db} = D, + Oid = lists:reverse(RevOid), + case Mod:read(Db, Oid) of + {value, #node_info{me = ME, mib_name = Mib}} -> + {variable, ME, Mib}; + false -> + ?vinfo("find_node -> could not find variable ME with" + "~n RevOid: ~p", [RevOid]), + false + end; +find_node(_D, {node, {variable, _MibName}}, [], _RevOid) -> + ?vtrace("find_node(tree,variable,[]) -> entry",[]), + {false, noSuchObject}; +find_node(_D, {node, {variable, _MibName}}, _, _RevOid) -> + ?vtrace("find_node(tree,variable) -> entry",[]), + {false, noSuchInstance}; +find_node(D, {node, subagent}, _RestOfOid, SARevOid) -> + ?vtrace("find_node(tree,subagent) -> entry with" + "~n SARevOid: ~p",[SARevOid]), + #mib_data{subagents = SAs} = D, + SAOid = lists:reverse(SARevOid), + case lists:keysearch(SAOid, 2, SAs) of + {value, {SubAgentPid, SAOid}} -> + {subagent, SubAgentPid, SAOid}; + false -> + ?vinfo("find_node -> could not find subagent with" + "~n SAOid: ~p" + "~n SAs: ~p", [SAOid, SAs]), + false + end; +find_node(_D, Node, _RestOfOid, _RevOid) -> + ?vtrace("find_node -> failed:~n~p",[Node]), + {false, noSuchObject}. + + +%%----------------------------------------------------------------- +%% Func: next/3 +%% Purpose: Finds the lexicographically next oid. +%% Returns: endOfMibView | +%% {subagent, SubAgentPid, SAOid} | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%% If a variable is returnes, it is in the MibView. +%% If a table or subagent is returned, it *may* be in the MibView. +%%----------------------------------------------------------------- +next(#mib_data{tree = T} = D, Oid, MibView) -> + case catch next_node(D, T#tree.root, Oid, [], MibView) of + false -> endOfMibView; + Else -> Else + end. + +%%----------------------------------------------------------------- +%% This function is used as long as we have any Oid left. Take +%% one integer at a time from the Oid, and traverse the tree +%% accordingly. When the Oid is empty, call find_next. +%% Returns: {subagent, SubAgentPid, SAOid} | +%% false | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%%----------------------------------------------------------------- +next_node(_D, undefined_node, _Oid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(undefined_node) -> entry", []), + false; + +next_node(_D, {tree, Tree, {table_entry, _Id}}, [Int | _Oid], + _RevOidSoFar, _MibView) + when Int+1 > size(Tree) -> + ?vtrace("next_node(tree,table_entry) -> entry when not found whith" + "~n Int: ~p" + "~n size(Tree): ~p", [Int, size(Tree)]), + false; +next_node(D, {tree, Tree, {table_entry, _MibName}}, + Oid, RevOidSoFar, MibView) -> + ?vtrace("next_node(tree,table_entry) -> entry when" + "~n size(Tree): ~p" + "~n Oid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", [size(Tree), Oid, RevOidSoFar, MibView]), + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + ?vdebug("next_node(tree,table_entry) -> not in mib view",[]), + false; + _ -> + #mib_data{module = Mod, node_db = Db} = D, + case Mod:read(Db, OidSoFar) of + false -> + ?vinfo("next_node -> could not find table_entry with" + "~n OidSoFar: ~p", [OidSoFar]), + false; + {value, #node_info{me = ME}} -> + ?vtrace("next_node(tree,table_entry) -> found: ~n ~p", + [ME]), + {table, OidSoFar, Oid, ME} + end + end; + +next_node(D, {tree, Tree, _Info}, [Int | RestOfOid], RevOidSoFar, MibView) + when (Int < size(Tree)) andalso (Int >= 0) -> + ?vtrace("next_node(tree) -> entry when" + "~n size(Tree): ~p" + "~n Int: ~p" + "~n RestOfOid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [size(Tree), Int, RestOfOid, RevOidSoFar, MibView]), + case next_node(D, element(Int+1,Tree), + RestOfOid, [Int|RevOidSoFar], MibView) of + false -> + find_next(D, {tree, Tree, _Info}, Int+1, RevOidSoFar, MibView); + Else -> + Else + end; +%% no solution +next_node(D, {tree, Tree, _Info}, [], RevOidSoFar, MibView) -> + ?vtrace("next_node(tree,[]) -> entry when" + "~n size(Tree): ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [size(Tree), RevOidSoFar, MibView]), + find_next(D, {tree, Tree, _Info}, 0, RevOidSoFar, MibView); +next_node(_D, {tree, Tree, _Info}, _RestOfOid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(tree) -> entry when" + "~n size(Tree): ~p", [size(Tree)]), + false; + +next_node(D, {node, subagent}, Oid, RevOidSoFar, MibView) -> + ?vtrace("next_node(node,subagent) -> entry when" + "~n Oid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [Oid, RevOidSoFar, MibView]), + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{subagents = SAs} = D, + case lists:keysearch(OidSoFar, 2, SAs) of + {value, {SubAgentPid, OidSoFar}} -> + {subagent, SubAgentPid, OidSoFar}; + _ -> + ?vinfo("next_node -> could not find subagent with" + "~n OidSoFar: ~p" + "~n SAs: ~p", [OidSoFar, SAs]), + false + end + end; + +next_node(D, {node, {variable, _MibName}}, [], RevOidSoFar, MibView) -> + ?vtrace("next_node(node,variable,[]) -> entry when" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [RevOidSoFar, MibView]), + OidSoFar = lists:reverse([0 | RevOidSoFar]), + case snmpa_acm:validate_mib_view(OidSoFar, MibView) of + true -> + #mib_data{module = Mod, node_db = Db} = D, + case Mod:read(Db, lists:reverse(RevOidSoFar)) of + false -> + ?vinfo("next_node -> could not find variable with" + "~n RevOidSoFar: ~p", [RevOidSoFar]), + false; + {value, #node_info{me = ME}} -> + {variable, ME, OidSoFar} + end; + _ -> + false + end; + +next_node(_D, {node, {variable, _MibName}}, _Oid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(node,variable) -> entry", []), + false. + + +%%----------------------------------------------------------------- +%% This function is used to find the first leaf from where we +%% are. +%% Returns: {subagent, SubAgentPid, SAOid} | +%% false | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%% PRE: This function must always be called with a {internal, Tree} +%% node. +%%----------------------------------------------------------------- +find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView) + when Idx < size(Tree) -> + case find_next(D, element(Idx+1, Tree), 0, [Idx| RevOidSoFar], MibView) of + false -> + find_next(D, {tree, Tree, internal}, Idx+1, RevOidSoFar, MibView); + Other -> + Other + end; +find_next(_D, {tree, _Tree, internal}, _Idx, _RevOidSoFar, _MibView) -> + false; +find_next(_D, undefined_node, _Idx, _RevOidSoFar, _MibView) -> + false; +find_next(D, {tree, Tree, {table, _MibName}}, Idx, RevOidSoFar, MibView) -> + find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView); +find_next(D, {tree, _Tree, {table_entry, _MibName}}, _Index, + RevOidSoFar, MibView) -> + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{module = Mod, node_db = Db} = D, + case Mod:read(Db, OidSoFar) of + false -> + ?vinfo("find_next -> could not find table_entry ME with" + "~n OidSoFar: ~p", [OidSoFar]), + false; + {value, #node_info{me = ME}} -> + {table, OidSoFar, [], ME} + end + end; +find_next(D, {node, {variable, _MibName}}, _Idx, RevOidSoFar, MibView) -> + OidSoFar = lists:reverse([0 | RevOidSoFar]), + case snmpa_acm:validate_mib_view(OidSoFar, MibView) of + true -> + #mib_data{module = Mod, node_db = Db} = D, + case Mod:read(Db, lists:reverse(RevOidSoFar)) of + false -> + ?vinfo("find_next -> could not find variable with" + "~n RevOidSoFar: ~p", [RevOidSoFar]), + false; + {value, #node_info{me = ME}} -> + {variable, ME, OidSoFar} + end; + _ -> + false + end; +find_next(D, {node, subagent}, _Idx, RevOidSoFar, MibView) -> + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{subagents = SAs} = D, + case lists:keysearch(OidSoFar, 2, SAs) of + {value, {SubAgentPid, OidSoFar}} -> + {subagent, SubAgentPid, OidSoFar}; + false -> + ?vinfo("find_node -> could not find subagent with" + "~n OidSoFar: ~p" + "~n SAs: ~p", [OidSoFar, SAs]), + false + end + end. + +%%%====================================================================== +%%% 3. Tree building functions +%%% Used when loading mibs. +%%%====================================================================== + +build_tree(Mes, MibName) -> + ?d("build_tree -> " + "~n Mes: ~p", [Mes]), + {ListTree, []} = build_subtree([], Mes, MibName), + {tree, convert_tree(ListTree), internal}. + +%%---------------------------------------------------------------------- +%% Purpose: Builds the tree where all oids have prefix equal to LevelPrefix. +%% Returns: {Tree, RestMes} +%% RestMes are Mes that should not be in this subtree. +%% The Tree is a temporary and simplified data structure that is easy to +%% convert to the final tuple tree used by the MIB process. +%% A Node is represented as in the final tree. +%% The tree is not represented as a N-tuple, but as an Index-list. +%% Example: Temporary: [{1, Node1}, {3, Node3}] +%% Final: {Node1, undefined_node, Node3} +%% Pre: Mes are sorted on oid. +%%---------------------------------------------------------------------- +build_subtree(LevelPrefix, [Me | Mes], MibName) -> + ?vtrace("build subtree -> ~n" + " oid: ~p~n" + " LevelPrefix: ~p~n" + " MibName: ~p", [Me#me.oid, LevelPrefix, MibName]), + EType = Me#me.entrytype, + ?vtrace("build subtree -> EType = ~p",[EType]), + case in_subtree(LevelPrefix, Me) of + above -> + ?vtrace("build subtree -> above",[]), + {[], [Me|Mes]}; + {node, Index} -> + ?vtrace("build subtree -> node at ~p",[Index]), + {Tree, RestMes} = build_subtree(LevelPrefix, Mes, MibName), + {[{Index, {node, {EType, MibName}}} | Tree], RestMes}; + {subtree, Index, NewLevelPrefix} -> + ?vtrace("build subtree -> subtree at" + "~n ~w with ~w", + [Index, NewLevelPrefix]), + {BelowTree, RestMes} = + build_subtree(NewLevelPrefix, Mes, MibName), + {CurTree, RestMes2} = + build_subtree(LevelPrefix, RestMes, MibName), + {[{Index, {tree, BelowTree, {EType,MibName}}}| CurTree], RestMes2}; + {internal_subtree, Index, NewLevelPrefix} -> + ?vtrace("build subtree -> internal_subtree at" + "~n ~w with ~w", + [Index,NewLevelPrefix]), + {BelowTree, RestMes} = + build_subtree(NewLevelPrefix, [Me | Mes], MibName), + {CurTree, RestMes2} = + build_subtree(LevelPrefix, RestMes, MibName), + {[{Index, {tree, BelowTree, internal}} | CurTree], RestMes2} + end; + +build_subtree(_LevelPrefix, [], _MibName) -> + ?vtrace("build subtree -> done", []), + {[], []}. + +%%-------------------------------------------------- +%% Purpose: Determine how/if/where Me should be inserted in subtree +%% with LevelPrefix. This function does not build any tree, only +%% determinses what should be done (by build subtree). +%% Returns: +%% above - Indicating that this ME should _not_ be in this subtree. +%% {node, Index} - yes, construct a node with index Index on this level +%% {internal_subtree, Index, NewLevelPrefix} - yes, there should be an +%% internal subtree at this index. +%% {subtree, Index, NewLevelPrefix} - yes, construct a subtree with +%% NewLevelPrefix and insert this on current level in position Index. +%%-------------------------------------------------- +in_subtree(LevelPrefix, Me) -> + case lists:prefix(LevelPrefix, Me#me.oid) of + true when length(Me#me.oid) > length(LevelPrefix) -> + classify_how_in_subtree(LevelPrefix, Me); + _ -> + above + end. + +%%-------------------------------------------------- +%% See comment about in_subtree/2. This function takes care of all cases +%% where the ME really should be in _this_ subtree (not above). +%%-------------------------------------------------- +classify_how_in_subtree(LevelPrefix, Me) + when (length(Me#me.oid) =:= (length(LevelPrefix) + 1)) -> + Oid = Me#me.oid, + case node_or_subtree(Me#me.entrytype) of + subtree -> + {subtree, lists:last(Oid), Oid}; + node -> + {node, lists:last(Oid)} + end; + +classify_how_in_subtree(LevelPrefix, Me) + when (length(Me#me.oid) > (length(LevelPrefix) + 1)) -> + L1 = length(LevelPrefix) + 1, + Oid = Me#me.oid, + {internal_subtree, lists:nth(L1, Oid), lists:sublist(Oid, 1, L1)}. + +%%-------------------------------------------------- +%% Determines how to treat different kinds om MEs in the tree building process. +%% Pre: all internal nodes have been removed. +%%-------------------------------------------------- +node_or_subtree(table) -> subtree; +node_or_subtree(table_entry) -> subtree; +node_or_subtree(variable) -> node; +node_or_subtree(table_column) -> node. + +%%-------------------------------------------------- +%% Purpose: (Recursively) Converts a temporary tree (see above) to a final tree. +%% If input is a ListTree, output is a TupleTree. +%% If input is a Node, output is the same Node. +%% Pre: All Indexes are >= 0. +%%-------------------------------------------------- +convert_tree({Index, {tree, Tree, Info}}) when Index >= 0 -> + L = lists:map(fun convert_tree/1, Tree), + {Index, {tree, dict_list_to_tuple(L), Info}}; +convert_tree({Index, {node, Info}}) when Index >= 0 -> + {Index, {node, Info}}; +convert_tree(Tree) when is_list(Tree) -> + L = lists:map(fun convert_tree/1, Tree), + dict_list_to_tuple(L). + +%%---------------------------------------------------------------------- +%% Purpose: Converts a single level (that is non-recursively) from +%% the temporary indexlist to the N-tuple. +%% Input: A list of {Index, Data}. +%% Output: A tuple where element Index is Data. +%%---------------------------------------------------------------------- +dict_list_to_tuple(L) -> + L2 = lists:keysort(1, L), + list_to_tuple(integrate_indexes(0, L2)). + +%%---------------------------------------------------------------------- +%% Purpose: Helper function for dict_list_to_tuple/1. +%% Converts an indexlist to a N-list. +%% Input: A list of {Index, Data}. +%% Output: A (usually longer, never shorter) list where element Index is Data. +%% Example: [{1,hej}, {3, sven}] will give output +%% [undefined_node, hej, undefined_node, sven]. +%% Initially CurIndex should be 0. +%%---------------------------------------------------------------------- +integrate_indexes(CurIndex, [{CurIndex, Data} | T]) -> + [Data | integrate_indexes(CurIndex + 1, T)]; +integrate_indexes(_Index, []) -> + []; +integrate_indexes(CurIndex, L) -> + [undefined_node | integrate_indexes(CurIndex + 1, L)]. + +%%%====================================================================== +%%% 4. Tree merging +%%% Used by: load mib, insert subagent. +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Arg: Two root nodes (that is to be merged). +%% Returns: A new root node where the nodes have been merger to one. +%%---------------------------------------------------------------------- +merge_nodes(Same, Same) -> + Same; +merge_nodes(Node, undefined_node) -> + Node; +merge_nodes(undefined_node, Node) -> + Node; +merge_nodes({tree, Tree1, internal}, {tree, Tree2, internal}) -> + {tree, merge_levels(tuple_to_list(Tree1),tuple_to_list(Tree2)), internal}; +merge_nodes(Node1, Node2) -> + throw({error_merge_nodes, Node1, Node2}). + +%%---------------------------------------------------------------------- +%% Arg: Two levels to be merged. +%% Here, a level is represented as a list of nodes. A list is easier +%% to extend than a tuple. +%% Returns: The resulting, merged level tuple. +%%---------------------------------------------------------------------- +merge_levels(Level1, Level2) when length(Level1) =:= length(Level2) -> + MergeNodes = fun(N1, N2) -> merge_nodes(N1, N2) end, + list_to_tuple(snmp_misc:multi_map(MergeNodes, [Level1, Level2])); +merge_levels(Level1, Level2) when length(Level1) > length(Level2) -> + merge_levels(Level1, Level2 ++ + undefined_nodes_list(length(Level1) - length(Level2))); +merge_levels(Level1, Level2) when length(Level1) < length(Level2) -> + merge_levels(Level2, Level1). + +undefined_nodes_list(N) -> lists:duplicate(N, undefined_node). + + +%%%====================================================================== +%%% 5. Tree deletion routines +%%% (for unload mib) +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Purpose: Actually kicks of the tree reconstruction. +%% Returns: {list of removed MEs, NewTree} +%%---------------------------------------------------------------------- +delete_mib_from_tree(MibName, {tree, Tree, internal}) -> + case delete_tree(Tree, MibName) of + [] -> + {tree, {undefined_node}, internal}; % reduce + LevelList -> + {tree, list_to_tuple(LevelList), internal} + end. + +%%---------------------------------------------------------------------- +%% Purpose: Deletes all nodes associated to MibName from this level and +%% all levels below. +%% If the new level does not contain information (that is, no +%% other mibs use it) anymore the empty list is returned. +%% Returns: {MEs, The new level represented as a list} +%%---------------------------------------------------------------------- +delete_tree(Tree, MibName) when is_tuple(Tree) -> + NewLevel = delete_nodes(tuple_to_list(Tree), MibName, []), + case lists:filter(fun drop_undefined_nodes/1,NewLevel) of + [] -> []; + _A_perhaps_shorted_list -> + NewLevel % some other mib needs this level + end. + +%%---------------------------------------------------------------------- +%% Purpose: Nodes belonging to MibName are removed from the tree. +%% Recursively deletes sub trees to this node. +%% Returns: {MEs, NewNodesList} +%%---------------------------------------------------------------------- +delete_nodes([], _MibName, AccNodes) -> + lists:reverse(AccNodes); + +delete_nodes([{node, {variable, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{node, {table_column, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, _Tree, {table, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, _Tree, {table_entry, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, Tree, Info}|T], MibName, AccNodes) -> + case delete_tree(Tree, MibName) of + [] -> % tree completely deleted + delete_nodes(T, MibName, [undefined_node | AccNodes]); + LevelList -> + delete_nodes(T, MibName, + [{tree, list_to_tuple(LevelList), Info} | AccNodes]) + end; + +delete_nodes([NodeToKeep|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [NodeToKeep | AccNodes]). + +drop_undefined_nodes(undefined_node) -> false; +drop_undefined_nodes(_) -> true. + + +%%%====================================================================== +%%% 6. Functions for subagent handling +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Returns: A new Root|{error, reason} +%%---------------------------------------------------------------------- +insert_subagent(Oid, OldRoot) -> + ListTree = build_tree_for_subagent(Oid), + case catch convert_tree(ListTree) of + {'EXIT', _Reason} -> + {error, 'cannot construct tree from oid'}; + Level when is_tuple(Level) -> + T = {tree, Level, internal}, + case catch merge_nodes(T, OldRoot) of + {error_merge_nodes, _Node1, _Node2} -> + {error, oid_conflict}; + NewRoot when is_tuple(NewRoot) andalso + (element(1, NewRoot) =:= tree) -> + NewRoot + end + end. + +build_tree_for_subagent([Index]) -> + [{Index, {node, subagent}}]; + +build_tree_for_subagent([Index | T]) -> + [{Index, {tree, build_tree_for_subagent(T), internal}}]. + + +%%---------------------------------------------------------------------- +%% Returns: A new tree where the subagent at Oid (2nd arg) has been deleted. +%%---------------------------------------------------------------------- + +delete_subagent({tree, Tree, Info}, [Index]) -> + {node, subagent} = element(Index+1, Tree), + {tree, setelement(Index+1, Tree, undefined_node), Info}; +delete_subagent({tree, Tree, Info}, [Index | TI]) -> + {tree, setelement(Index+1, Tree, + delete_subagent(element(Index+1, Tree), TI)), Info}. + + +%%%====================================================================== +%%% 7. Misc functions +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Installs the mibs found in the database when starting the agent. +%% Basically calls the instrumentation functions for all non-internal +%% mib-entries +%%---------------------------------------------------------------------- +install_mibs(Mod, MibDb, NodeDb) -> + MibNames = loaded(Mod, MibDb), + ?vtrace("install_mibs -> found following mibs in database: ~n" + "~p", [MibNames]), + install_mibs2(Mod, NodeDb, MibNames). + +install_mibs2(_, _, []) -> + ok; +install_mibs2(Mod, NodeDb, [MibName|MibNames]) -> + Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'}, + Nodes = Mod:match_object(NodeDb, Pattern), + MEs = [ME || #node_info{me = ME} <- Nodes], + ?vtrace("install_mibs2 -> installing ~p MEs for mib ~p", + [length(MEs), MibName]), + NewF = fun(ME) -> call_instrumentation(ME, new) end, + lists:foreach(NewF, MEs), + install_mibs2(Mod, NodeDb, MibNames). + + +%%---------------------------------------------------------------------- +%% Does all side effect stuff during load_mib. +%%---------------------------------------------------------------------- +install_mib(Mod, Db, Symbolic, Mib, MibName, FileName, NonInternalMes) -> + ?vdebug("install_mib -> entry with" + "~n Symbolic: ~p" + "~n MibName: ~p" + "~n FileName: ~p", [Symbolic, MibName, FileName]), + Rec = #mib_info{name = MibName, symbolic = Symbolic, file_name = FileName}, + Mod:write(Db, Rec), + install_mib2(Symbolic, MibName, Mib), + NewF = fun(ME) -> call_instrumentation(ME, new) end, + lists:foreach(NewF, NonInternalMes). + +install_mib2(true, MibName, Mib) -> + #mib{table_infos = TabInfos, + variable_infos = VarInfos, + mes = MEs, + asn1_types = ASN1Types, + traps = Traps} = Mib, + snmpa_symbolic_store:add_table_infos(MibName, TabInfos), + snmpa_symbolic_store:add_variable_infos(MibName, VarInfos), + snmpa_symbolic_store:add_aliasnames(MibName, MEs), + snmpa_symbolic_store:add_types(MibName, ASN1Types), + SetF = fun(Trap) -> + snmpa_symbolic_store:set_notification(Trap, MibName) + end, + lists:foreach(SetF, Traps); +install_mib2(_, _, _) -> + ok. + +install_mes(Mod, Db, MibName, MEs) -> + Write = fun(#me{oid = Oid} = ME) -> + Node = #node_info{oid = Oid, + mib_name = MibName, + me = ME}, + Mod:write(Db, Node) + end, + install_mes(Write, MEs). + +install_mes(_Write, []) -> + ok; +install_mes(Write, [ME|MEs]) -> + Write(ME), + install_mes(Write, MEs). + + +%%---------------------------------------------------------------------- +%% Does all side effect stuff during unload_mib. +%%---------------------------------------------------------------------- +uninstall_mib(Mod, Db, Symbolic, MibName, MEs) -> + ?vtrace("uninstall_mib -> entry with" + "~n Db: ~p" + "~n Symbolic: ~p" + "~n MibName: ~p", [Db, Symbolic, MibName]), + Res = Mod:delete(Db, MibName), + ?vtrace("uninstall_mib -> (mib) db delete result: ~p", [Res]), + uninstall_mib2(Symbolic, MibName), + DelF = fun(ME) -> call_instrumentation(ME, delete) end, + lists:foreach(DelF, MEs). + +uninstall_mib2(true, MibName) -> + snmpa_symbolic_store:delete_table_infos(MibName), + snmpa_symbolic_store:delete_variable_infos(MibName), + snmpa_symbolic_store:delete_aliasnames(MibName), + snmpa_symbolic_store:delete_types(MibName), + snmpa_symbolic_store:delete_notifications(MibName); +uninstall_mib2(_, _) -> + ok. + +uninstall_mes(Mod, Db, MibName) -> + Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'}, + Mod:match_delete(Db, Pattern). + + +%%---------------------------------------------------------------------- +%% Create a list of the names of all the loaded mibs +%%---------------------------------------------------------------------- +loaded(Mod, Db) -> + [N || #mib_info{name = N} <- Mod:tab2list(Db)]. + + +%%---------------------------------------------------------------------- +%% Calls MFA-instrumentation with 'new' or 'delete' operation. +%%---------------------------------------------------------------------- +call_instrumentation(#me{entrytype = variable, mfa={M,F,A}}, Operation) -> + ?vtrace("call instrumentation with" + "~n entrytype: variable" + "~n MFA: {~p,~p,~p}" + "~n Operation: ~p", + [M,F,A,Operation]), + catch apply(M, F, [Operation | A]); +call_instrumentation(#me{entrytype = table_entry, mfa={M,F,A}}, Operation) -> + ?vtrace("call instrumentation with" + "~n entrytype: table_entry" + "~n MFA: {~p,~p,~p}" + "~n Operation: ~p", + [M,F,A,Operation]), + catch apply(M, F, [Operation | A]); +call_instrumentation(_ShitME, _Operation) -> + done. + + +maybe_drop_me(#me{entrytype = internal}) -> false; +maybe_drop_me(#me{entrytype = group}) -> false; +maybe_drop_me(#me{imported = true}) -> false; +maybe_drop_me(_) -> true. + + +%%---------------------------------------------------------------------- +%% Code change functions +%%---------------------------------------------------------------------- + +code_change(down, _Vsn, _Extra, State) -> + ?d("code_change(down) -> entry when" + "~n Vsn: ~p" + "~n Extra: ~p", [_Vsn, _Extra]), + State; + +code_change(up, _Vsn, _Extra, State) -> + ?d("code_change(up) -> entry when" + "~n Vsn: ~p" + "~n Extra: ~p", [_Vsn, _Extra]), + State. + + diff --git a/lib/snmp/src/agent/snmpa_mib_storage.erl b/lib/snmp/src/agent/snmpa_mib_storage.erl new file mode 100644 index 0000000000..5c3f76d89b --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib_storage.erl @@ -0,0 +1,181 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2013. 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_storage). + +-export_type([ + mib_storage_fields/0, + mib_storage_table_type/0, + mib_storage_table_id/0 + ]). + + +%%% ---------------------------------------------------------------- +%%% This behaviour module defines the API for the mib-storage. +%%% This is how the agent stores its internal mib-data +%%% (symbolic-store and mib-server). +%%%----------------------------------------------------------------- + +-type mib_storage_fields() :: [atom()]. +-type mib_storage_table_type() :: set | bag. +-type mib_storage_table_id() :: term(). + + +%% --------------------------------------------------------------- +%% open +%% +%% Open or create a mib-storage table. +%% If any extra info needs to be communicated to the implementor +%% (of the behaviour), this is done using the *Options* argument. +%% --------------------------------------------------------------- + +%% Options is callback module dependant + +-callback open(Name :: atom(), + RecName :: atom(), + Fields :: mib_storage_fields(), + Type :: mib_storage_table_type(), + Options :: list()) -> + {ok, TabId :: mib_storage_table_id()} | {error, Reason :: term()}. + + +%% --------------------------------------------------------------- +%% close +%% +%% Close the mib-storage table. What this does is up to the +%% implementor (when using mnesia it may be a no-op but for ets +%% it may actually delete the table). +%% --------------------------------------------------------------- + +-callback close(TabId :: mib_storage_table_id()) -> + term(). + + +%% --------------------------------------------------------------- +%% read/2 +%% +%% Retrieve a record from the mib-storage table. +%% --------------------------------------------------------------- + +-callback read(TabId :: mib_storage_table_id(), + Key :: term()) -> + false | {value, Record :: tuple()}. + + +%% --------------------------------------------------------------- +%% write/2 +%% +%% Write a record to the mib-storage table. +%% --------------------------------------------------------------- + +-callback write(TabId :: mib_storage_table_id(), + Record :: tuple()) -> + ok | {error, Reason :: term()}. + + +%% --------------------------------------------------------------- +%% delete/1 +%% +%% Delete the mib-storage table. +%% --------------------------------------------------------------- + +-callback delete(TabId :: mib_storage_table_id()) -> + snmp:void(). + + +%% --------------------------------------------------------------- +%% delete/2 +%% +%% Delete a record from the mib-storage table. +%% --------------------------------------------------------------- + +-callback delete(TabId :: mib_storage_table_id(), + Key :: term()) -> + ok | {error, Reason :: term()}. + + +%% --------------------------------------------------------------- +%% match_object +%% +%% Search the mib-storage table for records which matches +%% the pattern. +%% --------------------------------------------------------------- + +-callback match_object(TabId :: mib_storage_table_id(), + Pattern :: ets:match_pattern()) -> + {ok, Recs :: [tuple()]} | {error, Reason :: term()}. + + +%% --------------------------------------------------------------- +%% match_delete +%% +%% Search the mib-storage table for records which matches the +%% pattern and deletes them from the database and return the +%5 deleted records. +%% --------------------------------------------------------------- + +-callback match_delete(TabId :: mib_storage_table_id(), + Pattern :: ets:match_pattern()) -> + {ok, Recs :: [tuple()]} | {error, Reason :: term()}. + + +%% --------------------------------------------------------------- +%% tab2list +%% +%% Return all records in the table in the form of a list. +%% --------------------------------------------------------------- + +-callback tab2list(TabId :: mib_storage_table_id()) -> + [tuple()]. + + +%% --------------------------------------------------------------- +%% info/1,2 +%% +%% Retrieve implementation dependent mib-storage table +%% information. +%% --------------------------------------------------------------- + +-callback info(TabId :: mib_storage_table_id()) -> + Info :: term(). + +-callback info(TabId :: mib_storage_table_id(), Item :: atom()) -> + Info :: term(). + + +%% --------------------------------------------------------------- +%% sync +%% +%% Dump mib-storage table to disc (if it has a disk component). +%% --------------------------------------------------------------- + +-callback sync(TabId :: mib_storage_table_id()) -> + snmp:void(). + + +%% --------------------------------------------------------------- +%% backup +%% +%% Make a backup copy of the mib-storage table. +%% --------------------------------------------------------------- + +-callback backup(TabId :: mib_storage_table_id(), + Dir :: file:filename()) -> + ok | {error, Reason :: term()}. + diff --git a/lib/snmp/src/agent/snmpa_mib_storage_dets.erl b/lib/snmp/src/agent/snmpa_mib_storage_dets.erl new file mode 100644 index 0000000000..e84e18e7ea --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib_storage_dets.erl @@ -0,0 +1,309 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2013. 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_storage_dets). + +-behaviour(snmpa_mib_storage). + + +%%%----------------------------------------------------------------- +%%% This module implements the snmpa_mib_storage behaviour. +%%% It uses dets for storage. +%%%----------------------------------------------------------------- + +-export([ + open/5, + close/1, + read/2, + write/2, + delete/1, + delete/2, + match_object/2, + match_delete/2, + tab2list/1, + info/1, info/2, + sync/1, + backup/2 + ]). + + +-define(VMODULE, "MS-DETS"). +-include("snmp_verbosity.hrl"). + +-record(tab, {id, rec_name}). + + +%% --------------------------------------------------------------- +%% open +%% +%% Open or create a mib-storage (dets) table. +%% +%% Opts - A list of implementation dependent options +%% dets_open_options() = [dets_open_option()] +%% dets_open_option() = {dir, filename()} | +%% {action, keep | clear} | +%% {auto_save, default | pos_integer()} | +%% {repair, force | boolean()} +%% +%% --------------------------------------------------------------- + +open(Name, RecName, _Fields, Type, Opts) -> + Dir = snmp_misc:get_option(dir, Opts), + Action = snmp_misc:get_option(action, Opts, keep), + AutoSave = snmp_misc:get_option(auto_save, Opts, default), + Repair = snmp_misc:get_option(repair, Opts, false), + File = dets_filename(Name, Dir), + OpenOpts = [{file, File}, + {type, Type}, + {keypos, 2}, + {repair, Repair}] ++ + case AutoSave of + default -> + []; + _ -> + [{auto_save, AutoSave}] + end, + case dets:open_file(Name, OpenOpts) of + {ok, ID} when (Action =:= keep) -> + {ok, #tab{id = ID, rec_name = RecName}}; + {ok, ID} when (Action =:= clear) -> + dets:match_delete(ID, '_'), + {ok, #tab{id = ID, rec_name = RecName}}; + {error, Reason} -> + {error, {dets_open, Reason}} + end. + +dets_filename(Name, Dir) -> + Dir1 = dets_filename1(Dir), + Dir2 = string:strip(Dir1, right, $/), + io_lib:format("~s/~p.dat", [Dir2, Name]). + +dets_filename1([]) -> "."; +dets_filename1(Dir) -> Dir. + + +%% --------------------------------------------------------------- +%% close +%% +%% Close the table. +%% --------------------------------------------------------------- + +close(#tab{id = ID}) -> + ?vtrace("close database ~p", [ID]), + dets:close(ID). + + +%% --------------------------------------------------------------- +%% read +%% +%% Retrieve a record from the database table. +%% --------------------------------------------------------------- + +read(#tab{id = ID}, Key) -> + ?vtrace("read from table ~p: ~p", [ID, Key]), + case dets:lookup(ID, Key) of + [Rec|_] -> {value, Rec}; + _ -> false + end. + + +%% --------------------------------------------------------------- +%% write +%% +%% Write a record to the database table. +%% --------------------------------------------------------------- + +write(#tab{id = ID, rec_name = RecName}, Rec) + when (is_tuple(Rec) andalso (element(1, Rec) =:= RecName)) -> + ?vtrace("write to table ~p", [ID]), + dets:insert(ID, Rec). + + +%% --------------------------------------------------------------- +%% delete +%% +%% Delete the database table. +%% --------------------------------------------------------------- + +delete(#tab{id = ID}) -> + ?vtrace("delete database ~p", [ID]), + File = dets:info(ID, filename), + case dets:close(ID) of + ok -> + file:delete(File); + Error -> + Error + end. + + +%% --------------------------------------------------------------- +%% delete +%% +%% Delete a record from the database table. +%% --------------------------------------------------------------- + +delete(#tab{id = ID}, Key) -> + ?vtrace("delete from table ~p: ~p", [ID, Key]), + dets:delete(ID, Key). + + +%% --------------------------------------------------------------- +%% match_object +%% +%% Search the database table for records witch matches the pattern. +%% --------------------------------------------------------------- + +match_object(#tab{id = ID}, Pattern) -> + ?vtrace("match_object in ~p of ~p", [ID, Pattern]), + dets:match_object(ID, Pattern). + + +%% --------------------------------------------------------------- +%% match_delete +%% +%% Search the database table for records witch matches the +%% pattern and deletes them from the database table. +%% --------------------------------------------------------------- + +match_delete(#tab{id = ID}, Pattern) -> + ?vtrace("match_delete in ~p with pattern ~p", [ID, Pattern]), + Recs = dets:match_object(ID, Pattern), + dets:match_delete(ID, Pattern), + Recs. + + +%% --------------------------------------------------------------- +%% tab2list +%% +%% Return all records in the table in the form of a list. +%% --------------------------------------------------------------- + +tab2list(#tab{id = ID} = Tab) -> + ?vtrace("tab2list -> list of ~p", [ID]), + match_object(Tab, '_'). + + +%% --------------------------------------------------------------- +%% info +%% +%% Retrieve implementation dependent mib-storage table +%% information. +%% --------------------------------------------------------------- + +info(#tab{id = ID}) -> + ?vtrace("info -> info of ~p", [ID]), + dets:info(ID). + + +info(TabId, all = _Item) -> + info(TabId); +info(#tab{id = ID}, memory = _Item) -> + ?vtrace("info on ~p (~w)", [ID, _Item]), + dets:info(ID, file_size); +info(#tab{id = ID}, Item) -> + ?vtrace("info on ~p (~w)", [ID, Item]), + dets:info(ID, Item). + + +%% --------------------------------------------------------------- +%% sync +%% +%% Dump mib-storage table to disc (if it has a disk component) +%% --------------------------------------------------------------- + +sync(#tab{id = ID}) -> + ?vtrace("sync -> sync ~p", [ID]), + dets:sync(ID). + + +%% --------------------------------------------------------------- +%% backup +%% +%% Make a backup copy of the mib-storage table. +%% --------------------------------------------------------------- + +backup(#tab{id = ID}, BackupDir) -> + ?vtrace("backup -> backup of ~p to ~p", [ID, BackupDir]), + case dets:info(ID, filename) of + undefined -> + {error, no_file}; + Filename -> + case filename:dirname(Filename) of + BackupDir -> + {error, db_dir}; + _ -> + Type = dets:info(ID, type), + KP = dets:info(ID, keypos), + dets_backup(ID, + filename:basename(Filename), + BackupDir, Type, KP) + end + end. + + +dets_backup(ID, Filename, BackupDir, Type, KP) -> + ?vtrace("dets_backup -> entry with" + "~n ID: ~p" + "~n Filename: ~p" + "~n BackupDir: ~p" + "~n Type: ~p" + "~n KP: ~p", [ID, Filename, BackupDir, Type, KP]), + BackupFile = filename:join(BackupDir, Filename), + ?vtrace("dets_backup -> " + "~n BackupFile: ~p", [BackupFile]), + Backup = list_to_atom(atom_to_list(ID) ++ "_backup"), + Opts = [{file, BackupFile}, {type, Type}, {keypos, KP}], + case dets:open_file(Backup, Opts) of + {ok, B} -> + ?vtrace("dets_backup -> create fun", []), + F = fun(Arg) -> + dets_backup(Arg, start, ID, B) + end, + dets:safe_fixtable(ID, true), + Res = dets:init_table(Backup, F, [{format, bchunk}]), + dets:safe_fixtable(ID, false), + ?vtrace("dets_backup -> Res: ~p", [Res]), + Res; + Error -> + ?vinfo("dets_backup -> open_file failed: " + "~n ~p", [Error]), + Error + end. + +dets_backup(close, _Cont, _ID, B) -> + dets:close(B), + ok; +dets_backup(read, Cont1, ID, B) -> + case dets:bchunk(ID, Cont1) of + {Cont2, Data} -> + F = fun(Arg) -> + dets_backup(Arg, Cont2, ID, B) + end, + {Data, F}; + '$end_of_table' -> + dets:close(B), + end_of_input; + Error -> + Error + end. + + +%%---------------------------------------------------------------------- + +%% user_err(F, A) -> +%% snmpa_error:user_err(F, A). diff --git a/lib/snmp/src/agent/snmpa_mib_storage_ets.erl b/lib/snmp/src/agent/snmpa_mib_storage_ets.erl new file mode 100644 index 0000000000..04faf46864 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib_storage_ets.erl @@ -0,0 +1,341 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2013. 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_storage_ets). + +-behaviour(snmpa_mib_storage). + +%%%----------------------------------------------------------------- +%%% This module implements the snmpa_mib_storage behaviour. +%%% It uses ets for storage. +%%%----------------------------------------------------------------- + +-export([ + open/5, + close/1, + read/2, + write/2, + delete/1, + delete/2, + match_object/2, + match_delete/2, + tab2list/1, + info/1, info/2, + sync/1, + backup/2 + ]). + + +-define(VMODULE,"MS-ETS"). +-include("snmp_verbosity.hrl"). + +-record(tab, {id, rec_name, file, checksum = false}). + + +%% --------------------------------------------------------------- +%% open +%% +%% Open or create an ets table. +%% Possibly also read data from a (specified) file (mirror) and +%% populate the table from that (the dir option). +%% +%% Opts - A list of implementation dependent options +%% ets_open_options() = [ets_open_option()] +%% ets_open_option() = {dir, filename()} | +%% {action, keep | clear} | +%% {checksum, boolean()} +%% +%% The RecName and Fields arguments are not used in this +%% implementation. +%% +%% --------------------------------------------------------------- + +%% This function creates the ets table +open(Name, RecName, _Fields, Type, Opts) -> + ?vtrace("open table ~p", [Name]), + case lists:keysearch(dir, 1, Opts) of + {value, {dir, Dir}} -> + Action = snmp_misc:get_option(action, Opts, keep), + Checksum = snmp_misc:get_option(checksum, Opts, false), + ?vtrace("open ~p database ~p - check if file exist", [Type, Name]), + File = filename:join(Dir, atom_to_list(Name) ++ ".db"), + case file:read_file_info(File) of + {ok, _} -> + ?vdebug("open ~p database ~p - file exist - try reading", + [Type, Name]), + case ets:file2tab(File, [{verify, Checksum}]) of + {ok, ID} -> + ?vtrace("open ~p database ~p - " + "data read from file", [Type, Name]), + {ok, #tab{id = ID, + rec_name = RecName, + file = File, + checksum = Checksum}}; + {error, Reason} when (Action =:= keep) -> + ?vinfo("open ~p database ~p - " + "failed reading from file (keep): " + "~n ~p", + [Type, Name, Reason]), + {error, {file2tab, Reason}}; + {error, Reason} -> + ?vlog("open ~p database ~p - " + "failed reading from file (clear): " + "~n ~p", [Type, Name, Reason]), + user_err("Warning: could not read file - " + "create new (empty): " + "~n File: ~p" + "~n Reason: ~p", [File, Reason]), + ID = ets:new(Name, [Type, protected, {keypos, 2}]), + write_ets_file(ID, File, Checksum), + {ok, #tab{id = ID, + rec_name = RecName, + file = File, + checksum = Checksum}} + end; + {error, enoent} -> + %% No such file - create it + ?vdebug("open ~p database ~p - " + "file does *not* exist - create", + [Type, Name]), + ID = ets:new(Name, [Type, protected, {keypos, 2}]), + write_ets_file(ID, File, Checksum), + {ok, #tab{id = ID, + rec_name = RecName, + file = File, + checksum = Checksum}}; + {error, Reason} when (Action =:= keep) -> + ?vinfo("open ~p database ~p - " + "failed reading file info (keep): " + "~n ~p", + [Type, Name, Reason]), + {error, {read_file_info, Reason}}; + {error, Reason} -> + ?vlog("open ~p database ~p - " + "failed reading file info (clear): " + "~n ~p", + [Type, Name, Reason]), + user_err("Warning: could not read file info - " + "create new file: " + "~n File: ~p" + "~n Reason: ~p", [File, Reason]), + ID = ets:new(Name, [Type, protected, {keypos, 2}]), + write_ets_file(ID, File, Checksum), + {ok, #tab{id = ID, + rec_name = RecName, + file = File, + checksum = Checksum}} + end; + false -> + ?vdebug("open ~p database ~p - ok", [Type, Name]), + ID = ets:new(Name, [Type, protected, {keypos, 2}]), + {ok, #tab{id = ID, rec_name = RecName}} + end. + + +%% --------------------------------------------------------------- +%% close +%% +%% Close the mib-storage table. +%% We will delete the table and if there is a file component, +%% will also be written to file. +%% --------------------------------------------------------------- +close(#tab{id = ID, file = undefined}) -> + ?vtrace("close (delete) table ~p", [ID]), + ets:delete(ID); +close(#tab{id = ID, file = File, checksum = Checksum}) -> + ?vtrace("close (delete) table ~p", [ID]), + write_ets_file(ID, File, Checksum), + ets:delete(ID). + + +%% --------------------------------------------------------------- +%% read +%% +%% Retrieve a record from the mib-storage table. +%% --------------------------------------------------------------- + +read(#tab{id = ID}, Key) -> + ?vtrace("read from table ~p: ~p", [ID, Key]), + case ets:lookup(ID, Key) of + [Rec|_] -> {value, Rec}; + _ -> false + end. + + +%% --------------------------------------------------------------- +%% write +%% +%% Write a record to the mib-storage table. +%% --------------------------------------------------------------- + +%% This is a very crude guard test is used instead of: is_record(Rec, RecName) +write(#tab{id = ID, rec_name = RecName}, Rec) + when (is_tuple(Rec) andalso (element(1, Rec) =:= RecName)) -> + ?vtrace("write to table ~p", [ID]), + ets:insert(ID, Rec). + + +%% --------------------------------------------------------------- +%% delete +%% +%% Delete the mib-storage table. +%% --------------------------------------------------------------- +delete(#tab{id = ID, file = undefined}) -> + ?vtrace("delete table ~p", [ID]), + ets:delete(ID); +delete(#tab{id = ID, file = File}) -> + ?vtrace("delete table ~p", [ID]), + file:delete(File), + ets:delete(ID). + + +%% --------------------------------------------------------------- +%% delete +%% +%% Delete a record from the mib-storage table. +%% --------------------------------------------------------------- +delete(#tab{id = ID}, Key) -> + ?vtrace("delete from table ~p: ~p", [ID, Key]), + ets:delete(ID, Key). + + +%% --------------------------------------------------------------- +%% match_object +%% +%% Search the mib-storage table for records witch matches +%% the pattern. +%% --------------------------------------------------------------- + +match_object(#tab{id = ID}, Pattern) -> + ?vtrace("match_object in ~p of ~p", [ID, Pattern]), + ets:match_object(ID, Pattern). + + +%% --------------------------------------------------------------- +%% match_delete +%% +%% Search the mib-storage table for records witch matches +%% the pattern and deletes them from the table. +%% --------------------------------------------------------------- + +match_delete(#tab{id = ID}, Pattern) -> + ?vtrace("match_delete in ~p with pattern ~p", [ID, Pattern]), + Recs = ets:match_object(ID, Pattern), + ets:match_delete(ID, Pattern), + Recs. + + +%% --------------------------------------------------------------- +%% tab2list +%% +%% Return all records in the mib-storage table in the form +%% of a list. +%% --------------------------------------------------------------- + +tab2list(#tab{id = ID}) -> + ?vtrace("tab2list -> list of ~p", [ID]), + ets:tab2list(ID). + + + +%% --------------------------------------------------------------- +%% info/1,2 +%% +%% Retrieve implementation dependent mib-storage table +%% information. +%% --------------------------------------------------------------- +info(#tab{id = ID}) -> + ?vtrace("info on ~p", [ID]), + case ets:info(ID) of + undefined -> + []; + L -> + L + end. + + +info(TabId, all = _Item) -> + info(TabId); +info(#tab{id = ID}, Item) -> + ?vtrace("info on ~p", [ID]), + ets:info(ID, Item). + + +%% --------------------------------------------------------------- +%% sync +%% +%% Dump mib-storage table to disc (if there is a file compionent) +%% --------------------------------------------------------------- + +sync(#tab{file = undefined}) -> + ok; +sync(#tab{id = ID, file = File, checksum = Checksum}) -> + ?vtrace("sync ~p", [ID]), + write_ets_file(ID, File, Checksum). + + +%% --------------------------------------------------------------- +%% backup +%% +%% Make a backup copy of the mib-storage table. Only valid id +%% there is a file component. +%% --------------------------------------------------------------- + +backup(#tab{file = undefined}, _BackupDir) -> + ok; +backup(#tab{id = ID, file = File, checksum = Checksum}, BackupDir) -> + ?vtrace("backup ~p to ~p", [ID, BackupDir]), + Filename = filename:basename(File), + case filename:join(BackupDir, Filename) of + File -> + %% Oups: backup-dir and db-dir the same + {error, db_dir}; + BackupFile -> + write_ets_file(ID, BackupFile, Checksum) + end. + + +%%---------------------------------------------------------------------- + +write_ets_file(ID, File, Checksum) when (Checksum =:= true) -> + do_write_ets_file(ID, File, [{extended_info, [md5sum]}]); +write_ets_file(ID, File, Checksum) when (Checksum =:= false) -> + do_write_ets_file(ID, File, []). + +do_write_ets_file(ID, File, Options) -> + TmpFile = File ++ ".tmp", + case ets:tab2file(ID, TmpFile, Options) of + ok -> + case file:rename(TmpFile, File) of + ok -> + ok; + Else -> + user_err("Warning: could not move file ~p" + " (~p)", [File, Else]) + end; + {error, Reason} -> + user_err("Warning: could not save file ~p (~p)", + [File, Reason]) + end. + + +%%---------------------------------------------------------------------- + +user_err(F, A) -> + snmpa_error:user_err(F, A). diff --git a/lib/snmp/src/agent/snmpa_mib_storage_mnesia.erl b/lib/snmp/src/agent/snmpa_mib_storage_mnesia.erl new file mode 100644 index 0000000000..192b5aa26e --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib_storage_mnesia.erl @@ -0,0 +1,302 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2013. 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_storage_mnesia). + + +-behaviour(snmpa_mib_storage). + +%%%----------------------------------------------------------------- +%%% This module implements the snmpa_mib_storage behaviour. +%%% It uses mnesia for storage. +%%%----------------------------------------------------------------- + +-export([ + open/5, + close/1, + read/2, + write/2, + delete/1, + delete/2, + match_object/2, + match_delete/2, + tab2list/1, + info/1, info/2, + sync/1, + backup/2 + ]). + + +-define(VMODULE,"MS-MNESIA"). +-include("snmp_verbosity.hrl"). + +-record(tab, {id}). + + +%% --------------------------------------------------------------- +%% open +%% +%% Open or create a mnesia table. +%% +%% Opts - A list of implementation dependent options +%% mnesia_open_options() = [mnesia_open_option()] +%% mnesia_open_option() = {action, keep | clear} | +%% {nodes, [node()]} +%% +%% --------------------------------------------------------------- + +open(Name, RecName, Fields, Type, Opts) -> + ?vtrace("open ~p table ~p for record ~p", + [Type, Name, RecName]), + Action = get_action(Opts), + Nodes = get_nodes(Opts), + case table_exists(Name) of + true when (Action =:= keep) -> + ?vtrace("open table ~p - exist (keep)", [Name]), + {ok, #tab{id = Name}}; + true when (Action =:= clear) -> + ?vtrace("open table ~p - exist (clear)", [Name]), + F = fun() -> mnesia:clear_table(Name) end, + case mnesia:transaction(F) of + {aborted, Reason} -> + {error, {clear, Reason}}; + {atomic, _} -> + {ok, #tab{id = Name}} + end; + false -> + ?vtrace("open table ~p - does not exist", [Name]), + Args = [{record_name, RecName}, + {attributes, Fields}, + {type, Type}, + {disc_copies, Nodes}], + case mnesia:create_table(Name, Args) of + {atomic, ok} -> + ?vtrace("open table ~p - ok", [Name]), + {ok, #tab{id = Name}}; + {aborted, Reason} -> + ?vinfo("open table ~p - aborted" + "~n Reason: ~p", [Name, Reason]), + {error, {create, Reason}} + end + end. + +table_exists(Name) -> + case (catch mnesia:table_info(Name, type)) of + {'EXIT', _Reason} -> + false; + _ -> + true + end. + + +%% --------------------------------------------------------------- +%% close +%% +%% Close the mib-storage table. +%% This does nothing in the mnesia case. +%% --------------------------------------------------------------- + +close(_) -> + ?vtrace("close mib-storage - ignore",[]), + ok. + + +%% --------------------------------------------------------------- +%% read +%% +%% Retrieve a record from the mib-storage table. +%% --------------------------------------------------------------- + +read(#tab{id = ID}, Key) -> + ?vtrace("read (dirty) from database ~p: ~p", [ID, Key]), + case (catch mnesia:dirty_read(ID, Key)) of + [Rec|_] -> {value,Rec}; + _ -> false + end. + + +%% --------------------------------------------------------------- +%% write +%% +%% Write a record to the mib-storage table. +%% --------------------------------------------------------------- + +write(#tab{id = ID}, Rec) -> + ?vtrace("write to database ~p", [ID]), + F = fun() -> mnesia:write(ID, Rec, write) end, + case mnesia:transaction(F) of + {aborted, _Reason} = ABORTED -> + {error, ABORTED}; + {atomic,_} -> + ok + end. + + +%% --------------------------------------------------------------- +%% delete +%% +%% Delete the mib-storage table. +%% --------------------------------------------------------------- + +delete(#tab{id = ID}) -> + ?vtrace("delete database: ~p", [ID]), + mnesia:delete_table(ID). + + +%% --------------------------------------------------------------- +%% delete +%% +%% Delete a record from the mib-storage table. +%% --------------------------------------------------------------- + +delete(#tab{id = ID}, Key) -> + ?vtrace("delete from database ~p: ~p", [ID, Key]), + F = fun() -> mnesia:delete(ID, Key, write) end, + case mnesia:transaction(F) of + {aborted, _Reason} = ABORTED -> + {error, ABORTED}; + {atomic, _} -> + ok + end. + + +%% --------------------------------------------------------------- +%% match_object +%% +%% Search the mib-storage table for records witch matches +%% the pattern. +%% --------------------------------------------------------------- + +match_object(#tab{id = ID}, Pattern) -> + ?vtrace("match_object in ~p of ~p", [ID, Pattern]), + F = fun() -> mnesia:match_object(ID, Pattern, read) end, + case mnesia:transaction(F) of + {aborted, _Reason} = ABORTED -> + {error, ABORTED}; + {atomic, Rs} -> + Rs + end. + + +%% --------------------------------------------------------------- +%% match_delete +%% +%% Search the mib-storage table for records witch matches +%% the pattern and deletes them from the table. +%% --------------------------------------------------------------- + +match_delete(#tab{id = ID}, Pattern) -> + ?vtrace("match_delete in ~p with pattern ~p", [ID, Pattern]), + F = fun() -> + Recs = mnesia:match_object(ID, Pattern, read), + lists:foreach(fun(Rec) -> + mnesia:delete_object(ID, Rec, write) + end, Recs), + Recs + end, + case mnesia:transaction(F) of + {aborted, _Reason} = ABORTED -> + {error, ABORTED}; + {atomic, Rs} -> + Rs + end. + + +%% --------------------------------------------------------------- +%% tab2list +%% +%% Return all records in the mib-storage table in the form of +%% a list. +%% --------------------------------------------------------------- + +tab2list(#tab{id = ID} = Tab) -> + ?vtrace("tab2list -> list of ~p", [ID]), + match_object(Tab, mnesia:table_info(ID, wild_pattern)). + + +%% --------------------------------------------------------------- +%% info +%% +%% Retrieve implementation dependent mib-storage table +%% information. +%% --------------------------------------------------------------- + +info(#tab{id = ID}) -> + case (catch mnesia:table_info(ID, all)) of + Info when is_list(Info) -> + Info; + {'EXIT', {aborted, Reason}} -> + {error, Reason} + end. + + +info(#tab{id = ID}, Item) -> + mnesia:table_info(ID, Item). + + +%% --------------------------------------------------------------- +%% sync +%% +%% Ignore +%% --------------------------------------------------------------- + +sync(_) -> + ok. + + +%% --------------------------------------------------------------- +%% backup +%% +%% Ignore. Mnesia handles its own backups. +%% --------------------------------------------------------------- + +backup(_, _) -> + ok. + + +%%---------------------------------------------------------------------- + +get_action(Opts) -> + snmp_misc:get_option(action, Opts, keep). + +get_nodes(Opts) -> + case snmp_misc:get_option(nodes, Opts, erlang:nodes()) of + [] -> + [node()]; + Nodes when is_list(Nodes) -> + Nodes; + all -> + erlang:nodes(); + visible -> + erlang:nodes(visible); + connected -> + erlang:nodes(connected); + db_nodes -> + try mnesia:system_info(db_nodes) of + DbNodes when is_list(DbNodes) -> + DbNodes; + _ -> + erlang:nodes() + catch + _:_ -> + erlang:nodes() + end + end. + +%% user_err(F, A) -> +%% snmpa_error:user_err(F, A). diff --git a/lib/snmp/src/agent/snmpa_supervisor.erl b/lib/snmp/src/agent/snmpa_supervisor.erl index 886fd074bc..aebcdbaa84 100644 --- a/lib/snmp/src/agent/snmpa_supervisor.erl +++ b/lib/snmp/src/agent/snmpa_supervisor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2012. All Rights Reserved. +%% Copyright Ericsson AB 1996-2013. 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 @@ -224,26 +224,101 @@ init([AgentType, Opts]) -> ets:insert(snmp_agent_table, {error_report_mod, ErrorReportMod}), %% -- mib storage -- + %% MibStorage has only one mandatory part: module + %% Everything else is module dependent and therefor + %% put in a special option: options MibStorage = - case get_opt(mib_storage, Opts, ets) of + case get_opt(mib_storage, Opts, [{module, snmpa_mib_storage_ets}]) of + + %% --- ETS wrappers --- + + ets -> + [{module, snmpa_mib_storage_ets}]; + {ets, default} -> + [{module, snmpa_mib_storage_ets}, + {options, [{dir, filename:join([DbDir])}, + {action, keep}]}]; + {ets, Dir} when is_list(Dir) -> + [{module, snmpa_mib_storage_ets}, + {options, [{dir, filename:join([Dir])}, + {action, keep}]}]; + {ets, default, Action} when ((Action =:= keep) orelse + (Action =:= clear)) -> + [{module, snmpa_mib_storage_ets}, + {options, [{dir, filename:join([DbDir])}, + {action, Action}]}]; + {ets, Dir, Action} when is_list(Dir) andalso + ((Action =:= keep) orelse + (Action =:= clear)) -> + [{module, snmpa_mib_storage_ets}, + {options, [{dir, filename:join([Dir])}, + {action, Action}]}]; + + %% --- DETS wrappers --- + dets -> - {dets, DbDir}; + [{module, snmpa_mib_storage_dets}, + {options, [{dir, filename:join([DbDir])}, + {action, keep}]}]; {dets, default} -> - {dets, DbDir}; - {dets, default, Act} -> - {dets, DbDir, Act}; - {ets, default} -> - {ets, DbDir}; + [{module, snmpa_mib_storage_dets}, + {options, [{dir, filename:join([DbDir])}, + {action, keep}]}]; + {dets, default, Action} when ((Action =:= keep) orelse + (Action =:= clear)) -> + [{module, snmpa_mib_storage_dets}, + {options, [{dir, filename:join([DbDir])}, + {action, Action}]}]; + {dets, Dir, Action} when is_list(Dir) andalso + ((Action =:= keep) orelse + (Action =:= clear)) -> + [{module, snmpa_mib_storage_dets}, + {options, [{dir, filename:join([Dir])}, + {action, Action}]}]; + + %% --- Mnesia wrappers --- + mnesia -> - {mnesia, erlang:nodes()}; - {mnesia, visible} -> - {mnesia, erlang:nodes(visible)}; - {mnesia, connected} -> - {mnesia, erlang:nodes(connected)}; - Other -> + [{module, snmpa_mib_storage_mnesia}, + {options, [{nodes, erlang:nodes()}, + {action, keep}]}]; + {mnesia, Nodes0} -> + Nodes = + if + Nodes0 =:= visible -> + erlang:nodes(visible); + Nodes0 =:= connected -> + erlang:nodes(connected); + Nodes0 =:= [] -> + [node()]; + true -> + Nodes0 + end, + [{module, snmpa_mib_storage_mnesia}, + {options, [{nodes, Nodes}, + {action, keep}]}]; + {mnesia, Nodes0, Action} when ((Action =:= keep) orelse + (Action =:= clear)) -> + Nodes = + if + Nodes0 =:= visible -> + erlang:nodes(visible); + Nodes0 =:= connected -> + erlang:nodes(connected); + Nodes0 =:= [] -> + [node()]; + true -> + Nodes0 + end, + [{module, snmpa_mib_storage_mnesia}, + {options, [{nodes, Nodes}, + {action, Action}]}]; + + Other when is_list(Other) -> Other end, - ?vdebug("[agent table] store mib storage: ~w",[MibStorage]), + + ?vdebug("[agent table] store mib storage: ~w", [MibStorage]), ets:insert(snmp_agent_table, {mib_storage, MibStorage}), %% -- Agent mib storage -- @@ -388,7 +463,7 @@ init([AgentType, Opts]) -> AgentSpec = worker_spec(snmpa_agent, - [Prio,snmp_master_agent,none,Ref,AgentOpts], + [Prio, snmp_master_agent, none, Ref, AgentOpts], Restart, 15000), AgentSupSpec = sup_spec(snmpa_agent_sup, [AgentSpec], diff --git a/lib/snmp/src/agent/snmpa_symbolic_store.erl b/lib/snmp/src/agent/snmpa_symbolic_store.erl index 6c58ffde41..00178f4bcd 100644 --- a/lib/snmp/src/agent/snmpa_symbolic_store.erl +++ b/lib/snmp/src/agent/snmpa_symbolic_store.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% Copyright Ericsson AB 1996-2013. 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 @@ -78,7 +78,7 @@ gen_server:start_link({local, ?SERVER}, ?MODULE, [Prio, Opts], [])). -endif. --record(state, {db, backup}). +-record(state, {module, db, backup}). -record(symbol, {key, mib_name, info}). @@ -112,6 +112,9 @@ backup(BackupDir) -> get_db() -> call(get_db). +which_module() -> + call(which_module). + %%---------------------------------------------------------------------- %% Returns: {value, Oid} | false @@ -202,49 +205,63 @@ verbosity(Verbosity) -> %%---------------------------------------------------------------------- %% DB access (read) functions: Returns: {value, Oid} | false %%---------------------------------------------------------------------- + aliasname_to_oid(Db, Aliasname) -> - case snmpa_general_db:read(Db, {alias, Aliasname}) of + Mod = which_module(), + aliasname_to_oid(Mod, Db, Aliasname). + +aliasname_to_oid(Mod, Db, Aliasname) -> + case Mod:read(Db, {alias, Aliasname}) of {value,#symbol{info = {Oid, _Enums}}} -> {value, Oid}; false -> false end. -oid_to_aliasname(Db,Oid) -> - case snmpa_general_db:read(Db, {oid, Oid}) of +oid_to_aliasname(Db, Oid) -> + Mod = which_module(), + oid_to_aliasname(Mod, Db, Oid). + +oid_to_aliasname(Mod, Db, Oid) -> + case Mod:read(Db, {oid, Oid}) of {value,#symbol{info = Aliasname}} -> {value, Aliasname}; _ -> false end. -which_notifications(Db) -> +which_notifications(Mod, Db) -> Pattern = #symbol{key = {trap, '_'}, _ = '_'}, - Symbols = snmpa_general_db:match_object(Db, Pattern), + Symbols = Mod:match_object(Db, Pattern), [{Name, Mib, Rec} || #symbol{key = {trap, Name}, mib_name = Mib, info = Rec} <- Symbols]. -which_aliasnames(Db) -> +which_aliasnames(Mod, Db) -> Pattern = #symbol{key = {alias, '_'}, _ = '_'}, - Symbols = snmpa_general_db:match_object(Db, Pattern), + Symbols = Mod:match_object(Db, Pattern), [Alias || #symbol{key = {alias, Alias}} <- Symbols]. -which_tables(Db) -> +which_tables(Mod, Db) -> Pattern = #symbol{key = {table_info, '_'}, _ = '_'}, - Symbols = snmpa_general_db:match_object(Db, Pattern), + Symbols = Mod:match_object(Db, Pattern), [Name || #symbol{key = {table_info, Name}} <- Symbols]. -which_variables(Db) -> +which_variables(Mod, Db) -> Pattern = #symbol{key = {variable_info, '_'}, _ = '_'}, - Symbols = snmpa_general_db:match_object(Db, Pattern), + Symbols = Mod:match_object(Db, Pattern), [Name || #symbol{key = {variable_info, Name}} <- Symbols]. -int_to_enum(Db,TypeOrObjName,Int) -> - case snmpa_general_db:read(Db, {alias, TypeOrObjName}) of + +int_to_enum(Db, TypeOrObjName, Int) -> + Mod = which_module(), + int_to_enum(Mod, Db, TypeOrObjName, Int). + +int_to_enum(Mod, Db, TypeOrObjName, Int) -> + case Mod:read(Db, {alias, TypeOrObjName}) of {value,#symbol{info = {_Oid, Enums}}} -> case lists:keysearch(Int, 2, Enums) of {value, {Enum, _Int}} -> {value, Enum}; false -> false end; false -> % Not an Aliasname -> - case snmpa_general_db:read(Db, {type, TypeOrObjName}) of + case Mod:read(Db, {type, TypeOrObjName}) of {value,#symbol{info = Enums}} -> case lists:keysearch(Int, 2, Enums) of {value, {Enum, _Int}} -> {value, Enum}; @@ -256,14 +273,18 @@ int_to_enum(Db,TypeOrObjName,Int) -> end. enum_to_int(Db, TypeOrObjName, Enum) -> - case snmpa_general_db:read(Db, {alias, TypeOrObjName}) of + Mod = which_module(), + enum_to_int(Mod, Db, TypeOrObjName, Enum). + +enum_to_int(Mod, Db, TypeOrObjName, Enum) -> + case Mod:read(Db, {alias, TypeOrObjName}) of {value,#symbol{info = {_Oid, Enums}}} -> case lists:keysearch(Enum, 1, Enums) of {value, {_Enum, Int}} -> {value, Int}; false -> false end; false -> % Not an Aliasname - case snmpa_general_db:read(Db, {type, TypeOrObjName}) of + case Mod:read(Db, {type, TypeOrObjName}) of {value,#symbol{info = Enums}} -> case lists:keysearch(Enum, 1, Enums) of {value, {_Enum, Int}} -> {value, Int}; @@ -278,8 +299,9 @@ enum_to_int(Db, TypeOrObjName, Enum) -> %%---------------------------------------------------------------------- %% DB access (read) functions: Returns: false|{value, Info} %%---------------------------------------------------------------------- -table_info(Db,TableName) -> - case snmpa_general_db:read(Db, {table_info, TableName}) of + +table_info(Mod, Db, TableName) -> + case Mod:read(Db, {table_info, TableName}) of {value,#symbol{info = Info}} -> {value, Info}; false -> false end. @@ -288,8 +310,8 @@ table_info(Db,TableName) -> %%---------------------------------------------------------------------- %% DB access (read) functions: Returns: false|{value, Info} %%---------------------------------------------------------------------- -variable_info(Db,VariableName) -> - case snmpa_general_db:read(Db, {variable_info, VariableName}) of +variable_info(Mod, Db, VariableName) -> + case Mod:read(Db, {variable_info, VariableName}) of {value,#symbol{info = Info}} -> {value, Info}; false -> false end. @@ -299,7 +321,7 @@ variable_info(Db,VariableName) -> %% Implementation %%---------------------------------------------------------------------- -init([Prio,Opts]) -> +init([Prio, Opts]) -> ?d("init -> entry with" "~n Prio: ~p" "~n Opts: ~p", [Prio,Opts]), @@ -317,102 +339,125 @@ do_init(Prio, Opts) -> put(sname,ss), put(verbosity,get_verbosity(Opts)), ?vlog("starting",[]), - Storage = get_mib_storage(Opts), + MibStorage = get_mib_storage(Opts), + Mod = snmp_misc:get_option(module, MibStorage), + MsOpts = snmp_misc:get_option(options, MibStorage, []), + %% type = bag solves the problem with import and multiple %% object/type definitions. - Db = snmpa_general_db:open(Storage, snmpa_symbolic_store, - symbol, record_info(fields,symbol), bag), - S = #state{db = Db}, - ?vdebug("started",[]), - {ok, S}. + case Mod:open(?MODULE, symbol, record_info(fields, symbol), bag, MsOpts) of + {ok, Db} -> + S = #state{module = Mod, db = Db}, + ?vdebug("started",[]), + {ok, S}; + {error, _} = ERROR -> + ERROR + end. handle_call(get_db, _From, #state{db = DB} = S) -> ?vlog("get db",[]), {reply, DB, S}; -handle_call({table_info, TableName}, _From, #state{db = DB} = S) -> +handle_call(which_module, _From, #state{module = Mod} = S) -> + ?vlog("which module",[]), + {reply, Mod, S}; + +handle_call({table_info, TableName}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("table info: ~p",[TableName]), - Res = table_info(DB, TableName), + Res = table_info(Mod, DB, TableName), ?vdebug("table info result: ~p",[Res]), {reply, Res, S}; -handle_call({variable_info, VariableName}, _From, #state{db = DB} = S) -> +handle_call({variable_info, VariableName}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("variable info: ~p",[VariableName]), - Res = variable_info(DB, VariableName), + Res = variable_info(Mod, DB, VariableName), ?vdebug("variable info result: ~p",[Res]), {reply, Res, S}; -handle_call({aliasname_to_oid, Aliasname}, _From, #state{db = DB} = S) -> +handle_call({aliasname_to_oid, Aliasname}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("aliasname to oid: ~p",[Aliasname]), - Res = aliasname_to_oid(DB,Aliasname), + Res = aliasname_to_oid(Mod, DB, Aliasname), ?vdebug("aliasname to oid result: ~p",[Res]), {reply, Res, S}; -handle_call({oid_to_aliasname, Oid}, _From, #state{db = DB} = S) -> +handle_call({oid_to_aliasname, Oid}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("oid to aliasname: ~p",[Oid]), - Res = oid_to_aliasname(DB, Oid), + Res = oid_to_aliasname(Mod, DB, Oid), ?vdebug("oid to aliasname result: ~p",[Res]), {reply, Res, S}; -handle_call(which_aliasnames, _From, #state{db = DB} = S) -> +handle_call(which_aliasnames, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("which aliasnames",[]), - Res = which_aliasnames(DB), + Res = which_aliasnames(Mod, DB), ?vdebug("which aliasnames: ~p",[Res]), {reply, Res, S}; -handle_call(which_tables, _From, #state{db = DB} = S) -> +handle_call(which_tables, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("which tables",[]), - Res = which_tables(DB), + Res = which_tables(Mod, DB), ?vdebug("which tables: ~p",[Res]), {reply, Res, S}; -handle_call(which_variables, _From, #state{db = DB} = S) -> +handle_call(which_variables, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("which variables",[]), - Res = which_variables(DB), + Res = which_variables(Mod, DB), ?vdebug("which variables: ~p",[Res]), {reply, Res, S}; -handle_call({enum_to_int, TypeOrObjName, Enum}, _From, #state{db = DB} = S) -> +handle_call({enum_to_int, TypeOrObjName, Enum}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("enum to int: ~p, ~p",[TypeOrObjName,Enum]), - Res = enum_to_int(DB, TypeOrObjName, Enum), + Res = enum_to_int(Mod, DB, TypeOrObjName, Enum), ?vdebug("enum to int result: ~p",[Res]), {reply, Res, S}; -handle_call({int_to_enum, TypeOrObjName, Int}, _From, #state{db = DB} = S) -> +handle_call({int_to_enum, TypeOrObjName, Int}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("int to enum: ~p, ~p",[TypeOrObjName,Int]), - Res = int_to_enum(DB, TypeOrObjName, Int), + Res = int_to_enum(Mod, DB, TypeOrObjName, Int), ?vdebug("int to enum result: ~p",[Res]), {reply, Res, S}; -handle_call({set_notification, MibName, Trap}, _From, #state{db = DB} = S) -> +handle_call({set_notification, MibName, Trap}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("set notification:" "~n ~p~n ~p", [MibName,Trap]), - set_notif(DB, MibName, Trap), + set_notif(Mod, DB, MibName, Trap), {reply, true, S}; -handle_call({delete_notifications, MibName}, _From, #state{db = DB} = S) -> +handle_call({delete_notifications, MibName}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("delete notification: ~p",[MibName]), - delete_notif(DB, MibName), + delete_notif(Mod, DB, MibName), {reply, true, S}; -handle_call(which_notifications, _From, #state{db = DB} = S) -> +handle_call(which_notifications, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("which notifications", []), - Reply = which_notifications(DB), + Reply = which_notifications(Mod, DB), {reply, Reply, S}; -handle_call({get_notification, Key}, _From, #state{db = DB} = S) -> +handle_call({get_notification, Key}, _From, + #state{module = Mod, db = DB} = S) -> ?vlog("get notification: ~p",[Key]), - Res = get_notif(DB, Key), + Res = get_notif(Mod, DB, Key), ?vdebug("get notification result: ~p",[Res]), {reply, Res, S}; -handle_call(info, _From, #state{db = DB} = S) -> +handle_call(info, _From, #state{module = Mod, db = DB} = S) -> ?vlog("info",[]), - Info = get_info(DB), + Info = get_info(Mod, DB), {reply, Info, S}; -handle_call({backup, BackupDir}, From, #state{db = DB} = S) -> +handle_call({backup, BackupDir}, From, #state{module = Mod, db = DB} = S) -> ?vlog("info to ~p",[BackupDir]), Pid = self(), V = get(verbosity), @@ -424,7 +469,7 @@ handle_call({backup, BackupDir}, From, #state{db = DB} = S) -> put(sname, albs), put(verbosity, V), Dir = filename:join([BackupDir]), - Reply = snmpa_general_db:backup(DB, Dir), + Reply = Mod:backup(DB, Dir), Pid ! {backup_done, Reply}, unlink(Pid) end), @@ -446,7 +491,7 @@ handle_call(Req, _From, S) -> {reply, Reply, S}. -handle_cast({add_types, MibName, Types}, #state{db = DB} = S) -> +handle_cast({add_types, MibName, Types}, #state{module = Mod, db = DB} = S) -> ?vlog("add types for ~p:",[MibName]), F = fun(#asn1_type{assocList = Alist, aliasname = Name}) -> case snmp_misc:assq(enums, Alist) of @@ -455,20 +500,21 @@ handle_cast({add_types, MibName, Types}, #state{db = DB} = S) -> Rec = #symbol{key = {type, Name}, mib_name = MibName, info = Es}, - snmpa_general_db:write(DB, Rec); + Mod:write(DB, Rec); false -> done end end, lists:foreach(F, Types), {noreply, S}; -handle_cast({delete_types, MibName}, #state{db = DB} = S) -> +handle_cast({delete_types, MibName}, #state{module = Mod, db = DB} = S) -> ?vlog("delete types: ~p",[MibName]), Pattern = #symbol{key = {type, '_'}, mib_name = MibName, info = '_'}, - snmpa_general_db:match_delete(DB, Pattern), + Mod:match_delete(DB, Pattern), {noreply, S}; -handle_cast({add_aliasnames, MibName, MEs}, #state{db = DB} = S) -> +handle_cast({add_aliasnames, MibName, MEs}, + #state{module = Mod, db = DB} = S) -> ?vlog("add aliasnames for ~p:",[MibName]), F = fun(#me{aliasname = AN, oid = Oid, asn1_type = AT}) -> Enums = @@ -480,20 +526,21 @@ handle_cast({add_aliasnames, MibName, MEs}, #state{db = DB} = S) -> end; _ -> [] end, - write_alias(AN, DB, Enums, MibName, Oid) + write_alias(Mod, AN, DB, Enums, MibName, Oid) end, lists:foreach(F, MEs), {noreply, S}; -handle_cast({delete_aliasname, MibName}, #state{db = DB} = S) -> +handle_cast({delete_aliasname, MibName}, #state{module = Mod, db = DB} = S) -> ?vlog("delete aliasname: ~p",[MibName]), Pattern1 = #symbol{key = {alias, '_'}, mib_name = MibName, info = '_'}, - snmpa_general_db:match_delete(DB, Pattern1), + Mod:match_delete(DB, Pattern1), Pattern2 = #symbol{key = {oid, '_'}, mib_name = MibName, info = '_'}, - snmpa_general_db:match_delete(DB, Pattern2), + Mod:match_delete(DB, Pattern2), {noreply, S}; -handle_cast({add_table_infos, MibName, TableInfos}, #state{db = DB} = S) -> +handle_cast({add_table_infos, MibName, TableInfos}, + #state{module = Mod, db = DB} = S) -> ?vlog("add table infos for ~p:",[MibName]), F = fun({Name, TableInfo}) -> ?vlog("add table info~n ~p -> ~p", @@ -501,19 +548,20 @@ handle_cast({add_table_infos, MibName, TableInfos}, #state{db = DB} = S) -> Rec = #symbol{key = {table_info, Name}, mib_name = MibName, info = TableInfo}, - snmpa_general_db:write(DB, Rec) + Mod:write(DB, Rec) end, lists:foreach(F, TableInfos), {noreply, S}; -handle_cast({delete_table_infos, MibName}, #state{db = DB} = S) -> +handle_cast({delete_table_infos, MibName}, + #state{module = Mod, db = DB} = S) -> ?vlog("delete table infos: ~p",[MibName]), Pattern = #symbol{key = {table_info, '_'}, mib_name = MibName, info = '_'}, - snmpa_general_db:match_delete(DB, Pattern), + Mod:match_delete(DB, Pattern), {noreply, S}; handle_cast({add_variable_infos, MibName, VariableInfos}, - #state{db = DB} = S) -> + #state{module = Mod, db = DB} = S) -> ?vlog("add variable infos for ~p:",[MibName]), F = fun({Name, VariableInfo}) -> ?vlog("add variable info~n ~p -> ~p", @@ -521,17 +569,18 @@ handle_cast({add_variable_infos, MibName, VariableInfos}, Rec = #symbol{key = {variable_info, Name}, mib_name = MibName, info = VariableInfo}, - snmpa_general_db:write(DB, Rec) + Mod:write(DB, Rec) end, lists:foreach(F, VariableInfos), {noreply, S}; -handle_cast({delete_variable_infos, MibName}, #state{db = DB} = S) -> +handle_cast({delete_variable_infos, MibName}, + #state{module = Mod, db = DB} = S) -> ?vlog("delete variable infos: ~p",[MibName]), Pattern = #symbol{key = {variable_info,'_'}, mib_name = MibName, info = '_'}, - snmpa_general_db:match_delete(DB, Pattern), + Mod:match_delete(DB, Pattern), {noreply, S}; handle_cast({verbosity,Verbosity}, State) -> @@ -565,9 +614,9 @@ handle_info(Info, S) -> {noreply, S}. -terminate(Reason, S) -> - ?vlog("terminate: ~p",[Reason]), - snmpa_general_db:close(S#state.db). +terminate(Reason, #state{module = Mod, db = DB}) -> + ?vlog("terminate: ~p", [Reason]), + Mod:close(DB). %%---------------------------------------------------------- @@ -575,18 +624,18 @@ terminate(Reason, S) -> %%---------------------------------------------------------- % downgrade -code_change({down, _Vsn}, #state{db = DB, backup = B}, downgrade_to_pre_4_7) -> - ?d("code_change(down) -> entry", []), - stop_backup_server(B), - S = {state, DB}, - {ok, S}; - -% upgrade -code_change(_Vsn, S, upgrade_from_pre_4_7) -> - ?d("code_change(up) -> entry", []), - {state, DB} = S, - S1 = #state{db = DB}, - {ok, S1}; +%% code_change({down, _Vsn}, #state{db = DB, backup = B}, downgrade_to_pre_4_7) -> +%% ?d("code_change(down) -> entry", []), +%% stop_backup_server(B), +%% S = {state, DB}, +%% {ok, S}; + +%% % upgrade +%% code_change(_Vsn, S, upgrade_from_pre_4_7) -> +%% ?d("code_change(up) -> entry", []), +%% {state, DB} = S, +%% S1 = #state{db = DB}, +%% {ok, S1}; code_change(_Vsn, S, _Extra) -> ?d("code_change -> entry [do nothing]", []), @@ -609,13 +658,13 @@ stop_backup_server({Pid, _}) when is_pid(Pid) -> %%----------------------------------------------------------------- %% Returns: {value, Value} | undefined %%----------------------------------------------------------------- -get_notif(Db, Key) -> - case snmpa_general_db:read(Db, {trap, Key}) of +get_notif(Mod, Db, Key) -> + case Mod:read(Db, {trap, Key}) of {value,#symbol{info = Value}} -> {value, Value}; false -> undefined end. -set_notif(Db, MibName, Trap) when is_record(Trap, trap) -> +set_notif(Mod, Db, MibName, Trap) when is_record(Trap, trap) -> #trap{trapname = Name} = Trap, Rec = #symbol{key = {trap, Name}, mib_name = MibName, info = Trap}, %% convert old v1 trap to oid @@ -625,40 +674,41 @@ set_notif(Db, MibName, Trap) when is_record(Trap, trap) -> Oid0 -> Oid0 ++ [0, Trap#trap.specificcode] end, - write_alias(Name, Db, MibName, Oid), - snmpa_general_db:write(Db, Rec); -set_notif(Db, MibName, Trap) -> + write_alias(Mod, Name, Db, MibName, Oid), + Mod:write(Db, Rec); +set_notif(Mod, Db, MibName, Trap) -> #notification{trapname = Name, oid = Oid} = Trap, Rec = #symbol{key = {trap, Name}, mib_name = MibName, info = Trap}, - write_alias(Name, Db, MibName, Oid), - snmpa_general_db:write(Db, Rec). + write_alias(Mod, Name, Db, MibName, Oid), + Mod:write(Db, Rec). -delete_notif(Db, MibName) -> +delete_notif(Mod, Db, MibName) -> Pattern = #symbol{key = {trap, '_'}, mib_name = MibName, info = '_'}, - snmpa_general_db:match_delete(Db, Pattern). + Mod:match_delete(Db, Pattern). -write_alias(AN, DB, MibName, Oid) -> - write_alias(AN, DB, [], MibName, Oid). +write_alias(Mod, AN, DB, MibName, Oid) -> + write_alias(Mod, AN, DB, [], MibName, Oid). -write_alias(AN, DB, Enums, MibName, Oid) -> +write_alias(Mod, AN, DB, Enums, MibName, Oid) -> ?vlog("add alias~n ~p -> {~p,~p}",[AN, Oid, Enums]), Rec1 = #symbol{key = {alias, AN}, mib_name = MibName, info = {Oid,Enums}}, - snmpa_general_db:write(DB, Rec1), + Mod:write(DB, Rec1), ?vlog("add oid~n ~p -> ~p",[Oid, AN]), Rec2 = #symbol{key = {oid, Oid}, mib_name = MibName, info = AN}, - snmpa_general_db:write(DB, Rec2). + Mod:write(DB, Rec2). + %% ------------------------------------- -get_info(DB) -> +get_info(Mod, DB) -> ProcSize = proc_mem(self()), - DbSz = tab_size(DB), - [{process_memory, ProcSize}, {db_memory, DbSz}]. + DbMemory = Mod:info(DB, memory), + [{process_memory, ProcSize}, {db_memory, DbMemory}]. proc_mem(P) when is_pid(P) -> case (catch erlang:process_info(P, memory)) of @@ -667,26 +717,15 @@ proc_mem(P) when is_pid(P) -> _ -> undefined end. -%% proc_mem(_) -> -%% undefined. - -tab_size(DB) -> - case (catch snmpa_general_db:info(DB, memory)) of - Sz when is_integer(Sz) -> - Sz; - _ -> - undefined - end. - %% ------------------------------------- get_verbosity(L) -> - snmp_misc:get_option(verbosity,L,?default_verbosity). + snmp_misc:get_option(verbosity, L, ?default_verbosity). get_mib_storage(L) -> - snmp_misc:get_option(mib_storage,L,ets). + snmp_misc:get_option(mib_storage, L). %% ------------------------------------- |