%%
%% %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).