%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2009. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% -module(snmpa_symbolic_store). %%---------------------------------------------------------------------- %% This module implements a multipurpose symbolic store. %% 1) For internal and use from application: aliasname_to_value/1. %% If this was stored in the mib, deadlock would occur. %% 2 table_info/1. Getting information about a table. Used by default %% implementation of tables. %% 3) variable_info/1. Used by default implementation of variables. %% 4) notification storage. Used by snmpa_trap. %% There is one symbolic store per node and it uses the ets table %% snmp_agent_table, owned by the snmpa_supervisor. %% %%---------------------------------------------------------------------- -include_lib("kernel/include/file.hrl"). -include("snmp_types.hrl"). -include("snmp_debug.hrl"). -include("snmp_verbosity.hrl"). -include("SNMPv2-MIB.hrl"). %% API -export([start_link/2, stop/0, info/0, backup/1, aliasname_to_oid/1, oid_to_aliasname/1, add_aliasnames/2, delete_aliasnames/1, which_aliasnames/0, which_tables/0, which_variables/0, enum_to_int/2, int_to_enum/2, table_info/1, add_table_infos/2, delete_table_infos/1, variable_info/1, add_variable_infos/2, delete_variable_infos/1, get_notification/1, set_notification/2, delete_notifications/1, which_notifications/0, add_types/2, delete_types/1]). %% API (for quick access to the db, note that this is only reads). -export([get_db/0, aliasname_to_oid/2, oid_to_aliasname/2, enum_to_int/3, int_to_enum/3]). %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([verbosity/1]). -define(SERVER, ?MODULE). -ifdef(snmp_debug). -define(GS_START_LINK(Prio, Opts), gen_server:start_link({local, ?SERVER}, ?MODULE, [Prio, Opts], [{debug,[trace]}])). -else. -define(GS_START_LINK(Prio, Opts), gen_server:start_link({local, ?SERVER}, ?MODULE, [Prio, Opts], [])). -endif. -record(state, {db, backup}). -record(symbol, {key, mib_name, info}). %%----------------------------------------------------------------- %% Func: start_link/1 %% Args: Prio is priority of mib-server %% Opts is a list of options %% Purpose: starts the mib server synchronized %% Returns: {ok, Pid} | {error, Reason} %%----------------------------------------------------------------- start_link(Prio, Opts) -> ?d("start_link -> entry with" "~n Prio: ~p" "~n Opts: ~p", [Prio,Opts]), ?GS_START_LINK(Prio,Opts). stop() -> call(stop). info() -> call(info). backup(BackupDir) -> call({backup, BackupDir}). %%---------------------------------------------------------------------- %% Returns: Db %%---------------------------------------------------------------------- get_db() -> call(get_db). %%---------------------------------------------------------------------- %% Returns: {value, Oid} | false %%---------------------------------------------------------------------- aliasname_to_oid(Aliasname) -> call({aliasname_to_oid, Aliasname}). oid_to_aliasname(OID) -> call({oid_to_aliasname, OID}). int_to_enum(TypeOrObjName, Int) -> call({int_to_enum,TypeOrObjName,Int}). enum_to_int(TypeOrObjName, Enum) -> call({enum_to_int,TypeOrObjName,Enum}). add_types(MibName, Types) -> cast({add_types, MibName, Types}). delete_types(MibName) -> cast({delete_types, MibName}). add_aliasnames(MibName, MEs) -> cast({add_aliasnames, MibName, MEs}). delete_aliasnames(MibName) -> cast({delete_aliasname, MibName}). which_aliasnames() -> call(which_aliasnames). which_tables() -> call(which_tables). which_variables() -> call(which_variables). %%---------------------------------------------------------------------- %% Returns: false|{value, Info} %%---------------------------------------------------------------------- table_info(TableName) -> call({table_info, TableName}). %%---------------------------------------------------------------------- %% Returns: false|{value, Info} %%---------------------------------------------------------------------- variable_info(VariableName) -> call({variable_info, VariableName}). add_table_infos(MibName, TableInfos) -> cast({add_table_infos, MibName, TableInfos}). delete_table_infos(MibName) -> cast({delete_table_infos, MibName}). add_variable_infos(MibName, VariableInfos) -> cast({add_variable_infos, MibName, VariableInfos}). delete_variable_infos(MibName) -> cast({delete_variable_infos, MibName}). %%----------------------------------------------------------------- %% Store traps %%----------------------------------------------------------------- %% A notification is stored as {Key, Value}, where %% Key is the symbolic trap name, and Value is %% a #trap record. %%----------------------------------------------------------------- %% Returns: {value, Val} | undefined %%----------------------------------------------------------------- get_notification(Key) -> call({get_notification, Key}). set_notification(Trap, MibName) -> call({set_notification, MibName, Trap}). delete_notifications(MibName) -> call({delete_notifications, MibName}). which_notifications() -> call(which_notifications). verbosity(Verbosity) -> cast({verbosity,Verbosity}). %%---------------------------------------------------------------------- %% DB access (read) functions: Returns: {value, Oid} | false %%---------------------------------------------------------------------- aliasname_to_oid(Db, Aliasname) -> case snmpa_general_db: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 {value,#symbol{info = Aliasname}} -> {value, Aliasname}; _ -> false end. which_notifications(Db) -> Pattern = #symbol{key = {trap, '_'}, _ = '_'}, Symbols = snmpa_general_db:match_object(Db, Pattern), [{Name, Mib, Rec} || #symbol{key = {trap, Name}, mib_name = Mib, info = Rec} <- Symbols]. which_aliasnames(Db) -> Pattern = #symbol{key = {alias, '_'}, _ = '_'}, Symbols = snmpa_general_db:match_object(Db, Pattern), [Alias || #symbol{key = {alias, Alias}} <- Symbols]. which_tables(Db) -> Pattern = #symbol{key = {table_info, '_'}, _ = '_'}, Symbols = snmpa_general_db:match_object(Db, Pattern), [Name || #symbol{key = {table_info, Name}} <- Symbols]. which_variables(Db) -> Pattern = #symbol{key = {variable_info, '_'}, _ = '_'}, Symbols = snmpa_general_db: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 {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 {value,#symbol{info = Enums}} -> case lists:keysearch(Int, 2, Enums) of {value, {Enum, _Int}} -> {value, Enum}; false -> false end; false -> false end end. enum_to_int(Db, TypeOrObjName, Enum) -> case snmpa_general_db: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 {value,#symbol{info = Enums}} -> case lists:keysearch(Enum, 1, Enums) of {value, {_Enum, Int}} -> {value, Int}; false -> false end; false -> false end end. %%---------------------------------------------------------------------- %% DB access (read) functions: Returns: false|{value, Info} %%---------------------------------------------------------------------- table_info(Db,TableName) -> case snmpa_general_db:read(Db, {table_info, TableName}) of {value,#symbol{info = Info}} -> {value, Info}; false -> false end. %%---------------------------------------------------------------------- %% DB access (read) functions: Returns: false|{value, Info} %%---------------------------------------------------------------------- variable_info(Db,VariableName) -> case snmpa_general_db:read(Db, {variable_info, VariableName}) of {value,#symbol{info = Info}} -> {value, Info}; false -> false end. %%---------------------------------------------------------------------- %% Implementation %%---------------------------------------------------------------------- init([Prio,Opts]) -> ?d("init -> entry with" "~n Prio: ~p" "~n Opts: ~p", [Prio,Opts]), case (catch do_init(Prio, Opts)) of {ok, State} -> {ok, State}; Error -> config_err("failed starting symbolic-store: ~n~p", [Error]), {stop, {error, Error}} end. do_init(Prio, Opts) -> process_flag(priority, Prio), process_flag(trap_exit, true), put(sname,ss), put(verbosity,get_verbosity(Opts)), ?vlog("starting",[]), Storage = get_mib_storage(Opts), %% 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}. 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) -> ?vlog("table info: ~p",[TableName]), Res = table_info(DB, TableName), ?vdebug("table info result: ~p",[Res]), {reply, Res, S}; handle_call({variable_info, VariableName}, _From, #state{db = DB} = S) -> ?vlog("variable info: ~p",[VariableName]), Res = variable_info(DB, VariableName), ?vdebug("variable info result: ~p",[Res]), {reply, Res, S}; handle_call({aliasname_to_oid, Aliasname}, _From, #state{db = DB} = S) -> ?vlog("aliasname to oid: ~p",[Aliasname]), Res = aliasname_to_oid(DB,Aliasname), ?vdebug("aliasname to oid result: ~p",[Res]), {reply, Res, S}; handle_call({oid_to_aliasname, Oid}, _From, #state{db = DB} = S) -> ?vlog("oid to aliasname: ~p",[Oid]), Res = oid_to_aliasname(DB, Oid), ?vdebug("oid to aliasname result: ~p",[Res]), {reply, Res, S}; handle_call(which_aliasnames, _From, #state{db = DB} = S) -> ?vlog("which aliasnames",[]), Res = which_aliasnames(DB), ?vdebug("which aliasnames: ~p",[Res]), {reply, Res, S}; handle_call(which_tables, _From, #state{db = DB} = S) -> ?vlog("which tables",[]), Res = which_tables(DB), ?vdebug("which tables: ~p",[Res]), {reply, Res, S}; handle_call(which_variables, _From, #state{db = DB} = S) -> ?vlog("which variables",[]), Res = which_variables(DB), ?vdebug("which variables: ~p",[Res]), {reply, Res, S}; handle_call({enum_to_int, TypeOrObjName, Enum}, _From, #state{db = DB} = S) -> ?vlog("enum to int: ~p, ~p",[TypeOrObjName,Enum]), Res = enum_to_int(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) -> ?vlog("int to enum: ~p, ~p",[TypeOrObjName,Int]), Res = int_to_enum(DB, TypeOrObjName, Int), ?vdebug("int to enum result: ~p",[Res]), {reply, Res, S}; handle_call({set_notification, MibName, Trap}, _From, #state{db = DB} = S) -> ?vlog("set notification:" "~n ~p~n ~p", [MibName,Trap]), set_notif(DB, MibName, Trap), {reply, true, S}; handle_call({delete_notifications, MibName}, _From, #state{db = DB} = S) -> ?vlog("delete notification: ~p",[MibName]), delete_notif(DB, MibName), {reply, true, S}; handle_call(which_notifications, _From, #state{db = DB} = S) -> ?vlog("which notifications", []), Reply = which_notifications(DB), {reply, Reply, S}; handle_call({get_notification, Key}, _From, #state{db = DB} = S) -> ?vlog("get notification: ~p",[Key]), Res = get_notif(DB, Key), ?vdebug("get notification result: ~p",[Res]), {reply, Res, S}; handle_call(info, _From, #state{db = DB} = S) -> ?vlog("info",[]), Info = get_info(DB), {reply, Info, S}; handle_call({backup, BackupDir}, From, #state{db = DB} = S) -> ?vlog("info to ~p",[BackupDir]), Pid = self(), V = get(verbosity), case file:read_file_info(BackupDir) of {ok, #file_info{type = directory}} -> BackupServer = erlang:spawn_link( fun() -> put(sname, albs), put(verbosity, V), Dir = filename:join([BackupDir]), Reply = snmpa_general_db:backup(DB, Dir), Pid ! {backup_done, Reply}, unlink(Pid) end), ?vtrace("backup server: ~p", [BackupServer]), {noreply, S#state{backup = {BackupServer, From}}}; {ok, _} -> {reply, {error, not_a_directory}, S}; Error -> {reply, Error, S} end; handle_call(stop, _From, S) -> ?vlog("stop",[]), {stop, normal, ok, S}; handle_call(Req, _From, S) -> info_msg("received unknown request: ~n~p", [Req]), Reply = {error, {unknown, Req}}, {reply, Reply, S}. handle_cast({add_types, MibName, Types}, #state{db = DB} = S) -> ?vlog("add types for ~p:",[MibName]), F = fun(#asn1_type{assocList = Alist, aliasname = Name}) -> case snmp_misc:assq(enums, Alist) of {value, Es} -> ?vlog("add type~n ~p -> ~p",[Name,Es]), Rec = #symbol{key = {type, Name}, mib_name = MibName, info = Es}, snmpa_general_db:write(DB, Rec); false -> done end end, lists:foreach(F, Types), {noreply, S}; handle_cast({delete_types, MibName}, #state{db = DB} = S) -> ?vlog("delete types: ~p",[MibName]), Pattern = #symbol{key = {type, '_'}, mib_name = MibName, info = '_'}, snmpa_general_db:match_delete(DB, Pattern), {noreply, S}; handle_cast({add_aliasnames, MibName, MEs}, #state{db = DB} = S) -> ?vlog("add aliasnames for ~p:",[MibName]), F = fun(#me{aliasname = AN, oid = Oid, asn1_type = AT}) -> Enums = case AT of #asn1_type{assocList = Alist} -> case lists:keysearch(enums, 1, Alist) of {value, {enums, Es}} -> Es; _ -> [] end; _ -> [] end, write_alias(AN, DB, Enums, MibName, Oid) end, lists:foreach(F, MEs), {noreply, S}; handle_cast({delete_aliasname, MibName}, #state{db = DB} = S) -> ?vlog("delete aliasname: ~p",[MibName]), Pattern1 = #symbol{key = {alias, '_'}, mib_name = MibName, info = '_'}, snmpa_general_db:match_delete(DB, Pattern1), Pattern2 = #symbol{key = {oid, '_'}, mib_name = MibName, info = '_'}, snmpa_general_db:match_delete(DB, Pattern2), {noreply, S}; handle_cast({add_table_infos, MibName, TableInfos}, #state{db = DB} = S) -> ?vlog("add table infos for ~p:",[MibName]), F = fun({Name, TableInfo}) -> ?vlog("add table info~n ~p -> ~p", [Name, TableInfo]), Rec = #symbol{key = {table_info, Name}, mib_name = MibName, info = TableInfo}, snmpa_general_db:write(DB, Rec) end, lists:foreach(F, TableInfos), {noreply, S}; handle_cast({delete_table_infos, MibName}, #state{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), {noreply, S}; handle_cast({add_variable_infos, MibName, VariableInfos}, #state{db = DB} = S) -> ?vlog("add variable infos for ~p:",[MibName]), F = fun({Name, VariableInfo}) -> ?vlog("add variable info~n ~p -> ~p", [Name,VariableInfo]), Rec = #symbol{key = {variable_info, Name}, mib_name = MibName, info = VariableInfo}, snmpa_general_db:write(DB, Rec) end, lists:foreach(F, VariableInfos), {noreply, S}; handle_cast({delete_variable_infos, MibName}, #state{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), {noreply, S}; handle_cast({verbosity,Verbosity}, State) -> ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]), put(verbosity,snmp_verbosity:validate(Verbosity)), {noreply, State}; handle_cast(Msg, S) -> info_msg("received unknown message: ~n~p", [Msg]), {noreply, S}. handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) -> ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]), gen_server:reply(From, {error, Reason}), {noreply, S#state{backup = undefined}}; handle_info({'EXIT', Pid, Reason}, S) -> %% The only other processes we should be linked to are %% either the master agent or our supervisor, so die... {stop, {received_exit, Pid, Reason}, S}; handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) -> ?vlog("backup done:" "~n Reply: ~p", [Reply]), gen_server:reply(From, Reply), {noreply, S#state{backup = undefined}}; handle_info(Info, S) -> info_msg("received unknown info: ~n~p", [Info]), {noreply, S}. terminate(Reason, S) -> ?vlog("terminate: ~p",[Reason]), snmpa_general_db:close(S#state.db). %%---------------------------------------------------------- %% Code change %%---------------------------------------------------------- % 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(_Vsn, S, _Extra) -> ?d("code_change -> entry [do nothing]", []), {ok, S}. stop_backup_server(undefined) -> ok; stop_backup_server({Pid, _}) when is_pid(Pid) -> exit(Pid, kill). %%----------------------------------------------------------------- %% Trap operations (write, read, delete) %%----------------------------------------------------------------- %% A notification is stored as {Key, Value}, where %% Key is the symbolic trap name, and Value is %% a #trap or a #notification record. %%----------------------------------------------------------------- %% Returns: {value, Value} | undefined %%----------------------------------------------------------------- get_notif(Db, Key) -> case snmpa_general_db:read(Db, {trap, Key}) of {value,#symbol{info = Value}} -> {value, Value}; false -> undefined end. set_notif(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 Oid = case Trap#trap.enterpriseoid of ?snmp -> ?snmpTraps ++ [Trap#trap.specificcode + 1]; Oid0 -> Oid0 ++ [0, Trap#trap.specificcode] end, write_alias(Name, Db, MibName, Oid), snmpa_general_db:write(Db, Rec); set_notif(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). delete_notif(Db, MibName) -> Pattern = #symbol{key = {trap, '_'}, mib_name = MibName, info = '_'}, snmpa_general_db:match_delete(Db, Pattern). write_alias(AN, DB, MibName, Oid) -> write_alias(AN, DB, [], MibName, Oid). write_alias(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), ?vlog("add oid~n ~p -> ~p",[Oid, AN]), Rec2 = #symbol{key = {oid, Oid}, mib_name = MibName, info = AN}, snmpa_general_db:write(DB, Rec2). %% ------------------------------------- get_info(DB) -> ProcSize = proc_mem(self()), DbSz = tab_size(DB), [{process_memory, ProcSize}, {db_memory, DbSz}]. proc_mem(P) when is_pid(P) -> case (catch erlang:process_info(P, memory)) of {memory, Sz} when is_integer(Sz) -> Sz; _ -> 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). get_mib_storage(L) -> snmp_misc:get_option(mib_storage,L,ets). %% ------------------------------------- call(Req) -> call(Req, infinity). call(Req, Timeout) -> gen_server:call(?SERVER, Req, Timeout). cast(Msg) -> gen_server:cast(?SERVER, Msg). %% ---------------------------------------------------------------- info_msg(F, A) -> error_logger:info_msg("~w: " ++ F ++ "~n", [?MODULE|A]). config_err(F, A) -> snmpa_error:config_err(F, A).