%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1996-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(snmpc_mib_to_hrl).
-include_lib("stdlib/include/erl_compile.hrl").
-include("snmp_types.hrl").
-include("snmpc_lib.hrl").
%% External exports
-export([convert/1, convert/3, compile/3]).
%%-----------------------------------------------------------------
%% Func: convert/1
%% Args: MibName = string() without extension.
%% Purpose: Produce a .hrl file with oid for tables and variables,
%% column numbers for columns and values for enums.
%% Writes only the first occurence of a name. Prints a
%% warning if a duplicate name is found.
%% Returns: ok | {error, Reason}
%% Note: The Mib must be compiled.
%%-----------------------------------------------------------------
convert(MibName) ->
MibFile = MibName ++ ".bin",
HrlFile = MibName ++ ".hrl",
put(verbosity, trace),
convert(MibFile, HrlFile, MibName).
convert(MibFile, HrlFile, MibName) ->
?vtrace("convert -> entry with"
"~n MibFile: ~s"
"~n HrlFile: ~s"
"~n MibName: ~s", [MibFile, HrlFile, MibName]),
case snmpc_misc:read_mib(MibFile) of
{ok, #mib{asn1_types = Types, mes = MEs, traps = Traps}} ->
?vdebug("mib successfully read", []),
resolve(Types, MEs, Traps, HrlFile,
filename:basename(MibName)),
ok;
{error, Reason} ->
?vinfo("failed reading mib: "
"~n Reason: ~p", [Reason]),
{error, Reason}
end.
resolve(Types, MEs, Traps, HrlFile, MibName) ->
?vtrace("resolve -> entry", []),
case file:open(HrlFile, [write]) of
{ok, Fd} ->
insert_header(Fd),
insert_begin(Fd, MibName),
insert_notifs(Traps, Fd),
insert_oids(MEs, Fd),
insert_range(MEs, Fd),
insert_enums(Types, MEs, Fd),
insert_defvals(MEs, Fd),
insert_end(Fd),
file:close(Fd),
?vlog("~s written", [HrlFile]);
{error, Reason} ->
?vinfo("failed opening output file: "
"~n Reason: ~p", [Reason]),
{error, Reason}
end.
insert_header(Fd) ->
?vdebug("insert file header", []),
io:format(Fd, "%%% This file was automatically generated by "
"snmpc_mib_to_hrl version ~s~n", [?version]),
{Y,Mo,D} = date(),
{H,Mi,S} = time(),
io:format(Fd, "%%% Date: ~2.2.0w-~s-~w::~2.2.0w:~2.2.0w:~2.2.0w~n",
[D,month(Mo),Y,H,Mi,S]).
insert_begin(Fd, MibName) ->
?vdebug("insert file begin", []),
io:format(Fd,
"-ifndef('~s').~n"
"-define('~s', true).~n", [MibName, MibName]).
insert_end(Fd) ->
?vdebug("insert file end", []),
io:format(Fd, "-endif.~n", []).
insert_oids(MEs, Fd) ->
?vdebug("insert oids", []),
io:format(Fd, "~n%% Oids~n", []),
insert_oids2(MEs, Fd),
io:format(Fd, "~n", []).
insert_oids2([#me{imported = true} | T], Fd) ->
insert_oids2(T, Fd);
insert_oids2([#me{entrytype = table_column, oid = Oid, aliasname = Name} | T],
Fd) ->
?vtrace("insert oid [table column]: ~p - ~w", [Name, Oid]),
io:format(Fd, "-define(~w, ~w).~n", [Name, lists:last(Oid)]),
insert_oids2(T, Fd);
insert_oids2([#me{entrytype = variable, oid = Oid, aliasname = Name} | T],
Fd) ->
?vtrace("insert oid [variable]: ~p - ~w", [Name, Oid]),
io:format(Fd, "-define(~w, ~w).~n", [Name, Oid]),
io:format(Fd, "-define(~w, ~w).~n", [merge_atoms(Name, instance),
Oid ++ [0]]),
insert_oids2(T, Fd);
insert_oids2([#me{oid = Oid, aliasname = Name} | T], Fd) ->
?vtrace("insert oid: ~p - ~w", [Name, Oid]),
io:format(Fd, "~n-define(~w, ~w).~n", [Name, Oid]),
insert_oids2(T, Fd);
insert_oids2([], _Fd) ->
ok.
insert_notifs(Traps, Fd) ->
?vdebug("insert notifications", []),
Notifs = [Notif || Notif <- Traps, is_record(Notif, notification)],
case Notifs of
[] ->
ok;
_ ->
io:format(Fd, "~n%% Notifications~n", []),
insert_notifs2(Notifs, Fd)
end.
insert_notifs2([], _Fd) ->
ok;
insert_notifs2([#notification{trapname = Name, oid = Oid}|T], Fd) ->
?vtrace("insert notification ~p - ~w", [Name, Oid]),
io:format(Fd, "-define(~w, ~w).~n", [Name, Oid]),
insert_notifs2(T, Fd).
%%-----------------------------------------------------------------
%% There's nothing strange with this function! Enums can be
%% defined in types and in mibentries; therefore, we first call
%% ins_types and then ins_mes to insert enums from different places.
%%-----------------------------------------------------------------
insert_enums(Types, MEs, Fd) ->
?vdebug("insert enums", []),
T = ins_types(Types, Fd, []),
ins_mes(MEs, T, Fd).
%% Insert all types, but not the imported. Ret the names of inserted
%% types.
ins_types([#asn1_type{aliasname = Name,
assocList = Alist,
imported = false} | T],
Fd, Res)
when is_list(Alist) ->
case lists:keysearch(enums, 1, Alist) of
{value, {enums, Enums}} when Enums =/= [] ->
case Enums of
[] -> ins_types(T, Fd, Res);
NewEnums ->
io:format(Fd, "~n%% Definitions from ~w~n", [Name]),
ins_enums(NewEnums, Name, Fd),
ins_types(T, Fd, [Name | Res])
end;
_ -> ins_types(T, Fd, Res)
end;
ins_types([_ | T], Fd, Res) ->
ins_types(T, Fd, Res);
ins_types([], _Fd, Res) -> Res.
ins_mes([#me{entrytype = internal} | T], Types, Fd) ->
ins_mes(T, Types, Fd);
ins_mes([#me{entrytype = table} | T], Types, Fd) ->
ins_mes(T, Types, Fd);
ins_mes([#me{aliasname = Name,
asn1_type = #asn1_type{assocList = Alist,
aliasname = Aname},
imported = false} | T],
Types, Fd)
when is_list(Alist) ->
case lists:keysearch(enums, 1, Alist) of
{value, {enums, Enums}} when Enums =/= [] ->
case Enums of
[] -> ins_mes(T, Types, Fd);
NewEnums ->
%% Now, check if the type is already inserted
%% (by ins_types).
case lists:member(Aname, Types) of
false ->
io:format(Fd, "~n%% Enum definitions from ~w~n",
[Name]),
ins_enums(NewEnums, Name, Fd),
ins_mes(T, Types, Fd);
_ -> ins_mes(T, Types, Fd)
end
end;
_ -> ins_mes(T, Types, Fd)
end;
ins_mes([_ | T], Types, Fd) ->
ins_mes(T, Types, Fd);
ins_mes([], _Types, _Fd) -> ok.
ins_enums([{Name, Val} | T], Origin, Fd) ->
EnumName = merge_atoms(Origin, Name),
io:format(Fd, "-define(~w, ~w).~n", [EnumName, Val]),
ins_enums(T, Origin, Fd);
ins_enums([], _Origin, _Fd) ->
ok.
%%----------------------------------------------------------------------
%% Solves the problem with placing '' around some atoms.
%% You can't write two atoms using ~w_~w.
%%----------------------------------------------------------------------
merge_atoms(TypeOrigin, Name) ->
list_to_atom(lists:append([atom_to_list(TypeOrigin), "_",
atom_to_list(Name)])).
insert_defvals(Mes, Fd) ->
?vdebug("insert default values", []),
io:format(Fd, "~n%% Default values~n", []),
insert_defvals2(Mes, Fd),
io:format(Fd, "~n", []).
insert_defvals2([#me{imported = true} | T], Fd) ->
insert_defvals2(T, Fd);
insert_defvals2([#me{entrytype = table_column, assocList = Alist,
aliasname = Name} | T],
Fd) ->
case snmpc_misc:assq(defval, Alist) of
{value, Val} ->
Atom = merge_atoms('default', Name),
io:format(Fd, "-define(~w, ~w).~n", [Atom, Val]);
_ -> ok
end,
insert_defvals2(T, Fd);
insert_defvals2([#me{entrytype = variable, assocList = Alist, aliasname = Name}
| T],
Fd) ->
case snmpc_misc:assq(variable_info, Alist) of
{value, VarInfo} ->
case VarInfo#variable_info.defval of
undefined -> ok;
Val ->
Atom = merge_atoms('default', Name),
io:format(Fd, "-define(~w, ~w).~n", [Atom, Val])
end;
_ -> ok
end,
insert_defvals2(T, Fd);
insert_defvals2([_ | T], Fd) ->
insert_defvals2(T, Fd);
insert_defvals2([], _Fd) -> ok.
insert_range(Mes, Fd) ->
?vdebug("insert range", []),
io:format(Fd, "~n%% Range values~n", []),
insert_range2(Mes, Fd),
io:format(Fd, "~n", []).
insert_range2([#me{imported = true} | T], Fd)->
insert_range2(T,Fd);
insert_range2([#me{asn1_type=#asn1_type{bertype='OCTET STRING',lo=Low,hi=High},aliasname=Name}|T],Fd)->
case Low =:= undefined of
true->
insert_range2(T,Fd);
false->
AtomLow = merge_atoms('low', Name),
AtomHigh = merge_atoms('high', Name),
io:format(Fd,"-define(~w, ~w).~n",[AtomLow,Low]),
io:format(Fd,"-define(~w, ~w).~n",[AtomHigh,High]),
insert_range2(T,Fd)
end;
insert_range2([#me{asn1_type=#asn1_type{bertype='Unsigned32',lo=Low,hi=High},aliasname=Name}|T],Fd)->
AtomLow = merge_atoms('low', Name),
AtomHigh = merge_atoms('high', Name),
io:format(Fd,"-define(~w, ~w).~n",[AtomLow,Low]),
io:format(Fd,"-define(~w, ~w).~n",[AtomHigh,High]),
insert_range2(T,Fd);
insert_range2([#me{asn1_type=#asn1_type{bertype='Counter32',lo=Low,hi=High},aliasname=Name}|T],Fd)->
AtomLow = merge_atoms('low', Name),
AtomHigh = merge_atoms('high', Name),
io:format(Fd,"-define(~w, ~w).~n",[AtomLow,Low]),
io:format(Fd,"-define(~w, ~w).~n",[AtomHigh,High]),
insert_range2(T,Fd);
insert_range2([#me{asn1_type=#asn1_type{bertype='INTEGER',lo=Low,hi=High},aliasname=Name}|T],Fd)->
case Low =:= undefined of
true->
insert_range2(T,Fd);
false->
AtomLow = merge_atoms('low', Name),
AtomHigh = merge_atoms('high', Name),
io:format(Fd,"-define(~w, ~w).~n",[AtomLow,Low]),
io:format(Fd,"-define(~w, ~w).~n",[AtomHigh,High]),
insert_range2(T,Fd)
end;
insert_range2([_|T],Fd) ->
insert_range2(T,Fd);
insert_range2([],_Fd) ->
ok.
month(1) -> "Jan";
month(2) -> "Feb";
month(3) -> "Mar";
month(4) -> "Apr";
month(5) -> "May";
month(6) -> "Jun";
month(7) -> "Jul";
month(8) -> "Aug";
month(9) -> "Sep";
month(10) -> "Oct";
month(11) -> "Nov";
month(12) -> "Dec".
%%%-----------------------------------------------------------------
%%% Interface for erl_compile.
%%%-----------------------------------------------------------------
%% Opts#options.specific
compile(Input, Output, Opts) ->
set_verbosity(Opts),
set_filename(Input),
?vtrace("compile -> entry with"
"~n Input: ~s"
"~n Output: ~s"
"~n Opts: ~p", [Input, Output, Opts]),
case convert(Input++".bin", Output++".hrl", Input) of
ok ->
ok;
{error, Reason} ->
io:format("~p", [Reason]),
error
end.
set_verbosity(#options{verbose = Verbose, specific = Spec}) ->
set_verbosity(Verbose, Spec).
set_verbosity(Verbose, Spec) ->
Verbosity =
case lists:keysearch(verbosity, 1, Spec) of
{value, {verbosity, V}} ->
case (catch snmpc_lib:vvalidate(V)) of
ok ->
case Verbose of
true ->
case V of
silence ->
log;
info ->
log;
_ ->
V
end;
_ ->
V
end;
_ ->
case Verbose of
true ->
log;
false ->
silence
end
end;
false ->
case Verbose of
true ->
log;
false ->
silence
end
end,
put(verbosity, Verbosity).
set_filename(Filename) ->
Rootname = filename:rootname(Filename),
Basename = filename:basename(Rootname ++ ".mib"),
put(filename, Basename).