#!/usr/bin/env escript
%% -*- erlang -*-
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2011. 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%
%%
%% SNMP MIB compiler frontend
%%
-mode(compile).
-include_lib("kernel/include/file.hrl").
-include_lib("snmp/include/snmp_types.hrl").
-record(state,
{
version = "%VSN%",
mfv,
file, % .mib or .bin depending on which are compiled
outdir = "./",
db = volatile,
include_dirs = ["./"],
include_lib_dirs = [],
deprecated = false,
group_check = true,
description = false,
reference = false,
imports = false,
module_identity = false,
module_compliance = false,
agent_capabilities = false,
module,
no_defaults = false,
relaxed_row_name_assign_check = false,
%% The default verbosity (silence) will be filled in
%% during argument processing.
verbosity,
warnings = false,
warnings_as_errors = false
}).
%% ------------------------------------------------------------------------
%% Valid arguments:
%% --o Dir [defaults to "./"]
%% --i Dir [defaults to "./"]
%% --il Dir
%% --sgc
%% --db DB [defaults to volatile]
%% --dep
%% --desc
%% --ref
%% --imp
%% --mi
%% --mc
%% --ac
%% --mod Mod
%% --nd
%% --rrnac
%% --version
%% --verbosity V
%% --warnings
%% --Werror | --wae | --warnings_as_errors
main(Args) when is_list(Args) ->
case (catch process_args(Args)) of
ok ->
usage();
{ok, State} when is_record(State, state) ->
compile(State);
{ok, Str} when is_list(Str) ->
io:format("~s~n~n", [Str]),
halt(1);
{error, ReasonStr} ->
usage(ReasonStr)
end;
main(_) ->
usage().
compile(State) ->
%% io:format("snmpc: ~p~n", [State]),
case mk_file(State) of
{mib, File} ->
Options = mk_mib_options(State),
case mib2bin(File, Options) of
{ok, _BinFileName} ->
ok;
{error, Reason} ->
io:format("ERROR: Failed compiling mib: "
"~n ~p~n", [Reason]),
halt(1)
end;
{bin, File} ->
Options = mk_hrl_options(State),
case bin2hrl(File, Options) of
ok ->
ok;
{error, Reason} ->
io:format("ERROR: Failed generating hrl from mib: "
"~n ~p~n", [Reason]),
halt(1)
end
end.
mib2bin(MibFileName, Options) ->
snmpc:compile(MibFileName, Options).
bin2hrl(BinFileName, {OutDir, Verbosity}) ->
MibName = filename:basename(BinFileName),
BinFile = BinFileName ++ ".bin",
HrlFile = filename:join(OutDir, MibName) ++ ".hrl",
put(verbosity, Verbosity),
snmpc_mib_to_hrl:convert(BinFile, HrlFile, MibName).
mk_file(#state{file = MIB}) ->
DirName = filename:dirname(MIB),
case filename:extension(MIB) of
".mib" ->
BaseName = filename:basename(MIB, ".mib"),
{mib, filename:join(DirName, BaseName)};
".bin" ->
BaseName = filename:basename(MIB, ".bin"),
{bin, filename:join(DirName, BaseName)};
BadExt ->
e(lists:flatten(io_lib:format("Unsupported file type: ~s", [BadExt])))
end.
mk_mib_options(#state{outdir = OutDir,
db = DB,
include_dirs = IDs,
include_lib_dirs = ILDs,
deprecated = Dep,
group_check = GC,
description = Desc,
reference = Ref,
imports = Imp,
module_identity = MI,
module_compliance = MC,
agent_capabilities = AC,
module = Mod,
no_defaults = ND,
relaxed_row_name_assign_check = RRNAC,
%% The default verbosity (silence) will be filled in
%% during argument processing.
verbosity = V,
warnings = W,
warnings_as_errors = WAE}) ->
[{outdir, OutDir},
{db, DB},
{i, IDs},
{il, ILDs},
{group_check, GC},
{verbosity, V},
{warnings, W},
{deprecated, Dep}] ++
if
(Mod =/= undefined) ->
[{module, Mod}];
true ->
[]
end ++
maybe_option(ND, no_defs) ++
maybe_option(RRNAC, relaxed_row_name_assign_check) ++
maybe_option(Desc, description) ++
maybe_option(Ref, reference) ++
maybe_option(Imp, imports) ++
maybe_option(MI, module_identity) ++
maybe_option(MC, module_compliance) ++
maybe_option(AC, agent_capabilities) ++
maybe_option(WAE, warnings_as_errors).
maybe_option(true, Opt) -> [Opt];
maybe_option(_, _) -> [].
mk_hrl_options(#state{outdir = OutDir,
verbosity = Verbosity}) ->
{OutDir, Verbosity}.
process_args([]) ->
e("No input file");
process_args(Args) ->
#mib{mib_format_version = MFV} = #mib{},
State = #state{},
process_args(Args, State#state{mfv = MFV}).
process_args([], #state{verbosity = Verbosity0, file = MIB} = State) ->
if
(MIB =:= undefined) ->
e("No input file");
true ->
Verbosity =
case Verbosity0 of
undefined ->
silence;
_ ->
Verbosity0
end,
IPath = lists:reverse(State#state.include_dirs),
IlPath = lists:reverse(State#state.include_lib_dirs),
{ok, State#state{verbosity = Verbosity,
include_dirs = IPath,
include_lib_dirs = IlPath}}
end;
process_args(["--help"|_Args], _State) ->
ok;
process_args(["--version"|_Args], #state{version = Version, mfv = MFV} = _State) ->
OtpVersion = otp_release(),
{ok, lists:flatten(
io_lib:format("snmpc ~s [Mib format version ~s] (OTP ~s)",
[Version, MFV, OtpVersion]))};
process_args(["--verbosity", Verbosity0|Args], #state{verbosity = V} = State)
when (V =:= undefined) ->
Verbosity = list_to_atom(Verbosity0),
case lists:member(Verbosity, [trace,debug,log,info,silence]) of
true ->
process_args(Args, State#state{verbosity = Verbosity});
false ->
e(lists:flatten(io_lib:format("Unknown verbosity: ~s", [Verbosity0])))
end;
process_args(["--verbosity"|_Args], #state{verbosity = V})
when (V =/= undefined) ->
e(lists:flatten(io_lib:format("Verbosity already set to ~w", [V])));
process_args(["--w"|Args], State) ->
process_args(Args, State#state{warnings = true});
process_args(["--warnings"|Args], State) ->
process_args(Args, State#state{warnings = true});
process_args(["--o", Dir|Args], State) ->
case (catch file:read_file_info(Dir)) of
{ok, #file_info{type = directory}} ->
process_args(Args, State#state{outdir = Dir});
{ok, #file_info{type = BadType}} ->
e(lists:flatten(io_lib:format("Not a directory: ~p (~w)", [Dir, BadType])));
_ ->
e(lists:flatten(io_lib:format("Bad directory: ~p", [Dir])))
end;
process_args(["--i", Dir|Args], State) ->
case (catch file:read_file_info(Dir)) of
{ok, #file_info{type = directory}} ->
IPath = [Dir | State#state.include_dirs],
process_args(Args, State#state{include_dirs = IPath});
{ok, #file_info{type = BadType}} ->
e(lists:flatten(io_lib:format("Not a directory: ~p (~w)", [Dir, BadType])));
_ ->
e(lists:flatten(io_lib:format("Bad directory: ~p", [Dir])))
end;
process_args(["--il", Dir|Args], State) ->
case (catch file:read_file_info(Dir)) of
{ok, #file_info{type = directory}} ->
IlPath = [Dir | State#state.include_lib_dirs],
process_args(Args, State#state{include_lib_dirs = IlPath});
{ok, #file_info{type = BadType}} ->
e(lists:flatten(io_lib:format("Not a directory: ~p (~w)", [Dir, BadType])));
_ ->
e(lists:flatten(io_lib:format("Bad directory: ~p", [Dir])))
end;
process_args(["--db", DB0|Args], State) ->
DB = list_to_atom(DB0),
case lists:member(DB, [volatile,persistent,mnesia]) of
true ->
process_args(Args, State#state{db = DB});
false ->
e(lists:flatten(io_lib:format("Invalid db: ~s", [DB0])))
end;
process_args(["--dep"|Args], State) ->
process_args(Args, State#state{deprecated = true});
process_args(["--sgc"|Args], State) ->
process_args(Args, State#state{group_check = false});
process_args(["--desc"|Args], State) ->
process_args(Args, State#state{description = true});
process_args(["--ref"|Args], State) ->
process_args(Args, State#state{reference = true});
process_args(["--imp"|Args], State) ->
process_args(Args, State#state{imports = true});
process_args(["--mi"|Args], State) ->
process_args(Args, State#state{module_identity = true});
process_args(["--mod", Module0|Args], #state{module = M} = State)
when (M =:= undefined) ->
Module = list_to_atom(Module0),
process_args(Args, State#state{module = Module});
process_args(["--mod"|_Args], #state{module = M})
when (M =/= undefined) ->
e(lists:flatten(io_lib:format("Module already set to ~w", [M])));
process_args(["--nd"|Args], State) ->
process_args(Args, State#state{no_defaults = true});
process_args(["--rrnac"|Args], State) ->
process_args(Args, State#state{relaxed_row_name_assign_check = true});
process_args(["--Werror"|Args], State) ->
process_args(Args, State#state{warnings_as_errors = true});
process_args(["--wae"|Args], State) ->
process_args(Args, State#state{warnings_as_errors = true});
process_args(["--warnings_as_errors"|Args], State) ->
process_args(Args, State#state{warnings_as_errors = true});
process_args([MIB], State) ->
Ext = filename:extension(MIB),
if
((Ext =:= ".mib") orelse (Ext =:= ".bin")) ->
case (catch file:read_file_info(MIB)) of
{ok, #file_info{type = regular}} ->
process_args([], State#state{file = MIB});
{ok, #file_info{type = BadType}} ->
e(lists:flatten(io_lib:format("~s not a file: ~w", [MIB, BadType])));
{error, enoent} ->
e(lists:flatten(io_lib:format("No such file: ~s", [MIB])));
_ ->
e(lists:flatten(io_lib:format("Bad file: ~s", [MIB])))
end;
true ->
e(lists:flatten(io_lib:format("Unknown option: ~s", [MIB])))
end;
process_args([Arg|Args], _State) when Args =/= [] ->
e(lists:flatten(io_lib:format("Unknown option: ~s", [Arg]))).
usage(ReasonStr) ->
io:format("ERROR: ~s~n", [ReasonStr]),
usage().
usage() ->
io:format("Usage: snmpc [options] MIB.mib|MIB.bin"
"~nCompile a MIB (.mib -> .bin) or generate an erlang header "
"~nfile from a compiled MIB file (.bin -> .hrl)"
"~nOptions:"
"~n --help - Prints this info."
"~n --version - Prints compiler version."
"~n --verbosity <verbosity> - Print debug info."
"~n verbosity = trace | debug | log | info | silence"
"~n Defaults to silence."
"~n --warnings | --W - Print warning messages."
"~n --o <output dir> - The output dir."
"~n Defaults to current working dir."
"~n --i <include dir> - Add this dir to the list of dirs that will be"
"~n searched for imported (compiled) MIB files."
"~n The current workin dir will always be included. "
"~n --il <include_lib dir> - Add this dir to the list of dirs that will be"
"~n searched for imported (compiled) MIB files."
"~n It assumes that the first element in the dir "
"~n name correspond to an OTP application. "
"~n For example snmp/mibs/ "
"~n The current workin dir and the "
"~n <snmp-home>/priv/mibs "
"~n are always listed last the includ path. "
"~n --db <DB> - Database to used for the default instrumentation."
"~n Defaults to volatile."
"~n --sgc - This option (skip group check), if present, "
"~n disables the \"group check\" of the mib compiler. "
"~n That is, should the OBJECT-GROUP and the "
"~n NOTIFICATION-GROUP macro(s) be checked for "
"~n correctness or not. "
"~n By default the check is done. "
"~n --dep - Keep deprecated definition(s)."
"~n If not specified the compiler will ignore"
"~n deprecated definitions."
"~n --desc - The DESCRIPTION field will be included."
"~n --ref - The REFERENCE field will be included."
"~n --imp - The IMPORTS field will be included."
"~n --mi - The MODULE-IDENTITY field will be included."
"~n --mc - The MODULE-COMPLIANCE field will be included."
"~n --ac - The AGENT-CAPABILITIES field will be included."
"~n --mod <module> - The module which implements all the "
"~n instrumentation functions. "
"~n The name of all instrumentation functions must"
"~n be the same as the corresponding managed object"
"~n it implements."
"~n --nd - The default instrumentation functions will *not* "
"~n be used if a managed object have no "
"~n instrumentation function. Instead this will be "
"~n reported as an error, and the compilation aborts. "
"~n --rrnac - This option, if present, specifies that the row "
"~n name assign check shall not be done strictly "
"~n according to the SMI (which allows only the "
"~n value 1). With this option, all values greater "
"~n than zero is allowed (>= 1). "
"~n This means that the error will be converted to "
"~n a warning. "
"~n By default it is not included, but if this "
"~n option is present it will be. "
"~n --wae | --Werror - Warnings as errors. "
"~n Indicates that warnings shall be treated as "
"~n errors. "
"~n "
"~n", []),
halt(1).
e(Reason) ->
throw({error, Reason}).
otp_release() ->
system_info(otp_release, string).
system_info(Tag, Type) ->
case (catch erlang:system_info(Tag)) of
{'EXIT', _} ->
"-";
Info when is_list(Info) andalso (Type =:= string) ->
Info;
Info ->
lists:flatten(io_lib:format("~w", [Info]))
end.